Tuesday, September 14, 2010

Apache Click 2.2.0: Explicit Binding

In this three part series I'll blog about some of the new features added in 2.2.0, namely:

  1. DataProviders (Japanese version)
  2. Explicit binding (Japanese version)
  3. Dynamic Form Validation ( Japanese version)

    In this second installment I'll cover explicit binding which allows developers to bind, or set, a control's value to incoming request parameters. Not all controls support binding though. The most common bindable controls include Field, Form and Abstractlink. Explicit binding simplifies dynamic Page and Form behavior as it allows you to bind and query control values whenever you need to, for example in the onInit event or the Page constructor.

    When talking about explicit binding it is also worth mentioning implicit binding.
    Implicit binding occurs automatically every request as part of the onProcess event. So Control values will automatically be set once the onProcess event has occurred.Click Controls uses a convention of handling bind logic in a method called bindRequestValue().

    A typical bindRequestValue() implementation looks like this:

    public void bindRequestValue() {
        Context context = getContext();
        String controlName = getName();
        String value = context.getRequestParameter(controlName);
        if(value != null) {
            setValue(value);
        }
    }
    
    // For completeness sake we show the onProcess implementation as well.
    // onProcess delegates the binding logic to bindRequestValue
    public void onProcess() {
        bindRequestValue();
        ...
    }
    
    

    Generally the control name is used to lookup the incoming request parameter, but
    other conventions can be used as well.

    Under most circumstances, implicit binding is all you need. But sometimes you might want to know the value of a control before the onProcess event occurs. For example, within the onInit()
    event, you might want to check which drop-down value a user selected in order to
    add another Field to the Form. Remember, only Controls that are created and
    attached to their parent page/container partakes in the onProcess event. That's why it's important to create and attach Controls to their parents prior to the onProcess event.

    As you probably know already, the onInit event occurs before onProcess, and because implicit binding only occurs during onProcess, the Control value is not available for querying from onInit.

    So how can you find out the value of a Control during the onInit event? This is
    where explicit binding comes into play. Explicit binding allows developers to
    dictate when the Control value is bound. So how can one explicitly bind the Control value?
    By invoking the Control method, bindRequestValue(). (Recall that not all Controls
    support binding and might not provide a bindRequestValue() method.)

    There are a couple of caveats to be aware of when invoking bindRequestValue
    directly.

    • Fields should only be bound if their parent Form has been submitted, otherwise if you have multiple forms on the page, you might end up in a situation where form1 fields could be bound to form2 fields.
    • Forwarded requests have already been processed and should not be used for binding purposes.

    To make things easier, Click provides a couple of helper methods that takes care
    of the caveats mentioned above. These helper methods are available from the
    ClickUtils class which sports a variety of bind() and bindAndValidate() methods.
    You can even pass in Containers such as Form to these methods and all child
    controls will be bound.

    The bindAndValidate() method will both bind and validate a Field or Form.



    It's worth mentioning that it's also possible to lookup request parameters directly through the Context object instead of having to bind the control value.

    Let's look at some examples next.

    In this first example we will look at a dynamic Form where a Checkbox determines
    whether a TextField should be added to the Form or not.


    public class DynamicFormDemo extends Page {
    
        @Override
        public void onInit() {
            super.onInit();
            Form form = new Form("form");
            addControl(form);
            Checkbox chk = new Checkbox("chk");
            form.add(chk);
    
            Submit ok = new Submit("ok");
            form.add(ok);
    
            // Explicitly bind the checkbox in the onInit event which allows us to query
            // whether the Checkbox was checked or not.
            ClickUtils.bind(chk);
            if(chk.isChecked()) {
                form.add(new TextField("name"));
            }
        }
    }
    

    We use the ClickUtils.bind() method to explicitly bind the Checkbox value so we
    can query whether it is checked or not.

    In this second example we expand the first by adding a Select field as well.



    public class DynamicFormDemo extends Page {
    
        @Override
        public void onInit() {
            super.onInit();
            Form form = new Form("form");
            addControl(form);
            Checkbox chk = new Checkbox("chk");
            form.add(chk);
            Select countries = new CountrySelect("countries");
            countries.getOptionList().add(Option.EMPTY_OPTION);
            form.add(countries);
    
            Submit ok = new Submit("ok");
            form.add(ok);
    
            // Explicitly bind the Form (and all it's child controls) in the onInit
            // event, allowing us to query whether he user checked the Checkbox and
            // which country was selected.
            ClickUtils.bind(form);
            if (chk.isChecked()) {
                form.add(new TextField("name"));
            }
    
            if (StringUtils.isNotBlank(countries.getValue())) {
                form.add(new TextField("location"));
            }
        }
    }
    

    Note, instead of binding the Checkbox and Select separately we pass the Form to
    the ClickUtils.bind() method. When passing a container such as a Form to the
    ClickUtils.bind() methods, all bindable controls in the container will have their
    values bound. This provides an easy shortcut to quickly bind multiple controls.

    In this third and final example we show how to both bind and validate a form.

    public class DynamicFormDemo extends BorderPage {
    
        @Override
        public void onInit() {
            super.onInit();
            Form form = new Form("form");
            addControl(form);
            Checkbox chk = new Checkbox("chk");
            form.add(chk);
            Select countries = new CountrySelect("countries", true);
            countries.getOptionList().add(Option.EMPTY_OPTION);
            form.add(countries);
    
            Submit ok = new Submit("ok");
            form.add(ok);
    
            // Explicitly bind and check that the Form (and all it's child
            // controls) is valid in the onInit event, allowing us to safely
            // query whether he user checked the Checkbox and
            // which country was selected.
            if (ClickUtils.bindAndValidate(form)) {
                if (chk.isChecked()) {
                    form.add(new TextField("name"));
                }
    
                // The form validation passed and since the countries field is required
                // we can safely assume that a valid country has been selected
                form.add(new TextField("location"));
            }
        }
    }
    

    The ClickUtils.bindAndValidate() methods will bind and validate the field/s and return true if the field/s are valid, false otherwise.

    No comments: