Apache Wicket provides a lot of predefined AJAX components like the AjaxButton and the AjaxEventBehavior which fulfill almost any of your AJAX-needs. However, in some special cases you may need to create your own AJAX behavior to pass some custom parameters from client to server via AJAX.
The Problem
The AJAX Behaviors Wicket provides all have a specialized function. The AjaxButton provides an onSubmit-Method to react on a form submission via AJAX. The more general AjaxBehavior provides an onEvent-Method in which you can update parts of your page via AJAX.
But what to do if you have some client-side javascript logic that makes some calculations and you want to send the result of that calculation to your server via AJAX? The following sections provide a surprisingly simple solution for this problem. Scroll to the bottom to see the full 30 lines of the finished behavior class if you want to skip the tutorial part :).
The Behavior
First, we have to create our own AjaxBehavior by subclassing AbstractDefaultAjaxBehavior. The respond-method has to be implemented. This method is called on the server-side when an AJAX call is made from the client. In this method, we want to somehow access some values that are passed from the client-side.
public class MyAjaxBehavior extends AbstractDefaultAjaxBehavior {
@Override
protected void respond(AjaxRequestTarget target) {
// here, we want to access some parameters that were sent
// by the client via AJAX
}
}
Calling the Behavior from Client-Side Javascript
The behavior does nothing so far. It has to be called from the client-side for the respond-method to be called on the server-side. To achieve this, we simply implement the renderHead() to execute some javascript when the page is loaded (you can also add this javascript to any other event of course, like clicking a button).
public class MyAjaxBehavior extends AbstractDefaultAjaxBehavior {
@Override
protected void respond(AjaxRequestTarget target) {
// here, we want to access some parameters that were sent
// by the client via AJAX
}
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(OnDomReadyHeaderItem.forScript(getCallbackScript()));
}
}
Remember to call super.renderHead(). Otherwise Wicket's javascript files will not be included. The AbstractDefaultAjaxBehavior class already provides a method that generates the javascript that is needed to issue an AJAX request: getCallbackScript(). This method will generate a javascript fragment something like the following, where "u" and "c" are two Wicket-specific parameters that are needed to find our AjaxBehavior on the server side an call its respond()-method.
Wicket.Ajax.ajax({"u":"./?1-1.IBehaviorListener.2-component","c":"component"});
Passing Parameters from Client to Server
Let's say we have two custom parameters, param1 and param2, that we want to pass from the client-side javascript to our server-side code. How to do that?
Wicket provides the concept of "extra parameters" that can be added to the javascript AJAX call by adding the "ep" parameter to the function call:
Wicket.Ajax.ajax({
"u":"./?1-1.IBehaviorListener.2-component",
"c":"component",
"ep":[
{"name":"param1","value":"value1"},
{"name":"param2","value":"value2"}]});
The Strings "value1" and "value2" will then be submitted via the AJAX call. Our Behavior will have to be altered to generate the extra parameters in it's javascript output. We do this by overriding the method updateAjaxAttributes():
public class MyAjaxBehavior extends AbstractDefaultAjaxBehavior {
@Override
protected void respond(AjaxRequestTarget target) {
// here, we want to access some parameters that were sent
// by the client via AJAX
}
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(OnDomReadyHeaderItem.forScript(getCallbackScript()));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getExtraParameters().put("param1", "value1");
attributes.getExtraParameters().put("param2", "value2");
}
}
However, our Ajax call will now always pass the values "value1" and "value2" for our two parameters. What we want is to pass values dynamically calculated in javascript.
Passing Dynamic Parameter Values
To pass dynamically calculated javascript values to the server, these values have to be calculated first, and then added into our "extra parameters" array.
To dynamically calculate the parameter values, our renderHead-method is altered. Remember: if you want to trigger the AJAX event somewhere else and not onDomReady, you can do so. For the calculation of the javascript values you can call any Javascript module you might use.
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
String script = "var param1Value = My.JavaScript.Module.calculate1();";
script += "var param2Value = My.JavaScript.Module.calculate2();";
script += getCallbackScript();
response.render(OnDomReadyHeaderItem.forScript(script));
}
Next, we have to pass the javascript variables param1Value and param2Value as "extra parameters" in our Wicket.Ajax.ajax function call instead of the fixed values "value1" and "value2". This can be done by overriding getCallbackScript() and slightly altering our implementation of updateAjaxAttributes() from above:
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getExtraParameters().put("param1", "PLACEHOLDER1");
attributes.getExtraParameters().put("param2", "PLACEHOLDER2");
}
public CharSequence getCallbackScript() {
String script = super.getCallbackScript().toString();
script = script.replace("\"PLACEHOLDER1\"", "param1Value");
script = script.replace("\"PLACEHOLDER2\"", "param2Value");
return script;
}
The method updateAjaxAttributes() now inserts placeholders into the extra parameters map. The method getCallbackScript() is overridden to replace all occurrences of these placeholders within quotes with the names of the javascript variables that contain outr calculated values.
The workaround with the placeholders is neccessary because otherwise we will have extra quotes around our javascript variable names (i.e. the value of param1 will always be the string "param1Value" instead of the actual value of the javascript variable with that name).
Accessing the passed Parameters on the Server
At last, we can access the values of the javascript variables on the server within the respond() method of our behavior:
@Override
public CharSequence getCallbackScript() {
RequestCycle cycle = RequestCycle.get();
WebRequest webRequest = (WebRequest) cycle.getRequest();
StringValue param1 = webRequest.getQueryParameters().getParameterValue("param1");
StringValue param2 = webRequest.getQueryParameters().getParameterValue("param2");
// do whatever you need with param1 and param2
}
The Complete Behavior
Finally, the whole behavior at a glance:
public class MyAjaxBehavior extends AbstractDefaultAjaxBehavior {
@Override
protected void respond(AjaxRequestTarget target) {
RequestCycle cycle = RequestCycle.get();
WebRequest webRequest = (WebRequest) cycle.getRequest();
StringValue param1 = webRequest.getQueryParameters().getParameterValue("param1");
StringValue param2 = webRequest.getQueryParameters().getParameterValue("param2");
// do whatever you need with param1 and param2
}
@Override
public CharSequence getCallbackScript() {
String script = super.getCallbackScript().toString();
script = script.replace("\"PLACEHOLDER1\"", "param1Value");
script = script.replace("\"PLACEHOLDER2\"", "param2Value");
return script;
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getExtraParameters().put("param1", "PLACEHOLDER1");
attributes.getExtraParameters().put("param2", "PLACEHOLDER2");
}
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
String script = "var param1Value = My.JavaScript.Module.calculate1();";
script += "var param2Value = My.JavaScript.Module.calculate2();";
script += getCallbackScript();
response.render(OnDomReadyHeaderItem.forScript(script));
}
}