The objective is to use Ajax links to update data in ListView without forcing the whole page to be reloaded from the server (why? firstly, because your page will be loaded at the top and you will lose your position on the page, and secondly, ajax is a faster user experience because less data is sent back and forward).
But in order to do this, we face two major challenges:
- We need to find a target for our AjaxRequestTarget to repaint so that our ListViews can be refreshed to reflect any changes to the list eg. if we changed the question order.
- We need find a way to update the underlying data for the ListView while also updating our domain objects to persist the data in the database.
But in the spirit of OO development, we have been embedding panels in panels and suddenly we need to get a handle on our rendering container for re-painting while also getting hold of the ListView data (by calling listview.getlist()) for updating.
So the strategy is:
Step 1
Firstly, add all our listviews to a WebMarkupContainer in order to target it for re-rendering (Listing 1 and 2). Note that we are passing the container into the subpanels because we need to keep track of it for re-rendering. It is also possible to retrieve this container by ID see Listing 4.
public class QuestionEditPanel extends QuestionRenderPanel {
public QuestionEditPanel(String id, final TemplateWebModel templatewebmodel) {
super(id);
Listinitial=templatewebmodel.getEntity().getQuestions();
final WebMarkupContainer container=new WebMarkupContainer("container"); //container that will hold our listviews for repainting
container.setOutputMarkupId(true);
ListView rowslistview = new ListView("rows", QuestionProcessor.getListToMatrixWithLF(initial)) {
@Override
protected void populateItem(ListItem item) {
List row = (List) item.getModelObject();
ListView rowlistview = new ListView("row", row) {
@Override
protected void populateItem(ListItem item) {
final QuestionBase question = (QuestionBase) item.getModelObject();
item.setModel(new CompoundPropertyModel(question));
QuestionBaseWebModel questionmodel=new QuestionBaseWebModel(question);
item.add(new EditableQuestionPanel("question", questionmodel,templatewebmodel,container)); //note that the container is what we will repaint
}
};
item.add(rowlistview);
}
};
add(container);
container.add(rowslistview);
}
}
Listing 1 QuestionEditPanel.java<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns:wicket>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>QuestionRenderPanel</title>
</head>
<body>
<wicket:extend>
<div id="document">
<span wicket:id="container">
<div wicket:id="rows">
<span wicket:id="row">
<span wicket:id="question" />
</span>
</div>
</span>
</div>
</wicket:extend>
</body>
</html>
Listing 2 QuestionEditPanel.htmlStep 2
Update both the domain model and the underlying data model for the ListView (Listing 3). Note the replication as you need to do the same thing twice (or you can update the domain model and retrieve the updated values to set the ListView model appropriately). So, to be clear, the ListView does not know when its data changes and therefore even if you update the Domain model, you still have to manually change the data and set the value on the ListView (Listing 4).
AjaxFallbackLink up = new AjaxFallbackLink("up") {
@Override
public void onClick(AjaxRequestTarget target) {
AbstractDataSet dataset = datasetmodel.getEntity(); //domain object where we store questions
dataset.moveQuestionUp(question); //move the question up on the domain object
List list = QuestionProcessor.getMatrixToList(getRowListData()); //turn our matrix into a list
QuestionProcessor.moveQuestionUp(list, question); //move the question up in the list
setRowListData(getListToMatrix(list)); //set the list as the underlying data for the listview
adsf.store(dataset); //store our domain model to not lose changes
target.addComponent(outercontainer); //add our container for re-rendering
}
};
Listing 3 QuestionNavigationPanel.java (part 1)private List getRowListData() {
MarkupContainer container = getParent();
while (!container.getId().equals("rows")) {
container = container.getParent();
}
ListView view = null;
if (container instanceof ListView) {
view = (ListView) container;
}
return view.getList();
}
private void setRowListData(List data) {
MarkupContainer container = getParent();
while (!container.getId().equals("rows")) { //We traverse all the parents until we find one call "rows"
container = container.getParent();
}
ListView view = null;
if (container instanceof ListView) {
view = (ListView) container;
}
view.setList(data);
}
Listing 4 QuestionNavigationPanel.java (part 2)In summary
Once this is done, your AjaxFallbackLink will move a question up, down or delete it from a row, update the domain model so that that the changes persist in the database and then modify the model for the ListView. The ListView must be added to a container that is then re-drawn by the Ajax behaviour.