How to create a text area with a heart beat with Wicket

19 06 2007

Say that in general your session time out works fine for you, but you have a couple of screens where you expect that users might hang around a bit longer than that. For instance on a page where people can submit their Resumes.

A good solution is to do what gmail or wordpress do: save drafts for your user and enable them to go on with these drafts at a later stage. If your session gets expired or you lose your browser or something, at least your user won’t have lost everything.

What you can also do, as an alternative or in addition to, is create a component that pings back to the server every once in a while to keep the session alive. Here is how you could do that.

The component we’ll be creating is a custom text area; that’s probably where you will need it for anyway.

public class KeepAliveTextArea extends TextArea {
    public KeepAliveTextArea(String id) {
      super(id);
      add(new KeepAliveBehavior());
    }

    public KeepAliveTextArea(String id, IModel model) {
      super(id, model);
      add(new KeepAliveBehavior());
  }

That’s the start. We just extend TextArea and override it’s two constructors. Those constructors in turn add a behavior that does the real work:

private static class KeepAliveBehavior extends AbstractDefaultAjaxBehavior {

  @Override
  protected void respond(AjaxRequestTarget target) {
    // prevent wicket changing focus
    target.focusComponent(null);
  }  @Override

  public void renderHead(IHeaderResponse response) {
    super.renderHead(response);
    response.renderOnLoadJavascript(
      "setInterval(function() { " +
      "  wicketAjaxGet('" + getCallbackUrl() + "', null, null, null); " +
      "}, 600000);");
  }
}

If you are familiar with Wicket, you probably already know that behaviors are a great way to extend the capabilities of components using composition rather than inheritance.

The first purpose of this Ajax behavior is to be a bucket for calls, which are received through the respond event method. Receiving a call will be enough for the application server to reset the session time out counter, which is the thing we’re after here.

Then, the Ajax behavior does a header contribution in the body of overridable method renderHead. It calls renderOnLoadJavascript on the header response, which will make Wicket register a script that is to be executed right after the page is loaded. The script creates a loop that calls wicketAjaxGet every ten minutes (600,000 miliseconds). That wicketAjaxGet function comes from Wicket’s standard Ajax support, which is automatically available for anything that extends AbstractDefaultAjaxBehavior or it’s subclasses.

The last interesting thing to note here is the call:getCallbackUrl()
This gives the URL to the Ajax behavior itself. This is what the loop calls every ten minutes,, and it results in the respond method being called on the behavior.

[update]
Al is right, it’s much easier to just extend AbstractAjaxTimerBehavior for this. I ripped the code for this post from a component that was doing much more than this, and in simplifying that, I didn’t think about the last step. Anyway, here it is, and using Duration makes it much nicer as well.

private static class KeepAliveBehavior extends AbstractAjaxTimerBehavior {
  public KeepAliveBehavior() {
    super(Duration.minutes(10));
  }

  @Override
  protected void onTimer(AjaxRequestTarget target) {
    // prevent wicket changing focus
    target.focusComponent(null);
  }
}

Actions

Information

13 responses

19 06 2007
Al Maw

Can’t you use AbstractAjaxTimerBehavior for this? And just override the onTimer() function to call your target.focusComponent(null);

19 06 2007
Eelco Hillenius

Of course, you are right! Let me update.

19 06 2007
James Cook

It’s useful for a Wicket noob to see both approaches. Can’t wait for the book.

In regards to AJAX, is Wicket using a homebrew AJAX library? I have been bitten in the past by rather complex applications running into the two-connections per browser limitations. Imagine if you will, several components that leverage AbstractAjaxTimerBehavior at varying delays. It probably wouldn’t take too long before they created an AJAX backlog.

I extended the AJAX layer for ActiveMQ that blocks a single connection to the server (with a 30-second timeout/reestablish loop) which allowed for great scalability and client response. It also enabled my AJAX methods to be message consumers and respond to server-initiated (push) messages.

The OpenAjax Alliance is also working on some of these issues by defining a hub implementation that can be shared between multiple AJAX platforms that may be running simultaneously in a single app or across various tabs in a single browser.

I’m just wondering how pluggable the AJAX infrastructure is in Wicket?

19 06 2007
Eelco Hillenius

James,

I can’t really fill you in on all the details of Wicket’s Ajax, but it’s certainly not just a quick hack. Amongst other things, we built in throttling for Ajax request.

If you have specific questions, it works best to send them to the user list. Especially Matej and Igor should be able to answer specifics.

Wicket’s Ajax support is very flexible. The support we ship as part of the core project is tightly bound to Wicket. It is not too generic (and thus not too big) and provides exactly the kind of functionality we need for Wicket. However, if you prefer to plugin another Ajax implementation, that is fine. In fact, there already are two projects that do this, one for Dojo and one for Scriptaculous. It’s fairly easy to plugin your own – it’s just JavaScript in the end, and in Wicket you will find to tools to create custom request receivers and such. I have no idea what your extensions for ActiveMQ look like, but I would be surprised if you wouldn’t be able to give that a place in Wicket if you wanted.

19 07 2007
swaroop belur

Eelco,

Getting hold of wicket behavior urls is really useful.
But i want to make an obervation over here.

Basically to get call the behavior u need to call getCallbackscript
which adds the javascript function in wicket-ajax.js

Since this method is protected, one needs to add by hand all the stuff
to call the js function.

I find it surprising that getCallbackurl is public but getcallbackscript
is protected. As one can make out , getcallbackscript is more useful
than getcallbackurl as that is the final url required.

Any thoughts?

-swaroop

19 07 2007
Eelco Hillenius

Good observation. I actually missed getCallbackScript myself. My example could probably be improved (if I were to ignore Al’s note) by doing this:

setInterval(function() { ” + getCallbackScript() + ” }, 600000);”);

About public/ protected… I don’t think it matters here that getCallbackScript is protected. I’d say it is more suprising that getCallbackUrl is public tbh. I think it is public because it is convenient for testing.

19 07 2007
sbelur

eelco

The blog may not be the right place to make the comment , but
since it is kind of continuation to ur earlier comment, i decided
to put my comment here…

In the project i am working on, there have been a couple of cases
when I was required to get hold of url for the behavior and use that
url from javascript confirm window.

So using the current strategy it is not possible to get hold of url just
by calling behavior. because it is protected.

This is the point i am making – the use case when u r required
to get hold of url for behavior from outside the behavior.

I think u get the point

-swaroop

19 07 2007
Eelco Hillenius

If you think you have a good case, you can always ask for it on either the user or the developer list. And/ or by filing a JIRA issue. It get’s a little bit more exposure there, so that other team members and users can join with their opion if they want. And JIRA helps us tracking what we need to do *and* generates a nice change list :)

17 08 2007
Anthony

If you were to implement the first scenario, that is to say “resumé” pages saving their content upon each heartbeat in order to restore textfields when the user, after having timed out, logs back on, where/under what form would you store this data?
Thanks
Anthony

17 08 2007
Eelco Hillenius

@Anthony

You mean the gmail solution? You could save drafts in your database… that’s what I would do I think.

And the idea of my proposal here is that users don’t get logged out in the first place, but without having to set the session time out to a rediculously large value.

17 08 2007
Anthony

That implies having tables for every kind of form endowed page which may be inconvenient especially when the form translates into multiple interlaced business objects on the db side…

17 08 2007
Eelco Hillenius

Sure. I’m not proposing it as a generic solution. And what I propose in this post is easier/ won’t require tables.

But if you would want to implement a generic draf system, you can do that of course, but you’ll have to think about what works best for you. You could for instance have just one table with a clob and a foreign key to the user and an identifier of the field the draft is for. Or use an object database. Many options. Maybe be topic for a next post if I ever have to do it myself. :)

1 09 2007
alexis

very good.thx ur exsample~~

Leave a comment