I've been working on Quadran a lot, and I'm creating pages for editing lists of entities. The CRUD example from the Struts 2 Showcase application does this in a very simple manner. It recreates all of the entities every time you update the list. That's fine for an example but unusable in real life.
In real life we have to present entities from our database and then put the values back into the same entities, and update our database. We can't just re-create them.
So, we'll start with a simple domain object and assume that we're using something like JPA or Hibernate to persist it via a Data Access Object (DAO), and that it contains a property called "id".
public class MyEntity {
private String id, value;
public String getId () { return id; }
public void setId (String in) { id = in; }
public String getValue () { return value; }
public void setValue (String in) { value = in; }
}
This is a simple JavaBean that contains two properties id and value.
In our action we will have a List
public class EditMyEntitiesAction extends ActionSupport implements Preparable {
// Service Beans
MyEntityDao myEntityDao = new MyEntityDao (); // A data access object for managing MyEntity objects.
// Model Beans
List<MyEntity> myEntities;
Map<String,MyEntity> myEntitiesMap;
public void prepare () {
// Get the List of MyEntity objects from the datastore.
this.setMyEntities (myEntityDao.findAll () );
// Create a Map using this.myEntities as the basis for it keyed on myEntity.id.
Map<String,MyEntity> this.myEntitiesMap = new HashMap ();
for (MyEntity myEntity : this.getMyEntities () ) {
this.myEntitiesMap.put (myEntity.getId (), myEntity);
}
}
public String execute () throws Exception {
// Iterate over the List of MyEntity objects and persist them using our DAO
for (MyEntity myEntity : this.getMyEntities () ) {
this.myEntityDao.updateDatabase (myEntity);
}
}
/* Accessors */
public void setMyEntities (List<MyEntity> in) {
this.myEntities = in;
}
public List<MyEntity> getMyEntities () {
return this.myEntities;
}
public Map<String,MyEntity> getMyEntitiesMap () {
return this.myEntitiesMap;
}
}
That's all there is to the action. In the prepare we get the List of MyEntity objects and then put them into a map keyed on MyEntity.id. Pretty straight forward. The Map is the key to the whole process. We will use the OGNL EL notation to access that Map and fill the values back into it when a user submits the form.
Here's the pertinent section of the JSP:
<s:iterator value="myEntities">
<s:textfield name="myEntitiesMap['%{id}'].value" value="%{value}" />
</s:iterator>
In this we iterate over the myEntities List. On the textfield tag we set the name to "myEntitiesMap['%{id}'].value", this would result in something that looks like "myEntitiesMap['1'].value". When the user submits the form Struts2 will evaluate this name as so:
| Expression | Result |
|---|---|
| myEntitiesMap | This will call getMyEntitiesMap() on our EditMyEntitiesAction. |
| ['1'] | This will call get("1") on the Map that was returned by getMyEntitiesMap(), which will return a MyEntity object. |
| .value | Will call setValue() on the MyEntity object returned previously. Setting the value of the property to the value that was submitted by our user. |
Basically myEntitiesMap['1'].value results in: EditMyEntitiesAction.getMyEntitiesMap().get ("1").setValue (<our user submitted value>).
It took me a while to figure out how to do this. I was struggling over how to get the values back into the "List" of entities in my action, then I hit on the idea of putting them into a Map keyed on the id of the entities, and voila, it all fits together.
This is by no means an enterprise ready update mechanism. The primary things I've left out is validation. This can be added by implementing a validate() method in our Action. Best practice would be to delegate that validation to some other bean responsible for the management of MyEntity objects.
As a new migration from Struts 1.x, this was a huge help for setting up a variable amount of mapped indexed properties. Thanks :)
I have not tried this, but reading this http://struts.apache.org/2.x/docs/type-conversion.html, I think the implication is that you should be able live without myEntitiesMap
You need a file called: EditMyEntitiesAction-conversion.properties with content
KeyProperty_myEntities=id
Element_myEntities=MyEntity
CreateIfNull_myEntities=false
Then you can use:
<s:iterator value="myEntities">
<s:textfield name="myEntities(%{id}).value" value="%{value}" />
</s:iterator>
Although whether it *actually* works is TBD
Tim Azzopardi
PS there is a full example of this at http://forums.opensymphony.com/thread.jspa?threadID=50841&tstart=0 towards the end.
(although it looks like the technique may have issues)
I was able to use the example from:
http://jira.opensymphony.com/browse/WW-1413
(which is linked from the forum you gave: http://forums.opensymphony.com/thread.jspa?threadID=50841&tstart=0)
It works well with no map, as long as my id does not contain a comma, but is very slow. Not sure if other solutions are faster, I like it as it is fairly simple to follow, but it is slow.
I am searching for a way to edit a list of entities without using the Map, too.
I tried the method in http://struts.apache.org/2.x/docs/type-conversion.html
Use some format like this in the jsp page:
<s:iterator value="myEntities" id="bean">
<stextfield name="myEntities(%{bean.id}).value" />
</s:iterator>
But it is not working, neither to display the data in jsp page or return value to the list after submit, I played with it and found that if I change it like this:
<s:iterator value="myEntities" id="bean">
<stextfield name="myEntities('%{bean.id}').value" />
</s:iterator>
It is able to display the right value in form, but still can't return value to the list in Action. The List is always reset to null.
Then I figure out a way as follows to make it work in both display and save back to the list in Action.
<s:iterator value="myEntities" status="status">
<s:textfield name="%{myEntities['+#status.index+'].value}" />
</s:iterator>
But I found out that it still create a new instance of myEntities, and save all the values in form to the new myEntities. If you don't display some filed in the form, the field will be null, and the old data is gone. I haven't try your 'Map' solution yet, does your method keep the old data in myEntities even you don't display them in the jsp page?
Looking at your example and reading the thread on the forum I think this might work. I'll have to give it a try. I'll admit I have done very little with the type conversion support in S2. I really haven't had time to dig into it.
Mark
Hi,
I Just started workign with Struts2.
Here is my doubt.
Assume that i am developing a loing application where user enters username and password, i have the setter/getter methods in my action for username& password.
Form action i have to invoke the Service/DAO method, which takes a Value Object as paramater, which contains username& password.
Here is few Questions:
=> Why can't we have a seperate bean for form properties and map it to the action class.
=> If the properties are defined in Action class, what is the right place to create the Value object (in my example with username& password).
Any Suggestions....
You can have a separate bean for the form properties. Take a look at the ModelDriven documentation.
Another alternative is to create the bean as a property of your action in your prepare() method. Then in your form you can reference the properties of your bean in your tags with 'name="bean.propety"'.
Mark
Displaying array of objects using iterator
Hi
I am facing a problem in Struts2, In my sample application I have Array of objects (Say person name) , I need to display these names as a editable text fields , I am using Iterators for this , I am successful in diaplying, But When I cange the same value and submit the form. I am not getting the entire array, intead the array holds null value.
Say for Exaple in my form bean I have a property
Name [] names;
In my JSP I have the iterator as
If there are 3 names then I can get these names on teh UI but when I edit and sumbit, then "names" array is not getting updated. Please help me in this regard