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: Building an Asynchronous Object Registry

Real-World AJAX Book Preview: Building an Asynchronous Object Registry

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.

Building an Asynchronous Object Registry
This approach, while simple, has a couple of major problems that makes it less than perfect for library functions. One of the first is the fact that the setInterval() and setTimeout methods may be invoked even after a page (or the browser) is closed. In general, this is likely to result in a JavaScript error that will stop the processing, but at the cost of not properly cleaning up the references to the clockNode or the token contents. This in turn translates into a fairly significant memory leak because the system hasn't properly de-allocated the memory space for those objects. Any better solution will have to take care of this.

The second problem comes from the fact that the default Date() string is fairly comprehensive and may in fact expose more information than might be desirable (or in a format that isn't acceptable). Considering that the default output looks something like:

The time is Tue May 16 2006 10:35:41 GMT-0700 (Pacific Daylight Time)

It may be preferable to provide a better processing mask.

The last point is more subtle and has to do with keeping the amount of JavaScript code in the HTML code as minimal as possible and preferably written with a bias towards an XML representation. In other words, rather than simply identifying a given element with an ID or class attribute, it might be preferable to create an XML tag that looks something like:

<c:clock id="clock1" type="simple"/>

This would define a "clock" element in a separate namespace, identify its type (in this case simple, meaning that it's just text-based output, but it could potentially be more complex like an imagebased clock), and a format that would indicate how it would be output.

One consequence of moving to XML is that the classes have to be more robust, and what's more, they should have the dignity to clean up after themselves. The clock, for instance, has the very real possibility of leaving memory leaks. But if there was a way of ensuring that any clocks so declared would automatically clean themselves up, this would cut down considerably on the potential corruption acting on the browser environment.

This is where thinking in somewhat more general terms can be valuable. In this particular case, for instance, one of the more useful solutions in such a situation is the creation of a registry. This particular registry keeps track of all instances of objects built using external tags (such as <clock/>), can initialize them when the page is first instantiated, and more importantly can clean up the code and properly dispose of the various variables that are defined in potentially dangerous closures (see Listing 2.2).

Listing 2.2 A Registry-Based Clock

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://www.metaphoricalWeb.com/
xmlns/clock">
    <head>
    <title>Clock Experiment 2</title>
    <style type="text/css"><![CDATA[
@namespace c url("http://www.metaphoricalWeb.com/xmlns/components");
.funky      { font-weight:bold;
             display:block;
             width:200px;
             border:outset 3px gray;
             padding:3px;
             float:right;
             }
             ]]>
    </style>
    <script type="text/JavaScript">//<![CDATA[
/* Define Namespaces object for common namespaces. */
      var Namespaces={
         nsXHTML:"http://www.w3.org/1999/xhtml",
         nsComponents: "http://www.metaphoricalWeb.com/xmlns/components"
}
/* Define the singleton window.Registry object */
      window.Registry = {registryClassStore:[],
           registryInstanceStore:{},
/* Registry.add() adds new tags to the registry and associates them with
a constructor class */
           add:function(namespace,tagname,fnClass){
             var nodes = document.getElementsByTagNameNS(namespace,tagname);
             tagKey = namespace+"#"+tagname;
             this.registryInstanceStore[tagKey] = [];
             for (var nodeCount = 0;nodeCount != nodes.length;nodeCount++){
               var node = nodes[nodeCount];
               var instance = new (fnClass)();
               instance.register(node);
               this.registryInstanceStore[tagKey].push(instance);
               }
           },
/* Registry.clear() unregisters each tag so added then removes them from the
    XML registry list */
         clear:function(){
           for (key in this.registryInstanceStore){
             while(this.registryInstanceStore[key].length != 0){
               var instance = this.registryInstanceStore[key].pop();
               instance.unregister();
               }
             }
           },
/* Registry.dump() shows what items are associated with each namespace key */
         dump:function(){
           for (key in this.registryInstanceStore){
             alert(key+":"+this.registryInstanceStore[key]);
             }
           }
         }
/* The clock object defines a clock tag intended to be used with the registry
This */
    var Clock = function(){
    // Create a safe handle for the clock instance.
    var clockInstance = this;
    // This is the obligatory registration function.
    this.register = function(node){
      // Associate the clock node with the method
      clockInstance.boundNode = node;
      // Create an HTML child of the clock element
      while(clockInstance.boundNode.firstChild != null){
         clockInstance.boundNode.removeChild(clockInstance.boundNode.firstChild);
         }
      var divNode = clockInstance.boundNode.appendChild(document.createElementNS
      (Namespaces.nsXHTML,"span"));
      var clockType = clockInstance.boundNode.getAttribute("type");
      // Retrieve the format, and map it to a regular expression
      var clockMask = clockInstance.boundNode.getAttribute("format");
      clockType = (clockType != null)?clockType:'simple';
      clockMask = (clockMask != null)?clockMask:"hh:nn:ss am";
      // This creates a map from the standard JavaScript toLocaleString() method
      // to a final regex map.
      this.reTime = /^(\w{3}\w+\,\s\w{3}\w*\s\d+\,\s\d{4}\s(\d+)\:(\d+)\:(\d+)\s(\
      w{2})\s(?:\w+\s){4}(\d+).*$)/;
      var matchTimeMap = {
         HH:"$2",
         nn:"$3",
         ss:"$4",
         am:"$5",
         hh:"$6" }
      this.finalMask = clockMask;
      // replace the format with the corresponding regex keys from reTime
      for (key in matchTimeMap){
         clockInstance.finalMask = this.finalMask.replace(key,matchTimeMap[key]);
         }
      // Start the clock.
      clockInstance.paint();
      clockInstance.ivalToken = setInterval(function(){
         clockInstance.paint();
         }, 500);
      }
    // This handles the individual rendering for the simple class.
    this.paint = function(){
      // get a date and map critical information to a regex.
      var dt = new Date();
      dtStr = dt.toLocaleString() + " " + dt;
      var timeOutput = dtStr.replace(clockInstance.reTime,clockInstance.finalMask);
      // If the style attribute has changed, change the style in the
      // subordinate <span>
      if (clockInstance.boundNode.firstChild.getAttribute("style") !=
      clockInstance.boundNode.getAttribute("style")){
         clockInstance.boundNode.firstChild.setAttribute("style",
         clockInstance.boundNode.getAttribute("style"));
         }
      // update the subordinate span with the new time.
      clockInstance.boundNode.firstChild.innerHTML = timeOutput;
      }
    // This is called to unregister the individual clock
    this.unregister = function(){
      // Kill the setInterval method powering the clock.
      window.clearInterval(clockInstance.ivalToken);
      }
    }
    // This is the generalized main() method.
    function main(){
      // Register all clock nodes.
      window.Registry.add(Namespaces.nsComponents,"clock",Clock);
    // window.Registry.dump();
      // When the window unloads, unregister all objects.
      window.addEventListener("unload",function(){
         window.Registry.clear();
         },false);
      }
    // ]]></script>
    </head>
<body onload="main()">
      <p>The time is <c:clock id="clock1" type="simple"/>.</p>
      <p>Here's another clock: <c:clock id="clock2" format="HH:nn:ss" style="fontsize:
24pt;border:inset 2px lightBlue;-moz-border-radius:6pt;background-color:lightBlue;p
adding:2px;"/></p>
      <p>Here's a class oriented clock: <c:clock id="clock3" format="It has been hh
hours, nn minutes and ss seconds since midnight" class="funky"/></p>
    </body>
</html>

A screenshot of this particular page is shown in Figure 2.4.

Notice in the listing the use of two distinct types of objects. The first, the Registry object, is a singleton - there is only one Registry in the page at any time. Because of this, there's no specific reason to create a constructor class function for the function.

The Registry works in large measure by providing a list of new elements to be added (in this particular case the <c:clock> tag), then searching through the HTML document to find all instances of this particular tag and a specific JavaScript class to the tag. This in turn makes the requisite bindings to the element that make it aware of the class, modifies the children and attributes of the class as appropriate, and gives the otherwise undefined tag some functionality.

The Registry object in this case contains three methods: add(), clear() and dump(). The add() method creates a key consisting of the namespace of the tag followed by a hash character and the name of the tag. For instance, the clock object would create a hash key of the form:

Namespaces.nsComponents = http://www.metaphoricalWeb.com/xmlns/components#clock

The add() method, in turn, tags the namespace and element name, along with the constructor function that will bind to the element, in this case, the Clock constructor:

window.Registry.add(Namespaces.nsComponents,"clock",Clock);

This method searches through the DOM document and retrieves all instances of the <c:clock> element. For each instance, a new object is instantiated from the constructor, the element is assigned internally to the bound property, and a required function, the register() method, is invoked on the constructor to complete the bindings. Once registered, the constructed object is pushed into the stack of all c:clock objects.

The Registry.clear() method, on the other hand, is called when the window is unloading and its purpose is twofold. It pops each defined instance off of all of the defined registry stacks (one stack for each tag), and then calls the unregister() method on that instance. This in turn makes it possible for each object to perform the actions necessary to clean themselves up. In the case of the clock, for instance, the unregister() method calls clearInterval() to stop the interval timer from firing (and consequently ensuring that you won't have any orphaned closure instances that would lead to memory leaks). The unregister() method is roughly analogous to a destructor, although it also removes the item from the appropriate stacks.

I did want to make a quick note on the main() function's role in all of this. In languages such as Java or C++, the class definitions are (or at least should be) self-contained and well encapsulated. However, to run a class as an executable, you have to have some kind of hook that lets the runtime engine know where the insertion point is to manipulate the class. This is typically done as a main() method, which is the default method that will be called by the runtime to start the ball rolling.

JavaScript, on the other hand, is generally executed in the order that it is encountered in the Web page. The problem that this causes is that everything else is instantiated in the same order, which means that if you reference an object that hasn't been instantiated yet, your page will generate an error.

However, the HTML body element is unique among all elements in that it will automatically fire an event (the onload event) whenever everything in the page has finished instantiating. By calling the main() method from this event, you are guaranteed of having the page be completely instantiated.

The whole main() method can be broken down then as:

function main(){
    window.Registry.add(Namespaces.nsComponents,"clock",Clock);
    window.addEventListener("unload",function(){
         window.Registry.clear();
         },false);
    }

The whole main() method can then be broken down into two parts - add all associated tags and instantiate their constructors, then attach an event on the window object so that when the window is unloaded (say prior to a move to a new page), the Registry.clear() method will automatically be called.

The addEventListenter() method on the window, used to add events to objects according to the W3C DOM specification, isn't currently supported by Microsoft's Internet Explorer. Instead, you have to use the attachEvent() method, which takes only two arguments: the name of the event (taking care to remove the "on" part") and the functional event handler, similarly, with the removeEventListener() method.

The actual Clock constructor reflects these design changes and illustrates how an object of some complexity can be constructed in JavaScript. One trick that can prove invaluable in building such objects is to assign the current instance to some other variable that can then be used via closure to refer to the instance, even if this has been redefined in some other context.

var Clock = function(){
      var clockInstance = this;
      // This is the obligatory registration function.

The register() method passes the <c:clock> element to a boundNode variable, then attaches to that node an HTML <span> element (the <c:clock> element just passes content through unchanged, but lacks certain methods that the HTML DOM elements support, including innerHTML).

    this.register = function(node){
     // Associate the clock node with the method
      clockInstance.boundNode = node;
      // Create an HTML child of the clock element
      while(clockInstance.boundNode.firstChild != null){
        clockInstance.boundNode.removeChild(clockInstance.boundNode.first
           Child);
        }
      var divNode = clockInstance.boundNode.appendChild
      (document.createElementNS(Namespaces.nsXHTML,"span"));

The format attribute (passed to the variable clockMask) is used to generate a regular expression replacement path. So, if you set the format attribute to "HH:nn:ss am", the time 17:45:17 will be represented as "5:45:17 pm." The reTime regular expression works with the JavaScript output to retrieve the associated fields from the resulting Date() object's toLocaleString() method, and the finalMask property will contain a regular expression replacement string based on that particular format.

      var clockMask = clockInstance.boundNode.getAttribute("format");
      clockMask = (clockMask != null)?clockMask:"hh:nn:ss am";
      // This creates a map from the standard JavaScript toLocaleString() method
      // to a final regex map.
      this.reTime = /^(\w{3}\w+\,\s\w{3}\w*\s\d+\,\s\d{4}\s(\d+)\:(\d+)\:(\
      d+)\s(\w{2})\s(?:\w+\s){4}(\d+).*$)/;
      var matchTimeMap = {
         HH:"$2",
         nn:"$3",
         ss:"$4",
         am:"$5",
         hh:"$6" }
      this.finalMask = clockMask;
      // replace the format with the corresponding regex keys from reTime
      for (key in matchTimeMap){
         clockInstance.finalMask = this.finalMask.replace(key,matchTimeMap
               [key]);
         }

Once this is defined, the clock is rendered once automatically, then a setInterval() method calls the render method again once every half-second (this smoothes out occasional jerkiness due to system load, though on healthy systems, once every 1000ms is probably fine).

// Start the clock.
clockInstance.paint();
clockInstance.ivalToken = setInterval(function(){
      clockInstance.paint();
      }, 500);
}

The paint() method isn't called by the Registry, only by the internal Clock object. It handles the actual generation of the content to be displayed and the assignment in the display field. It also updates any changes brought from the style and class attributes on the initial object, passing these to the child element. In this way you can ensure proper stylistic changes get propagated properly.

    // This handles the individual rendering for the simple class.
    this.paint = function(){
      // get a date and map critical information to a regex.
      var dt = new Date();
      dtStr = dt.toLocaleString() + " " + dt;
      var timeOutput = dtStr.replace(clockInstance.reTime,clockInstance.finalMask);
      // If the style attribute has changed, change the style in the
      // subordinate <span>
      if (clockInstance.boundNode.firstChild.getAttribute("style") !=
      clockInstance.boundNode.getAttribute("style")){
         clockInstance.boundNode.firstChild.setAttribute("style",
         clockInstance.boundNode.getAttribute("style"));
         }
      if (clockInstance.boundNode.firstChild.getAttribute("class") !=
      clockInstance.boundNode.getAttribute("class")){
      clockInstance.boundNode.firstChild.setAttribute("class",
      clockInstance.boundNode.getAttribute("class"));
      }
    // update the subordinate span with the new time.
    clockInstance.boundNode.firstChild.innerHTML = timeOutput;
    }

One point to consider - the use of paint() here isn't dissimilar to how such methods would be invoked in Java.

Finally, as mentioned before, the unregister() method calls clearInterval with the token saved from the setInterval call:

    // This is called to unregister the individual clock
    this.unregister = function(){
      // Kill the setInterval method powering the clock.
      window.clearInterval(clockInstance.ivalToken);
      }
    }

This whole process would admittedly be overkill if there was only one type of new element introduced into the mix. If you have multiple instances of that element, or if you have multiple different elements, providing a consistent framework for registering and unregistering such objects can save you a considerable headache and dramatically improve your code production and maintenance.

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.