Click provides a rich set of Controls which support client side rendering and server side processing. This section covers the following topics:
While this section provides an overview how Controls work please see the Javadoc, which provides extensive information and examples.
Controls handle the processing of user input in the onProcess method and render their HTML display using the toString() method. The execution sequence for a Control being processed and rendered is illustrated below in Figure 1.
Figure 1. Post Sequence Diagram - created with Enterprise Architect courtesy Sparx Systems
In Click all control classes must implement the Control interface. The Control interface is depicted below in Figure 2.
Figure 2. Control Interface Diagram - created with Enterprise Architect courtesy Sparx Systems
Methods on the Control interface include:Click supports two styles of action listeners, the first is using the ActionListener interface which provides compile time safety. The second is to register the action listener via the setListener(Object, String) method where you specify the call back method via its name. This second style uses less lines of code, but has no compile time safety.
Examples of these two action listener styles are provided below:
public class ActionDemo extends BorderPage {All call back listener methods must return a boolean value. If they return true the further processing of other controls and page methods should continue. Otherwise if they return false, then any further processing should be aborted. By returning false you can effectively exit at this point and redirect or forward to another page. This execution logic is illustrated in the Page Execution Activity Diagram.
// Uses listener style 1
public ActionLink link = new ActionLink();
// Uses listener style 2
public ActionButton button = new ActionButton();
public ActionDemo() {
// Verbose but provides compile time safety
link.setActionListener(new ActionListener() {
public boolean onAction(Control source) {
return onLinkClick(source);
}
});
// Succinct but typos will cause runtime errors
button.setListener(this, "onButtonClick");
}
// Event Handlers ---------------------------------------------------------
public boolean onLinkClick(Control source) {
..
return true;
}
public boolean onButtonClick() {
..
return true;
}
}
Being able to stop further processing and do something else can be very handy. For example your Pages onRender() method may perform an expensive database operation. By returning false in an event handler you can skip this step and render the template or forward to the next page.
Extended control classes are provided in the Click Extras package net.sf.click.extras.control. Click Extras classes can contain dependencies to 3rd party frameworks.
A subset of these control classes are depicted below in Figure 3.
Figure 3. Package Class Diagram - created with Enterprise Architect courtesy Sparx Systems
The key control classes include:You can also aggregate controls to build more complex controls. For example the CreditCardField uses a Select control to render the different credit card types.
/com/mycorp/page/Login.propertiesIf you want to tailor messages for a particular page this is where to place them.
/click-page.propertiesIf you want messages to be used across your entire application this is where to place them.
/com/mycorp/control/CustomTextField.properties
/click-control.properties
Note when customizing the message properties you must include all the properties, not just the ones you want to override.
# Click Control messages field-maxlength-error={0} must be no longer than {1} characers field-minlength-error={0} must be at least {1} characters field-required-error=You must enter a value for {0} file-required-error=You must enter a filename for {0} label-required-prefix= label-required-suffix=<span class="required">*</span> label-not-required-prefix= label-not-required-suffix= not-checked-error=You must select {0} number-maxvalue-error={0} must not be larger than {1} number-minvalue-error={0} must not be smaller than {1} select-error=You must select a value for {0} table-first-label=First table-first-title=Go to first page table-previous-label=Prev table-previous-title=Go to previous page table-next-label=Next table-next-title=Go to next page table-last-label=Last table-last-title=Go to last page table-goto-title=Go to page table-page-banner=<span class="pagebanner">{0} items found, displaying {1} to {2}.</span> table-page-banner-nolinks= <span class="pagebanner-nolinks">{0} items found, displaying {1} to {2}.</span> table-page-links=<span class="pagelinks">[{0}/{1}] {2} [{3}/{4}]</span> table-page-links-nobanner=<span class="pagelinks-nobanner">[{0}/{1}] {2} [{3}/{4}]</span> table-no-rows-found=No records found. table-inline-first-image=/click/paging-first.gif table-inline-first-disabled-image=/click/paging-first-disabled.gif table-inline-previous-image=/click/paging-prev.gif table-inline-previous-disabled-image=/click/paging-prev-disabled.gif table-inline-next-image=/click/paging-next.gif table-inline-next-disabled-image=/click/paging-next-disabled.gif table-inline-last-image=/click/paging-last.gif table-inline-last-disabled-image=/click/paging-last-disabled.gif table-inline-page-links=Page {0} {1} {2} {3} {4} # Message displayed when a error occurs when the application is in "production" mode production-error-message= <div id='errorReport' class='errorReport'>The application encountered an unexpected error. </div>
Container enables components to add, remove and retrieve other controls.
Listed below are example Containers:
Figure 4. Containers Class Diagram
The following classes provides convenient extension points for creating custom Containers: Lets cover each of them here.public class Div extends AbstractContainer {Lets try out the newly created Container above: (note the MockContext used in this test is described in the Mock Test Support documentation)
public Div(String name) {
super(name);
}
public String getTag() {
// Return the control's HTML tag.
return "div";
}
}
public class Test {
public static void main (String args[]) {
// Create mock context in which to test the container.
MockContext.initContext();
// Create a div instance called "mydiv"
String containerName = "mydiv";
Div mydiv = new Div(containerName);
// Add a control to the container
mydiv.add(new TextField("myfield"));
System.out.println(mydiv);
}
}
Executing the above example results in the following output:
<div name="mydiv" id="mydiv">
<input type="text" name="myfield" id="myfield" value="" size="20" />
</div>
Below is an example of how AbstractContainerField might be used:
public class FieldAndContainer extends AbstractContainerField {
public FieldAndContainer(String name) {
super(name);
}
// Return the html tag to render
public String getTag() {
return "div";
}
}
To test the new class we use the following snippet:
public class Test {Executing the snippet produces the output:
public static void main (String args[]) {
// Create mock context in which to test the container.
MockContext.initContext();
// Create a FieldContainer instance called "field_container"
String containerName = "field_container";
FieldAndContainer fieldAndContainer = new FieldAndContainer(containerName);
// Add a couple of fields to the container
fieldAndContainer.add(new TextField("myfield"));
fieldAndContainer.add(new TextArea("myarea"));
System.out.println(fieldAndContainer);
}
}
<div name="field_container" id="field_container">
<input type="text" name="myfield" id="myfield" value="" size="20"/>
<textarea name="myarea" id="myarea" rows="3" cols="20"></textarea>
</div>
However for custom or complex layouts, Form is not always the best choice.
There are two approaches for creating custom layouts.
// EmployeePage.javaLets imagine we want to create a layout using Div <div> and HTML List <ol> tags.
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("submit", "Add Employee"));
// Add form the page
addControl(form);
}
}
We could provide the markup for the employee.htm template as shown below, using a template engine such as Velocity:
<!-- employee.htm -->Using a CSS stylesheet, the markup above can be styled and transformed into a fancy looking form.
${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()}
There are pros and cons to using the template approach.
One of the advantages of the Template approach, 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 of the Template approach, 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.
It is also possible to "generify" the layout using template engines. Macro.vm is an example of a generic form layout using Velocity.
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 shown in the example below.
When creating custom layouts, the HTML construct List <ul> is pretty useful. Since Click does not provide this component, lets create it as shown here:
// HtmlList.java
// Create a list <ol> html element, that accepts <li> elements as children
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);
}
}
// ListItem.javaAnother component that will be used in the example below is a FieldLabel which renders an HTML label element for a specified Field.
// Create a listItem <li> element
public class ListItem extends AbstractContainer {
public String getTag() {
return "li";
}
}
// FieldLabel.javaNow the form can be assembled.
// Create an html <label> element for a specified Field
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 which produces:
//
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());
}
}
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 a custom layout:
// EmployeePage.javaNow the employee.htm template would only need to specify the name of the top level component, in this case form.
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("submit", "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);
}
}
<!--employee.htm-->which produces the following markup:
${form}
<!-- employee.htm -->Again using a CSS stylesheet, the markup above can be styled and transformed into a fancy looking form.
<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="submit" id="form_submit" value="Add Employee"/>
</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 has to be updated. No need to change the template as the layout is taken care of by the CSS and markup produced by the components.
A disadvantage is that it is harder to visualize what output would be rendered by the containers.
Whether you use the template or programmatic layout approach, is up to you. Both work well and have advantages and disadvantages over the other.