- 19. Working with AJAX
- 19.1. How to use AJAX components and behaviors
- 19.2. Build-in AJAX components
- 19.3. Built-in AJAX behaviors
- 19.4. Using an activity indicator
- 19.5. AJAX request attributes and call listeners
- 19.6. Creating custom AJAX call listener
- 19.7. Stateless AJAX components/behaviors
- 19.8. Lambda support for components
- 19.9. Lambda support for behaviors
- 19.10. Summary
19. Working with AJAX
AJAX has become a must-have for nearly all kinds of web application. This technology does not only help to achieve a better user experience but it also allows to improve the bandwidth performance of web applications. Using AJAX usually means writing tons of JavaScript code to handle asynchronous requests and to update user interface, but with Wicket we can leave all this boilerplate code to the framework and we don’t even need to write a single line of JavaScript to start using AJAX.
In this chapter we will learn how to leverage the AJAX support provided by Wicket to make our applications fully Web 2.0 compliant.
19.1. How to use AJAX components and behaviors
Wicket support for AJAX is implemented in file wicket-ajax-jquery.js which makes complete transparent to Java code any detail about AJAX communication.
AJAX components and behaviors shipped with Wicket expose one or more callback methods which are executed when they receive an AJAX request. One of the arguments of these methods is an instance of interface org.apache.wicket.ajax.AjaxRequestTarget.
For example component AjaxLink (in package org.apache.wicket.ajax.markup.html) defines abstract method onClick(AjaxRequestTarget target) which is executed when user clicks on the component:
new AjaxLink<Void>("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
//some server side code...
}
};
Using AjaxRequestTarget we can specify the content that must be sent back to the client as response to the current AJAX request. The most commonly used method of this interface is probably add(Component… components). With this method we tell Wicket to render again the specified components and refresh their markup via AJAX:
new AjaxLink<Void>("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
//modify the model of a label and refresh it on browser
label.setDefaultModelObject("Another value 4 label.");
target.add(label);
}
};
Components can be refreshed via Ajax only if they have rendered a markup id for their related tag. As a consequence, we must remember to set a valid id value on every component we want to add to AjaxRequestTarget. This can be done using one of the two methods seen in paragraph 6.3:
final Label label = new Label("labelComponent", "Initial value.");
//autogenerate a markup id
label.setOutputMarkupId(true);
add(label);
//...
new AjaxLink<Void>("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
//modify the model of a label and refresh it on client side
label.setDefaultModelObject("Another value 4 label.");
target.add(label);
}
};
Another common use of AjaxRequestTarget is to prepend or append some JavaScript code to the generated response. For example the following AJAX link displays an alert box as response to user’s click:
new AjaxLink<Void>("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
target.appendJavaScript(";alert('Hello!!');");
}
};
Repeaters component that have org.apache.wicket.markup.repeater.AbstractRepeater as base class (like ListView, RepeatingView, etc…) can not be directly updated via AJAX. |
If we want to refresh their markup via AJAX we must add one of their parent containers to the AjaxRequestTarget.
The standard implementation of AjaxRequestTarget used by Wicket is class org.apache.wicket.ajax.AjaxRequestHandler. To create new instances of AjaxRequestTarget a Wicket application uses the provider object registered with method setAjaxRequestTargetProvider:
setAjaxRequestTargetProvider(
Function<Page, AjaxRequestTarget> ajaxRequestTargetProvider)
The provider is an implementation of interface java.util.function.Function, hence to use custom implementations of AjaxRequestTarget we must register a custom provider that returns the desired implementation:
private static class MyCustomAjaxRequestTargetProvider implements
Function<Page, AjaxRequestTarget>
{
@Override
public AjaxRequestTarget apply(Page page)
{
return new MyCustomAjaxRequestTarget();
}
}
During request handling AjaxRequestHandler sends an event to its application to notify the entire component hierarchy of the current page: |
//'page' is the associated Page instance
page.send(app, Broadcast.BREADTH, this);
The payload of the event is the AjaxRequestHandler itself.
19.2. Build-in AJAX components
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.
19.2.1. 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.
19.2.2. 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.
19.2.3. 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).
19.2.4. 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 on examples site:
19.2.5. 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:
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):
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
}
};
Wicket module wicket-examples contains page class AutoCompletePagePage.java which shows an example of autocomplete text field. The running example is available on examples site .
19.2.6. Modal dialog
Class org.apache.wicket.extensions.ajax.markup.html.modal.ModalDialog is an implementation of a modal dialog based on AJAX:
The content of a modal dialog is another component. The id of this component used as content must be ModalDialog#CONTENT_ID.
To display a modal dialog we must call its method open(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 BasicModalDialogExample and illustrate a basic usage of a modal dialog:
HTML:
<body>
<h2>Modal Dialog example</h2>
<a wicket:id="open">Open the Dialog!</a>
<div wicket:id="modal"></div>
</body>
Java Code:
public HomePage(final PageParameters parameters) {
super(parameters);
final ModalDialog modal = new ModalDialog("modal");
modal.add(new DefaultTheme());
modal.closeOnClick();
Label label = new Label(ModalDialog.CONTENT_ID, "I'm a modal dialog!");
modal.setContent(label);
add(modal);
add(new AjaxLink<Void>("open") {
@Override
public void onClick(AjaxRequestTarget target) {
modal.open(target);
}
});
}
Just like any other component also ModalDialog must be added to a markup tag, like we did in our example using a
The modal dialog can be closed from code using its method close(AjaxRequestTarget target).
19.2.7. 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:
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:
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:
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...
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:
//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
//...
}
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
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):
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());
}
}
}
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:
@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 + ");");
}
The JavaScript code can be found inside file autocheckedFolder.js which is added to the header section as package resource:
@Override
public void renderHead(IHeaderResponse response) {
PackageResourceReference scriptFile = new PackageResourceReference(this.getClass(),
"autocheckedFolder.js");
response.render(JavaScriptHeaderItem.forReference(scriptFile));
}
19.2.8. 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 tag containing the markup id of the hidden component:
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<Void>("ajaxLink"){
@Override
public void onClick(AjaxRequestTarget target) {
//turn label to visible
label.setVisible(true);
target.add(label);
}
};
Please note that in the code above we didn’t invoked method setOutputMarkupId(true) as setOutputMarkupPlaceholderTag already does it internally.
19.3. Built-in AJAX behaviors
In addition to specific components, Wicket offers also a set of built in AJAX behaviors that can be used to easily add AJAX functionalities to existing components. As we will see in this paragraph AJAX behaviors can be used also to ajaxify components that weren’t initially designed to work with this technology. All the following behaviors are inside package org.apache.wicket.ajax.
19.3.1. AjaxEventBehavior
AjaxEventBehavior allows to handle a JavaScript event (like click, change, etc…) on server side via AJAX. Its constructor takes in input the name of the event that must be handled. Every time this event is fired for a given component on client side, the callback method onEvent(AjaxRequestTarget target) is executed. onEvent is abstract, hence we must implement it to tell AjaxEventBehavior what to do when the specified event occurs.
In project AjaxEventBehaviorExample we used this behavior to build a “clickable” Label component that counts the number of clicks. Here is the code from the home page of the project:
HTML:
<body>
<div wicket:id="clickCounterLabel"></div>
User has clicked <span wicket:id="clickCounter"></span> time/s on the label above.
</body>
Java Code:
public class HomePage extends WebPage {
public HomePage(final PageParameters parameters) {
super(parameters);
final ClickCounterLabel clickCounterLabel =
new ClickCounterLabel("clickCounterLabel", "Click on me!");
final Label clickCounter =
new Label("clickCounter", new PropertyModel(clickCounterLabel, "clickCounter"));
clickCounterLabel.setOutputMarkupId(true);
clickCounterLabel.add(new AjaxEventBehavior("click"){
@Override
protected void onEvent(AjaxRequestTarget target) {
clickCounterLabel.clickCounter++;
target.add(clickCounter);
}
});
add(clickCounterLabel);
add(clickCounter.setOutputMarkupId(true));
}
}
class ClickCounterLabel extends Label{
public int clickCounter;
public ClickCounterLabel(String id) {
super(id);
}
public ClickCounterLabel(String id, IModel<?> model) {
super(id, model);
}
public ClickCounterLabel(String id, String label) {
super(id, label);
}
}
In the code above we have declared a custom label class named ClickCounterLabel that exposes a public integer field called clickCounter. Then, in the home page we have attached a AjaxEventBehavior to our custom label to increment clickCounter every time it receives a click event.
The number of clicks is displayed with another standard label named clickCounter.
19.3.2. AjaxFormSubmitBehavior
This behavior allows to send a form via AJAX when the component it is attached to receives the specified event. The component doesn’t need to be inside the form if we use the constructor version that, in addition to the name of the event, takes in input also the target form:
Form<Void> form = new Form<>("form");
Button submitButton = new Button("submitButton");
//submit form when button is clicked
submitButton.add(new AjaxFormSubmitBehavior(form, "click"){});
add(form);
add(submitButton);
AjaxFormSubmitBehavior does not prevent JavaScript default event handling. For <input type=submit you’ll have to call AjaxRequestAttributes.setPreventDefault(true) to prevent the form from being submitted twice. |
19.3.3. AjaxFormComponentUpdatingBehavior
This behavior updates the model of the form component it is attached to when a given event occurs. The standard form submitting process is skipped and the behavior validates only its form component.
The behavior doesn’t work with radio buttons and checkboxes. For these kinds of components we must use AjaxFormChoiceComponentUpdatingBehavior:
Form<Void> form = new Form<>("form");
TextField textField = new TextField("textField", Model.of(""));
//update the model of the text field each time event "change" occurs
textField.add(new AjaxFormComponentUpdatingBehavior("change"){
@Override
protected void onUpdate(AjaxRequestTarget target) {
//...
}
});
add(form.add(textField));
19.3.4. AbstractAjaxTimerBehavior
AbstractAjaxTimerBehavior executes callback method onTimer(AjaxRequestTarget target) at a specified interval. The behavior can be stopped and restarted at a later time with methods stop(AjaxRequestTarget target) and restart(AjaxRequestTarget target):
Label dynamicLabel = new Label("dynamicLabel");
//trigger an AJAX request every three seconds
dynamicLabel.add(new AbstractAjaxTimerBehavior(Duration.seconds(3)) {
@Override
protected void onTimer(AjaxRequestTarget target) {
//...
}
});
add(dynamicLabel);
By default AJAX components and behaviors are stateful, but as we will see very soon they can be easily turned to statelss if we need to use them in stateless pages. |
19.4. Using an activity indicator
One of the things we must take care of when we use AJAX is to notify user when an AJAX request is already in progress. This is usually done displaying an animated picture as activity indicator while the AJAX request is running.
Wicket comes with a variant of components AjaxButton, AjaxLink and AjaxFallbackLink that display a default activity indicator during AJAX request processing. These components are respectively IndicatingAjaxButton, IndicatingAjaxLink and IndicatingAjaxFallbackLink.
The default activity indicator used in Wicket can be easily integrated in our components using behavior AjaxIndicatorAppender (available in package org.apache.wicket.extensions.ajax.markup.html) and implementing the interface IAjaxIndicatorAware (in package org.apache.wicket.ajax).
IAjaxIndicatorAware declares method getAjaxIndicatorMarkupId() which returns the id of the markup element used to display the activity indicator. This id can be obtained from the AjaxIndicatorAppender behavior that has been added to the current component. The following code snippet summarizes the steps needed to integrate the default activity indicator with an ajaxified component:
//1-Implement interface IAjaxIndicatorAware
public class MyComponent extends Component implements IAjaxIndicatorAware {
//2-Instantiate an AjaxIndicatorAppender
private AjaxIndicatorAppender indicatorAppender =
new AjaxIndicatorAppender();
public MyComponent(String id, IModel<?> model) {
super(id, model);
//3-Add the AjaxIndicatorAppender to the component
add(indicatorAppender);
}
//4-Return the markup id obtained from AjaxIndicatorAppender
public String getAjaxIndicatorMarkupId() {
return indicatorAppender.getMarkupId();
}
//...
}
If we need to change the default picture used as activity indicator, we can override method getIndicatorUrl() of AjaxIndicatorAppender and return the URL to the desired picture.
19.5. AJAX request attributes and call listeners
Starting from version 6.0 Wicket has introduced two entities which allow us to control how an AJAX request is generated on client side and to specify the custom JavaScript code we want to execute during request handling. These entities are class AjaxRequestAttributes and interface IAjaxCallListener, both placed in package org.apache.wicket.ajax.attributes.
AjaxRequestAttributes exposes the attributes used to generate the JavaScript call invoked on client side to start an AJAX request. Each attribute will be passed as a JSON parameter to the JavaScript function Wicket.Ajax.ajax which is responsible for sending the concrete AJAX request. Every JSON parameter is identified by a short name. Here is a partial list of the available parameters:
Short name | Description | Default value |
u | The callback URL used to serve the AJAX request that will be sent. | |
c | The id of the component that wants to start the AJAX call. | |
e | A list of event (click, change, etc…) that can trigger the AJAX call. | domready |
m | The request method that must be used (GET or POST). | GET |
f | The id of the form that must be submitted with the AJAX call. | |
mp | If the AJAX call involves the submission of a form, this flag indicates whether the data must be encoded using the encoding mode “multipart/form-data”. | false |
sc | The input name of the submitting component of the form | |
async | A boolean parameter that indicates if the AJAX call is asynchronous (true) or not. | true |
wr | Specifies the type of data returned by the AJAX call (XML, HTML, JSON, etc…). | XML |
ih, bh, pre, bsh, ah, sh, fh, coh, dh | This is a list of the listeners that are executed on client side (they are JavaScript scripts) during the lifecycle of an AJAX request. Each short name is the abbreviation of one of the methods defined in the interface IAjaxCallListener (see below). | An empty list |
A full list of the available request parameters as well as more details on the related JavaScript code can be found at https://cwiki.apache.org/confluence/display/WICKET/Wicket+Ajax . |
Parameters ‘u’ (callback URL) and ‘c’ (the id of the component) are generated by the AJAX behavior that will serve the AJAX call and they are not accessible through AjaxRequestAttributes.
Here is the final AJAX function generate for the behavior used in example project AjaxEventBehavior Example:
Wicket.Ajax.ajax({"u":"./?0-1.IBehaviorListener.0-clickCounterLabel", "e":"click",
"c":"clickCounterLabel1"});
Even if most of the times we will let Wicket generate request attributes for us, both AJAX components and behaviors give us the chance to modify them overriding their method updateAjaxAttributes (AjaxRequestAttributes attributes).
One of the attribute we may need to modify is the list of IAjaxCallListeners returned by method getAjaxCallListeners().
IAjaxCallListener defines a set of methods which return the JavaScript code (as a CharSequence) that must be executed on client side when the AJAX request handling reaches a given stage:
getInitHandler(Component): returns the JavaScript code that will be executed on initialization of the Ajax call, immediately after the causing event. The code is executed in a scope where it can use variable attrs, which is an array containing the JSON parameters passed to Wicket.Ajax.ajax.
getBeforeHandler(Component): returns the JavaScript code that will be executed before any other handlers returned by IAjaxCallListener. The code is executed in a scope where it can use variable attrs, which is an array containing the JSON parameters passed to Wicket.Ajax.ajax.
getPrecondition(Component): returns the JavaScript code that will be used as precondition for the AJAX call. If the script returns false then neither the Ajax call nor the other handlers will be executed. The code is executed in a scope where it can use variable attrs, which is the same variable seen for getBeforeHandler.
getBeforeSendHandler(Component): returns the JavaScript code that will be executed just before the AJAX call is performed. The code is executed in a scope where it can use variables attrs, jqXHR and settings:
attrs is the same variable seen for getBeforeHandler.
jqXHR is the the jQuery XMLHttpRequest object used to make the AJAX call.
settings contains the settings used for calling jQuery.ajax().
getAfterHandler(Component): returns the JavaScript code that will be executed after the AJAX call. The code is executed in a scope where it can use variable attrs, which is the same variable seen before for getBeforeHandler.
getSuccessHandler(Component): returns the JavaScript code that will be executed if the AJAX call has successfully returned. The code is executed in a scope where it can use variables attrs, jqXHR, data and textStatus:
attrs and jqXHR are same variables seen for getBeforeSendHandler:
data is the data returned by the AJAX call. Its type depends on parameter wr (Wicket AJAX response).
textStatus it’s the status returned as text.
getFailureHandler(Component): returns the JavaScript code that will be executed if the AJAX call has returned with a failure. The code is executed in a scope where it can use variable attrs, which is the same variable seen for getBeforeHandler.
getCompleteHandler(Component): returns the JavaScript that will be invoked after success or failure handler has been executed. The code is executed in a scope where it can use variables attrs, jqXHR and textStatus which are the same variables seen for getSuccessHandler.
getDoneHandler(Component): returns the JavaScript code that will be executed after the Ajax call is done, regardless whether it was sent or not. The code is executed in a scope where it can use variable attrs, which is an array containing the JSON parameters passed to Wicket.Ajax.ajax.
In the next paragraph we will see an example of custom IAjaxCallListener designed to disable a component during AJAX request processing.
19.6. Creating custom AJAX call listener
Displaying an activity indicator is a nice way to notify user that an AJAX request is already running, but sometimes is not enough. In some situations we may need to completely disable a component during AJAX request processing, for example when we want to avoid that impatient users submit a form multiple times. In this paragraph we will see how to accomplish this goal building a custom and reusable IAjaxCallListener. The code used in this example is from project CustomAjaxListenerExample.
19.6.1. What we want for our listener
The listener should execute some JavaScript code to disable a given component when the component it is attached to is about to make an AJAX call. Then, when the AJAX request has been completed, the listener should bring back the disabled component to an active state.
When a component is disabled it must be clear to user that an AJAX request is running and that he/she must wait for it to complete. To achieve this result we want to disable a given component covering it with a semi-transparent overlay area with an activity indicator in the middle.
The final result will look like this:
19.6.2. How to implement the listener
The listener will implement methods getBeforeHandler and getAfterHandler: the first will return the code needed to place an overlay
To move and resize the overlay area we will use another module from JQueryUI library that allows us to position DOM elements on our page relative to another element.
So our listener will depend on four static resources: the JQuery library, the position module of JQuery UI, the custom code used to move the overlay
Ajax call listeners can contribute to header section by simply implementing interface IComponentAwareHeaderContributor. Wicket provides adapter class AjaxCallListener that implements both IAjaxCallListener and IComponentAwareHeaderContributor. We will use this class as base class for our listener.
19.6.3. JavaScript code
Now that we know what to do on the Java side, let’s have a look at the custom JavaScript code that must be returned by our listener (file moveHiderAndIndicator.js):
DisableComponentListener = {
disableElement: function(elementId, activeIconUrl){
var hiderId = elementId + "-disable-layer";
var indicatorId = elementId + "-indicator-picture";
elementId = "#" + elementId;
//create the overlay <div>
$(elementId).after('<div id="' + hiderId
+ '" style="position:absolute;">'
+ '<img id="' + indicatorId + '" src="' + activeIconUrl + '"/>'
+ '</div>');
hiderId = "#" + hiderId;
//set the style properties of the overlay <div>
$(hiderId).css('opacity', '0.8');
$(hiderId).css('text-align', 'center');
$(hiderId).css('background-color', 'WhiteSmoke');
$(hiderId).css('border', '1px solid DarkGray');
//set the dimention of the overlay <div>
$(hiderId).width($(elementId).outerWidth());
$(hiderId).height($(elementId).outerHeight());
//positioning the overlay <div> on the component that must be disabled.
$(hiderId).position({of: $(elementId),at: 'top left', my: 'top left'});
//positioning the activity indicator in the middle of the overlay <div>
$("#" + indicatorId).position({of: $(hiderId), at: 'center center',
my: 'center center'});
},
//function hideComponent
Function DisableComponentListener.disableElement places the overlay
The rest of custom JavaScript contains function DisableComponentListener.hideComponent which is just a wrapper around the JQuery function remove():
hideComponent: function(elementId){
var hiderId = elementId + "-disable-layer";
$('#' + hiderId).remove();
}
};
19.6.4. Java class code
The code of our custom listener is the following:
public class DisableComponentListener extends AjaxCallListener {
private static PackageResourceReference customScriptReference = new
PackageResourceReference(DisableComponentListener.class, "moveHiderAndIndicator.js");
private static PackageResourceReference jqueryUiPositionRef = new
PackageResourceReference(DisableComponentListener.class, "jquery-ui-position.min.js");
private static PackageResourceReference indicatorReference =
new PackageResourceReference(DisableComponentListener.class, "ajax-loader.gif");
private Component targetComponent;
public DisableComponentListener(Component targetComponent){
this.targetComponent = targetComponent;
}
@Override
public CharSequence getBeforeHandler(Component component) {
CharSequence indicatorUrl = getIndicatorUrl(component);
return ";DisableComponentListener.disableElement('" + targetComponent.getMarkupId()
+ "'," + "'" + indicatorUrl + "');";
}
@Override
public CharSequence getCompleteHandler(Component component) {
return ";DisableComponentListener.hideComponent('"
+ targetComponent.getMarkupId() + "');";
}
protected CharSequence getIndicatorUrl(Component component) {
return component.urlFor(indicatorReference, null);
}
@Override
public void renderHead(Component component, IHeaderResponse response) {
ResourceReference jqueryReference =
Application.get().getJavaScriptLibrarySettings().getJQueryReference();
response.render(JavaScriptHeaderItem.forReference(jqueryReference));
response.render(JavaScriptHeaderItem.forReference(jqueryUiPositionRef));
response.render(JavaScriptHeaderItem.forReference(customScriptReference) );
}
}
As you can see in the code above we have created a function (getIndicatorUrl) to retrieve the URL of the indicator picture. This was done in order to make the picture customizable by overriding this method.
Once we have our listener in place, we can finally use it in our example overwriting method updateAjaxAttributes of the AJAX button that submits the form:
//...
new AjaxButton("ajaxButton"){
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new DisableComponentListener(form));
}
}
//...
19.6.5. Global listeners
So far we have seen how to use an AJAX call listener to track the AJAX activity of a single component. In addition to these kinds of listeners, Wicket provides also global listeners which are triggered for any AJAX request sent from a page.
Global AJAX call events are handled with JavaScript. We can register a callback function for a specific event of the AJAX call lifecycle with function Wicket.Event.subscribe(‘
‘/ajax/call/init’: called on initialization of an ajax call
‘/ajax/call/before’: called before any other event handler.
‘/ajax/call/beforeSend’: called just before the AJAX call.
‘/ajax/call/after’: called after the AJAX request has been sent.
‘/ajax/call/success’: called if the AJAX call has successfully returned.
‘/ajax/call/failure’: called if the AJAX call has returned with a failure.
‘/ajax/call/complete’: called when the AJAX call has completed.
‘/ajax/call/done’: called when the AJAX call is done.
‘/dom/node/removing’: called when a component is about to be removed via AJAX. This happens when component markup is updated via AJAX (i.e. the component itself or one of its containers has been added to AjaxRequestTarget)
‘/dom/node/added’: called when a component has been added via AJAX. Just like ‘/dom/node/removing’, this event is triggered when a component is added to AjaxRequestTarget.
The callback function takes in input the following parameters: attrs, jqXHR, textStatus, jqEvent and errorThrown. The first three parameters are the same seen before with IAjaxCallListener while jqEvent is an event internally fired by Wicket. The last parameter errorThrown indicates if an error has occurred during the AJAX call.
To see a basic example of use of a global AJAX call listener, let’s go back to our custom datepicker created in chapter 19. When we built it we didn’t think about a possible use of the component with AJAX. When a complex component like our datepicker is refreshed via AJAX, the following two side effects can occur:
After been refreshed, the component loses every JavaScript handler set on it. This is not a problem for our datepicker as it sets a new JQuery datepicker every time is rendered (inside method renderHead).
The markup previously created with JavaScript is not removed. For our datepicker this means that the icon used to open the calendar won’t be removed while a new one will be added each time the component is refreshed.
To solve the second unwanted side effect we can register a global AJAX call listener that completely removes the datepicker functionality from our component before it is removed due to an AJAX refresh (which fires event ‘/dom/node/removing’).
Project CustomDatepickerAjax contains a new version of our datepicker which adds to its JavaScript file JQDatePicker.js the code needed to register a callback function that gets rid of the JQuery datepicker before the component is removed from the DOM:
Wicket.Event.subscribe('/dom/node/removing',
function(jqEvent, attributes, jqXHR, errorThrown, textStatus) {
var componentId = '#' + attributes['id'];
if($(componentId).datepicker !== undefined)
$(componentId).datepicker('destroy');
}
);
The code above retrieves the id of the component that is about to be removed using parameter attributes. Then it checks if a JQuery datepicker was defined for the given component and if so, it removes the widget calling function destroy.
19.7. Stateless AJAX components/behaviors
Wicket makes working with AJAX easy and pleasant with its component-oriented abstraction. However as side effect, AJAX components and behaviors make their hosting page stateful. This can be quite annoying if we are working on a page that must be stateless (for example a login page). Starting from version 7.4.0 Wicket has made quite easy forcing existing AJAX components to be stateless. All we have to do is to override component’s method getStatelessHint returning true:
final Link<?> incrementLink = new AjaxFallbackLink<Void>("incrementLink")
{
...
@Override
protected boolean getStatelessHint()
{
return true;
}
};
Just like components also AJAX behaviors can be turned to stateless overriding getStatelessHint(Component component)
final AjaxFormSubmitBehavior myBehavior = new AjaxFormSubmitBehavior(form, event)
{
...
@Override
protected boolean getStatelessHint(Component component)
{
return true;
}
};
19.7.1. Usage
Stateless components and behaviors follows the same rules and conventions of their standard stateful version, so they must have a markup id in order to be manipulated via JavaScript. However in this case calling setOutputMarkupId on a component is not enough. Since we are working with a stateless page, the id of the component to refresh must be unique but also static, meaning that it should not depend on page instance. In other words, the id should be constant through different instances of the same page. By default calling setOutputMarkupId we generate markup ids using a session-level counter and this make them not static. Hence, to refresh component in a stateless page we must provide them with static ids, either setting them in Java code (with Component.setMarkupId) or simply writing them directly in the markup:
<span id="staticIdToUse" wicket:id="componentWicketId"></span>
See examples page for a full showcase of AJAX-stateless capabilities.
19.8. Lambda support for components
Just like we have seen for regular links, WicketStuff project offers a lambda-based factory class to build Ajax links and Ajax submitting component:
//create an AJAX link component
add(ComponentFactory.ajaxLink("id", (ajaxLink, ajaxTarget) -> {/*do stuff*/});
//create a submit link with error handler
add(ComponentFactory.ajaxSubmitLink("id", (ajaxLink, ajaxTarget) -> {/*do submit stuff*/},
(ajaxLink, ajaxTarget) -> {/*do error stuff*/});
For more examples see WicketStuff module wicketstuff-lambda-components.
19.9. Lambda support for behaviors
Ajax behaviors classes come with lambda-based factory methods which make their creation easier and less verbose. For example AjaxEventBehavior can be instantiated like this:
AjaxEventBehavior.onEvent("click", ajaxtarget -> //some lambda stuff)
In the following table are listed these factory methods along with their behavior classes:
Class Name | Method Name |
---|---|
AbstractAjaxTimerBehavior | onTimer |
AjaxEventBehavior | onEvent |
AjaxNewWindowNotifyingBehavior | onNewWindow |
AjaxSelfUpdatingTimerBehavior | onSelfUpdate |
AjaxFormChoiceComponentUpdatingBehavior | onUpdateChoice |
AjaxFormComponentUpdatingBehavior | onUpdate |
AjaxFormSubmitBehavior | onSubmit |
OnChangeAjaxBehavior | onChange |
19.10. Summary
AJAX is another example of how Wicket can simplify web technologies providing a good component and object oriented abstraction of them.
In this chapter we have seen how to take advantage of the AJAX support provided by Wicket to write AJAX-enhanced applications. Most of the chapter has been dedicated to the built-in components and behaviors that let us adopt AJAX without almost any effort.
In the final part of the chapter we have seen how Wicket physically implements an AJAX call on client side using AJAX request attributes. Then, we have learnt how to use call listeners to execute custom JavaScript during AJAX request lifecycle.