views:

38

answers:

1

Hello, I'm trying to have a dynamic form and, depending on an attribute type, I would like to display a different input style (textfield, radio buttons, dropdown, checklist, ...).

In order to have the dynamic form, I've set up the ActionForm with a Map.

Map<String, Object> values;
public void setValue(String key, Object value);
public Object getValue(String key);

My problem comes when I try to set up a checklist or multibox. The ActionForm only passes one value, although I would have expected that the String[] would be mapped to the Object argument.

Any idea about how can I solve this?

EDIT: in the JSP:

<input type=checkbox name="value(foo)" />
+1  A: 

I looked into this issue and found out what was happening. The problem is not with Struts but with BeanUtils (which Struts uses for populating the form with the request parameters).

I managed to duplicate this by extracting a (test only) snippet of code from the framework:

public class MyForm {
  // assume this is your Struts ActionForm
  public void setValue(String key, Object val) {
    System.out.println(key + "=" + val);
  }
}

public class Test {
  public static void main(String[] args) 
      throws IllegalAccessException, InvocationTargetException {
    MyForm s = new MyForm();
    Map<String, Object> properties = new HashMap<String, Object>();
    // Your request should be like yourActionUrl?value(foo)=1&value(foo)=2&value(foo)=3 
    // and Struts calls bean utils with something like:
    properties.put("value(foo)", new String[] {"1", "2", "3"});
    BeanUtils.populate(s, properties);
  }
}

When your run this you get printed one value only (just as you desrbibed):

foo=1

The thing is that BeanUtils considers this a mapped property and treats it as such, going for a scalar value for the key. Since your value is an array it just uses the first element:

...
} else if (value instanceof String[]) {
  newValue = getConvertUtils().convert(((String[]) value)[0], type);
...

What you can do is modify your JSP and ActionForm to treat the list of values separately. Example modified:

public class MyForm {
  private Map<String, Object> map = new HashMap<String, Object>();

  public void setValue(String key, Object val) {
    map.put(key, val);
  }

  public void setPlainValue(String[] values) {
    // this is correctly called; now delegate to what you really wanted 
    setValue("foo", values);
  }
}

public class Test {
  public static void main(String[] args) 
      throws IllegalAccessException, InvocationTargetException {
    MyForm s = new MyForm();
    Map<String, Object> properties = new HashMap<String, Object>();
    // Notice the change to your URL..
    // yourActionUrl?plainValue=1&plainValue=2&plainValue=3
    properties.put("plainValue", new String[] {"1", "2", "3"});
    BeanUtils.populate(s, properties);
  }
}

The above means that you use

<input type="..." name="value(foo)" ... />

for all the single elements in your JSP, while for your checkboxes (extending it to multivalue elements in general) you use

<input type="checkbox" name="plainValue" ... />

and you delegate to the map once in your ActionForm.

dpb
The problem with this approach is that I don't know the value of the 'foo' value at compile time, only at runtime.
Gothmog
@Gothmog: In your question you specify that you take some action "depending on an attribute type". You could use this in the action form to determine what the "foo" value would be (I assume that you use this attribute type in your JSP too). Additionally, have you considered doing this in another way, like using a DynaActionForm?
dpb
The attribute type is not related with foo. What I do is give the ability to define form questions, so for example I can define 'name' of type textfield, 'sex' of type radio button and 'hobbies' of type checklist. 'name', 'sex' and 'hobbies' would be the values of 'foo', but those names could be anything else. The attribute type would be 'textfield', 'radiobutton' and 'checklist' and those are predefined.
Gothmog