Announcement Announcement Module
Collapse
No announcement yet.
binding dynamic forms Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • binding dynamic forms

    i'm not sure this is the best place for this post but it's the best place my frazzled mind can think of. i am implementing an AbstractWizardFormController that should read an excel file, return the headers to the user, who can then map these header columns to specific types and send the data back to me. i've had no problem creating the first step, and even into the second view. the problem is when i try to bind these custom form fields. i never know in advance how many fields i will have, and i'm a little confused on how to bind them.

    i've been all over the net, reading posts like http://opensource.atlassian.com/proj.../browse/SPR-52, but i keep running into the same problem. i attempted to use each form field as part of a larger array, and pass that array in to be bound and processed, but it always chokes on submission, trying to bind each field individually. i'm having a hard time figuring out how to a.) bind these fields and b.) process them once they get into the controller. do i need to create a custom binder? can i use the built in onBindAndValidate?

    i can't seem to get past this problem and i know that it has to be something fairly simple. i know i'm not the only person who has to deal with this type of data. i'm sure i'll feel dumb the minute i read a reply to this post, but any help would be greatly appreciated.

  • #2
    Can you show us the command source code and a sample of the html/jsp/... code which is used to grab input from the user in the second step ?

    Comment


    • #3
      here's the command class (i've taken out all the javadoc comments, comments and debugger lines for brevity):

      Code:
      public class NewPayrollTemplateController extends AbstractWizardFormController {
      
          protected final Log logger = LogFactory.getLog(getClass());
          protected List storedData = new ArrayList();
          protected List columnNumbers = new ArrayList();
          protected HashMap columns = new HashMap();
          protected HashMap columnData = new HashMap();
          protected HashMap options = new HashMap();
          protected String fileName;
          
          public NewPayrollTemplateController() {
          	setPages(new String[] {"newPayrollView", "mapNewPayrollTemplate","completeNewPayrollTemplate"});
          }
          
          protected void initBinder (HttpServletRequest request, ServletRequestDataBinder binder)
              throws ServletException {
      
              binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
          }
      
          public Object formBackingObject(HttpServletRequest request) throws ServletException {
              NewPayrollTemplate template = new NewPayrollTemplate();
              return template;
          }
          
          protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors, int page) {
           
          	if (page == 0) {
               
                  NewPayrollTemplate template = (NewPayrollTemplate)command;
                  MultipartHttpServletRequest uploadRequest = (MultipartHttpServletRequest) request;
                  ReadTemplateHeaders readTemplate = new ReadTemplateHeaders();
                  
                  storedData = readTemplate.readHeaders(template,uploadRequest);
                  columnNumbers = readTemplate.getColumnNumbers();
                  columnData = readTemplate.getColumnData();
                  
              } else if (page == 1) {
                  
                  MapPayrollTemplate mapTemplate = new MapPayrollTemplate();             mapTemplate.parseColumns(request.getParameterMap(), columnNumbers, columnData);
                  
                  columns = mapTemplate.getAllData();
                  
                  options = mapTemplate.mapOptions();
                  
              }
              
          }
          
          protected Map referenceData(HttpServletRequest request, int page) {
              if (page == 1) {
                  Map model = new HashMap();
                  model.put("templateData", storedData);
                  model.put("options", options);
                  return model;
              }
              return null;
          }
          
          protected int getTargetPage(HttpServletRequest request, Object command, Errors errors, int currentPage) {
              NewPayrollTemplate template = (NewPayrollTemplate)command;
              if (currentPage == 0) {
                  return 1;
              }
              else {
                  return 2;
              }
          }
          
          protected void validatePage(Object command, Errors errors, int page) {
              NewPayrollTemplate template = (NewPayrollTemplate)command;
              NewPayrollTemplateValidator validator = (NewPayrollTemplateValidator) getValidator();
              errors.setNestedPath("order");
              switch (page) {
                  case 0:
                      validator.validateUpload(template, errors);
                      break;
                  case 1:
                      validator.validateMap(template, errors);
              }
              errors.setNestedPath("");
          }
          
          protected ModelAndView processFinish(
                  HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) {
              NewPayrollTemplate template = (NewPayrollTemplate)command;
              Map model = new HashMap();
              model.put("message", "test message");
              return new ModelAndView("completeNewPayrollTemplate", model);
          }
      
      }
      i've tried two different ways to access the data in the command class. the simple way:

      Code:
      private String[] type;
      ...
          public void setType(String[] type) { 
              this.type = type; 
          } 
          
          public String[] getType() { 
              return type; 
          }
      and the overly complicated tricky way because i was trying anything i could think of:

      Code:
          public void parseColumns(Map parameterMap, List columnNumbers, HashMap columnData) {
              
              Set keys = parameterMap.keySet();
              
              Iterator i = keys.iterator();
              
              while (i.hasNext()) {
               
                  String keyName = (String)i.next();
                  if (keyName.startsWith("type_")) {
                      Object o = parameterMap.get(keyName);
      
                      String[] value = (String[])parameterMap.get(keyName);
                  	if (value.equals("--")) {
                  		logger.debug(keyName + " has no relevance");
                      } else {
                      	String split[] = keyName.split("_");
                          String num = (String)split[1];
                          
                          String columnName = columnData.get(num).toString();
                          
                          HashMap tmp = new HashMap();
                          tmp.put("type", value[0]);
                          tmp.put("name", columnName);
                          
                          allData.put(num, tmp);
                      }
                      
                  }
                  
              }
              
          }
      incidentally, this code works when i pass in a test map.

      i'm using velocity, and i've tried two different ways to label the fields. as an array, to use with the "simple" command class:

      Code:
      #foreach ($key in $datum.keySet())
      ...
      #set($bind = "command.type[$key]")
      ...
      <td align="center">#springFormSingleSelect&#40;$bind $options $attribute&#41;</td>
      where $options is the options of the drop down and $attributes is empty

      and

      Code:
      #set&#40;$bind = "command.type_$key"&#41;
      i've also tried:

      Code:
      #springBind&#40;"command.type&#91;$key&#93;"&#41;
      <select name="type&#91;$key&#93;"> ... </select>
      ...
      and

      Code:
      #springBind&#40;"command.type_$key"&#41;
      <select name="type_$key"> ... </select>
      ...

      sorry for so much code. please let me know if anything doesn't make sense or you need anything else. thank you so much for your help, you are saving me from having to check myself into a mental institution.

      Comment


      • #4
        aha

        it was a simple matter of setting multiple getters and setters, or beanlets, if you will. knew it would be something that made me feel dumb.

        Comment


        • #5
          klb, could you explain that a little more? I'm having a similar problem with dynamic lists from which the user may select any number of items.

          Matthew

          Comment


          • #6
            ok, here's what fixed it. in my velocity form, i bound this form field:

            Code:
            "command.type&#91;$i&#93;.columnType"
            where $i is a simple iterator. note how there is a field with a property at the end. that's the important part.

            in velocity, type[$i].columnType is equivalent to type[$i].getColumnType().

            in this case, i have a several drop down menus. each will have a column number and a corresponding column value. the $i doesn't reflect the column number, it's just an iterator.

            now i create a class that sets the properties of this field:

            Code:
            package com.ccg.template;
            
            public class PayrollColumnBinder &#123;
             
                protected String columnNumber;
                protected String columnType;
                 
            	public String getColumnNumber&#40;&#41; &#123;
            		return columnNumber;
            	&#125;
            
            	public void setColumnNumber&#40;String columnNumber&#41; &#123;
            		this.columnNumber = columnNumber;
            	&#125;
            
            	public String getColumnType&#40;&#41; &#123;
            		return columnType;
            	&#125;
            
            	public void setColumnType&#40;String columnType&#41; &#123;
            		this.columnType = columnType;
            	&#125;
            &#125;
            in the controller, i know the number of fields i'm going to send to the page. so i keep that number handy, and use it to loop through setting up the getters and setters so spring will be able to bind to them.

            Code:
            for &#40;int i = 0; i < columnNumbers.size&#40;&#41;; i++&#41; &#123;
                        	
                            PayrollColumnBinder binder = new PayrollColumnBinder&#40;&#41;;
                            binder.setColumnNumber&#40;&#40;String&#41;columnNumbers.get&#40;i&#41;&#41;;
                            template.getType&#40;&#41;.add&#40;i, binder&#41;;
                            
                        &#125;
            "template" is the base class that holds the getters and setters for the form(s). columnNumbers is a list that contains my IDs ("column number"). note how i iterate through the sets, which matches the iteration in the velocity form.

            now, in the validator, i loop through again to check for errors.

            Code:
            Iterator i = template.getType&#40;&#41;.iterator&#40;&#41;;
                    while &#40;i.hasNext&#40;&#41;&#41; &#123;
                        PayrollColumnBinder binder = &#40;PayrollColumnBinder&#41;i.next&#40;&#41;;
                        String value = binder.getColumnType&#40;&#41;;
            ...
            if everything validates ok, <poof>magic happens</poof> and i'm onto the next page in my wizard.

            hope this helps!

            Comment

            Working...
            X