Do you know that

Using Ajax technologies in web applications provides many challenges for developers interested in adhering to WAI accessibility guidelines. In addition there are numerous development groups working on USA government projects which require strict adherence to Section 508 Compliance standards. Failure to comply with these standards can often lead to cancellation of contracts or lawsuits intended to ensure compliance.

Also interesting

Tourist details and villas of Huelva Holidays Costa de la Luz Spain

JavaScript AJAX Recommendations, part II

increase speed of your programs

Store state specific to the view in JavaScript and state that span pages on the server

In the previous example we showed a Cart both as a server and client side object. Whether to store state on the client or server is a difficult question. If you are designing your JavaScript clients as a single page application it might make sense to have the Cart only on the client. In this case the JavaScript Cart could may interact with the server only at checkout.

As a general rule store view state related to a specific page using JavaScript objects. Keep in mind that JavaScript objects are specific to a HTML page and will be lost if the "Reload" button is pressed, if the browser is relaunched/crashes, or if you navigate to another page.

Store state that spans pages on the server as a Java object scoped to the HttpSession. The client and server objects should be synched on page refreshes and page loads.

If you are developing an AJAX client that needs to work offline (such as when you are on a plane trip) there are options for storing state on the client but they are not standard. Dojo provides the dojo.storage API for storing up to 100KB of content for offline usage from a JavaScript client. As standards for client storage emerge this I'm sure this API will be adapted to support the standard. If the state you are saving is confidential or if it needs to be accessed by more than one computer consider saving the state on a server.

Write reusable JavaScript

JavaScript should not be tied to a specific component unless absolutely necessary. Consider not hard coding data in your functions that can be parameterized. The following example shows a reusable autocomplete JavaScript function.

<script type="text/javascript">
 doSearch(serviceURL, srcElement, targetDiv) {
  var targetElement = document.getElementById(srcElement);
  var targetDivElement = document.getElementById(targetDiv);
  // get completions based on serviceURL and srcElement.value
  // update the contents of the targetDiv element
 }
</script>

<form onsubmit="return false;">
 Name: <input type="input" id="ac_1" autocomplete="false" 
              onkeyup="doSearch('nameSearch','ac_1','div_1')">

 City: <input type="input" id="ac_2" autocomplete="false" 
              onkeyup="doSearch('citySearch','ac_2','div_2')">
 <input type="button"  value="Submit">
</form>

<div class="complete" id="div_1"></div>
<div class="complete" id="div_2"></div>

The doSearch() function in the example above can be reused because it is parameterized with the String id of the element, service URL, and the <div> to update. This script could later be used in another page or application.

Use object literals as flexible function parameters

Object literals are objects defined using braces ({}) that contain a set of comma separated key value pairs much like a map in Java.

 {key1: "stringValue", key2: 2, key3: ['blue','green','yellow']}

The example above shows an object literal which contains a string, number, and array of strings. As you may imagine object literals are very handy in that they can be used as generic for passing in arguments to a function. The function signature does not change if you choose to require more properties in a function. Consider using object literal as the parameters for methods.

 function doSearch(serviceURL, srcElement, targetDiv) {
     var params = {service: serviceURL, method: "get", type: "text/xml"};
     makeAJAXCall(params);
 }

 function makeAJAXCall(params) {
    var serviceURL = params.service;
    ...
} 

Also note that with object literals you can pass anonymous functions as may be seen in the following example:

function doSearch() {
     makeAJAXCall({serviceURL: "foo",
                    method: "get", 
                      type: "text/xml",
                  callback: function(){alert('call done');}
     });
 }
 
 function makeAJAXCall(params) {
     var req = // getAJAX request;
     req.open(params.serviceURL, params.method, true);
     req.onreadystatechange = params.callback;
     ...
 } 

Object literals should not be confused with JSON which has similar syntax. For more on object literals see the resources section below.

Compress your JavaScript

Not to be confused with a zip compression scheme compression refers to removing the white spaces and shortening the names of variables and functions in a file. Consider compressing your JavaScript resources when you deploy your application. While in development mode keep your scripts readable so that they may be debugged easier. If you use a 3rd party JavaScript library use the compressed version if one is provided. There can be a great savings in bandwidth by using the compressed scripts. With Dojo 0.2.2 for example, the compressed dojo.js is 130KB versus the non-compressed which is 208KB. The Dojo team provides a compressor service called ShrinkSafe that allows you to easily compress your own JavaScript files.

Avoid compressing your jar files that contain your scripts

If you are delivering the scripts or styles from a jar file via a server side component, do not use zip compression when creating the jar file in that the server will have to decompress the file for each client request for the script or resource. Larger scripts could cause performance problems for your server.

Protect your serverside assets

Never put business logic or server-side access code in JavaScript. Be careful for example when exposing JSF method/value binding expressions like #{SomeBean.someMethod} or classes/methods names to be invoked on the server as it exposes your internal domain model and could be exploited. If you provide such a mechanism make sure a JavaScript client can call only the methods intended to be available to the client. Never put SQL statements in JavaScript code. Always remember JavaScript code is visible to the client with the click of a button.

Always validate request parameters on the server regardless of whether the request originated from an AJAX client or not.

Consider loading JavaScript on demand

If you have a large library or set of libraries you don't need to load everything when a page is loaded. JavaScript may be loaded dynamically at runtime using a library such as JSAN or done manually by using AJAX to load JavaScript code and calling eval() on the JavaScript. Following is an example of a JavaScript snippet of code that is loaded dynamically on the client and used to create an instance of a Cart object.

cart.js

 function Cart () {
     this.items = [];

     this.checkout = function() {
       // checkout logic
     }
 }

Now from your code evaluate the text from the cart.js and create a Cart object.

 // get cart.js using an AJAX request
 eval(javascriptText);
 var cart = new Cart();
 // add items to the cart
 cart.checkout();

Keep in mind the objects defined in the javascriptText will only be available in the context/scope where you called eval(javascriptText);.

Consider using JSON for model data transport

While the XML is still a valid format for model data transport in AJAX (especially in cases where you're communicating with XML based services or your services must also address non-AJAX based clients), you should consider using JSON to communicate data from your server to your JavaScript based client. The following XML document representing two product categories each contain three products.

<categories>

 <category id="0" name="Vegetables">
  <products>
   <product>
    <name>Onion</name>
    <price>.75</price>

   </product>
   <product>
    <name>Carrot</name>
    <price>.50</price>
   </product>

   <product>
    <name>Eggplant</name>
    <price>1.50</price>
   </product>
  </products>

 </category>
  <category id="1" name="Fruit">
  <products>
   <product>
    <name>Orange</name>

    <price>1.25</price>
   </product>
   <product>
    <name>Apple</name>
    <price>1.30</price>

   </product>
   <product>
    <name>Tomato</name>
    <price>.40</price>
   </product>

  </products>
 </category>
</categories>

The XML document above would require a significant amount JavaScript code on the client to walk the XML document DOM and bind the data to a object representation in JavaScript. Object definitions of the category and product elements along with their relationship would need to be defined in the code. The example below shows the JavaScript needed to parse the document above and bind it to an object representation. The example uses an array of Category objects which each contain an array of Product objects.

 function Category (id, name) {
     this.id = id;
     this.products = [];
 }

 function Product (name,price) {
     this.name = name;
     this.price = price;
 }
 
 var categories = [];

 // Callback with the XML returned from an AJAX request
 function processResults(responseXML) { 
     var categoryElements = responseXML.getElementsByTagName("category");
     for (var l=0; l < categoryElements.length; l++) {
         var categoryElement = categoryElements[l];
         var catId = categoryElement.getAttribute("id");
         var catName = categoryElement.getAttribute("name");
         var category = new Category(catId, catName);
         var products = categoryElement.getElementsByTagName("product");
         for (var pl=0; pl < products.length; pl++) {
             var prodName = products[pl].getElementsByTagName("name")[0].firstChild.nodeValue;
             var prodPrice = products[pl].getElementsByTagName("name")[0].firstChild.nodeValue;
             category.products.push(new Product(prodName,prodPrice));
         }
         categories.push(category);
     }
     var veggies = categories[0].products[0];
 }

As you may see in the example above there is a bit of lifting on the client required to take an XML document and convert it into a data model on the client.

JSON is a simple format native to JavaScript where the data along with the object definitions and relationships are defined in a simple plain text format. The following code example uses JSON to represent the same data as the XML document above.

[
  {"id": "0", "name":"Vegetables", "products": 
  [{"name": "Onion", "price": .75},
  {"name":  "Carrot", "price":.50},
  {"name":  "Eggplant", "price":1.50} ]},

  {"id": "1", "name":"Fruit", "products": 
  [{"name": "Orange", "price": 1.25},
  {"name":  "Apple", "price":1.30},
  {"name":  "Tomato", "price":.40} ]},
]

To create an array of Category objects containing an array of Product objects you only need to retrieve the JSON as plain text using an AJAX call and use the JavaScript eval() function to assign it variable (in our example this is categories).

var jsonText = // get the JSON text listed above
var categories  = eval(jsonText);
var veggies = categories[0].products[0];

That is it! No XML processing code on the client. That said, your server side logic will need to render data in the JSON format.

Provide a clean separation of content, CSS, and JavaScript

A rich web application user interface is made up of content (HTML/XHTML), styles (CSS), and JavaScript. JavaScript is key in that it is invoked by user gestures such as mouse clicks and can manipulate the content. Separating the CSS styles from the JavaScript is a practice which will make your code more manageable, easier to read, and easier to customize. It is recommended that the CSS and JavaScript are placed in separate files.

If you are rendering HTML from a Java based component such as JSP, Servlet, or JSF renderer you may be tempted to output the styles and JavaScript to each page as appears below.

<style>
#Styles
</style>

<script type="text/javascript">
// JavaScript logic
<script>

<body>The quick brown fox...</body>
If you embed the content in each page there will be a potential bandwidth loading cost each time the page is loaded. JavaScript and CSS files may be cached and re-used across pages if you reference instead of embed them as may be seen below.
 <link rel="stylesheet" type="text/css" href="cart.css">

 <script type="text/javascript" src="cart.js">
 
 <body>The quick brown fox...</body>

Links may be mapped to static resources on your server or to a JSP, Serlvet, or JSF component that generates the resource dynamically. If you are developing JSF components consider using Shale Remoting which provides base classes that provide the core functionality for writing the script/CSS links and access to resources that may even be served from a JSF component jar file.

In JavaScript code use element.className over element.setAttribute("class", STYLE_CLASS) when dynamically changing the style of an element as element.className is supported on the widest range of browsers (it is also most effective way to support style changes with IE).

On your tag library defintions with JavaServer Faces (JSF) and plian Java tag libraries you can not use the attribute "class" to specify the style as there is a getClass() method on all Java objects and you can not override. Use the attribute name "styleClass" instead.

Design with I18n in mind for more details.

Like with GUI components use caution when statically setting the layout sizes when static content may be provided from an external source as the new content may exceed the boundries of your component.

Use caution with element.innerHTML

Using element.innerHTML instead of the DOM style element.appendChild() may be very tempting to use element.innerHTML as it is much simpler to program to and may processed by the browser much quicker than using the DOM APIs however, it is important to understand that there are drawbacks that related to this approach.

If you choose to use element.innerHTML try to write JavaScript that generates minimal HTML. Rely instead on CSS for enhancing the presentation. Remember to always strive to separate content from presentation. Make sure to de-register event listeners in the existing innerHTML of the element before re-setting the element.innerHTML as it will lead to memory leaks. Keep in mind that DOM elements inside the element.innerHTML are lost when you replace the content and references to those elements will be lost as well.

Consider using the DOM APIs such as element.appendChild() for dynamically creating elements in JavaScript. The DOM APIs are standard, they provide programatic access the elements as variables as they are created, and problems such as memory leaks or lost references encountered when using element.innerHTML will be easier to avoid. That said, keep in mind that creating tables IE using DOM can be problematic as the APIs in IE do not follow DOM. See MSDN Documentation on Tables for the necessary APIs. Adding elements other than tables in IE is not a problem.

De-reference unused objects

JavaScript uses garbage collection like Java and just like with Java you need to remember that garbage collection is not a a comprehensive solution to memory management. Make sure you de-reference variables when they are no longer needed so they may be garbage collected. If you are using element.innerHTML, make sure you de-reference any listeners in the code before it is replaced.

Event handing code tends to be prone to memory leaks. Consider using an event wrapper such as the one provided in Dojo or MochiKit as they are designed to prevent leaks.

Use care with closures

Closures are easy to create and in some cases can cause problems. Developer that are accustomed to object oriented development will tend to bind behavior related to an object with an object. The following example is a shows a closure that will cause a memory leak.

function BadCart() {
    var total;
    var items = [];
 
    this.addItem = function(text) {
        var cart = document.getElementById("cart");
        var itemRow = document.createElement("div");
        var item = document.createTextNode(text);
        itemRow.appendChild(item);
        cart.appendChild(item);
        itemRow.onclick =  function() {
            alert("clicked " + text);
        }
    }
 }

So what is wrong with this example? The addItem reference is to a DOM node that is not in the scope of the BadCart. The anonymous "onClick" handler function is holding on to a reference to the itemRow and not allowing it to not be garbage collected unless we explicitly set itemRow to null. The following code in GoodCart will fix this:

function GoodCart() {
    var total;
    var items = [];
 
    this.addItem = function(text) {
        var cart = document.getElementById("cart");
        var itemRow = document.createElement("div");
        var item = document.createTextNode(text);
        itemRow.appendChild(item);
        cart.appendChild(item);
        itemRow.onclick =  function() {
            alert("clicked " + text);
        }
        itemRow = null;
        item = null;
        cart = null;
    }
 }

Setting the itemRow will remove external references to it by the anonymous function set as the itemRow.onclick handler. It is a good practice to clean up and also set item and cart to null to allow everything to be garbage collected. Another solution to prevent the memory leak would not to use an use a external function in place of the anonymous function in BadCart.

If you use closures take caution not to hold on to references to browser objects such as DOM related objects using the local variables of a closure as it can lead to a memory leak. Objects are not garbage collected until all references to objects are gone. For more information on closures see Javascript Closures.

Enable resource overrides in your components

Provide a means to override the loading of resources such as dependent scripts, CSS files, or images by your JavaScript centric, JSP, Servlets, or JSF components. If you jar your component for distribution with a JSP tag or JSF component, move those resource files into the WEB-INF directory and create code to stream them out to the client. In JSF this can be done using a phase listener and with other Java web technologies this may be a servlet or filter may be used. This way during the develpment of the component you can easily modify the style and JavaScript the component without having to repackage and deploy the component. Your customers will have a nice neat bundle and not need to worry about deploying your resource files to their own web application. This solution would allow your customers to customize the component styles or JavaScript on a case by case basis without rebuilding and re-packaging the component.

Design with I18n in mind

Internationalization should not be an after thought. From the JavaScript client's perspective you need to do two things: Set the page encoding and make sure localized text is passed back to your server backend.

For the HTML page encoding UTF-8 as to supports widest range of languages.

 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >

When passing localized form field variables use the JavaScript encodeURIComponent() function to convert the field variable into a HTTP friendly representation. The encoding used will match that of the HTML page content type. The example below shows how you would encode the value of an form input field with the id "complete-field" and make an AJAX request using Dojo.

  var completeField = document.getElementById("complete-field");
  var bindArgs = {
            url:  "autocomplete?action=complete&id=" + encodeURIComponent(completeField.value),
            mimetype: "text/xml",
            load: function(type, data) {
               processSearch(data);
             }
  };
  dojo.io.bind(bindArgs);

Consider using XML as the return content type for localized data as XML can specify encoding information that is understood and applied by the XMLHttpRequest object. If you are passing around localized content you need to make sure you server encodes the return content properly.

Greg Murray
https://blueprints.dev.java.net/bpcatalog/conventions/javascript-recommendations.html

RETURN TO PART I