There are two frequent issues the confront people when dealing with populating a collection of beans in an ActionForm.
- How can I generate the appropriate html using the struts taglibs?
- I have a request scoped ActionForm and am getting an "index out of range" error when I submit the form - what do I do?
More info on indexed properties available in the FAQs
The Indexed Properies section below deals with generating the html and the Lazy List section shows possible solutions for avoiding the "index out of range" error.
Struts html tags have an indexed attribute which will generate the appropriate html to populate a collection of beans when the form is submitted. The trick is to name the id attribute to the same as the indexed property.
For example the following jsp...
...will generate the following html
When the form is submitted BeanUtils will first call the getSkills(index) method to retrieve the indexed bean followed by setSkillId(..) on the retrieved bean.
Using nested tags
An easy-to-use alternative to the html iterate tag and indexed attribute are the nested tags. The nested tag equivalent of the example above would be:
The generated html would be the same.
Lazy List Behaviour
A common problem with indexed properties, is that people then get "index out of range" errors with ActionForms that are in Request scope. The indexed property (List or Array) needs to be able to automatically grow during the ActionForm population process. The key to achieving this is in the getXXXX(index) method.
The following sections show three examples of how to achieve lazy list processing.
BeanUtils Indexed Properties Issue
If you define the following types of getter/setter methods for your indexed property, in JDK 1.3 you shouldn't have any problem, but in JDK 1.4 the getter/setter which use the java.util.List are ignored by Bean Utils.
The solution is either to use DynaBeans which don't have any issues or use method signatures with Arrays rather than java.util.List. For example the following should work...
Hand Cranking lazy List in the ActionForm
Using Commons Collections for lazy Lists
Commons collections have a lazy list decorator which automatically grows the list when the get(int) method is called. In the example below the ActionForm implements the Factory interface, providing the create() method to populate an entry in the List.
The syntax in Commons-Collections 2.1.1 is;
An easier/cleaner way? ( Rick Reumann )
Someone feel free to edit this, but doesn't that solution above tie your ActionForm to just one List? What if your form has several lists that need to grow?
Someone gave me this solution and it works great. Since reset is always called it makes sure you have your LazyList initialized and then it just grows as needed. You can set this up for other Lists you need in your form as well.
N.B. Solutions here require Struts 1.2.4 and Bean Utils 1.7.0
LazyDynaBean in Commons BeanUtils has the lazy List type processing. LazyValidatorForm is built using the BeanUtils LazyDynaBean and can be used by simply configuring it through the struts-config.xml.
In fact using Arrays (rather than Lists) a LazyDynaBean can be used directly, in the following way:
Using the Decorator Pattern Underlying LazyList with Instrumented Forms : One Alternative Suggested Some Time Ago
Rather than use collections classes that are decorated in forms, why not make the forms themselves instrumentable by having them decorate the collections? I suggested this sometime ago on this wiki, but the idea could not be implemented until the processing for forms with the collections classes caught up with the idea, distinguishing between a mere Map, for example, and an ActionForm that was also a decorated Map. I called these Instrumented Forms and used the recommendations of Joshua Bloch to code them. ["http://www.michaelmcgrady.com" – see the coding ideas section and then instrumented forms]
The above solution using LazyDynaBean looks like a simple drop in replacement which is great, however it assumes that the Struts Validator is active. If you are not familiar with the validator (I wasn't when I got directed to this page for a solution) then it can cost you a number of hours of head scratching and frustration whilst you figure it out. To summarise and hopefully give someone a kick start to this, you need to:
- Include the Commons Validator jar in your project.
2. Modify your struts-config.xml to include the validator plugin.
3. Copy the the validator-rules.xml file from the struts download or source one from somewhere else.
4. Create and amend the valilation.xml file to include a section for each of your forms you wish to use LazyDynaBeans for.
I found the struts doco assumed you already know how all of this worked so wasn't much help to getting started. The best place to find help is in referenced resources down the bottom of http://struts.apache.org/userGuide/dev_validator.html
Extending the struts DynaActionForm
I came up with this code after looking around and not really being satified with any of the solutions I found. They either required a lot of fiddling and setup in the application, or involved install all sorts of packages I didn't really want to use. Basically I don't like making things any more complex than they should be. So I came up with this class which extends the struts DynaActionForm to effective lazy load lists and arrays without requiring any additional software or setup over the standard DynaActionForm.
Enjoy, Derek Clarkson
Problem - How do we deal with Lists of objects that contain Lists?
I'm still stumped with the best way to handle cases where your nesting goes a bit deeper than the examples shown here. For example lets assume you want to edit on a single form a list of Employees - where you want to be able to edit their name AND a List of phone numbers:
So we might have basically a List of Employee objects where Employee has:
Here's the problem.... in the reset method you can set a LazyList to wrap the Employees but there is still the problem of the internal List of phoneNumbers in each Employee.
I was trying:
Of course all of these problems go away if I just use the Session to store the form, but I really want to figure out a nice way to get this to work using the Request. Feel free to delete this once a nice solution is proposed.
Somewhat of a solution:
The problem above is that I was using a List of Strings (phoneNumbers) as the property in my Employee object. Everything ends up working fine if instead I create a List of "Contact" objects (where each Contact object has a "phoneNumber" - and I also added a "label"):
The reset was then modified to:
Still not very satisified
I'm still not happy with the solutions provided (including my own). Many of the above solutions assume you know how deep your items are nested, or that you create nested objects up front that can handle the lazy situation. Where I'm not happy is where you can get back a nested structure of unknown depth. I have a case now where the generated content that can be editable is a questionnaire and each question can end up having sub-questions. Other than using Session scope I'm not sure of the best approach to use. I'm messing around with LazyDynaMaps to see if that might work. Not the ideal solution since I'd prefer to use straight value objects from the midtier, but I'll compromise if need be.
NestedLazyBean does n level indexed properties. For straight forward object graphs (e.g.
person.address.line1) where you want an "address" DynaBean automatically created when
person.get("address").set("line1", ...) is called then its slightly more problematic, since how do you know when it hits
get("address") that it should be a DynaBean property rather than some other regular kind? Using BeanUtils's Map notation (i.e. using
person[address][line1]) works from a LazyDynaMap PoV, but you will need the BeanUtils 1.8.0-BETA, since nested maps were not supported until that version.