Controls such as Form takes care of layout and error reporting automatically, and for many use cases the auto-layout approach is good enough. It is certainly very productive.
However for custom or complex layouts, auto-layout is not always the best choice. There are two approaches for creating custom layouts.
Template approach - use a template engine such as Velocity, Freemarker or JSP to declare the layout as HTML markup.
Programmatic approach - build custom layout components using Java. This option is very similar to building components using Swing.
The Template approach separates the Page and layout logic. The Page is used to implement the presentation logic such as creating controls, registering listeners and copying data to domain objects, while the template is used to layout the Page controls.
Lets walk through an example using the template approach. Below we create an EmployeePage which contains a Form and a bunch of fields and submit button.
// EmployeePage.java public EmployeePage extends Page { private Form form; public void onInit() { // Create form Form form = new Form("form"); // Add a couple of fields to the form form.add(new TextField("firstname")); form.add(new TextField("lastname")); form.add(new IntegerField("age")); form.add(new DoubleField("salary")); // Add a submit button to form form.add(new Submit("add", "Add Employee")); // Add form the page addControl(form); } }
Lets imagine we want to create a layout using the HTML tags, <div> and <ol>.
We would then provide the markup for the employee.htm
template as shown below, using a template engine such as Velocity:
<!-- employee.htm --> ${form.startTag()} <div style="margin: 1em;"> <ol> <li> <label for="firstname">Firstname:</label> ${form.fields.firstname} </li> <li> <label for="lastname">Lastname:</label> ${form.fields.lastname} </li> <li> <label for="age">Age:</label> ${form.fields.age} </li> <li> <label for="salary">Salary:</label> ${form.fields.salary} </li> </ol> </div> ${form.fields.submit} ${form.endTag()}
Using CSS the markup above can further be styled and transformed into a nice looking form.
There are pros and cons to using the template approach.
One of the advantages is that the layout is explicit and one can easily tweak it if needed. For example instead of using divs and ordered lists, one can change the template to leverage a table layout.
A disadvantage is added redundancy. In the example above we created the fields in Java, and laid them out using markup in the template. If the requirements should change to add a new field for example, one will have to add the field in the Page as well as the template.
However it is possible to "generify" the layout using template engines such as Velocity, Freemarker and JSP. Macro.vm is an example of a generic and reuable form layout using Velocity.
Panels provide another good way to build generic and reusable template based layouts.
Once your generic templates are in place, they can easily be reused in your project or even shared across multiple projects.
To combat the redundancy introduced by the Template approach, you can take a programmatic approach and use normal Java and some Click classes to build custom layouts.
Click extras provides two useful classes in this situation namely, HtmlForm and HtmlFieldSet.
Unlike Form and FieldSet which renders its controls using a Table layout, HtmlForm and HtmlFieldSet renders its controls in the order they were added and does not add any extra markup. HtmlForm will be used in the examples below.
To make it easy to compare the two layout approaches we will recreate the example from the template layout section, but using the programmatic approach.
When creating custom layouts, the HTML construct List <ul> is pretty useful. Since Click does not provide this component, we will create it as shown below. First we create the HTML list element <ol>, to which list item elements <li> can be added:
// HtmlList.java public class HtmlList extends AbstractContainer { public String getTag() { return "ol"; } // Can only add ListItems: <li> tags public Control add(Control control) { if (!(control instanceof ListItem)) { throw new IllegalArgumentException("Only list items can be added."); } return super.add(control); } }
Next we create the HTML list item element <li>:
// ListItem.java public class ListItem extends AbstractContainer { public String getTag() { return "li"; } }
Another component that will be used in the example is a FieldLabel which renders an HTML label element for a target Field.
// FieldLabel.java public class FieldLabel extends AbstractControl { private Field target; private String label; public FieldLabel(Field target, String label) { this.target = target; this.label = label; } public String getTag() { return "label"; } // Override render to produce an html label for the specified field. public void render(HtmlStringBuffer buffer) { // Open tag: <label buffer.elementStart(getTag()); // Set attribute to target field's id setAttribute("for", target.getId()); // Render the labels attributes appendAttributes(buffer); // Close tag: <label for="firstname"> buffer.closeTag(); // Add label text: <label for="firstname">Firstname: buffer.append(label); // Close tag: <label for="firstname">Firstname:</label> buffer.elementEnd(getTag()); } }
Now the form can be assembled. Continuing with the employee example
from the template approach, we again
create an EmployeePage
, but this time an
HtmlForm
and HtmlList
is used
to create the custom layout:
// EmployeePage.java public class EmployeePage extends Page { // A form instance variable private HtmlForm form; // Build the form when the page is initialized public void onInit() { // Create an HtmlForm which is ideal for composing manual layouts form = new HtmlForm("form"); // Create a list and add it to the form. HtmlList list = new HtmlList(); form.add(list); // Add firstname field and pass in its name, label and the list to add the field to addTextField("firstname", "Firstname:", list); addTextField("lastname", "Lastname:", list); addTextField("age", "Age:", list); addTextField("salary", "Salary:", list); // Add a submit button to form form.add(new Submit("add", "Add Employee")); // Add the form to the page addControl(form); } // Provide a helper method to add fields to the form private void addTextField(String nameStr, String labelStr, List list) { // Create a new ListItem <li> and add it to the List ListItem item = new ListItem(); list.add(item); // Create a textfield with the specified name Field field = new TextField(nameStr); // Create a field label, which associates the label with the field id. // label.toString would output: <label for="firstname">Firstname:</name> FieldLabel label = new FieldLabel(field, labelStr); // Next add the label and field to the list item. // item.toString would then produce: // <li> // <label for="firstname">Firstname:</name> // <input type="text" name="firstname" id="form_firstname" value="" size="20"/> // </li> // item.add(label); item.add(field); } }
And lastly the employee.htm
template would only
need to specify the name of the top level component, in this case
form
.
<!--employee.htm-->
${form}
which produces the following markup:
<form method="post" id="form" action="/myapp/employee.htm"> <input type="hidden" name="form_name" id="form_form_name" value="form"/> <ol> <li> <label for="firstname">Firstname:</label> <input type="text" name="firstname" id="form_firstname" value="" size="20"/> </li> <li> <label for="lastname">Lastname:</label> <input type="text" name="lastname" id="form_lastname" value="" size="20"/> </li> <li> <label for="age">Age:</label> <input type="text" name="age" id="form_age" value="" size="20"/> </li> <li> <label for="salary">Salary:</label> <input type="text" name="salary" id="form_salary" value="" size="20"/> </li> </ol> <input type="submit" name="add" id="form_add" value="Add Employee"/> </form>
Again using a CSS stylesheet, the markup above can be styled and transformed into a fancy looking form.
There is a live demo showing the programmatic approach.
The advantage of the programmatic approach is that there is no redundancy. Each Field is created and added using normal Java. There is no need to specify where the Field must reside in the markup.
If new requirements arrive and more fields added, only the Page needs to change. There is no need to change the template as the layout is taken care of by CSS and the markup produced by the components.
Disadvantages are that more upfront work is needed to write the components and it is more difficult to visualize what output would be rendered by the components.
However once your custom layout components are in place, they can easily be reused in your project or even shared across multiple projects.
Whether you use the template or programmatic layout approach, is up to you. Both work well and have advantages and disadvantages over the other.