Monday, 13 May 2013

Spring MVC, Ajax and JSON Part 3 - The Client Side Code

If you’ve been following this short series of blogs on Spring, Ajax and JSON you’ll recall that I’ve got as far as creating a Spring MVC web application that displays a form, which allows the user to select a bunch of items and submit a request to the server to purchase them. The server then replies with some JSON allowing the user to confirm their purchases. If you already know all this you can now jump to HERE. If you’re wondering what I’m talking about then take a look at the first two blogs in this series:


HERE


Having completed the server side code then the next thing to do is to move on to the client side code, which involves writing some JavaScript. Now, between you and me I'm not an expert in JavaScript though I, like most server side developers, seem to muddle through. The good news for people like me is that Javascript has come on in leaps and bounds over the last few years with lots of tools and libraries to ease the pain of development and, amongst all of these, jQuery seems to be the defacto standard with it's reliance on chaining and "write less and do more" philosophy.

Having stated that I'm using jQuery the next step is to setup the JSP so that I can start writing the client side code. If you take a look at shopping.jsp's HTML <head> element you’ll see that it contains the following links:

<link rel="stylesheet" href="<c:url value='/resources/blueprint/screen.css'/>" type="text/css" media="screen, projection"/>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/print.css'/>" type="text/css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/ie.css' />" type="text/css" media="screen, projection" />
<![endif]-->  
 
<link rel="stylesheet" href="<c:url value='/resources/style.css'/>" type="text/css" media="screen, projection"/>

The first three links are the includes for Blueprint, which, as I said in my first blog, are to make my life easier when it comes to screen formatting. The final link, to style.css, is the interesting one. It contains two css values that I've shameless borrowed from Keith Donald's original Spring MVC AJAX sample code. These css values are #mask and #popup, which are applied to the following hidden div's that I've added to my JSP:

     <div id="mask" style="display: none;"></div>
     <div id="popup" style="display: none;">
          <div class="container" id="insertHere">
               <div class="span-1 append-23 last">
                    <p><a href="#" onclick="closePopup();">Close</a></p>
               </div>
          </div>
     </div>

The mask div is used to grey out the contents of the browser, whilst the popup div is used to display a popup box into which I'm going to write the JSON results of my AJAX call to the server. Note the insertHere id, it's important later on...

The last two lines of the JSP's HTML head tag include the JavaScript files for this page:

     <script type="text/javascript" src="<c:url value='/resources/jQuery-1.9.1.js' /> "></script>
     <script type="text/javascript" src="<c:url value='/resources/shopping.js' /> "></script>

The first of these imports is jQuery version 1.9.1. The version is important here. If you're using version 1.7.x or below, then the javascript below won't work as the jQuery Guys changed the way that the AJAX call works in version jQuery 1.8; however, the JavaScript code could be easily modified if necessary.

The second JavaScript import is shopping.js, which includes all the code required for this application, the crux of which is:

$(document).ready(
   
function() {
     
$('form').submit(
         
function() {
           
           
$('.tmp').remove();    // Remove any divs added by the last request
                 
           
if (request) {
             
request.abort()// abort any pending request
           
}
           
           
var $form = $(this);
           
// let's select and cache all the fields
           
var $inputs = $form.find("input");
           
// serialize the data in the form
           
var serializedData = $form.serialize();

           
// let's disable the inputs for the duration of the ajax request
           
$inputs.prop("disabled", true);

           
// fire off the request to OrderController
           
var request = $.ajax({
             
url : "http://localhost:8080/store/confirm",
              type :
"post",
              data : serializedData
           
});

           
// This is jQuery 1.8+
            // callback handler that will be called on success
           
request.done(function(data) {

             
console.log("Resulting UUID: " + data.uuid);
              $
("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
             
.appendTo('#insertHere');

             
var items = data.items;
             
// Add the data from the request as <div>s to the pop up <div>
             
for ( var i = 0; i < items.length; i++) {
               
var item = items[i];

                var newDiv =
"<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
newDiv = "<div class='span-4 append-10 last tmp'><p>&#163;" + item.price + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                   
+ item.price);
             
}

            })
;

           
// callback handler that will be called on failure
           
request.fail(function(jqXHR, textStatus, errorThrown) {
             
// log the error to the console
             
alert("The following error occured: " + textStatus, errorThrown);
           
});

           
// callback handler that will be called regardless if the request failed or succeeded
           
request.always(function() {
             
$inputs.prop("disabled", false)// re-enable the inputs
           
});

            event.preventDefault
();   // prevent default posting of form

           
showPopup();
         
});
   
});

All the action takes place inside functions that have been submitted to jQuery's ready() function and, as is usual with JavaScript, there are functions passed to functions passed to functions - the chaining I was talking about earlier. Remember that the ready() function will be called when the document is 'ready' for interaction.

The first inner function is $('form').submit(…). If you don’t know jQuery then $ is the main entry point to the jQuery library and is simply shorthand for writing jQuery. In this call I'm selecting all forms in the document (and there's only one) and passing a function argument to the submit(…)method.

The thing about jQuery is that you use it to select objects within your document model and then do something with them. jQuery has its own selection technique which uses strings passed to the jQuery(…) method. The strings have the following basic format: HTML elements such as 'form', 'a' 'div' etc. are written in plain English and when individually passed to jQuery will select all instances of that HTML type within the document.  Words with a '.' appended are CSS values and words with a '#' appended are html id attributes. So, for example, if I wrote: $('form#bill').submit(...) then I'd be selecting all forms with an id of bill, or if I wrote $('.fred').submit(…) then I'd be selecting all document objects with a class attribute of fred. Once you've got your head around this query language, then the rest is plain sailing.

When it comes to jQuery I find the O'Reilly jQuery Cookbook really useful.

The function that's passed to the $('form').submit(…) method is where all the work takes place. Before making an Ajax request there are a few house keeping tasks to complete. These include removing any document objects with a class of tmp (the first time this is called it'll do nothing, but bare with me here); aborting any outstanding requests to the server (this will do nothing most of the time); disabling any form inputs and serialising the data that the Ajax request will post to the server. The key chunk of the JavaScript code is the jQuery Ajax request:

            // fire off the request to OrderController
           
var request = $.ajax({
             
url : "http://localhost:8080/store/confirm",
              type :
"post",
              data : serializedData
           
});

The format of this function is usually: ajax, url, settings. The URL I'm using is http://localhost:8080/store/confirm that corresponds to the Spring RequestMapping I described last week. The settings you can use are optional key value pairs and fully described in the  jQuery Ajax documentation. In this instance I'm sending the serialised form data using a post request.

Having made the request, there are couple of final house keeping tasks to take care of. These are to prevent the HTML form submitting anything to the server and to 'popup' a div into which the results of the Ajax request are written. This uses the two divs previously mentioned with ids of popup and mask.

function showPopup() {
 
$('body').css('overflow', 'hidden');
 
$('#popup').fadeIn('fast');
 
$('#mask').fadeIn('fast');
}

Getting back to the Ajax request... The $.ajax(...) function call returns an object named request. This is of type jqXHR, where the confusing acronym of XHR stands for XML HTTP Request. The jqXHR object as a number of callback methods designed to allow your code to deal with certain events. In this case I've implemented: fail(…), always(…) and done(…). In the case of a request failure, the browser will call fail(…) to display a simple alert(…). always(…) is an aptly named method that is always called irrespective of the request's success or failure. In this case it re-enables all form’s input types. Finally there's the done(…) method that's called when the Ajax request is successful.

            request.done(function(data) {

             
console.log("Resulting UUID: " + data.uuid);
              $
("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
             
.appendTo('#insertHere');

             
var items = data.items;
             
// Add the data from the request as <div>s to the pop up <div>
             
for ( var i = 0; i < items.length; i++) {
               
var item = items[i];

                var newDiv =
"<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
newDiv = "<div class='span-4 append-10 last tmp'><p>&#163;" + item.price + "</p></div>";
                $
(newDiv).appendTo('#insertHere');

               
console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                   
+ item.price);
             
}

            })
;

The done(…) method is the most important here. As its argument it gets passed a function and the argument to this function is the JSON data we're interested in. This isn't any old raw JSON string, jQuery has converted the JSON to an object that's got uuid and items attributes; a doppelgänger of the server side OrderForm object from my last blog. Using this data object, all that's left for me to do is to display the results on the screen. This means looping through the data and creating a newDiv variable for each of the OrderForm’s attributes and converting them to HTML. This is simple string formatting so, for example:

"<div class='span-4  tmp'><p>" + item.name + "</p></div> "

becomes:

<div class='span-4  tmp'><p>Socks</p></div>

This div contains some useful of class attributes. These attributes are the Blueprint display attributes and one called tmp. The tmp class attribute ties into the previously mentioned $('.tmp').remove(); call. This is used to delete a user's previous selections from the popup div when the user makes multiple submissions.

Having created the newDiv variable, the final step is to append it to the popup div using jQuery's appendTo(…) function with an argument of '#insertHere':

                $(newDiv).appendTo('#insertHere');

If you run the application, you’ll now get the following shopping form from which you can select the items you wish to buy:


Pressing Confirm Purchase will now request the JSON from the server, format it and display the following popup div:


Unless I’ve missed something out, then that’s about it. These three blogs cover the creation of the app, adding the server side code and formatting it with some client side JavaScript.

There are a couple of final points. Firstly, I’m not a Javascript or client side expert, so if there any experts out there who spot any errors then I look forward to hearing from you... Secondly, I’ve forgotten to mention that the JSON part of this project is RESTFul, so thanks to Josh Long and his mention on This Week in Spring for reminding me. I guess that it didn’t occur to me to mention this because as a general rule then it should go without saying that every app should be as RESTFul as possible.


For the full source code to this blog, see GitHub - https://github.com/roghughe/captaindebug/tree/master/ajax-json


1 comment:

Unknown said...

Hi Roger,

Thanks for giving a guidance as to how to use ajax-json with spring. I followed the instructions and could create the sample project successfully. Thanks for the explanation.