Fork me on GitHub

JavaScript, AJAX, JSON Tutorial

This tutorial looks at using JavaScript within WoOF templates and interacting with the WoOF server by AJAX (by both HTTP parameters and JSON).

To focus on JavaScript and AJAX, the below simple application will undertake adding or subtracting from a number. While this can be accomplished client-side with JavaScript, the tutorial demonstrates AJAX by sending the values to the WoOF server to undertake the calculations.

JavaScriptApp screen shot.

Tutorial Source

JavaScript

The below is the HTML and JavaScript for the page. While production applications should use a JavaScript framework (e.g. jQuery) to handle issues such as cross browser differences, the tutorial will use raw JavaScript to show it is JavaScript framework agnostic.

The reason that WoOF may use any JavaScript framework is that it does not interpret the web page contents. WoOF treats the content of a web page as text, providing only the necessary tag replacements. In the case below, WoOF provides the URLs for the AJAX calls. This means any JavaScript code (whether raw or part of a framework) may be included in the WoOF templates. It even means that WoOF templates can be used for other content types such as XML. The focus of this tutorial will be on including JavaScript within HTML web pages.

<html>

<head>
<script type="text/javascript">
	
	function addition() {
		var numberOne = document.getElementById("result").innerHTML
		var numberTwo = document.getElementById("value").value
		doAjax( { url: "#{addition}"
				, contentType: "application/x-www-form-urlencoded"
				, payload: "numberOne=" + numberOne + "&numberTwo=" + numberTwo
				, handler: function(response) { document.getElementById("result").innerHTML = response; }
				})
	}

	function subtraction() {
		var numberOne = document.getElementById("result").innerHTML
		var numberTwo = document.getElementById("value").value;
		doAjax( { url: "#{subtraction}"
				, contentType: "application/json"
				, payload: JSON.stringify({numberOne: numberOne, numberTwo: numberTwo})
				, handler: function(response) { 
						document.getElementById("result").innerHTML = JSON.parse(response).result
					}
				})
	}
	
	function doAjax( request ) {
		var ajax = new XMLHttpRequest();
		ajax.open("POST", request.url, false)
		ajax.setRequestHeader("Content-type", request.contentType)
		ajax.onreadystatechange = function() {
			if (ajax.readyState == 4) {
				request.handler.call( request, ajax.responseText )
			}
		}
		ajax.send( request.payload )
	}
		
</script>
</head>

<body>

	<h1 id="result">1</h1>
	
	<p><input id="value" type="text" value="1" size="4" />
		<input type="button" value="add" onclick="addition()" />
		<input type="button" value="subtract" onclick="subtraction()" /></p>

</body>
</html>

Different payload types (HTTP parameters and JSON) are used for the respective AJAX requests. This is to demonstrate in the next sections how WoOF handles each payload type.

AJAX with HTTP parameters

The handling HTTP parameters in the AJAX request is very similar to handling HTML form submissions. The difference is that the default behaviour of re-rendering the template's content is to be avoided. To avoid re-rendering the template's content the below annotation is added to the servicing method to flag to not re-render the template.

public class TemplateLogic {

	@Data
	@HttpParameters
	public static class AdditionRequest implements Serializable {
		private static final long serialVersionUID = 1L;

		private String numberOne;
		private String numberTwo;
	}

	@NotRenderTemplateAfter
	public void addition(AdditionRequest request, ServerHttpConnection connection) throws IOException {

		// Add the numbers
		int result = Integer.parseInt(request.getNumberOne()) + Integer.parseInt(request.getNumberTwo());

		// Return the result
		connection.getResponse().getEntityWriter().write(String.valueOf(result));
	}

AJAX with JSON

To handle JSON content in both the request and response, the JSON content is mapped to Java Objects. The below demonstrates the:

  • @HttpObject annotation on the parameter class to have the injected object loaded from the request's entity content. Due to the Content-Type this will trigger JSON mapping.
  • ObjectResponse to write the object response. Due to either Accept, Content-Type headers of the request, an appropriate object mapper is used. In this case, JSON.
    	@Data
    	@HttpObject
    	public static class SubtractionRequest implements Serializable {
    		private static final long serialVersionUID = 1L;
    
    		private String numberOne;
    		private String numberTwo;
    	}
    
    	@Data
    	public static class JsonResponse {
    		private final String result;
    	}
    
    	@NotRenderTemplateAfter
    	public void subtraction(SubtractionRequest request, ObjectResponse<JsonResponse> response) throws IOException {
    
    		// Subtract the numbers
    		int result = Integer.parseInt(request.getNumberOne()) - Integer.parseInt(request.getNumberTwo());
    
    		// Return the result
    		response.send(new JsonResponse(String.valueOf(result)));
    	}
    
    }
    

The underlying implementation of JSON to Java Object mapping is via Jackson. Please see their documentation regarding additional annotations available to Java Objects to fine tune mapping of JSON to/from Java Objects.

Generic Object handling

You will notice in the JSON example that there is no mention of JSON. The reason is that the code is agnostic to the underlying wire format and therefore re-usable for say receiving / sending XML.

To integrate your own wire formats provide implementations of the following services:

Unit Test

The following unit tests manually undertake the AJAX calls to the WoOF server and verifying the correct responses.

@ExtendWith(OfficeFloorExtension.class)
public class JavaScriptAppTest {

	@RegisterExtension
	public HttpClientExtension client = new HttpClientExtension();

	@Test
	public void testHttpParameters() throws IOException {
		String response = this.doAjax("addition", "application/x-www-form-urlencoded", "numberOne=2&numberTwo=1");
		assertEquals("3", response, "Incorrect response");
	}

	@Test
	public void testHttpJson() throws IOException {
		String response = this.doAjax("subtraction", "application/json",
				"{ \"numberOne\" : \"3\", \"numberTwo\" : \"1\" }");
		assertEquals("{\"result\":\"2\"}", response, "Incorrect response");
	}

	private String doAjax(String link, String contentType, String payload) throws IOException {
		HttpPost post = new HttpPost("http://localhost:7878/template+" + link);
		post.addHeader("content-type", contentType);
		post.setEntity(new StringEntity(payload));
		HttpResponse response = this.client.execute(post);
		String entity = EntityUtils.toString(response.getEntity());
		assertEquals(200, response.getStatusLine().getStatusCode(), "Should be successful: " + entity);
		return entity;
	}

Next

The next tutorial looks at sectioning the template.