07
Sep 09

Grails multi-control property editor implementation

Data binding using current frameworks, Spring, Grails, etc… usually provide a way to marshal/unmarshal input control data into data types. The frameworks usually transparently handle the most common datatypes and allow you to configure/implement others. That process is pretty straightforward and in many cases trivial. When using Spring MVC, for example, one would extend java.beans.PropertyEditorSupport and implement the setAsText and maybe getAsText methods.

I think this handles a large portion of data bindings in web applications, but there wasn’t (at least in frameworks I worked with), a straightforward/reusable way of binding multiple html fields to one data type. I’ve always either had to roll my own custom solution and/or rely on a command (transfer) objects to have a one to one representation of form fields, which I would then bind to the domain object inside a controller action. Some use cases can really benefit from command objects and its decoupling from the domain, but I try to bind directly to domain objects as much as I can, reducing the amount of noise that transfer objects generate. Another solution which I’ve personally never implemented because I thought it was too ugly and leaked too much domain binding implementation into the view layer, was to use a hidden field and then format the multi-field controls into a single text value representation during a submit event, though allowing you to still have a one to one binding between the hidden control and the domain object field.

Today I discovered Grails StructuredPropertyEditor, and after figuring out how to implement it, I was on my way to reducing a bunch of binding boilerplate. This solution is not documented, as many other grails related intricacies, though it does seem like a stable/supported API. After digging through the source code, I figured it out and wanted to share it. So here it is…

I have a form which collects date and time information. The date uses a custom datepicker control and the time uses a custom time control. So two controls, that I need to marshal into one DateTime field in my domain class.

We start off by implementing a custom property editor, which extends PropertyEditorSupport and implements a grails specific StructuredPropertyEditor interface. I decided to use an excellent DateTime implementation class which comes with Joda Time library to store the date/time information. You can easily use any other type of your liking, including the java.lang.Date class.

  public class DateTimePropertyEditor extends PropertyEditorSupport implements StructuredPropertyEditor {

    DateTimeFormatter dateTimeFormat;

    public DateTimePropertyEditor(String format) {
      this.dateTimeFormat = DateTimeFormat.forPattern(format);
    }

    public List getRequiredFields() {
      List requiredFields = new ArrayList();
      requiredFields.add("date");
      return requiredFields;
    }

    public List getOptionalFields() {
      List optionalFields = new ArrayList();
      optionalFields.add("time");
      return optionalFields;
    }

    public Object assemble(Class type, Map fieldValues) throws IllegalArgumentException {
      if (!fieldValues.containsKey("date")) {
        throw new IllegalArgumentException("Can't populate a date without a year");
      }

      String date = (String) fieldValues.get("date");

      try {
        if (StringUtils.isBlank(date)) {
          throw new IllegalArgumentException("Can't populate date/time without a date");
        }
        String time = (String) fieldValues.get("time");
        if (StringUtils.isBlank(time)) time = "00:00 AM";
        String dateTime = date + " " + time;

        return dateTimeFormat.parseDateTime(dateTime);
      }
      catch (Exception nfe) {
        throw new IllegalArgumentException("Unable to parse structured DateTime from request for date.");
      }
    }

  }

Now that we have the property editor defined, we should create a registrar to register it with the application.

  public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

      public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(DateTime.class, new DateTimePropertyEditor("MM/dd/yyyy hh:mm a"));
      }
  }

Now that we have both the property editor and its registrar, we have to declare the registrar bean in the spring beans resources file /grails-app/conf/spring/resources.groovy

  beans = {
    propertyEditorRegistrar(CustomPropertyEditorRegistrar)

    // .... other beans ....
  }

That’s it, we’re done. The only thing left to do is actually create the html controls. There are a few conventions that must be followed in order for the StructuredPropertyEditor to grab the right fields.

  1. You must have a hidden field that’s there pretty much to declare the name of the field you’re binding to and also to state that it’s a ‘struct’ field, which means it’s a structured control made up of various other controls.
  2. The second rule, is that the names of the sub-fields that you are using in StructuredPropertyEditor, in our case ‘date’ and ‘time’, have to be prepended with the field name. So if the field name is ‘myField’, the sub-fields would be prepended with ‘myField_’. In our case, the html inputs should be named ‘myField_date’ and ‘myField_time’.

Here is a GSP snippet…

  <g:hiddenField name="startTime" value="struct" />
  <g:textField name="startTime_date"/>
  <g:textField name="startTime_time"/>

Notice how the field we’re binding is ‘startTime’ and the sub-fields that make up the whole, are ‘startTime_date’ and ‘startTime_time’ linked to ‘date’ and ‘time’ fields used in the assembly of the final field.

Now, all you have to do in the controller, is bind as you usually would, in my case…

bindData(domainObj, params)

That’s it, quick and simple and you have reusable marshaling of multi-control data types.

Tags: , , , ,

5 comments

  1. Thanks for this very nice article. I’ve been googling for this information. Best regards, Søren

  2. Thanks for your post, Its a great article on Property Editor usage in Grails. This approach will apply the property editor to all the controllers using DataTime class.

    Is there a way to associate the PropertyEditor to a specific Controller. I might want to have two PropertyEditor’s for DateTime class, and want to associate them to different controllers.

  3. OMG OMG!!! This just gave me some hope! I’m going to try this after my lunch and see how it goes! 😀

  4. Hi? Am trying to follow this but have an Integer field instead of date and it has failed

    class YearMonthPropertyEditorRegistrar implements PropertyEditorRegistrar{

    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Integer.class, new YearMonthPropertyEditor());
    }
    

    }that is my registrar and here is my custom editor

    public class YearMonthPropertyEditor extends PropertyEditorSupport implements StructuredPropertyEditor{

    YearMonth yearMonth

    public YearMonthPropertyEditor(){ yearMonth = new YearMonth() }

    public List getRequiredFields() { log.debug("getRequiredFields") //TODO do we have any List requiredFields = new ArrayList(); return requiredFields; }

    public List getOptionalFields() { log.debug("getOptionalFields") List optionalFields = new ArrayList(); optionalFields.add("years"); optionalFields.add("months"); return optionalFields; }

    // @Override // void setAsText(String text) { // log.debug(“setting it to this : ” + text) // //return (years * 12 ) + months // } // // @Override // String getAsText() { // log.debug(“retreiving value”) // return “12” // }

    public Object assemble(Class type, Map fieldValues) throws IllegalArgumentException {
        if (fieldValues.containsKey("years")) {
            yearMonth.years = (Integer) fieldValues.get("years")
        }

    if (fieldValues.containsKey("months")) {
        yearMonth.months = (Integer) fieldValues.get("months")
    }
    log.debug("The years are: ${years} and months: ${months} field values" + fieldValues.each{it} + ", type:" + type)
    if(yearMonth.years == null &amp;&amp; yearMonth.months == null) return 0
    return (yearMonth.years * 12 ) + yearMonth.months
    

    }

    } it seems to call the commented out methods only and passes in the value of struct Thanks

  5. Hi,

    did your errors object get filled if there are invalid inputs from the user while EDIT the date? E.g. are you redirected to edit.gsp after …throw new IllegalArgumentException(“Unable to parse structured DateTime from request for date.”); It doesn’t work – so what have to be done to get the error handling done?

    Thx for any advice! Regards, Roland.

Leave a comment