Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
rdiger_plantiko2
Active Contributor

In my last blog, I exposed a Web client against a REST API with XML as data transfer language. However, some people find everything XML-related uncool and boring, and would prefer to work with a data format like JSON instead.

This blog is dedicated to these people. :smile:

I describe a web application with exactly the same functionality as that of my former blog, but working with the JavaScript framework jQuery on the client, and with JSON as data transfer format. On the server side, exactly the same REST API is addressed. Here is the URL of this web application:

http://bsp.mits.ch/buch/zz_jobs_json/jobs.htm

Before checking it in as a BSP, I had gradually developped it in jsfiddle.net, a "playground for web developers" (self-description).

Rolling Your Own Samples with jsfiddle

jsfiddle.net is a perfect platform for such kind of experiments. In a quadripartite screen, jsfiddle displays the HTML, CSS, and JavaScript code, and in the bottom right part the actual result built of these ingredients. You can choose the framework to work with, or even embed any other ressource into your sample. See here the result of the jQuery-based demo application with JSON as data transfer format:

http://jsfiddle.net/rplantiko/vzysD/   -  To inspect it, you will need a browser supporting CORS, like Firefox 3.5 and above, Chrome 3 and above, or MSIE 10. The jsfiddle page will not work with Internet Explorer 9.

Without saving, you can simply edit all the ressources constituting a jsfiddle web application and hit "Run" to see the effect of your changes.

How to Make a Polyglot API

The usual way to make a REST API polyglot - i.e. to make it talk in more than one data transfer languages - is to put its attention on the special header field Accept. If the field

Accept: application/json

is present in the request header, the API knows that JSON is the desired data transfer format. The response will therefore be a JSON object (and not XML, as would be the case otherwise).

There is much confusion about the right MIME type for JSON data - we find values like text/x-json, application/x-javascript, text/javascript, text/x-javascript and the like. But the JSON specification (RFC4627) clearly specifies the MIME type to be application/json.

In our example API, with the above header you get the data in this form:


  "JOBS":
      {"0001":
        {
         "REPID":"RSNAST00",
         "VARID":"UXPD_KUBE_KV",
         "PRIO":"2",
         "RESTART":"X",
         "CONTACT":"Rainer Zufall",
         "DESCR":"Output all sales order confirmations"}
        },
  "MESSAGE":"",
  "TYPE":""
}

Cross-Origin Ressource Sharing (CORS)

The test application residing on jsfiddle.net, the "Same Origin Policy" (SOP) would usually forbid requesting data from the domain mits.ch, where the REST API is located: "Ajax can only phone home." But if the browser supports CORS, there is a way to perform such cross-domain request: The browser sends a special request with the HTTP method OPTIONS to the URL it wants to request. If the service that is responsible for this URL, sends a special header field in its reply, for example:

Access-Control-Allow-Origin: http://jsfiddle.net

then the browser will not block the cross-domain request. This way, it will be possible to integrate web services from whatever source into the own web application - as long as that web service explicitly allows cross-origin requests.

With the final application http://bsp.mits.ch/buch/zz_jobs_json/jobs.htm, there is no CORS necessary, since it is on the same domain as the API.

Application Design

Following the design of the last blog, I have divided the application layout into several areas:

  • The Message Area, reserved for output of single messages (error, info, success or whatever),
  • the Input Area, containing all the input fields (text input, drop-down listboxes, checkboxes),
  • the Button Area,
  • and the Table Area containing the actual job data, rendered as a jQuery datatable.

The application logic somehow reflects this layout structure. There are global objects table, inputArea and msg, connected to the Table Area, the Input Area, and the Message Area, respectively. In addition to this "view" part, there are

  • a controller object,
  • an object representing the job data themselves (to be distinguished from the table which only presents these data),
  • an events object for registering event handlers and triggering events,
  • and a server object, responsible for all the Ajax requests.

// The "global players" :
var jobs, server,             // Model parts (client and stub for server)
    controller,                   // Controller
    events,                       // A tiny event registration service
    table, inputArea, msg    // View parts
    ;

Server Requests

Following this separation of concerns, it must be the server object which is responsible for performing all the REST API calls.

Let's look at an example. The server object holds a method saveJob() which will be called when the user hits the Save button. The data entered in the input area will be collected into a hash variable job.

Server.prototype.saveJob = function(job,callback) {
  $.ajax({
      url:this.JOB_MANAGER_URL+job.ID, 
      type:"PUT",
      data:JSON.stringify(job),
      success: this.onSuccess(callback)
    });
  };

This is the way to send an Ajax request with JSON data using jQuery: The $.ajax() method accepts a hash with all the actual request properties. With JSON.stringify(), JavaScript data can be converted into a JSON string - this will be used here to pass the job data in the HTTP request body. The URL contains the job ID - and the HTTP method for saving is PUT (not POST).

You may miss some options here - for example: How does the server know that the HTTP body contains JSON data?

Indeed, there is a method $.ajaxSetup() which can be used to set the preferences for any Ajax request on the page - so that they don't have to be specified with each particular $.ajax() request.

The right place to specify these preferences is in the constructor of the Server class:

// The object for remote calls to the server
function Server() {
  this.JOB_MANAGER_URL = "/buch/job/attributes/";
  this.VARIANTS_URL = "/buch/job/variants/"
  $.ajaxSetup({
    url:this.JOB_MANAGER_URL,
    type:"GET",
    dataType:"json",
    contentType:"application/json",
    data:"", 
    processData:false
    });
  }     

If the Ajax request returned successfully, the onSuccess method will be called. Here, onSuccess has been set to server.onSuccess(callback), which is a method producing functions as return value - the advantage is that the callback function can be passed dynamically when the request is called (i.e. when the saveJob() function is performed, because the user hits Save).

Server.prototype.onSuccess=function(callback) {
  return function(data) {
    controller.updateFromServer(data,callback);
    }
  };

At this point, the server delegates further followup actions to the controller - passing the API's return data as data, and the required callback function as callback to the controller method updateFromServer().

The controller method updateFromServer() provides space for general followup actions, and will finally call the callback function:

Controller.prototype.updateFromServer=function(data,callback) {
    msg.show( data );   
    callback(data);       
    }

If the API return data contain a message, it will be displayed via the msg.show( data ) call. Further action is performed in the callback.

Model-View-Controller in the Browser

For an application involving a user interface, it is good practice to apply the Model-View-Controller architectural pattern.

The model part, the core logic and data of our application is split into two parts (which is a consequence of the client/server split):

  1. The REST API contains logic which is processed on the server via the server object. It operates on data which reside on the server (the job attributes table).
  2. Further logic is implemented in JavaScript and executed in the browser. This is the jobs object.

For simplicity, the jobs object on the client is modelled as a hash, since there is only one instance required, and there is no need for polymorphy. Basically, this means that we only want to have a defined namespace for a group of functions.

The jobs object contains an attribute JOBS, a hash with all the job data, using the ID as hash key. It offers methods for manipulating these data. For example, consider the update() method:

// --- A representation of the job data on client side
jobs = {
  JOBS:{},
//   ...
  update: function(jobData) {
    var $elf = this, update = false;
    $.each( jobData, function(id,job) {
      if (id) $elf.JOBS[id] = job;
      update = true;
      });
    if (update) events.raise( "update", jobData ); 
    },
// ...

As you see, the update method first updates the own data according to the input. After this, it raises the "update" event to notify other parts of the application about the change. The coupling through the events object helps keeping the layers separate from each other. This is in accordance with the MVC model - it corresponds to the dashed arrow "Change Notification" in Sun's well-known MVC graphic:

An example for a User Gesture is when the user hits the Save button. Let's see what happens on this:

  • The controller has a click handler for this event, sending the save request to the server.
  • It reads the data from the Input Area (view!) and sends them as payload of the request to the server
  • As a callback, when the server notifies that his State Change was OK, the jobs object receives this State Change request - by a call of the update() method.
  • The data for the jobs.update() call are taken from the Ajax response (data.JOBS).
  • The jobs.update() method contains the Change Notification of the view parts. The data in the Table Area (view!) have to be updated with the new values.

Controller.prototype.click_save = function() {
    if (inputArea.okForPosting()) {
      server.saveJob(inputArea.get(),function(data){
        jobs.update(data.JOBS);
        }); 
      }
    }

Coupling with the Events Object

Once, when the application is loaded, the registration for the model events has to be performed. This is done in the constructor of the controller object:

// --- Controller manages/dispatches tasks of view and model
function Controller() {
  var controller = this;     
  ...
// Register for model notifications
  events.register({
    update:       [[table, "update"],   [inputArea,"onUpdate"]],
    replace:      [[table, "replace"],  [inputArea,"reset"]],
    deleteSingle: [[table, "deleteRow"],[inputArea,"reset"]]
    });   
  ...
  }     

The events object only has two methods register() and raise(). With register(), object methods are registered for later call. As can be seen from the above code, each registry entry consists of an object instance and a method name belonging to this object. From these arguments, a callback function is generated for later call, and stored in the events.registry hash. Observe that this function calls the instance method by passing the object instance as this argument. So it's not only a function call, but really the call of the object method:

// --- An event registration service
events = {
    registry:{},
    register: function( entries ) {
      var $elf = this;
      $.each( entries, function(event,handlers) {
        if (!$elf.registry[event]) $elf.registry[event] = [];
        $.each( handlers, function( index, handler ) {
          $elf.registry[event].push( function(data) {
            handler[0][handler[1]].call(handler[0],data);
            });         
          });
        }); 
      },
  ...
  };

Later, when the event is raised, all the registered callbacks will simply be invoked. Actual data can be passed to the handler as argument in the events.raise() call:

// --- An event registration service
events = {
  ...
    raise: function(event,data) {
      var handlers = this.registry[event];
      $.each( handlers, function(index,registeredHandler) {
        registeredHandler(data);
        });
      }         
  }; 

Controller Tasks

The controller is mainly a delegator: Depending on the user action, it has to notify the view and model components that are linked to this change. It contains event handler implementations and registrations.

The controller instance will be constructed when the DOM is ready:

$(function(){ controller = new Controller()});

All the page's initialization tasks will therefore be executed in the constructor.

function Controller() {
  var controller = this;
  server = new Server();
  ...
  }

We have already seen the events.register() call, which is part of the Controller constructor. Apart from this, there are further registrations concerning the view elements.

The handlers for click buttons have to be registered. Using an appropriate naming convention for the handler methods, the method can be called dynamically. Also, before the specific click handler is called, there is room for generic actions to be performed with each button click: Here, a msg.clear() is called for removing messages from former dialogue actions:

// Register the button handlers
  $.each(["save","new","copy","delete","reset"],function(index,id) {
      $("#"+id).click( function() {
        msg.clear();
        controller["click_"+this.id].call( controller );
        });
    });     

Then, the table (the object responsible for the Table Area) has to be constructed. At this point in time, there are no rows in the table. Therefore, we have to add a dynamic registration for row clicks using the jQuery.on( ) function. Whenever a row appears in the DOM at a later time, the method controller.click_row() will be registered for its click event.

// Create the table
  table = new Table();
// Register for all clicks on rows
  $("#result tbody").on( "click", "tr", function( e ) {
    msg.clear();
    controller.click_row(this);
    });

The drop-down list for variants has to be re-built whenever the report name is changed:

// Register for change of report id
    $("#REPID").change( function() {
      msg.clear();
      server.getVariants(
        $("#REPID").val(),
        function(variants) {
          inputArea.fillVariants(variants);
        })
      });

As the final step of the constructor, the current jobs are selected via the REST API, using a GET request, and placed into the jobs model object.

// Fill it with the jobs
  server.getJobs(function(data){
    jobs.update( data.JOBS );
    });

The Table Object

For the presentation of the job data, we are using DataTables, a very powerful and well-documented jQuery plugin. The Table Area is provided by the table object, which contains the jQuery DataTable as a delegate:

// --- Table object for presenting the data
function Table() {
// Create datatable object
    this.dataTable = $("#result").dataTable({
      aoColumns : this.aoColumns(jobs.fields,jobs.field_text),
      bFilter:false,
      bPaginate:false,
      bInfo:false
      });
    }

The purpose of the table object is to encapsulate the details of the table presentation. In the interface, it gets arguments like jobData, but no data types that are internal to DataTables. Only the method implementations contain the details of the DataTables API. This way, the dependency with DataTables is concentrated in one class. When I decided to replace the DataTables object by something else, the necessary changes can be localized to this single class.

The table.update() method may serve as an example. It uses API methods to detect whether the jobs are already contained in the table. If yes, these rows are updated. Otherwise, new rows are inserted (similar in spirit to the Open SQL statement MODIFY ). For unknown reasons, the dynamical table update is one of the tasks for which there is no straightforward API call. Here, I am using a solution by Emil Åström which may be interesting for other DataTables users, too:

Table.prototype.update = function(jobData) {
  var dataTable = this.dataTable,
      settings  = dataTable.fnSettings(),
      data      = this.mapToDataTable(jobData);
  $.each( data, function(index,row) {
    var tr = document.getElementById(row.DT_RowId);
    if (tr)  // row exists -> update
      dataTable.fnUpdate(row,tr);
    else     // new row    -> add it
      dataTable.oApi._fnAddData(settings, row);
    });
  settings.aiDisplay = settings.aiDisplayMaster.slice();
  dataTable.fnDraw();
  };

Summary

If a REST API handles the application/json content type, it is possible to write a web client as a Business Server Page, performing all the API calls, all the interaction and presentation tasks on the client with JavaScript. The BSP environment only serves as a container for - as viewed from server-side - "static" ressources like HTML, JavaScript and CSS files. For cases like this, the jQuery library is a good choice.

2 Comments