Update: sendResponse now uses a TextRequestHandler instead of writing the response directly. This prevents an IllegalStateException “Header was already written to response” when sending the data.[/Update]

Update 2: Better let the behavior render the callback URL - see information below tagged as “Update 2”.[/Update 2]

A while ago I wrote the blog post A wicked problem with jQuery and Wicket. Back then I did not find a solution to the problem. I found some information on Stack Overflow, but I could not make it work properly.

Here is the problem again: On the client (browser), I want to use jQuery to create an AJAX request and send some data (encoded as JSON) to the server, which runs on Wicket 1.5. The server should send some JSON data back.

Now I have a version that works, and I want to share it here.

The HTML page

I have a base page which include jQuery on all pages:

BasePage.html

<!DOCTYPE html>

<html>
	<head>
		<wicket:link>
			<script src="jquery-1.5.2.min.js" type="text/javascript"></script>
		</wicket:link>
	</head>
	<body>
		<wicket:child/>
	</body>
</html>

Then there is the component where the AJAX request should be sent. This component is a wicket panel and is meant to be included in a page. The including page must load the jQuery script (in my application, all pages include the jQuery script, since all pages are derived from BasePage).

Component.html

<html>
	<wicket:head>
		<script type="text/javascript">
$(document).ready(function() {
	$('#canvas').click(function() {
		var testArray = [1, 2, 3, 4, 5];
		
		$.ajax({
			url : $('#canvas').attr('my:canvas.callback'),
			type : 'post',
			cache : false,

			data : JSON.stringify(testArray),
			contentType : 'application/json',
			dataType : 'json',
			complete : function(xhr, status) {
				alert("completed: "+xhr.responseText)
			}
		});
	});
});
		</script>
	</wicket:head>
	
	<body>
		<wicket:panel>
			<canvas 
				class="drawing_canvas" 
				wicket:id="drawingCanvas">
			</canvas>
		</wicket:panel>
	</body>
</html>

The Java-Code: Component.java

The component class contains an inner class for the AJAX behavior. It also contains 2 member variables: The WebMarkupContainer that represents the canvas and the ajax behavior. We also have to override onBeforeRender (which is explained later).

public class Component extends Panel {
	private static final long serialVersionUID = 1L;
	private final WebMarkupContainer drawingCanvas;
	private final CanvasAjaxBejavior canvasAjaxBejavior;
	
	public Component() {
		//initialization
	}
	
	@Override
	protected void onBeforeRender() {
		//called before the component is rendered
	}
	
	private final class CanvasAjaxBejavior extends AbstractAjaxBehavior {
		private static final long serialVersionUID = 1L;

		//the canvas ajax behavior
	}
}

Now we have to initialize the canvas and the AJAX behavior:

public Component() {
	//initialization
	drawingCanvas = new WebMarkupContainer("drawingCanvas");
	drawingCanvas.setMarkupId("canvas");
	canvasAjaxBejavior = new CanvasAjaxBejavior();
	add(canvasAjaxBejavior);
	add(drawingCanvas);
}

Note that we add the ajax behavior to the component, not to the canvas. I guess it does not really matter where it is added, but I have not tried to add it anywhere else. The above code works for me, so why bother ;)

Now we have to add the callback URL of the ajax behavior to the output. The Javascript code reads the callback URL from the attribute “my:canvas.callback” from the canvas. We can not do this in the constructor because the method “getCallbackUrl” requires a page. In “onBeforeRender” the parent page of the component is available, so we put the code there:

@Override
protected void onBeforeRender() {
	//called before the component is rendered
	String callbackUrl = canvasAjaxBejavior
		.getCallbackUrl().toString();
	drawingCanvas.add(
		new AttributeModifier(
			"my:canvas.callback", 
			callbackUrl));
	super.onBeforeRender();
}

Note that the super call “super.onBeforeRender();” is mandatory! (Side note: I don’t like it when frameworks do that. A super call should always be optional. Maybe I should include this somewhere in my mini series about framework design.)

The AJAX behavior

Ok, we are almost done here! All that’s still missing is the AJAX behavior. Here we have to override the method “onRequest”:

private final class CanvasAjaxBejavior extends AbstractAjaxBehavior {
	private static final long serialVersionUID = 1L;

	//the canvas ajax behavior
	@Override
	public void onRequest() {
		RequestCycle requestCycle = RequestCycle.get();
		readRequestData(requestCycle);
		sendResponse(requestCycle);
	}
	
	//read the request data...
	//send the response...
}

Update 2: The behavior itself can set the attribute for the callback URL. Then you do not need to override “onBeforeRender” in the page anymore. For this, the behavior has to be added to the canvas component, not to the page:

public Component() {
	//initialization
	drawingCanvas = new WebMarkupContainer("drawingCanvas");
	drawingCanvas.setMarkupId("canvas");
	canvasAjaxBejavior = new CanvasAjaxBejavior();
	drawingCanvas.add(canvasAjaxBejavior);
	add(drawingCanvas);
}

Then the CanvasAjaxBehavior can override the method “onComponentTag” and add the callback URL to the component tag:

private final class CanvasAjaxBejavior extends AbstractAjaxBehavior {
	private static final long serialVersionUID = 1L;

	//onRequest...
	//read the request data...
	//send the response...

	@Override
	protected void onComponentTag(final ComponentTag tag) {
		tag.put("my:canvas.callback", 
			getCallbackUrl().toString());
	}
}

[/Update 2]

The easiest way to read the request data is to get the HTTP Servlet request and read it from there:

//read the request data...
private void readRequestData(final RequestCycle requestCycle) {
	WebRequest wr=(WebRequest)requestCycle.getRequest();

	HttpServletRequest hsr= 
		(HttpServletRequest) wr.getContainerRequest();

	try {
		BufferedReader br = hsr.getReader();

		String  jsonString = br.readLine();
		if((jsonString==null) || jsonString.isEmpty()){
			System.out.println(" no json found");
		}
		else {
			System.out.println(" json  is :"+ jsonString);
		}
		br.close();
	} catch (IOException ex) {
		throw new RuntimeException(ex);
	}
}

Sending the response is just a matter of writing to the object that is found in the “response” property of “requestCycle”:

//send the response...
private void sendResponse(final RequestCycle requestCycle) {
	requestCycle.scheduleRequestHandlerAfterCurrent(
		new TextRequestHandler("application/json", 
		"UTF-8", "[5, 4, 3, 2, 1]"));
}

And now we’re done! This code has been tested with Apache Wicket 1.5.2 and jQuery 1.5.2 (The matching version number is a coincidence. Really!). I have modified the code a little bit for this blog posting - that is, I shortened it a little bit.

You might also be interested in...