With AJAX there are many choices on how to use different aspects of JavaScript. This document proposes some recommendations for developers creating AJAX components with a focus on using JavaScript on the client and Java on the server. Many of these recommendations will carry over to other server-side technologies. The recommendations are as follows:
element.innerHTML
Like with Java having a standardize style makes code easy to work with and maintain especially in larger organizations. The folks over at Dojo have a great set of JavaScript Programming Conventions.
Believe it or not you can write JavaScript in an object oriented way. Doing so will allow for better reusable code, enabling your objects to be organized and allow for dynamic loading or objects. The following is a JavaScript version of a shopping cart followed by equivalent Java code.
function Cart() {
this.items = [];
}
function Item (id,name,desc,price)) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
// Create an instance of the cart and add an item
var cart = new Cart();
cart.items.push(new Item("id-1","paper","something you write on",5));
cart.items.push(new Item("id-1","Pen", "Something you write with", 3);
var total;
while (var l; l < cart.items.length; l++) {
total = total + cart.items[l].price;
}
The Cart
object above provides basic support for maintaining an
internal array of Item
objects.
The equivalent Java object representation of a cart may look as follows.
import java.util.*;
public class Cart {
private ArrayList items = new ArrayList();
public ArrayList getItems() {
return items;
}
}
public class Item {
private String id;
private String name;
private String desc;
private double price;
public Item (String id, String name, String desc, double price) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
public String getId() {return id;}
public String getName() {return name;}
public String getDesc() {return desc;}
public float getPrice() {return price;}
}
The example above represents a server-side object representation of a cart. This
object would need to be kept in the HttpSession
by a JSP, Servlet, or JSF managed
bean. AJAX interactions could be used to add Items to the Cart or retrieve the current state.
In JavaScript there is the potential for object names to collide.
In Java package names are used to
prevent naming collisions. JavaScript does not provide package names
like Java however you can. When writing components use objects and
object hierarchies to organize related
objects and prevent naming collision. The example below creates a top
level object
BLUEPRINTS
which acts in a sense like a namespace for related objects. The objects are set as
properties of the parent object.
// create the base BLUEPRINTS object if it does not exist.
if (!BLUEPRINTS) {
var BLUEPRINTS = new Object();
}
BLUEPRINTS.Cart = function () {
this.items = [];
this.addItem = function(id) {
items.push(new Item(id);
}
function Item (id,qty) {
this.id = id;
this.qty = qty;
}
}
// create an instance of the cart and add an item
var cart = new BLUEPRINTS.Cart();
cart.addItem("id-1",5);
This technique will prevent naming collisions and is a good practice if your code will be used where there is the potential of a naming collision.
The prototype property is a language feature of JavaScript. The property is available on all objects. Property resolution in JavaScript is designed to look at the value the prototype property if a property is not found in the current object. If the value of the object defined as the protoype object does not contain the property the value of its prototype property is examined. Prototype property chains (hierarchies) are often used to provide inheritance in JavaScript objects. The following example will show how we can add behavior to an existing object using the prototype property.
function Cart() {
this.items = [];
}
function Item (id,name,desc,price)) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
function SmartCart() {
this.total;
}
SmartCart.prototype = new Cart();
SmartCart
extends the Cart
object inheriting its properties and adds a total property.
Next we will some convenience functions to the Cart
that will allow items to be
added and the total to be calculated. While you could add the functions directly to the SmartCart
object,
this will result in a new function being created for each instance of the SmartCart
.
In JavaScript functions are objects so for object where there are many instances being created a shared
instance of the behaviors would save resources. The following code declares a shared calcualteTotal
and a addItem
function and adds them as a property of the SmartCart
prototype member.
Cart.prototype.addItem = function(id,name,desc,price) {
this.items.push(new Item(id,name,desc,price));
}
Cart.prototype.calculateTotal = function() {
for (var l=0; l < this.items.length; l++) {
this.total = this.total + this.items[l].price;
}
return this.total;
}
// Create an instance of the cart and add an item
var cart = new SmartCart();
cart.addItem("id-1","Paper", "Something you write on", 5);
cart.addItem("id-1","Pen", "Soemthing you write with", 3);
alert("total is: " + cart.calculateTotal());
As you can see extending an object is pretty simple. In this case there is a single instance of the calcualteTotal
addItem
functions are shared across all instances of the SmartCart
object
(similar to a static method in Java). Note that the scope of the items is still this.items
even
though the functions are declared separately from the SmartCart
object. When the new protoype
functions are executed they will be in the scope of the SmartCart
object.
It is recommended to use the prototype property to define shared behavior when there may be many instances of the objects as it will reduce the number of objects in JavaScript and its use provides a good separation of behavior from data. The prototype property is also ideal for providing defaults for objects (which is not discussed here). There is much more that can be done with the prototype property beyond the scope of this document. See the Resources section of this document for more information.
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.
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.
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.
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.
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.
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.
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);
.
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.
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>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.
#Styles
</style>
<script type="text/javascript">
// JavaScript logic
<script>
<body>The quick brown fox...</body>
<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.
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.
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.
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.
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.
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.
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. For more on what to do on the server see How do I provide internationalized AJAX interactions?.
See the JavaScript style guide done by the Dojo folks at:
http://dojotoolkit.org/docs/js_style_guide.html
See the following link for more information on object oriented JavaScript
http://www.cs.rit.edu/%7Eatk/JavaScript/manuals/jsobj/index.htmLearn about Emulating Namespaces at:
"http://www.justatheory.com/computers/programming/javascript/emulating_namespaces.htmlLearn about JSON at:
Learn about JavaScript object literals at:
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Literals
To learn more about JavaScript closures see:
http://jibbering.com/faq/faq_notes/closures.html
See QuirksMode for various tips on JavaScript:
For a discussion on separation of CSS, Content and JavaScript see:
http://thinkingandmaking.com/entries/63