DHTML Interfaces: Taking The Next Step

any DHTML developers are tired of building complex interfaces around poorly implemented browsers only to have to reinvent the wheel on the next project. Many more are beginning to understand that maintaining interface state on the server is a very bad option from a usability standpoint. This article should impart some hope to the DHTML development community that we need not abandon DHTML to build robust, usable, and compelling interfaces.

That said, DHTML developers must develop a better toolset if they are to build dynamic Web-application interfaces that don’t require hundreds (or thousands) of man-hours of custom coding. In this article, I propose a basic starting point for defining a framework that allows better code reuse across projects and application interfaces. This framework will provide basic component lookup and creation services, and will allow developers to create components and widgets that are truly generic. In other words, the components generated by this framework are not tied to any specific environment or data set; they can be dropped in anywhere you need a widget of a given type. You can then simply tie them to the calculations or processes that they must support without the need to worry about how the components are constructed. In effect, this framework provides DHTML developers with a separation between data and presentation logic similar to that available to “regular” HTML developers via CSS and XHTML.

You can see a simple demonstration of the framework outlined in this article in the test_page.html file. The sample will not work on 4.x browsers, however the sample code for this article has been tested to work on a multitude of platforms, and should perform correctly on any modern browser, such as Internet Explorer (IE) version 5 or higher on both the Mac and PC platforms, Mozilla, Netscape Navigator (NN) 6, and Konqueror. The example is simplistic but serves adequately to illustrate the scripts that make it work and for examining why and how a framework can help you construct arbitrarily complex interfaces on the client side with minimal recurring effort.The Example Page, Step-by-Step
Now that you’ve tried the sample page, I’ll explain why it’s special. The first thing to note is the relative simplicity of the contents of the tag:

         

click anywhere in the document to spawn a new button widget

Despite the fact that the page consists of nothing more than a single headline element and a function call, it’s able to spawn and track an arbitrary number of button widgets. The sample page proves:

  • that you can create widgets after the page has fully loaded
  • widget creation is not dependent data sent from the server nor on initialization variables
  • widget creation can be fully event driven

The addButton() function does the heavy lifting. Stripped of comments and extraneous code, the function looks like this:

    function addButton(){      var tmpButton = new buttonWidget('button', null,          "alert('button ' + this.IDNum + ' clicked');");      tmpButton.generate();      document.body.appendChild(tmpButton.compNode);      document.body.appendChild(document.createElement("br"));   }

Four lines to spawn a widget, give it attributes, attach an event handler, add it to the document and add spacing isn’t bad?and that’s all end developers normally ever need to see or know about. The object constructor new buttonWidget() calls a constructor included from another script. You need to understand the constructor’s arguments to make full use of the widget.

The first argument specifies the text or “label” for the new button widget. The second and third (both optional) arguments provide methods for defining the behavior of the widget. The second argument accepts a function pointer to a method to be evaluated when the button is clicked, or null if it is unused. The third argument is the string containing script that evaluated via the eval() function when the button is clicked. In this example, the code describes an alert():

 alert('button ' + this.IDNum + ' clicked');
Figure 1: The hierarchy of objects in the framework.

Interestingly, the value of this.IDNum is not defined in the addButton() function or anywhere else in the global namespace. Instead, it’s a member of the buttonWidget object itself. This works because code executed via eval() in the scope of an object inherits the scope of that object, and not the scope of the calling function. The code looks up a specific JavaScript object when we click on the button, and accesses the members of that object from an event fired from a DOM node. But what is IDNum? And how does the environment know which buttonWidget was clicked so it can obtain the right IDNum value?

To answer these questions, we’ll need to look deeper into the framework and the button widget’s responsibilities. By understanding how this simple button widget works, you can leverage the framework to build other widgets from a shared code base.

Here’s how the framework adds the button widget to the page. First, it doesn’t simply add the object returned from the constructor; instead, it adds one of its members, compNode. In other words, the return value from the constructor isn’t a DOM node as you might expect, it’s a JavaScript object, and compNode is a DOM node member of that object. Digging a little deeper, you’ll find that there’s an entire object hierarchy at work here, and that the buttonWidget class is actually a subclass of a base class that’s part of the framework (see Figure 1).

First I’ll show you the base class and you can see what it does, and then I’ll show you the buttonWidget class and you can see how it uses the services provided by the base class to make the end developers’ lives easier.Exploring the components.js File
The first include in the sample page is a file named components.js. This script contains the core of the framework you can build upon to create components and widgets. The ComponentClass() function defines the superclass that buttonWidget eventually inherits from.

Author Note: If you’re unfamiliar with the object-based nature of JavaScript or of prototype-based inheritance, declaring a class as a function may seem odd, but the use of the keyword this should tip you off that the code is a class definition and not a regular function.

   // class constructor for the component superclass   function ComponentClass(){      //   data members      // the DOM node for the component      this.compNode = null;   // default, override in subclass      this.autoGenerate = false;       // -1 signifies that no ID has been set      this.IDNum = -1;       //   class methods      this.setID = function(){         /*implementation not important here*/      }      this.getID = function(){         /*implementation not important here*/      }      // subclasses _must_ redefine generate()      this.generate = function(){return true;}         //   constructor code      // optional DOM node as constructor arg      if(arguments.length > 0){          this.compNode = arguments[0];      }      if(this.autoGenerate){this.generate();   }      // register this component      compReg.register(this); }

The ComponentClass acts as a skeleton for a real widget to fill in, but contains several important methods and properties. The compNode property links the visible DOM node to the JavaScript object that controls its behavior and models the data it presents. I’ll explain this relationship further when you examine the component registry. The setID property gives the component a way to request a globally unique ID number from the framework. The getID property provides an access method which is a shortcut to the property IDNum. The final line of the object definition tells a globally available object, compReg, to register the object being created with the system. The compReg object is a singleton object (only one instance can exist within a page) that “keeps track” of the components you create and use, thereby freeing you from the tedium of manually tying DOM nodes to logical objects and assuring uniqueness.

The ComponentClass class forms the base object type that creates and tracks components in the framework. The class ensures that every framework object has some properties that you can rely upon to provide services to the end developer. The most important of these services is lookup. The compReg object that handles lookup and registration is a single instance of the class ComponentRegistryClass. The framework creates the compReg object and makes it globally available immediately after defining a ComponentRegistryClass in the console.js file.

After that, the framework automatically registers every newly instantiated object that subclasses ComponentClass with the compReg object. The compReg object serves to associate the visible component (DOM node) and its logical (invisible) counterpart?a ComponentClass object. The result frees end developers from the burden of “keeping track” of an arbitrary number of dynamically generated widgets.

Here’s a quick rundown of the compReg object’s methods that enable generic lookup:

compReg.getByNode()?takes single DOM node as only argument. Returns a corresponding logical component object if the passed node is the descendant of a component node in the DOM tree. The method returns null if no ancestor node of the argument corresponds to a component.

compReg.getByID()?takes a component ID string as the only argument. If a component with the given ID is currently registered with the system, the method returns a reference to that component; otherwise, it returns null.

compReg.unregister()?takes a component ID string as the only argument. If a component with the given ID is currently registered with the system, the registered object is deleted. The method does not return a value.

compReg.unregisterByComponent()?takes a single argument?a component object reference. The method deletes the passed component reference from the registry. The method does not return a value.

compReg.register()?takes a single argument?a component object reference?and registers the passed component object as only argument. The method registers the component object with the framework. The method does not return a value.

The getByNode and getByID functions are the most useful, because you can use them to look up the parent object of a component without knowing anything about how and when the component was created. All the functions that accept a component object as argument or return value, assume that the component is either an instance of ComponentClass or an instance of one of its subclasses.Widgets, Widgets Everywhere
Now that you know about the objects providing registration and lookup, you can explore how the example ties everything together to create dynamic user interface components. You should be able to bind components to both data and behavior at creation time (and subsequently modify those bindings) while simultaneously shielding end developers from the complexity of creating the component or specifying its structure and formatting. To accomplishing this goal, the widget object definition must encapsulate its behaviors, and its visible representation, and the component’s code representation (the HTML DOM nodes) must be created dynamically at runtime.

Before you can create a widget, you must first link it to the parent class defined earlier so that it can make use of the lookup and tracking functions. The file button_widget.js defines the sample widget and is the only top-level function/class definition in the file. JavaScript does not provide (good) native inheritance support; therefore you need two steps to subclass from the parent class. The first step is something of a hack, but if repeated several times would allow you to import variables and methods from several parent classes. The first three lines of the class illustrate this.

   this.parent = ComponentClass; // set inheritance   this.parent(); // call parent constructor   // remove parent reference, avoid namespace pollution.   delete this.parent; 

You need a link to the “primary” parent class so that JavaScript can add the class to the scope chain for the buttonWidget class. You create the link with the prototype property. This allows classes that subclass buttonWidget to still be able to “see” its parent classes in the scope chain, which is useful when building composite widgets or when creating highly abstracted interfaces that require hierarchical organization.

   //deep inheritance support   buttonWidget.prototype = new ComponentClass; 

You must examine two more methods of the class buttonWidget before you’ll have a firm grip on how the framework enables truly generic widgets. The generate function is the factory that creates the visible DOM nodes that represent the widget. Separating the code that creates a widget’s visible HTML and the code that models it is important because it lets you change the look and feel of the widget without modifying any calling code. That calling code often contains the most browser-specific code and workarounds, so encapsulating it helps improve portability by limiting the changes needed to provide broader client compatibility. The member function generate provides this factory, creating the HTML element and giving it style. The code to create and style the element used for the button is:

 this.compNode = document.createElement("span");   with(this.compNode.style){      border = "1px solid black";      padding = "2px 5px 2px 5px";      backgroundColor = "#CCCCCC";      color = "black";      letterSpacing = "1px:";      fontFamily = "helvetica, arial, sans-serif";      fontSize = "12px";      lineHeight = "15px";   }

The “with” block simply shortens otherwise repetitive code. Since the code creates the button in the context of the buttonWidget object, you can simply assign the created element to the compNode member. Thus, when you add the compNode member of a button widget in the sample page, you are adding the DOM node created and styled by this code. Later, the function calls setID, which ties the compNode element to the unique ID given to the object, which facilitates speedy lookup via DOM nodes.

The next important section of the generate function assigns an event handler to the DOM node. A lookup function (compReg.getByNode) ties the handler back to the parent object, which then fires a member method of the buttonWidget object.

   this.compNode.onclick = function(evt){      var src = null;      // get a reference to the node that fired the event      // IE specific method      if(document.all){          // use implicit event reference in IE         src = event.srcElement;          // stop event bubbling in IE         event.cancelBubble = true;       }      // standards-compliant method      else{          src = evt.target;         //stop event bubbling on Moz         evt.stopPropagation();         }      compReg.getByNode(src).doAction();   }

To some this may seem like far too much indirection than is strictly necessary, but to provide a scalable widget framework that avoids namespace or object collision, you must implement reference and lookup via a central object. Approaches that attempt to provide uniqueness in widget-specific ways often wind up relying on properties that cannot always be assumed to be unique or duplicating code (providing further opportunity for error). Using a single lookup and reference mechanism for all widgets lets you avoid both hard-to-debug collisions and bloat.

The doAction method of the buttonWidget is also interesting, because it shows how you can build event handling mechanisms that do not depend on assigning specific events to DOM (HTML) elements, but rather on catching an event with a general handler and then calling other functions from that handler. You can use this approach (with a bit more sophistication) to provide dynamic eventhandlers that know nothing about the widgets’ internal structure. Here’s the code for a simplified doAction method that fires actions to be performed when a user clicks the buttonWidget:

   // perform the action registered on the widget   this.doAction = function(){      if(this.fp!=null){this.fp();}      if(this.evalStr!=null){eval(this.evalStr);}   }

The buttonWidget members named fp and evalStr are the second and third arguments passed to the constructor in the example page. If either exist, then they are fired. These properties could just as easily be arrays of strings or function pointers to be called when an event is fired on the widget. The current generic event handling system for the netWindows framework API uses just such an approach. The cut-down framework sample in this article derives from that approach as well.

The approach taken by this system differs from that of other DHTML APIs because it creates widgets and components when they are requested rather than at page-load time. Neither does the sample page create a “pool” of objects which are then selectively hidden and made visible as some DHTML coders tend to do. Instead, the framework defines a component “template” from which new widgets and components of a given type are then “stamped” when needed, and not before. If DHTML interfaces are ever to handle interface state management, this type of behavior is essential. Moving interface state management to the client frees server-side resources to manage session state and perform data manipulation.

The simple API presented in this article for creating generic, reusable widgets and components is just a first step in creating a toolkit that provides DHTML developers with robust tools for creating long-lived event and data-driven interfaces rather than page-refresh driven interfaces. Using DOM-based DHTML and a bit of common plumbing you can create DHTML interfaces with the same level of abstraction currently enjoyed by mature interface toolkits aimed at compiled languages such as C++ and Java.

If you are interested in either developing interfaces using this approach or contributing to the development of the type of robust client-side interface toolkit needed to make the next generation of DHTML applications a reality, check out the netWindows project.. The netWindows API provides a rich set of pre-built widgets, and supports themes and out-of-band content loading, which allows applications to add or change data without losing interface state.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

your company's audio

4 Areas of Your Company Where Your Audio Really Matters

Your company probably relies on audio more than you realize. Whether you’re creating a spoken text message to a colleague or giving a speech, you want your audio to shine. Otherwise, you could cause avoidable friction points and potentially hurt your brand reputation. For example, let’s say you create a

chrome os developer mode

How to Turn on Chrome OS Developer Mode

Google’s Chrome OS is a popular operating system that is widely used on Chromebooks and other devices. While it is designed to be simple and user-friendly, there are times when users may want to access additional features and functionality. One way to do this is by turning on Chrome OS

homes in the real estate industry

Exploring the Latest Tech Trends Impacting the Real Estate Industry

The real estate industry is changing thanks to the newest technological advancements. These new developments — from blockchain and AI to virtual reality and 3D printing — are poised to change how we buy and sell homes. Real estate brokers, buyers, sellers, wholesale real estate professionals, fix and flippers, and beyond may