Browser-based applications are widely used and we like the fact that we can access them from anywhere. But from the users' perspective, the productivity level of Web applications still doesn't approximate the productivity of desktop programs. The good news is the gap is closing: the accumulated potential of multiple technologies has boosted a whole new breed of HTML-based apps that are as powerful as the desktop ones. Meet AJAX.
What Is AJAX?
The name stands for Asynchronous JavaScript + XMLHTTPRequest and means you can establish socket communication between browser-based JavaScript and the server. AJAX isn't a new technology. It's a successful branding of possibilities implanted in several technologies available in modern browsers. All AJAX applications deliver a rich UI via extensive JavaScript manipulation of the HTML Document Object Model based on the precision-pointed data retrieval via XMLHttpRequest. Typical examples of AJAX applications are Google Maps and Google Suggest from Google Labs (http://labs.google.com). These applications actively monitor user input and provide real-time page updates. Most importantly, this happens without a page refresh while the user navigates through the map or types a search string.
In fact, the technologies behind these wonders have been around for a while, although they require sophisticated skills in using browser-specific tricks. Proprietary offerings with similar capabilities - Macromedia Flash plug-in, Java Applets or .NET runtime - have been available for quite some time too. The idea of integrating a scriptable transport component talking to the server into the browser was pioneered by IE 5.0. Then Firefox and other popular browsers joined the club of browsers in supporting XMLHTTPRequest as a built-in object. With cross-browser availability, these technologies gained visibility and in March of 2004 a company called Adaptive Path introduced AJAX.
In short, backing from Google and having the right browser technologies available out-of-the-box tipped the scale: these days everyone is adding client-side technologies to Web applications for a "better user experience."
AJAX vs. Classical Applications
A classic Web application model is literally a triumph of form over substance: users are forced to submit forms in exchange for pages. That said, the form submission and page delivery aren't guaranteed: worse case the user clicks again though some pages specifically warn against that. It's quite different with AJAX, where the data travels across the wire instead of entire HTML pages. This data exchange is scripted via a specific browser object - XMLHttpRequest; the appropriate logic handles the outcome of each data request, the specifc region of the page is updated instead of the entire page. The results are more speed, less traffic, and better control of information delivery.
Traditional "click-refresh" Web applications force users to interrupt the work process while waiting for the page to reload. With AJAX, a client-side script can asynchronously talk to the server while the user keeps entering data. Besides being transparent to the user, such asynchrony means more time for the server to process the request.
Classic Web applications delegate all processing to the server and force the server to manage the state. AJAX allows flexible partitioning of the application logic and state management between the client and the server. This eliminates a "click-refresh" dependancy and provides better server scalability. When the state is stored on the client-side you don't have to maintain sessions across the servers or save/expire state: the lifespan is defined by client.
AJAX: Distributed MVC
Although AJAX applications rely on JavaScript for the presentation layer, the processing power and knowledge base remain on the server. For that matter, AJAX applications talk heavily to J2EE servers, feeding data to and from Web Services and servlets. The difference between J2EE applications with an AJAX-based presentation tier and standard J2EE application is that in the first case MVC is distributed over the wire. With AJAX, View is local, while Model and Controller are distributed giving the developer the flexibility to decide which components will be client-based. Specifically, a local View renders graphics by manipulating with HTML DOM; the controller handles user input locally and at the developer's discretion extends the processing to the server via HTTP requests (Web Services, XML/RPC or others); the remote part of the Model is downloaded as needed to the client achieving in-place real-time updates of the client page; and state is collected on the client.
In future AJAX articles we'll talk about each of these components in depth and provide examples of how they came to play together. Now, without further ado, let's dive into a simple AJAX example.
Zip Codes Validation and Lookup
We'll create an HTML page containing three INPUT fields: Zip, City, and State. We'll make sure that as soon as the user enters the first three digits of the zip code, the state will get populated with the first matching state value. Once the user types in all five zip digits, we'll instantly determine and populate the appropriate city. If the zip code isn't valid (not found in the server's database), we'll turn the zip's border color to red. Such visual clues are helpful to users and have become standard in modern browsers (as an example, Firefox finds matching words in an HTML page by highlighting them in the browser search field while you type).
Let's start with a simple HTML containing three input fields: zip, city, and state. Please note that the method zipChanged() is called as soon as a character is entered in the zip field. In turn, the JavaScript function zipChanged() (see below) calls the function updateState() when the zip length is three and up-dateCity() when the length of the zip is five. Both updateCity() and updateState() delegate most of the work to another function - ask().
Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" style="width:60"/>
City: <input id="city" disabled maxlength="32" style="width:160"/>
State:<input id="state" disabled maxlength="2" style="width:30"/>
<script src="xmlhttp.js"></script>
<script>
var zipField = null;
function zipChanged(){
zipField = document.getElementById("zipcode")
var zip = zipField.value;
zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):"";
}
function updateState(zip) {
var stateField = document.getElementById("state");
ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField);
}
function updateCity(zip) {
var cityField = document.getElementById("city");
ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField);
}
</script>
The function ask() communicates with the server and assigns a callback to process the server's response (see the following code). Later, we'll look at the content of the dual-natured resolveZip.jsp that looks up the city or state information depending on the number of characters in the zip field. Importantly, ask() uses the asynchronous flavor of the XmlHttpRequest so that populating the state and city fields or coloring the zip border is done without slowing data entry down. First, we call request.open(), which opens the socket channel with the server using one of the HTTP verbs (GET or POST) as the first argument and the URL of the data provider as a second one. The last argument of the request.open() is set to true, which indicates the asynchronous nature of the request. Note that the request hasn't been submitted yet. That happens with the request.send() call, which can provide any necessary payload for POST. With asynchronous requests we have to assign the request's callback using the request.onreadystatechanged attribute. (If the request had been synchronous, we could have processed the results immediately after request.send, but we would have blocked the user until the request was completed.)
HTTPRequest = function () {
var xmlhttp=null;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (_e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (_E) { }
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
try {
xmlhttp = new XMLHttpRequest();
} catch (e) {
xmlhttp = false;
} }
return xmlhttp;
}
function ask(url, fieldToFill, lookupField) {
var http = new HTTPRequest();
http.open("GET", url, true);
http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill, lookupField)};
http.send(null);
}
function handleHttpResponse(http, fieldToFill, lookupField) {
if (http.readyState == 4) {
result = http.responseText;
if ( -1 != result.search("null") ) {
lookupField.style.borderColor = "red";
fieldToFill.value = "";
} else {
lookupField.style.borderColor = "";
fieldToFill.value = result;
} } }
The HttpRequest() function (see above) used by ask() is a cross-browser constructor of an instance of the XMLHTTPRequest; we'll look at it a bit later. For now, note how the invocation of handleResponse() is wrapped by an anonymous function (a so-called closure) function (){ handleHttpResponse(http, fieldToFill, lookupField)}.
The code for that function is dynamically created and compiled every time we do an assignment to the http.onreadstatechange property. As a result, JavaScript creates a pointer to the context with all variables that the enclosing method - ask() - has access to. It's done so the anonymous function and handleResponse() are guaranteed full access to all context-hosted variables until the reference to the anonymous function is garbage-collected. In other words, whenever our anonymous function gets invoked, it can refer to the request, fieldToFill, and lookupField variables as seamlessly as if they were global. It's also true that every invocation of ask() will create a separate copy of the environment with the variables holding the values of the moment the closure was formed.
Let's look at the function handleResponse(). Since it can be invoked at different states of the request processing, the function ignores all cases except the one when the request processing is complete. This corresponds to the request.readyState property equal to 4 ("Completed"). At this point the function reads the server's response text. Contrary to what its name may suggest, neither the input nor the output of XmlHttpRequest has to be restrained to XML. In particular, our resolveZip.jsp (see Listing 1) returns plain text. If the return value is "unknown" the function assumes that the zip code was invalid and changes the border color of the lookup field (zip) to red. Otherwise, the return value is used to populate the fill field (state or city), and zip's border is assigned a default color.
XMLHttpRequest - the Transport Object
Let's return
to our cross-browser implementation of XMLHTTPRequest. The last listing
contains an HttpRequest() function that's upward-compatible with IE5.0
and Mozilla 1.8/FireFox. For simplicity's sake, we just try to create a
Microsoft XMLHTTPRequest object - and if that fails we assume it's
Firefox/Mozilla.
At the heart of this function is the XMLHTTPRequest - a native browser object, which facilitates anything that involves HTTP protocol in commu-nicating with the server. It allows specifying any HTTP verbs, headers, and payload and works in either asynchronous or synchronous mode. No downloads or plugins are required, although in the case of IE, XMLHTTPRequest is an ActiveX integrated inside the browser. Accordingly, the "Run ActiveX Control and Plugins" default IE permission should be in place to use it.
Most important, XMLHTTPRequest allows an RPC-style programmatic query to the server without any page refresh.
It does it in a predictable, controlled way, offering complete access to all details of the HTTP protocol, including the headers and any custom formatting of the data. In future articles, we'll show you industrial protocols that you can run on top of this transport including Web Services and XML-RPC that greatly simplify developing and maintaining large-scale applications.
The Server-Side Logic
Finally, the server-side
resolveZip.jsp is invoked from the function ask() as shown in Listing
1. The resolveZip.jsp is called in two separate scenarios
differentiated by the current length of the zip code (see the
zipChanged() function.) The value of the request parameter lookupType
is either state or city. For simplicity's sake, we'll assume that two
files, state.properties and city.properties, are located in the root
directory of the c: drive of the server. The resolveZip.jsp logic is
confined to returning the lookup value with the appropriate pre-loaded
file - once in each case of course.
Our AJAX-enabled page is ready. The complete working example is available at: www.ajaxmaker.com:8080/blog/zipsearch.htm.
Remote Scripting - An Alternative Approach
Some
older AJAX implementations are based on so-called remote scripting. The
idea is that the user's actions result in querying the server via
IFRAME, and the server responds with the JavaScript, which is
immediately executed as soon as it reaches the client. This is a big
difference compared to XMLHttpRequest approach, where the server
responds with the data and the client interprets the data. The
advantage is that this solution supports older browsers.
The HTML portion of the IFRAME-based example (see Listing 2) is similar to the one we've used in the XMLHTTPRequest scenario, but this time we'll introduce an extra IFRAME element - controller:
Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" style="width:60" size="20"/>
City: <input id="city" disabled maxlength="32" style="width:160" size="20"/>
State:<input id="state" disabled maxlength="2" style="width:30" size="20"/>
<iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>
We keep calling zipChanged() per every key stroke, but this time the function ask(), called from zipChanged() (see Listing 3), sets the IFRAME's src property, instead of invoking an XMLHTTPRequest:
function ask(url, fieldToFill, lookupField)
{
var controller = document.getElementById("controller");
controller.src = url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
}
The server-side logic is presented by a sketchy resolveZip.jsp (see Listing 4). It's different from its XMLHTTPRequest counterpart in that it returns JavaScript statements, which set the global values of the variables field lookup and city and the call function response() from the global window's execution context as soon as it gets to the browser.
The function response() is a modified version of the handleResponse() which is absolved from dealing with uncompleted requests (see Listing 2.)
The Fine Print
For simplicity's sake, we've "overlooked" some important issues in our sample code:
1. The fact that the instances of the XMLHTTPRequest object and callback invocations haven't been destroyed after being used, which causes memory leaks after every call. Properly written code should destroy or reuse such instances in the object pool. Object management techniques common to the server software have to be used for the client
2. In quite a few places the errors weren't handled properly. For example, the call to request.open() in the method ask() can throw an exception that has to be caught and processed even though JavaScript exceptions don't have to be checked. The handleResponse() function is another example. It has to check headers and responseText for possible server-side and communication errors. In case of an error, it has to try to recover and/or report an error. Properly developed AJAX applications eliminate loosing data on "submissions" due to disconnects and other low-level communication problems via a robust, self-recovering framework.
3. Current server-side frameworks provide quite a few functions that have to be reconciled with a refresh-free approach. For example, let's consider a custom server-side authentication with a timeout. In that case we'd have to intercept security system response to the XMLHTTPRequest calls, bring up the login screen, and then re-issue the request after the user was authenticated.
All these problems are typical of any application code working with low-level APIs and all of them can be resolved. The good news is that the technologies needed to resolve these issues are quite familiar to most Java developers like Web Services, custom tags, and XML/XSLT. The only difference is that nowadays these technologies come to the rescue on the client in the form of:
Part 2
The publicity that AJAX grabbed over the
last half a year is based on closing the gap between the Web
applications and the desktop applications, combining the "reach" and
"rich." At the same time, the gap between the technological level of
AJAX and what corporate developers expect in their modern arsenal is
really astonishing. After all, AJAX is neither a tool nor a platform.
There is no AJAX standards committee or community process in place.
While software vendors are crafting proprietary development platforms
on top of AJAX (which pretty much means "from scratch"), early adopters
of AJAX are left on their own.
In the previous section, we touched on the foundation of AJAX - the ability to establish script-to-server communication. This is what makes HTML pages dynamic and responsive. Does it mean we are ready to kick-off our own version of Yahoo mail? No, we are not. Here is why: AJAX is a mixed blessing. On one hand it enables us to create rich, desktop-class applications on the Web. On the other, if we compare "page-flipping" Web applications with the client/server or Swing ones, the development practices are not quite the same. What about management expectations? We'll need to get used to the fact that it takes time to build a rich UI. The more flexibility with more permutations the user is allowed - the more time it takes.
The answer, of course, is component libraries, frameworks, and industrial-strength tools. Leaving tools aside, this article concentrates on what is available for AJAX enthusiasts today. Addressing a need to build reusable business components, it focuses on the "hidden" object-oriented power of JavaScript. Also, by addressing a need to build custom-rich UI components, it illustrates a convenient way to encapsulate presentation logic in custom client-side HTML tags.
AJAX Language: Object-Oriented JavaScript
By
definition, JavaScript is the language of classic AJAX. Unlike Java,
JavaScript does not enforce the OO style of coding. That said, it's
surprising how often it's overlooked that Java-Script fully supports
all the major attributes of an OO language: inheritance, polymorphism,
and encapsulation. Douglas Crockford even named Java Script "The
World's Most Misunderstood Programming Language." Let's review the
object-oriented side of JavaScript.
Data Types
In Java, a class defines a combination
of data and its associated behaviors. While JavaScript reserves the
class keyword, it does not support the same semantic as in conventional
OOP languages.
It may sound strange but in JavaScript, functions are used as object definitions. By defining a function in the example below you, in fact, define a simple empty class - Calculator:
function Calculator() {}
A new instance is created the same way as in Java - by using the new operator:
var myCalculator = new Calculator();
The function not only defines a class, but also acts as a constructor. The operator new does the magic, instantiating an object of class Calculator and returning an object reference in contrast to merely calling the function.
Creating an empty class is nice but not really useful in real life. We are going to fill-in the class definition using a Java-Script prototype construct. JavaScript uses prototype to serve as a template for object creation. All prototype properties and methods are copied by reference into each object of a class, so they all have the same values. You can change the value of a prototype property in one object, and the new value overrides the default, copied from the prototype, but only in that one instance. The following statement will add a new property to the prototype of the Calculator object:
Calculator.prototype._prop = 0;
Since JavaScript does not provide a way to syntactically denote a class definition, we'll use the with statement to mark the class definition boundaries. This will also make the example code smaller as the with statement is allowed to perform a series of statements on a specified object without qualifying the attributes.
function Calculator() {};
with (Calculator) {
prototype._prop = 0;
prototype.setProp = function(p) {_prop = p};
prototype.getProp = function() {return _prop};
}
So far we have defined and initialized the public _prop variable as well as provided getter and setter methods for it.
Need to define a static variable? Just think of the static variable as being a variable owned by the class. Because classes in JavaScript are represented by function objects, we just need to add a new property to the function:
Calculator.iCount = 0;
Now that the iCount variable is a property of the Calculator object, it will be shared between all instances of the class calculator.
function Calculator() {
Calculator.iCount++;
};
The above code will count all created instances of the class Calculator.
Encapsulation
Using "Calculator", as defined
above, permits access to all the "class" data, increasing the risk of
name collisions in inherited classes. We clearly need encapsulation to
view objects as self-contained entities.
A standard language mechanism of data encapsulation is private variables. And a common JavaScript technique for emulating a private variable is to define a local variable in the constructor, so that this local variable is accessible exclusively via getter and setter - inner functions of the very same constructor. In the following example, the _prop variable is defined within the Calculator function and is not visible outside of the function scope. Two anonymous inner functions, assigned to setProp and getProp attributes, provide access to our "private" variable. Also, please note the use of this, quite similar to how it is used in Java:
function Calculator() {
var _prop = 0;
this.setProp = function (p){_prop = p};
this.getProp = function() {return _prop};
};
What is often overlooked is the cost of such encapsulation in JavaScript. It can be tremendous, because inner function objects get repeatedly created for each instance of the "class".
Accordingly, since constructing objects based on the prototype is faster and consumes less memory, we cast our vote in favor of public variables wherever performance is critical. You can use naming conventions to avoid name collisions, for example, by prefixing public variables with the class name.
Inheritance
At first glance, JavaScript lacks
support for the class hierarchy similar to what programmers of
conventional object-oriented languages expect from the modern language.
However, although JavaScript syntax does not support class inheritance
as in Java, inheritance can still be implemented by copying an instance
of a previously defined class into the prototype of the derived one.
Before we provide an illustration, we need to introduce a constructor property. JavaScript makes sure that every prototype contains constructor, which holds a reference to the constructor function. In other words, Calculator.prototype.constructor contains a reference to Calculator().
Now, the code below shows how to derive the class ArithmeticCalculator from the base class Calculator. "Line 1" results in borrowing all properties of the Calculator, while "Line 2" restores the value of the prototype, constructor back to ArithmeticCalculator:
function ArithmeticCalculator() { };
with (ArithmeticCalculator) {
ArithmeticCalculator .prototype = new Calculator(); //Line 1
prototype.constructor = ArithmeticCalculator; //Line 2
}
Even if the example above looks like a composition rather than inheritance, the JavaScript engine knows about the prototype chain. In particular, the instanceof operator will work correctly with both the base and derived classes. Assuming you create a new instance of a class ArithmeticCalculator:
var c = new ArithmeticCalculator;
expressions c instanceof Calculator and c instanceof ArithmeticCalculator will both evaluate to true.
Notice, that the constructor of the base class in the example above is called at the point when the ArithmeticCalculator prototype is initialized and not when an instance of the derived class is created. This could have unwanted side effects and you should consider creating a separate function for initialization purposes. As the constructor is not a member function, it can't be called through this reference directly. We will need to create a "Calculator" member function to be able to call super:
function Calculator(ops) { ...};
with (Calculator) {
prototype.Calculator = Calculator;
}
Now we can write an inherited class that explicitly calls the constructor in the base class:
function ArithmeticCalculator(ops) {
this.Calculator(ops);
};
with (ArithmeticCalculator) {
ArithmeticCalculator .prototype = new Calculator;
prototype.constructor = ArithmeticCalculator;
prototype.ArithmeticCalculator = ArithmeticCalculator;
}
Polymorphism
JavaScript is a non-typed language
where everything is an object. Accordingly, if there are two classes A
and B, both defining method foo(), JavaScript will allow polymorphic
invocation of foo() across instances of A and B even if there is no
hierarchical relation (albeit implementational) whatsoever. From that
perspective, JavaScript provides a wider polymorphism then Java. The
flexibility, as usual, comes at a price. In this case, it is a price of
delegating the type checking job to application code. Specifically, if
there is a need to check that a reference indeed points to a desired
base class, it can be done with the instanceof operator.
On the other hand, JavaScript doesn't check parameters in the function calls, which prevents from defining polymorphic functions with the same name and different parameters (and let the compiler choose the right signature). Instead, JavaScript provides an argument object - Java 5 style - within a function scope that allows you to implement a different behavior depending on the parameter's type and quantity.
Example
Listing 5
implements a calculator that calculates expressions in a reverse Polish
notation. It illustrates the main techniques described in the articles
and also shows the usage of the unique JavaScript features, such as
accessing object properties as an array element for a dynamic function
call.
To make Listing 5 work we also need to provide a piece of code that instantiates the calculator objects and calls the evaluate method:
var e = new ArithmeticCalcuator([2,2,5,"add","mul"]);
alert(e.evaluate());
AJAX Component Authoring
All AJAX component
authoring solutions known today can be logically divided into two
groups. The first group specifically targets the seamless integration
with the HTML-based UI definition. The second group drops HTML as a UI
definition language in favor of certain XML. In this article we
illustrate one approach from the first group, an analog to JSP tags,
albeit in the browser. These browser-specific component authoring
extensions are called element behaviors in the IE case or extensible
bindings in the case of the latest versions of Firefox, Mozilla, and
Netscape 8.
Custom Tag Dialects
Internet Explorer, starting
with version 5.5, enables the JavaScript authoring of custom,
client-side HTML elements. Unlike JSP tags, these objects are not
preprocessed into HTML on the server side. Rather, they're legitimate
extensions of a standard HTML object model and everything, including
control construction, happens dynamically on the client. Similarly,
Gecko-engine based browsers can dynamically decorate any existing HTML
element with a reusable functionality.
It's possible, therefore, to build a library of rich UI components with methods, events, and attributes that will have HTML syntax. Such components can be freely mixed with standard HTML. Internally, these components will communicate with application servers, AJAX style. In other words, it's possible (and relatively simple) to build your own AJAX object model.
The IE flavor of this approach is called HTC or HTML components; the Gecko version is called XBL - eXtensible Bindings Language. For the purposes of this article, we'll focus on IE.
Enter the HTML Components - HTC
HTC or HTML
components are also called behaviors. In turn they are divided into
attached behaviors that decorate any existing HTML element with a set
of properties, events, and methods, and element behaviors that look
like an extended set of custom HTML tags to the hosting page. Together,
element and attached behaviors provide a simple solution for authoring
both components and applications. Here we'll illustrate the most
comprehensive case, element behaviors.
Data-Bound Checkbox Control
As an illustration of
element behavior, we'll construct a custom data-bound checkbox. The
rationale behind building such a control is that a standard HTML
checkbox has several noticeable shortcomings:
<checkbox id="cbx_1" value="N" labelonleft="true"
label="Show Details:" onValue="Y" offValue="N"/>
In addition, our control will support the cancelable event onItemChanging and the notification event onItemChanged.
Custom Tag Definition Structurally, a custom tag is a file with an HTC extension that describes its properties, methods, and events between <PUBLIC:COMPONENT> and </PUBLIC:COMPONENT>.
To define a custom CHECKBOX tag, we create a file checkbox.htc as in the following snippet where the first line sets the tag name of the component:
<PUBLIC:COMPONENT NAME="cbx" tagName="CHECKBOX">
<PROPERTY NAME="value" GET="getValue" PUT="putValue" />
// Here we place all other properties of the component <METHOD NAME="show" />
// Here we place all other methods of the components <EVENT NAME="onItemChanging" ID="onItemChanging"/>
// Here we place all other events that component will fire to application
<ATTACH EVENT="oncontentready" HANDLER="constructor" />
// Here we place all other events that component handles itself <SCRIPT >
// Here we place all methods, properties getters and setters and event handlers
</SCRIPT>
</PUBLIC:COMPONENT>
Custom Tag Use Case
While the contents of the HTC
file matter a lot, the name of the file is irrelevant, although,
ultimately, the URL to the HTC file needs to be specified using the
IMPORT instruction. It has to be done before the corresponding custom
tag is mentioned for the first time (on the page). Here is how the
simplest possible page utilizing a custom checkbox might look, assuming
the page and the HTC file are located in one folder:
<HTML xmlns:myns>
<?IMPORT namespace="myns" implementation="checkbox.htc" >
<BODY>
<myns:checkbox id='cbx_1' label='Hello'/>
</BODY>
</HTML>
Please notice how custom CHECKBOX has been mapped to a nondefault namespace "myns" in the opening HTML tag. The IMPORT instruction performs a synchronous load of the HTC into the browser's memory and also instructs the browser how to perform name resolution for the appropriate namespace (HTC to namespace association can be many-to-one).
Constructor of the Custom Tag
The best way to
initialize HTC, once it's loaded, is to process the "oncontentready"
event. Accordingly, we define a handler function, which for sheer
clarity is called constructor:
<ATTACH EVENT="oncontentready" HANDLER="constructor" />
The logic of constructor() is simple: concatenate a regular HTML checkbox and HTML label in the order dependent on the value of property labelonleft (see property definition below):
function constructor() {
// We will add an HTML checkbox and label to the element body
// See Listing 6 for details
}
Defining Custom Tag Properties
To define property labelonleft, we add one more line to the <PUBLIC:COMPONENT> section:
<PROPERTY NAME="labelonleft" VALUE="true"/>
Please note that this property does not contain getter and/or setter methods. Properties onValue and offValue that provide the mapping of the checkbox status into a business value domain also don't need getters and setters:
<PROPERTY NAME="onValue" VALUE="true"/>
<PROPERTY NAME="offValue" VALUE="false" />
However, property checked is defined with both getter and setter:
<PROPERTY NAME="checked" GET="getChecked" PUT="putChecked" />
Accordingly, we have definitions for both methods in the <SCRIPT> section. As you can see, setter putChecked(), which is triggered every time checked is modified, sets the value property to one of two variants: onValue or OffValue. Please note that putChecked() will get triggered not only by the script on the checkbox-hosting page, but also by any assignment to checked done inside this very checkbox.htc.
var _value;
function putChecked( newValue ) {
value = (newValue?onValue:offValue);
}
function getChecked(){
return ( _value == onValue);
}
Defining Events for the Custom Tag
Let's look at
the definition of onItemChanging and onItemChanged events and how these
events are being fired and processed inside the setter for value
property (see Listing 6).
Method putValue() has a couple of points of interest. First, it can be called during the parsing on the CHECKBOX tag, as long as the HTML value attribute is specified. That's why we have a separate logic branch for not constructed objects, to differentiate the process of construction from a reaction to a user click. Second, we illustrate here the creation and processing of the custom event onItemChanging, which allows the application to cancel (block) the action. Please note that both clicking as well as programmatic assignment to the value can get cancelled this way.
Event Canceling
To cancel the event, an
application should intercept the event and set event.returnValue to
false. The schematic snippet of code illustrating how the application
would cancel the processing is presented below:=
cbx_1::onItemChanging() {
. . . . .
if (canNotBeAllowed) {
event.returnValue=false;
. . . . .
}
If the event is not cancelled, putValue() sets the internal, plain HTML checkbox's checked property as per the current value: if that is equal to onValue, the internal checkbox will get checked; if it is equal to offValue (there is no third option), unchecked (the full listing is presented in Listing 6).
HTML Internals of the CHECKBOX
The painting of our
control is done by the helper functions addLabel() and addCheckBox()
and is called from within a constructor(). These functions inject HTML
inside the element's innerHTML (element is the analog for this in HTC
parlor). The injected HTML, in the simplified form, looks like the
following:
<LABEL for=cb_{uniqueID}>Show Details:</LABEL>
<INPUT id=cb_{uniqueID} type=checkbox />
where uniqueID is a unique (within a page) string literal generated by IE, which identifies the instance of the HTC.
Encapsulation ... Again
There is one shortcoming
in our CHECKBOX. The way we built it, the HTML injected during the
constructor() contributes to the DOM of the page that hosts the HTC.
Also global JavaScript variables like_value fall into the global scope
of the document they're included in. This is dangerous since we run
into the possibility of name clashes: the most obvious case is more
than one instance of the same control. In addition, it presents a
possibility that our control may accidentally reference other objects
with the same names and vice versa.
To put it simply, a special mechanism is required to enable a truly modular approach for object authoring. Fortunately, HTC technology supports an intelligent answer - viewLink.
The easiest way to declare a control as encapsulated is to put one extra declaration between opening and closing PUBLIC:COMPONENT tags:
<PUBLIC:DEFAULTS viewLinkContent/>
Instantly, the control becomes encapsulated; it has its own HTML document tree, atomic to the master document. Every instance of the object has its own set of instantiated values and only public methods and properties can be accessed by the outside code. In other words, the viewLink mechanism fully enables the design and implementation of sophisticated Web applications using a true OO component-based approach.
In particular, we can simplify our code by removing uniqueID suffixes from the definition of internal checkboxes and HTML labels, since we are not afraid of name clashes anymore. Accordingly, we may replace the line:
eval( 'cb_'+uniqueID).checked = ( _value == onValue );
with the
cb.checked = ( _value == onValue );
as well as change addCheckbox() and addLabel() accordingly.
Conclusion
Since the AJAX race has just started,
there are no AJAX standards and no commonly accepted RAD tools you can
rely on to build your applications. While software vendors have a long
way to go to create the robust development platforms, AJAX enthusiasts
can prepare by encapsulating reusable blocks of code as business
components with a well-defined API.
Navigating in this direction, this article outlined the OO "powers" of the AJAX language - JavaScript. It also illustrated one of the available component-authoring strategies - client-side custom tags technology. While we presented only IE-specific custom tags, we also provide a downloadable example of extensible bindings example for the Mozilla browser. All article-related examples can be downloaded from www.ajaxmaker.com/JDJ/AJAX/partII.html.