AJAX Components | Page 3 | WebReference

AJAX Components | Page 3

[previous] [next]

AJAX Components

Declarative Google Map

A declaration is just an abstraction layered over the top of imperative code. Elements in a declaration roughly map to objects and attributes to fields or properties on those objects. Although a declaration does not specify anything about the methods of an object, and it shouldn't, it can express everything about the state of an object or, perhaps more familiar to you, the serialized form of an object. In the case of our imperative Google Maps example, we create, set up, and render a Google Map entirely through error prone and uncompiled JavaScript resulting in a map that has a certain zoom level and is centered on some lat/long coordinates. Ideally, we can instead take an XML description of the map containing all the information about the map—zoom level, center coordinates, and so on—and instantiate a map based on that state information stored in the XML. So, rather than defining our Google map with JavaScript, you can use a custom XHTML declaration that describes the state of a serialized map, which gets deserialized (by some code that you can write) resulting in a map as though you had explicitly written the JavaScript code. A Google Map declaration based on the imperative code we wrote previously might look something like this:

The parallels between this declaration and the imperative code are clear—almost every line in the declaration can be identified as one of the JavaScript lines of code. The biggest difference is that, as we have discussed, the declaration specifies only how the map should be displayed independent of any programming language and in an industry-standard, easily machine-readable, and valid (according to an XML Schema) format. The actual code used to convert that to an instance of a Google Map is left up to the declaration processor, which again, can be implemented in any language or platform—in our case, we stick with the web browser and JavaScript. Furthermore, the dependence on order of statements in imperative coding—that you must create the map object before setting properties on it—is masked by the nested structure of the XHTML declaration, making it unnecessary for a developer to understand any dependencies on the order in which code is executed. However, they must understand the XML schema for the declaration. Let's take a closer look at what we have defined here for our map declaration.

First, we defined the root of our declaration using a DOM node with the special name of <g:map> <g:map> DOM node where the g prefix is used to specify a special namespace. This makes the HTML parser recognize those tags that don't belong to the regular HTML specification. When the component is loaded from the declaration, we want it to result in a Google Map created in place of the declaration, and that map will have the specified dimensions, zoom level, and center point. Similarly, it will result in a polyline drawn on the map with the given color and start and end points. The only trick is that we need to write the JavaScript code to go from the declaration to an instance of a map!

Because the web browser has no knowledge of our custom XHTML-based declaration, it does not have any built-in code to find and create our component based on the declaration. To go from our component declaration to an instance of an AJAX component, we need to use almost all the technologies that we have learned about so far. To start with, we need to bootstrap using one of the techniques discussed in Chapter 3, "AJAX in the Web Brower,"—the exact same as we would need to do to with an imperative component. Our Google Map sample page now becomes the following:

There is now no sign of any JavaScript on the web page, but we added two additional external JavaScript files that are responsible for parsing the declaration, moved the special CSS into an external file, and added the declaration itself. This is the sort of page that a designer or someone unfamiliar with JavaScript could write.

The included entajax.toolkit.js file contains all the helper classes and functions that we need to make this work on Firefox, Internet Explorer, and Safari. In gmaps.js is where all the magic happens. The contents of gmaps.js looks like this:

The initComponents() method depends on a few things. First, to facilitate a flexible and extensible approach to building JavaScript components from XHTML, we use a global hash where the keys are the expected HTML element names and the values contain additional metadata about how to deserialize that XHTML element into a JavaScript. This approach is analogous to a more general technique that can deserialize a component based on the XHTML schema. For a small, representative subset of the available parameters that can be used to create a Google Map, the entAjax.elements hash might look something like this:

We have defined five keys in the entAjax.elements hash that are map, smallmapcontrol, maptypecontrol, polyline, and center. For each of these keys, which relate to expected DOM node names, we define an object with a method field and a possible styles field. The method refers to the JavaScript function used to deserialize the DOM node with the specified node name, and the styles is an array that we use to map possible attributes from the <g:map> element to CSS style values—in this case, we want to transform <g:map width="370px" height="380px"> to an HTML element that looks like <div id= "map-1" style="width:370;height:380px;">.

We used the entAjax.getElementsByTagNameNS function to obtain references to the custom XHTML elements rather than the native DOM getElementsByTagNameNS method. The reason for this is that Internet Explorer does not support element selection by namespace, and other browsers such Firefox, Safari, and Opera use it only when the web page is served as XHTML, meaning that it must have content-type application/xhtml+xml set in the HTTP header on the server. Internet Explorer has one more quirk in that it completely ignores the element namespace and selects elements based entirely on the local name, such as "map." On the other hand, other browsers accept a fully qualified tag name such as "g:map" when not operating as XHTML. The entAjax.get ElementsByTagNameNS function effectively hides these browser nuances.

After getting a list of all the custom tags in the web page, we then use the tag constructor definitions in the entAjax.elementshash to find the method that we have written to instantiate that element into the equivalent JavaScript object.

We pass one argument to the root tag constructors, which is the declaration element from which the entire component can then be built. Each of the methods in the entAjax.elements hash can be thought of as factories according to the Factory pattern. In the case of the <g:map> XHTML element, the createGMap function is called. The createGMap function is a custom function used to create an instance of the GMap2 class as well as set up all the child controls and relevant properties:

For each <g:map> element, we create a standard <div> element <div> elements to which the map will be attached. This will generally be the case that a component needs to be attached to a standard HTML <div> element and then create an instance of the GMap2 class with the newly created <div> element as the single constructor argument. Two general operations need to be performed for declaration parsing; first, all attributes on the declaration node must be processed, and second, all child elements of the declaration node need to be processed. Due to the way that the GMap2 component was designed, we also need to copy some custom style information from the declaration node, such as the width and height, onto the <div> container element. Many of these special cases can be generalized in a component framework but are much less elegant when wrapping declarative functionality around a JavaScript component built without declarative functionality in mind.

Alternative Approaches

Although we used custom XHTML for our declaration, it is also possible to use other techniques for configuring your components. The most popular alternative to configuring components with an XML-based declaration is to use a simple JavaScript object. For our map example, the following would be a likely format for a map configuration:

This configuration can then be used as the single argument passed to the map factory and would result in a map just the same as the XHTML declarative approach we outlined. Using a JavaScript object such as that is the way that Yahoo's AJAX user-interface components accept configurations.

Another way to configure an AJAX component, although it is currently fairly uncommon, is to use CSS properties. Using CSS to configure AJAX components is particularly effective because CSS can be linked through external files using the HTML <link> element, and the technology is familiar to most web designers today. However, CSS does have considerably less expressiveness when compared to either a JavaScript object or an XHTML declaration, and some dynamic CSS functionality is not available in browsers such as Safari. Chapter 2, "AJAX Building Blocks," covered how to dynamically access and manipulate stylesheet rules through the DOM API.

Looking at the Google Map example and seeing how to convert an existing component to a declaration should have been helpful in identifying not only how a declarative approach can make AJAX development easier, but how we can use it to build rich Internet applications. Having gone through this exercise with a Google Map, there might be a few questions in your head now such as how we can deal with events, data binding, or data templating in a declarative manner. We look at of those issues and more in the next section.

This chapter is excerpted from Enterprise AJAX: Strategies for Building High Performance Web Applications, authored by David Johnson, Alexei White, and Andre Charland, published by Prentice Hall, © Copyright 2007 Prentice Hall. All rights reserved.

Digg This Add to del.icio.us

[previous] [next]