Wicket in Action

Repainting only newly-created repeater items via ajax

23 October 2008, by ivaynberg

Ajax in Wicket is great, for the most part its usage is transparent and things work as you would expect. Where Ajax does not work perfectly, yet, is when it comes to partially updating repeaters. The problem is that repeater components do not have a markup tag of their own and so Wicket cannot transparently figure out where the updates should go.

Consider a simple case of a repeater rendering a list of contacts. Wicket markup looks like this:

1 <body>
2   <table>
3     <tr wicket:id="contacts">
4       <td><span wicket:id="name"></span></td>
5     </tr>
6   </table>
7 </body>

And the rendered markup looks like this:

1 <body>
2   <table>
3     <tr><td><span>Lisa Simpson</span></td></tr>
4     <tr><td><span>Bart Simpson</span></td></tr>
5   </table>
6 </body>

As you can see, if we add a new item to the ListView and try to repaint it via Ajax there is no root markup tag for the Wicket Ajax to replace. This is why it is necessary to add the repeater to a container and then repaint the container instead. In this case we would add a WebMarkupContainer to the table tag and repaint it via Ajax instead. This, however, will result in all table rows being rendered and sent over from server to client, which for a lot of cases is undesirable.

It is not too hard to support the usecase of "I added something new to the repeater and want to have just that row added via Ajax". The trick is to give Wicket a tag to repaint via Ajax which can be accomplished by doing the following:

  1. create the markup tag to represent the new item
  2. add it to the right place in the markup
  3. have Wicket repaint it via Ajax

Accomplishing this is pretty easy. Suppose we create the new item by submitting a form via an AjaxButton:

form.add(new AjaxButton("submit")
  {
    @Override
    protected void onSubmit(AjaxRequestTarget target, Form< ? > f)
    {
      // retrieve the newly added contact bean
      Contact contact = form.getModelObject();
      // add it to the collection on serverside
      contacts.add(contact);
      // create the new repeater item and add it to the repeater
      Component item = buildItem(contact);
      // first execute javascript which creates a placeholder tag in markup for this item
      target.prependJavascript(
        String.format(
        "var item=document.createElement(&#039;%s&#039;);item.id=&#039;%s&#039;;"+
        "Wicket.$(&#039;%s&#039;).appendChild(item);",
        "tr", item.getMarkupId(), container.getMarkupId()));

        // notice how we set the newly created item tag&#039;s id to that of the newly created
        // Wicket component, this is what will link this markup tag to Wicket component
        // during Ajax repaint
 
        // all thats left is to repaint the new item via Ajax
        target.addComponent(item);
      }
    });

When the form is submitted using the above AjaxButton only the newly added item is repainted via Ajax instead of the entire table. A functional project is attached below.

This concept can be taken to the next level where a repeater can be "synced" with the markup on the client side. This would involve:

  1. Building a list of item ids currently in the repeater
  2. Invoking #onBeforeRender on the repeater to make it generate new items
  3. Building a list of new item ids in the repeater
  4. Syncing the client markup from old items to new items by deleting removed items and adding new items via the same technique as above

Maybe next time :)

Sample project: partialajax.zip