AJAX-Enabled Select Lists with Wicket
By now I’m guessing most people know about Wicket’s AJAX ability and how developers aren’t required to write any JavaScript for the vast majority of applications. AJAX is completely integrating to the component framework, which, I believe, is how AJAX should be.
A common use case for AJAX are dependent select lists. For example, a user selects from a list of States in the first list and the list of Cities is updated based on the selected State. Wicket makes this easy. There are a few steps we need to complete:
- Define the models used by the two DropDownChoice objects.
- Create the DropDownChoice objects.
- Wire the AJAX behavior.
If you’re not familiar with Wicket, it uses models as backing objects for Pages, Forms, and even FormComponents, like TextFields and DropDownChoices.
Let’s take a look at the complete form object, then we’ll examine the various portions below.
public class MyForm extends Form {
private String selectedState;
private String selectedCity;
public MyForm(String id) {
super(id);
IModel stateChoiceModel = new AbstractReadOnlyModel() {
public Object getObject(Component component) {
return listOfStates;
}
};
IModel cityChoiceModel = new AbstractReadOnlyModel() {
public Object getObject(Component component) {
if (selectedState == null) {
return Collections.EMPTY_LIST;
}
return cityService.getCities(selectedState);
}
};
DropDownChoice state = new DropDownChoice("state", new PropertyModel(this, "selectedState"), stateChoiceModel);
state.setOutputMarkupId(true);
add(state);
DropDownChoice city = new DropDownChoice("city", new PropertyModel(this, "selectedCity"), cityChoiceModel);
city.setOutputMarkupId(true);
add(city);
state.add(new AjaxFormComponentUpdatingBehavior("onchange") {
protected void onUpdate(AjaxRequestTarget target) {
target.addComponent(city);
}
}
}
public void setSelectedState(String selectedState) {
this.selectedState = selectedState;
}
public String getSelectedState() {
return this.selectedState;
}
public void setSelectedCity(String selectedCity) {
this.selectedCity = selectedCity;
}
public String getSelectedCity() {
return this.selectedCity;
}
}
With the form defined, we’ll need some markup:
<form wicket:id="form"> <select wicket:id="state"></select> <select wicket:id="city"></select> </form>
Now I’ll point out the where the form handles the three steps I mentioned above.
1. Define the models used by the two DropDownChoice objects.
This step is completed in the following code:
IModel stateChoiceModel = new AbstractReadOnlyModel() {
public Object getObject(Component component) {
return listOfStates;
}
};
IModel cityChoiceModel = new AbstractReadOnlyModel() {
public Object getObject(Component component) {
if (selectedState == null) {
return Collections.EMPTY_LIST;
}
return cityService.getCities(selectedState);
}
};
The ’stateChoiceModel’ provides the list of states to the DropDownChoice. Since we need to populate that when the page is first rendered, the list is simply returned. The list can be Strings, or a collection of POJOs. (Although if you’re using POJOs, you’ll need to look at IChoiceRenderer as well.)
Things start to get more interesting for the ‘cityChoiceModel’. Since we can’t populate the list of cities without the state, we fall back to an empty list. If the state populated, as it will be after the user selects a state, the list of cities will be populated accordingly.
2. Create the DropDownChoice objects.
Let’s take a look at the code that creates the DropDownChoices, since this is where you might be getting confused:
DropDownChoice state = new DropDownChoice("state", new PropertyModel(this, "selectedState"), stateChoiceModel);
state.setOutputMarkupId(true);
add(state);
DropDownChoice city = new DropDownChoice("city", new PropertyModel(this, "selectedCity"), cityChoiceModel);
city.setOutputMarkupId(true);
add(city);
You’ll notice that the code for both the ’state’ and ‘city’ drop-downs are the same except for a few Strings. Let’s look at one of the constructors:
new DropDownChoice("state", // #1
new PropertyModel(this, "selectedState"), // #2
stateChoiceModel); // #3
- Creates a new object with the given id. This id must match the markup.
- Creates a new PropertyModel. This is the model that’s updated when the form is submitted or when the form component is updated via AJAX. Since we’ve called new PropertyModel(this, “selectedState”), the form object’s ’selectedState’ property will be updated. This could’ve just as easily been a POJO or a HashMap.
- Pass in the model with the states. This is used by the DropDownChoice to create the options in the select list.
The same steps are take for the ‘city’ DropDownChoice. You’ll also notice that I called:
city.setOutputMarkupId(true);
This inserts an id attribute in the generated markup used to update the DOM. The generated id would look something like:
<select id="form_city">
Wicket handles inserting this id for you, so you don’t need to know what it is ahead of time. However, you must call ’setOutputMarkupId(true)’ for all components that you want to be updated via AJAX.
3. Wire the AJAX behavior.
Finally, we need to tell the ’state’ DropDownChoice to update the ‘city’ DropDownChoice when the select list changes. This is shockingly easy:
state.add(new AjaxFormComponentUpdatingBehavior("onchange") {
protected void onUpdate(AjaxRequestTarget target) {
target.addComponent(city);
}
}
The AjaxFormComponentUpdatingBehavior is called with the ‘onchange’ event is fired. By adding the ‘city’ component to as a component of the AjaxRequestTarget, the model used by the ‘city’ DropDownChoice is updated, this time with a non-null selectedState.
That’s all there is to dependent select-lists with Wicket.