blob: 99f56e9a66853e662918704e62f08d4b1a63ad9f [file] [log] [blame]
Wicket distribution comes with a number of built-in AJAX components ready to be used. Some of them are the ajaxified version of common components like links and buttons, while others are AJAX-specific components.
AJAX components are not different from any other component seen so far and they don't require any additional configuration to be used. As we will shortly see, switching from a classic link or button to the ajaxified version is just a matter of prepending “Ajax” to the component class name.
This paragraph provides an overview of what we can find in Wicket to start writing AJAX-enhanced web applications.
h3. Links and buttons
In the previous paragraph we have already introduced component AjaxLink. Wicket provides also the ajaxified versions of submitting components SubmitLink and Button which are simply called AjaxSubmitLink and AjaxButton. These components come with a version of methods onSubmit, onError and onAfterSubmit that takes in input also an instance of @AjaxRequestTarget@.
Both components are in package @org.apache.wicket.ajax.markup.html.form@.
h3. Fallback components
Building an entire site using AJAX can be risky as some clients may not support this technology. In order to provide an usable version of our site also to these clients, we can use components @AjaxFallbackLink@ and @AjaxFallbackButton@ which are able to automatically degrade to a standard link or to a standard button if client doesn't support AJAX.
h3. AJAX Checkbox
Class @org.apache.wicket.ajax.markup.html.form.AjaxCheckBox@ is a checkbox component that updates its model via AJAX when user changes its value. Its AJAX callback method is @onUpdate(AjaxRequestTarget target)@. The component extends standard checkbox component @CheckBox@ adding an @AjaxFormComponentUpdatingBehavior@ to itself (we will see this behavior later in [paragraph 19.3.3|guide:ajax_3]).
h3. AJAX editable labels
An editable label is a special label that can be edited by the user when she/he clicks on it. Wicket ships three different implementations for this component (all inside package @org.apache.wicket.extensions.ajax.markup.html@):
* *AjaxEditableLabel*: it's a basic version of editable label. User can edit the content of the label with a text field. This is also the base class for the other two editable labels.
* *AjaxEditableMultiLineLabel*: this label supports multi-line values and uses a text area as editor component.
* *AjaxEditableChoiceLabel*: this label uses a drop-down menu to edit its value.
Base component AjaxEditableLabel exposes the following set of AJAX-aware methods that can be overriden:
* *onEdit(AjaxRequestTarget target)*: called when user clicks on component. The default implementation shows the component used to edit the value of the label.
* *onSubmit(AjaxRequestTarget target)*: called when the value has been successfully updated with the new input.
* *onError(AjaxRequestTarget target)*: called when the new inserted input has failed validation.
* *onCancel(AjaxRequestTarget target)*: called when user has exited from editing mode pressing escape key. The default implementation brings back the label to its initial state hiding the editor component.
Wicket module wicket-examples contains page class @EditableLabelPage.java@ which shows all these three components together. You can see this page in action at "http://www.wicket-library.com/wicket-examples-6.0.x/ajax/editable-label":http://www.wicket-library.com/wicket-examples-6.0.x/ajax/editable-label :
!edit-label-example-screenshot.png!
h3. Autocomplete text field
On Internet we can find many examples of text fields that display a list of suggestions (or options) while the user types a text inside them. This feature is known as autocomplete functionality.
Wicket offers an out-of-the-box implementation of an autocomplete text field with component @org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteTextField@.
When using AutoCompleteTextField we are required to implement its abstract method getChoices(String input) where the input parameter is the current input of the component. This method returns an iterator over the suggestions that will be displayed as a drop-down menu:
!autocomplete-example-screenshot.png!
Suggestions are rendered using a render which implements interface @IAutoCompleteRenderer@. The default implementation simply calls toString() on each suggestion object. If we need to work with a custom render we can specify it via component constructor.
AutoCompleteTextField supports a wide range of settings that are passed to its constructor with class @AutoCompleteSettings@.
One of the most interesting parameter we can specify for @AutoCompleteTextField@ is the throttle delay which is the amount of time (in milliseconds) that must elapse between a change of input value and the transmission of a new Ajax request to display suggestions. This parameter can be set with method @setThrottleDelay(int)@:
{code}
AutoCompleteSettings settings = new AutoCompleteSettings();
//set throttle to 400 ms: component will wait 400ms before displaying the options
settings.setThrottleDelay(400);
//...
AutoCompleteTextField field = new AutoCompleteTextField<T>("field", model) {
@Override
protected Iterator getChoices(String arg0) {
//return an iterator over the options
}
};
{code}
Wicket module wicket-examples contains page class @AutoCompletePagePage.java@ which shows an example of autocomplete text field. The running example is available at "http://www.wicket-library.com/wicket-examples-6.0.x/ajax/autocomplete":http://www.wicket-library.com/wicket-examples-6.0.x/ajax/autocomplete .
h3. Modal window
Class @org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow@ is an implementation of a "modal window":http://en.wikipedia.org/wiki/Modal_window based on AJAX:
!modal-window-example-screenshot.png!
The content of a modal window can be either another component or a page. In the first case the id of the component used as content must be retrieved with method getContentId().
If instead we want to use a page as window content, we must implement the inner interface @ModalWindow.PageCreator@ and pass it to method @setPageCreator@. The page used as content will be embedded in a <iframe> tag.
To display a modal window we must call its method @show(AjaxRequestTarget target)@. This is usually done inside the AJAX callback method of another component (like an @AjaxLink@). The following markup and code are taken from project @BasicModalWindowExample@ and illustrate a basic usage of a modal window:
*HTML:*
{code:html}
<body>
<h2>Modal Windod example</h2>
<a wicket:id="openWindow">Open the window!</a>
<div wicket:id="modalWindow"></div>
</body>
{code}
*Java Code:*
{code}
public HomePage(final PageParameters parameters) {
super(parameters);
final ModalWindow modalWindow = new ModalWindow("modalWindow");
Label label = new Label(modalWindow.getContentId(), "I'm a modal window!");
modalWindow.setContent(label);
modalWindow.setTitle("Modal window");
add(modalWindow);
add(new AjaxLink("openWindow") {
@Override
public void onClick(AjaxRequestTarget target) {
modalWindow.show(target);
}
});
}
{code}
Just like any other component also @ModalWindow@ must be added to a markup tag, like we did in our example using a <div> tag. Wicket will automatically hide this tag in the final markup appending the style value display:none.
The component provides different setter methods to customize the appearance of the window:
* *setTitle(String)*: specifies the title of the window
* *setResizable(boolean)*: by default the window is resizeable. If we need to make its size fixed we can use this method to turn off this feature.
* *setInitialWidth(int) and setInitialHeight(int)*: set the initial dimensions of the window.
* *setMinimalWidth(int) and setMinimalHeight(int)*: specify the minimal dimensions of the window.
* *setCookieName(String)*: this method can be used to specify the name of the cookie used on client side to store size and position of the window when it is closed. The component will use this cookie to restore these two parameters the next time the window will be opened. If no cookie name is provided, the component will not remember its last position and size.
* *setCssClassName(String)*: specifies the CSS class used for the window.
* *setAutoSize(boolean)*: when this flag is set to true the window will automatically adjust its size to fit content width and height. By default it is false.
The modal window can be closed from code using its method @close(AjaxRequestTarget target)@. The currently opened window can be closed also with the following JavaScript instruction:
{code}
Wicket.Window.get().close();
{code}
@ModalWindow@ gives the opportunity to perform custom actions when window is closing. Inner interface @ModalWindow.WindowClosedCallback@ can be implemented and passed to window's method @setWindowClosedCallback@ to specify the callback that must be executed after window has been closed:
{code}
modalWindow.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
@Override
public void onClose(AjaxRequestTarget target) {
//custom code...
}
});
{code}
h3. Tree repeaters
Class @org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree@ is the base class of another family of repeaters called tree repeaters and designed to display a data hierarchy as a tree, resembling the behavior and the look & feel of desktop tree components. A classic example of tree component on desktop is the tree used by nearly all file managers to navigate file system:
!file-system-trees.png!
Because of their highly interactive nature, tree repeaters are implemented as AJAX components, meaning that they are updated via AJAX when we expand or collapse their nodes.
The basic implementation of a tree repeater shipped with Wicket is component @NestedTree@. In order to use a tree repeater we must provide an implementation of interface @ITreeProvider@ which is in charge of returning the nodes that compose the tree.
Wicket comes with a built-in implementation of ITreeProvider called TreeModelProvider that works with the same tree model and nodes used by Swing component @javax.swing.JTree@. These Swing entities should be familiar to you if you have previously worked with the old tree repeaters (components @Tree@ and @TreeTable@) that have been deprecated with Wicket 6 and that are strongly dependent on Swing-based model and nodes. @TreeModelProvider@ can be used to migrate your code to the new tree repeaters.
In the next example (project @CheckBoxAjaxTree@) we will build a tree that displays some of the main cities of three European countries: Italy, Germany and France. The cities are sub-nodes of a main node representing the relative county. The nodes of the final tree will be also selectable with a checkbox control. The whole tree will have the classic look & feel of Windows XP. This is how our tree will look like:
!AJAX-tree-repeater.png!
We will start to explore the code of this example from the home page. The first portion of code we will see is where we build the nodes and the @TreeModelProvider@ for the three. As tree node we will use Swing class @javax.swing.tree.DefaultMutableTreeNode@:
{code}
public class HomePage extends WebPage {
public HomePage(final PageParameters parameters) {
super(parameters);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Cities of Europe");
addNodes(addNodes(root, "Italy"), "Rome", "Venice", "Milan", "Florence");
addNodes(addNodes(root, "Germany"),"Stuttgart","Munich", "Berlin","Dusseldorf", "Dresden");
addNodes(addNodes(root, "France"), "Paris","Toulouse", "Strasbourg","Bordeaux", "Lyon");
DefaultTreeModel treeModel = new DefaultTreeModel(root);
TreeModelProvider<DefaultMutableTreeNode> modelProvider = new
TreeModelProvider<DefaultMutableTreeNode>( treeModel ){
@Override
public IModel<DefaultMutableTreeNode> model(DefaultMutableTreeNode object){
return Model.of(object);
}
};
//To be continued...
{code}
Nodes have been built using simple strings as data objects and invoking custom utility method addNodes which converts string parameters into children nodes for a given parent node. Once we have our tree of @DefaultMutableTreeNodes@ we can build the Swing tree model (@DefaultTreeModel@) that will be the backing object for a @TreeModelProvider@. This provider wraps each node in a model invoking its abstract method model. In our example we have used a simple @Model@ as wrapper model.
Scrolling down the code we can see how the tree component is instantiated and configured before being added to the home page:
{code}
//Continued from previous snippet...
NestedTree<DefaultMutableTreeNode> tree = new NestedTree<DefaultMutableTreeNode>("tree",
modelProvider)
{
@Override
protected Component newContentComponent(String id, IModel<DefaultMutableTreeNode>model)
{
return new CheckedFolder<DefaultMutableTreeNode>(id, this, model);
}
};
//select Windows theme
tree.add(new WindowsTheme());
add(tree);
}
//implementation of addNodes
//...
}
{code}
To use tree repeaters we must implement their abstract method @newContentComponent@ which is called internally by base class @AbstractTree@ when a new node must be built. As content component we have used built-in class @CheckedFolder@ which combines a @Folder@ component with a @CheckBox@ form control.
The final step before adding the tree to its page is to apply a theme to it. Wicket comes with two behaviors, WindowsTheme and HumanTheme, which correspond to the classic Windows XP theme and to the Human theme from Ubuntu.
Our checkable tree is finished but our work is not over yet because the component doesn't offer many functionalities as it is. Unfortunately neither NestedTree nor CheckedFolder provide a means for collecting checked nodes and returning them to client code. It's up to us to implement a way to keep track of checked nodes.
Another nice feature we would like to implement for our tree is the following user-friendly behavior that should occur when a user checks/unchecks a node:
* When a node is checked also all its children nodes (if any) must be checked. We must also ensure that all the ancestors of the checked node (root included) are checked, otherwise we would get an inconsistent selection.
* When a node is unchecked also all its children nodes (if any) must be unchecked and we must also ensure that ancestors get unchecked if they have no more checked children.
The first goal (keeping track of checked node) can be accomplished building a custom version of @CheckedFolder@ that uses a shared Java Set to store checked node and to verify if its node has been checked. This kind of solution requires a custom model for checkbox component in order to reflect its checked status when its container node is rendered. This model must implement typed interface @IModel<Boolean>@ and must be returned by @CheckedFolder@'s method @newCheckBoxModel@.
For the second goal (auto select/unselect children and ancestor nodes) we can use @CheckedFolder@'s callback method onUpdate(AjaxRequestTarget) that is invoked after a checkbox is clicked and its value has been updated. Overriding this method we can handle user click adding/removing nodes to/from the Java Set.
Following this implementation plan we can start coding our custom @CheckedFolder@ (named @AutocheckedFolder@):
{code}
public class AutocheckedFolder<T> extends CheckedFolder<T> {
private ITreeProvider<T> treeProvider;
private IModel<Set<T>> checkedNodes;
private IModel<Boolean> checkboxModel;
public AutocheckedFolder(String id, AbstractTree<T> tree,
IModel<T> model, IModel<Set<T>> checkedNodes) {
super(id, tree, model);
this.treeProvider = tree.getProvider();
this.checkedNodes = checkedNodes;
}
@Override
protected IModel<Boolean> newCheckBoxModel(IModel<T> model) {
checkboxModel = new CheckModel();
return checkboxModel;
}
@Override
protected void onUpdate(AjaxRequestTarget target) {
super.onUpdate(target);
T node = getModelObject();
boolean nodeChecked = checkboxModel.getObject();
addRemoveSubNodes(node, nodeChecked);
addRemoveAncestorNodes(node, nodeChecked);
}
class CheckModel extends AbstractCheckBoxModel{
@Override
public boolean isSelected() {
return checkedNodes.getObject().contains(getModelObject());
}
@Override
public void select() {
checkedNodes.getObject().add(getModelObject());
}
@Override
public void unselect() {
checkedNodes.getObject().remove(getModelObject());
}
}
}
{code}
The constructor of this new component takes in input a further parameter which is the set containing checked nodes.
Class CheckModel is the custom model we have implemented for checkbox control. As base class for this model we have used @AbstractCheckBoxModel@ which is provided to implement custom models for checkbox controls.
Methods @addRemoveSubNodes@ and @addRemoveAncestorNodes@ are called to automatically add/remove children and ancestor nodes to/from the current Set. Their implementation is mainly focused on the navigation of tree nodes and it heavily depends on the internal implementation of the tree, so we won't dwell on their code.
Now we are just one step away from completing our tree as we still have to find a way to update the checked status of both children and ancestors nodes on client side. Although we could easily accomplish this task by simply refreshing the whole tree via AJAX, we would like to find a better and more performant solution for this task.
When we modify the checked status of a node we don't expand/collapse any node of the three so we can simply update the desired checkboxes rather than updating the entire tree component. This alternative approach could lead to a more responsive interface and to a strong reduction of bandwidth consumption.
With the help of JQuery we can code a couple of JavaScript functions that can be used to check/ uncheck all the children and ancestors of a given node. Then, we can append these functions to the current @AjaxRequest@ at the end of method onUpdate:
{code}
@Override
protected void onUpdate(AjaxRequestTarget target) {
super.onUpdate(target);
T node = getModelObject();
boolean nodeChecked = checkboxModel.getObject();
addRemoveSubNodes(node, nodeChecked);
addRemoveAncestorNodes(node, nodeChecked);
updateNodeOnClientSide(target, nodeChecked);
}
protected void updateNodeOnClientSide(AjaxRequestTarget target,
boolean nodeChecked) {
target.appendJavaScript(";CheckAncestorsAndChildren.checkChildren('" + getMarkupId() +
"'," + nodeChecked + ");");
target.appendJavaScript(";CheckAncestorsAndChildren.checkAncestors('" + getMarkupId() +
"'," + nodeChecked + ");");
}
{code}
The JavaScript code can be found inside file autocheckedFolder.js which is added to the header section as package resource:
{code}
@Override
public void renderHead(IHeaderResponse response) {
PackageResourceReference scriptFile = new PackageResourceReference(this.getClass(),
"autocheckedFolder.js");
response.render(JavaScriptHeaderItem.forReference(scriptFile));
}
{code}
h3. Working with hidden components
When a component is not visible its markup and the related id attribute are not rendered in the final page, hence it can not be updated via AJAX. To overcome this problem we must use Component's method @setOutputMarkupPlaceholderTag(true)@ which has the effect of rendering a hidden <span> tag containing the markup id of the hidden component:
{code}
final Label label = new Label("labelComponent", "Initial value.");
//make label invisible
label.setVisible(false);
//ensure that label will leave a placeholder for its markup id
label.setOutputMarkupPlaceholderTag(true);
add(label);
//...
new AjaxLink("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
//turn label to visible
label.setVisible(true);
target.add(label);
}
};
{code}
Please note that in the code above we didn't invoked method @setOutputMarkupId(true)@ as @setOutputMarkupPlaceholderTag@ already does it internally.