| Apache Click > Index > Click 2.1 Wishlist |
There are scenarios where manipulating a Control's HTML imports would be beneficial, especially for Ajax components.
Ajax libraries such as Prototype, JQuery, YUI etc promotes Unobtrusive JavaScript, which is basically the ability to add functionality and events to HTML markup without mixing the JavaScript and markup. In other words all the JavaScript can be added to the Page HEAD section.
The only thing a Control needs to do is render the necessary markup, and append the JavaScript to the HTML imports.
Because one cannot manipulate a Control's HTML imports the only way to write JavaScript/Ajax controls currently, is to create subclasses of a specific control and override the method getHtmlImports to return the required JavaScript.
If we provide the ability to manipulate the HTML imports from outside the Control, it becomes possible to write Ajax helpers that can add the necessary JavaScript without having to subclass Controls. It might even be possible to write a single generic Ajax helper to manipulate all of the Controls.
As far as implementation goes, the Control interface could provide the methods public boolean hasImports and public List getImports.
getImports can be used to add new HTML imports to the Controls, while hasImports indicates whether imports are available.
The Form control adds a couple of hidden fields to its list of controls and this can mess up insertion logic because the user won't be aware of these hidden fields.
The obvious solution is for Form to add the hidden fields later, at render time (perhaps by overriding onRender). There are two problems with this approach however. If Form subclasses override onRender and forget to invoke super.onRender, Form will break. But the bigger issue is if users skip the onRender phase by returning false from a listener.
Seems we need a solution where a callback to the Form can be registered which will be fired at rendering time. Thus its not possible for users to override or interfere with the callback. We could piggyback on an enhanced ControlRegistry which allows one to register callbacks between event phases. For example a POST_RENDER_PHASE callback could be used by form to add the hidden fields.
Currently Form does a check if its submitted and in doing so decides whether its child controls will be processed or not. This restriction works well for Fields, however problems arise when adding components such as Tables or Trees which needs to be processed even when Form is not submitted. Ajaxified Fields also falls into this category.
One option worth investigating is moving the Form.onFormSubmission check to the Field class which allows the lower level component making the decision on whether to process itself or not. Tables and Trees will now integrate properly with Forms without having to resort to workarounds.
Issue: this will effect backwards compatibility as custom Fields will have to guard against Form submissions.
Repeater is an invisible container that is bound to a list of items (e.g. Products), and generates dynamic components for each item in the list.
As the Repeater iterates its items, it will allow one to dynamically construct UI components for each item.
Repeater will also ensure that its child components are properly indexed, meaning the rendered HTML markup will have the item's index appended to names and ID's.
A common use case for this is dynamic forms where certain UI aspects must be repeated a number of times. For example a form might have a text field where users can enter a category. But what if users can specify multiple categories? How many text fields should be rendered? In such scenarios the Repeater can render the field and two buttons, "Add" and "Remove" next to each field. This allows the user to dynamically add more text fields on the fly.
Example repeater:
public class ProductRepeater extends Repeater { /** * buildRow method must be implemented and acts as a template for each item. * * @param item (product) the current item which must be built * @param row the row for the current item * @param index the index of the current item */ public void buildRow(final Object item, final RepeaterRow row, final int index) { // Each product will have a form to edit the product Form form = new Form("form") { row.add(form); FieldSet fieldSet = new FieldSet("product"); form.add(fieldSet); fieldSet.add(new TextField("name")).setRequired(true); fieldSet.add(new DoubleField("price")).setRequired(true); Submit save = new Submit("save"); save.setActionListener(new ActionListener() { public boolean onAction(Control source) { return onSubmit(item, index); } }); fieldSet.add(save); } /** * The onSubmit event */ public boolean onSubmit(Object item, int index) { List products = getProducts(); // Find the current items row in the repeater RepeaterRow row = (RepeaterRow) repeater.getControls().get(index); // Lookup the Form in the row Form form = (Form) container.getControl("form"); if (form.isValid()) { // Copy the form values to the item form.copyTo(item); } return true; } }
Example page:
public RepeaterDemo extends Page { public void onInit() { Repeater repeater = new ProductRepeater("repeater"); repeater.setItems(getProducts()); addControl(repeater); } private List getProducts() { ... } }
Other functionality include the ability to add a new item which automatically creates and builds a new row. Moving items up and down will also move the Repeaters row up and down.