Welcome!

Kurt Cagle

Subscribe to Kurt Cagle: eMailAlertsEmail Alerts
Get Kurt Cagle via: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: RIA Developer's Journal, AJAX World RIA Conference

RIA & Ajax: Article

Real-World AJAX Book Preview: Combining Components and Services

Real-World AJAX Book Preview: Combining Components and Services

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Combining Components and Services
With the above feed service, incredible possibilities are opened up for the XInclude component discussed earlier. In essence, the XInclude component becomes a newsfeed reader, displaying the contents of the newsfeed either as a list of numbered entries or showing just one "page" of that feed. Note that the contents here are just the summaries of the pages, not (in general) the pages themselves unless the newsfeeds actually are used to transmit the entire content.

For instance, the code in Articles.xhtml show the invocation of two newsfeeds: one from Scientific American and the other from CNN.com (displaying the buttons shown and buttons hidden modes):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <title></title>
      <binding src="xincludeBinding.js" target="xinclude"/>
      <script type="text/javascript" src="bindings.js" defer="no"/>
      <style type="text/css"><![CDATA[
@import url('jsbinding.css');]]>
</style>
   </head>
   <body>
<xinclude src="feedTransformer.php?x=stub.xml;xt=processNewsFeed.
xsl;feed=http://www.sciam.com/xml/sciam.xml" buttons="top" disable-cache="no"
title="h1"/>
      <xinclude src="feedTransformer.php?x=stub.xml;xt=processNewsFeed.
xsl;feed=http://rss.cnn.com/rss/cnn_topstories.rss" buttons="top" disablecache="
no" title="h1" page-buttons="yes"/>
      <xinclude src="feedTransformer.php?x=stub.xml;xt=processNewsFeed.
xsl;feed=http://rss.cnn.com/rss/cnn_topstories.rss" buttons="top" disablecache="
no" title="h1" page-buttons="no"/>
   </body>
</html>

If the following CSS is used (jsbinding.css):

xinclude {
      width:4in;
      text-align:justify;
      display:block;
      background-color:lightYellow;
      padding:5px;
      border:inset 2px gray;
      }
p {font-size:9pt;font-family:Arial;}
pre {font-size:8pt;}
.xincludeButton {font-size:8pt;}
.xincludeButtonSelected {font-weight:bold;background-color:#FFFFE0;font-size:8pt;}
.xincludeButtonBar {
      width:100%;
      background-color:lightBlue;
      margin-left:-5px;
      padding:5px;
      padding-top:3px;}
.xincludeButtonBar_top {margin-top:-5px;}
.xincludeButtonBar_bottom {margin-bottom:-5px;}
.xincludeTitle {
      font-size:12pt;
      font-weight:bold;
      font-family:Arial;
      width:100%;
      background-color:#E0E0FF;
      padding:5px;
      margin-left:-5px;
      margin-top:-5px;
      margin-bottom:-5px;}
.xinclude_toc {max-height:2.5in;overflow-y:auto;font-size:9pt;font-family:Arial;}
.xinclude_TOC_item {cursor:pointer;}
.xinclude_TOC_item:hover {color:blue;}
.item {overflow-x:auto;}
.item_title {font-size:9pt;font-family:Arial;text-decoration:none;}
.item_description {font-size:9pt;font-family:Arial;}

This will produce the following output:

There's still a fair amount that will be done with this particular code, but the power of this is obvious - by encapsulating the binding of AJAX code into an XML interface, you are able to create highly functional "building blocks" for your own specialized "XHTML+".

It is worth discussing other frameworks, especially as they can often significantly cut down on the amount of coding that you have to do in order to get to a point where such binding systems make sense. One of the most fundamental of these, key to many of the more sophisticated libraries and pages out there, is the prototype.js library.

The Power of Prototype
Computer languages evolve across an interesting number of vectors, and not always in ways that the original designers had planned. For every high-level, top-down decision to implement new features and capabilities, there are interesting bits of best practices, useful libraries, and design patterns that can, subtly and sometimes not so subtly, change the course of direction of a language in critical ways.

AJAX is a good case in point - in the process of writi ng my chapters for this book, I occasionally have to step out of my own preconceived notions of where the language (principally refering to JavaScript here and not the XML side) has been and look at where the language is going in terms of its own long and winding path. Certainly Ruby has been influencing things by bundling interesting JavaScript components on the server side, but I think a more interesting case in point is the use of a set of libraries - collected together as prototype.js - that are rapidly reshaping how we use the language, especially in the context of Web browsers.

The prototype.js libraries, developed by Sam Stephenson at http://prototype.conio.net/, seemed to have evolved out of the Ruby on Rails project to take on a life of its own. It includes a number of extraordinarily useful library functions and introduces the "$" as a notation within JavaScript. This library now underlies many of the AJAX frameworks in use on the Web, and it's not unlikely that it will creep into the "core" implementation over time.

One of the central things that prototype.js does is define a set of additional useful objects, including a new Hash object, a new Enumerable class, ranges, an easy-to-use AJAX class, as well as extensions to such core classes as number, string and array. It also provides the most sane SOAP and rational shortcuts to entirely too verbose methods such as getElementById.

To illustrate that last point, prototype.js defines a function called "$" ... yep, that's right - the dollar sign. Turns out that the dollar sign is in fact a valid character for names in JavaScript (a fact obscured by years of dominance by JScript, which didn't recognize this salient fact). The prototype.js library defines $() as a function to replace the ubiquitous (and painful) document.getElement-ById() method, with an added twist that if an element (or other object) is passed into it, the object gets passed out the other side. This means that if you want to refer to an element with ID "foo", you'd use the expression:

var foo = $("foo");
var foo2 = $(foo);

Rather than:

var foo = document.getElementById("foo");
if (typeof foo == "string"){
      var foo2 = document.getElementById("foo");
         }
else {
      var foo2 = foo;
      }

Given a typical browser function, this can turn something as painful as:

var updateFunctionList=function(){
     var functionMenu = document.getElementById("functionMenu");
     var functionDisplay = document.getElementById("functionDisplay");
     var functionTabs = document.getElementById("functionTabs");
     var functionTabPanels = document.getElementById("functionTabPanels");
         tabCt=0;
     for (key in this.functionType){
         tabCt++;
         var tab = "<tab xmlns='"+namespaces.xul+"' label='"+this.
functionType[key]+"'/>";
         var tabNode = (new DOMParser()).parseFromString(tab,"text/xml")
.documentElement.cloneNode(true);
         if (tabCt ==1){
           tabNode.setAttribute("selected","true");
           }
         functionTabs.appendChild(tabNode);
         var tabPanel = "<tabpanel xmlns='"+namespaces.xul+"' id='function_"+key+"'
orient='vertical' class='functionPanel'/>";
         var tabPanelNode = (new DOMParser()).parseFromString(tabPanel,"text/xml").
documentElement.cloneNode(true);
         functionTabPanels.appendChild(tabPanelNode);
         }
     for (key in this.functionSet){
         var menuitem = "<menuitem label='"+this.functionSet[key].name+"'
xmlns='"+namespaces.xul+"' oncommand='window.calculator.exec(\""+key+"\")'/>";
         menuitemNode = (new DOMParser()).parseFromString(menuitem,"text/xml").
documentElement.cloneNode(true);
         functionMenu.appendChild(menuitemNode);
         var button = "<button label='"+this.functionSet[key].name+"'
xmlns='"+namespaces.xul+"' oncommand='window.calculator.exec(\""+key+"\")'/>";
         buttonNode = (new DOMParser()).parseFromString(button,"text/xml").documentElement.cloneNode(true);
         var panel = document.getElementById("function_"+ this.functionSet[key].functionType);
         panel.appendChild(buttonNode);
         }
     }

into something at least a little cleaner:

//$E is my own function, in the same mold, for creating elements from strings
var $E=function(eltStr){
     if (typeof(eltStr)=="string"){
       return (new DOMParser()).parseFromString(eltStr,"text/xml").documentElement.
cloneNode(true);
       }
     else {
       return eltStr;
       }
     }
     var updateFunctionList = function(){
     var tabCt=0;
     for (key in this.functionType){
       tabCt++;
       var tab = "<tab xmlns='"+namespaces.xul+"' label='"+this.
functionType[key]+"'/>";
       var tabNode = $E(tab);
       if (tabCt ==1){
         tabNode.setAttribute("selected","true");
         }
       $('functionTabs').appendChild(tabNode);
       var tabPanel = "<tabpanel xmlns='"+namespaces.xul+"' id='function_"+key+"'
orient='vertical' class='functionPanel'/>";
       $('functionTabPanels').appendChild($E(tabPanel));
       }
     for (key in this.functionSet){
       var menuitem = "<menuitem label='"+this.functionSet[key].name+"'
xmlns='"+namespaces.xul+"' oncommand='window.calculator.exec(\""+key+"\")'/>";
       $('functionMenu').appendChild($E(menuitem););
       var button = "<button label='"+this.functionSet[key].name+"'
xmlns='"+namespaces.xul+"' oncommand='window.calculator.exec(\""+key+"\")'/>";
       // the following references an individual panel content
       $("function_"+ this.functionSet[key].functionType).
appendChild($E(button));
       }
     }

The upside of this should be obvious - less code needed, the code isn't a dense tangle of getElementById statements, and legibility is significantly improved.

The hash and array capabilities are similarly defined (and are cross-platform). One of the more intriguing problems that I've encountered with JavaScript arrays is that they are not terribly enumeration friendly. While you can use the built-in object enumeration that is part of JavaScript on Arrays, such enumerations not only return the numbered items in the array, but also all of the method and property handlers for that array, meaning that you have to specifically filter to stop once the array has exceeded the length:

var arr=["red","green","yellow","blue"]; var ct=0; for (var index=0;index !=arr.length;indexx++){      var item = arr[index];      print (arr[index]);      }

The global $A() function turns arrays into fully enumeratable arrays, and in the process adds a few additional (and very useful) methods:

var arr=$A(["red","green","yellow","blue"]);
arr.each(function(color){print(color.toUpperCase());});

The each() method on the arrays incorporates a for loop for iterating through each of the items in the array. So far, this isn't that different from the use of the for each keywords. However, prototype.js then goes on to use this method to invoke more sophisticated methods. For instance, suppose you had a character generator for a game. You can use the prototype.js methods to significantly simplify many of the key array operations:

   var NPCharacters = function(numChars){
     var NPCharacter = function(charName){
       var rollDie= function(numDie,pips,bias){
         var sum = 0;
         numDie.times(function(index){
           sum +=Math.ceil(Math.random()*pips + bias);
           if (sum >20){
             sum =20;
             }
         });
         return sum;
       };
     this.pcProps=$A(["strength","intelligence","wisdom","dexterity", "constitut
ion","charisma"]);
     this.generateCharacter=function(charName){
       ch = new Object();
       ch.gender = (Math.random()>0.5)?"female":"male";
       ch.name = charName;
       this.pcProps.each(function(pcProp){
         ch[pcProp] = rollDie(3,6,0.2);
         });
       this._character = ch;
       }
     this.toString=function(){
       var buf ="{";
       var recStack = [];
       for (key in this._character){
         recStack.push(key+":'"+this._character[key]+"'");
         }
       buf += recStack.join(", ")+"}";
       return buf;
       }
     this.generateCharacter(charName);
     }
   this.generateCharacters=function(numChars){
     var characterSet = [];
     numChars.times(function(index){
       var npcharacter = new NPCharacter("Character "+index);
       characterSet.push(npcharacter);
       });
     this.characterSet = characterSet;
     };
   this.query = function(fn){
       return $A(this.characterSet).findAll(fn);
       }
     this.toString = function(){
       var buf = "[";
       var npArr = [];
       var charSet = this.characterSet;
       charSet.length.times(function(index){
         npArr.push(charSet[index].toString());
           });
       buf += npArr.join(",n");
       return buf+"]";
       }
     this.generateCharacters(numChars);
   }

There are a number of interesting functions covered here, illustrating how to build a character set generator. When passing an integer argument into the NPCharacters() constructor, the class creates that number of characters automatically.

var npcs = new NPCharacters(5);
print(npcs);
=>
[{gender:'male', name:'Character 0', strength:'9', intelligence:'11', wisdom:'12',
dexterity:'15', constitution:'16', charisma:'13'},
{gender:'male', name:'Character 1', strength:'14', intelligence:'12', wisdom:'8',
dexterity:'14', constitution:'13', charisma:'9'},
{gender:'female', name:'Character 2', strength:'15', intelligence:'12', wisdom:'
12', dexterity:'10', constitution:'10', charisma:'14'},
{gender:'female', name:'Character 3', strength:'11', intelligence:'12', wisdom:'
11', dexterity:'15', constitution:'9', charisma:'13'},
{gender:'female', name:'Character 4', strength:'9', intelligence:'15', wisdom:'
12', dexterity:'10', constitution:'11', charisma:'10'}]

However, I think one of the cooler features is the findAll() method, which is used in the NPCharacter. query() method. It takes a callback function with the item and an index as a signature, returning true if a criterion is met and false otherwise.

this.query = function(fn){
       return $A(this.characterSet).findAll(fn);
}

Thus, if you wanted to retrieve an array of all characters that are both intelligent (intelligence >14) and female (gender = "female"), you'd write it as:

npcs.query(function(record,index){with(record._character){return intelligence >14
&& gender == "female";}});

(There are simpler ways of representing it, but this gets the idea across.)

The $A() function not only appends certain methods to the Array object, it also inherits from the Enumeration class, which make group operations easier to do, including find, findAll, reject, pluck, partition, and so forth. A full listing of these and other enumerable methods can be found at www.sergiopereira.com/articles/prototype.js.html.

Iterative loops can be created with the times() method, which takes an integer and uses that as the upper-bound for an incremental loop on a function:

var rollDie= function(numDie,pips,bias){
       var sum = 0;
       numDie.times(function(index){
         sum +=Math.ceil(Math.random()*pips + bias);
         if (sum >20){
           sum =20;
           }
         });

Similarly you can make ranged arrays with the $R(min,max,includeBounds) function, which returns an incremental array of numbers from the min to either the max or just below the max, depending upon the includeBounds implementation.

The Hash object (implemented via the $H() function) provides additional objects on hashes (associative arrays such as the JavaScript base object) including keys(), values(), merge(),toQueryString(), and inspect(). While these can generally be obtained without the need for the special rider functions (i.e., with for loops), these can make for somewhat cleaner and more followable code.

Other extensions to the Array class include the following methods: clear() [Clears the array], compact() [removes null and undefined entries], first() [gets the first item of the array], flatten() [turns multidimensional arrays into linear ones], indexOf(value) [returns the index of the first selected item), inspect() [returns a pretty printed output], last() [returns the last item in and array], reverse(), shift() [remove one item from the beginning], and without() [excludes the given items passed from the array.

Given the move to push arrays as first-class data stack objects, these methods offer a dramatic improvement to the capabilities of most applications, and what's more, they are rapidly becoming standardized as prototype.js becomes adopted.

This is just touching the surface of what prototype has to offer. In a future column, I'll be touching on the AJAX and DOM functionality that prototype exposes. More information about the classes exposed with prototype can be found at www.sergiopereira.com/articles/prototype.js.html., and the prototype.js core can be found at http://prototype.conio.net/.

Final Thoughts on Components and Design Principles
While the components described here are certainly useful, what is perhaps more important with regard to this chapter is the use of a JavaScript binding layer to make it possible to flesh out the behaviors of such components. In essence, the XHTML here serves as an abstraction layer, making it possible to define the components that will be used on a given page. The scripting is organized along objective lines - a class that defines constructors, methods and events on each object instance - without having to carry around a secondary object layer that forces you to manipulate objects via JavaScript proxies. While this particular binding system doesn't directly support inheritance, it's not hard to see how inheritance could be implemented. Finally, it ensures that the relevant objects can be cached in the most efficient manner possible by piggy-backing on the browser's own caching system, something that all good AJAX implementations should strive to do.

While the issue is certainly debatable about the long-term role of XML (and XHTML) in AJAXlike systems, the model espoused here, one that makes componentization not only possible but straightforward to implement, will most likely end up being typical of Web development in the next few years. Such a model makes it easier to separate the core roles in creating Web applications - editorial, component developers, integrators, and presentation artists - permitting a level of specialization that can speed up Web development significantly, especially as libraries of components emerge as a natural part of the process.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

More Stories By Kurt Cagle

Kurt Cagle is a developer and author, with nearly 20 books to his name and several dozen articles. He writes about Web technologies, open source, Java, and .NET programming issues. He has also worked with Microsoft and others to develop white papers on these technologies. He is the owner of Cagle Communications and a co-author of Real-World AJAX: Secrets of the Masters (SYS-CON books, 2006).

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.