Javascript

Client-Side Controls With the Microsoft AJAX Library


February 22, 2008 08:42 by joel

Introduction

The purpose of this post is to explain how to build a client control using the Microsoft AJAX Library. This is cool because it lays the foundation for building extenders and script controls. If you've been working at all with ASP.NET AJAX, you'll definitely know what an extender is, as they are the simplest way to add AJAX functionality to a page using the ASP.NET framework. A script control is also an AJAX enabled control, but it doesn't rely on external components. It is a fully contained, AJAX enabled server control. I'm getting ahead of myself, though, and I'll save that topic for another post.

My post today is going to focus on the client-side, where my passion really lies. The control that I'm going to walk you through was created for a specific purpose: to provide a dynamic container which displays thumbnail images, allowing a single image to be selected at any given time. You could say that it acts as a sort of thumbnail menu. You can download the source for the control here.

Before I get into actually building the control, I'll lay out the list of requirements that I started out with:

  • The control needed to be dynamic. There had to be functionality for adding or removing images at any time.
  • There had to be a means for providing visual feedback when an image was hovered over, or selected.
  • There needed to be an external event which fired when an image was selected.
  • The control needed to always be aware of which image was selected.

Classes in the Microsoft AJAX Library 

A control in its most basic form is just a class, defined with the Microsoft AJAX Library, which inherits from Sys.UI.Control. Although there are some differences building a simple class with the library is very similar to doing it with straight-up JavaScript. You first define a constructor function, which contains the properties and their inital values, and you define the prototype object, which typically contains the methods of the class. The main differences are outside of the actual class definition, and lie in the two registration methods. The first is used if you want the class to reside in a namespace. The Type.registerNamespace method is used to define the namespace, and needs to be called before the class definition. Using a namespace is not required (it is, however, highly recommended). The second is the registerClass method, which is called after the class is defined. The only required argument for the method is the fully-qualified name of the class. As a brief example, here is an empty class, defined using the Microsoft AJAX Library:

Type.registerNamespace('Example');
Example.SampleClass = function() {
    // Class Properties
}
Example.SampleClass.prototype = {
    // Class methods.
}
Example.SampleClass.registerClass('Example.SampleClass');

A Control 'Skeleton' 

That is all that is needed to create a simple class. As I said earlier, a control is actually just a class that inherits from Sys.UI.Control. Because controls utilize class inheritance based within the Microsoft library, there are a few specific things that need to be implemented in your control class. Here is the skeleton of the ImageSelectorDiv control:

Type.registerNamespace('PhotoLoc');
PhotoLoc.ImageSelectorDiv = function(element) {
    PhotoLoc.ImageSelectorDiv.initializeBase(this, [element]);
    // Properties removed for simplicity.
}
PhotoLoc.ImageSelectorDiv.prototype =  {
    initialize : function() {
        PhotoLoc.ImageSelectorDiv.callBaseMethod(this, 'initialize');
        // The initialize method is inherited from Sys.UI.Control 
        // and is typically overridden in each inheriting class
        // to provide functionality when the class is initialized.
        // The callBaseMethod() method does precisely what it says. .. 
        // It calls the initialize method of the inherited class.
    },
    dispose : function() {
        // This method is called, just as the control is being disposed, 
        // allowing you to clean up event handlers, or anything else.
        PhotoLoc.ImageSelectorDiv.callBaseMethod(this, 'dispose');
    }
    // The remainder of the class methods are defined here. 
}
PhotoLoc.ImageSelectorDiv.registerClass('PhotoLoc.ImageSelectorDiv', Sys.UI.Control); 

The first thing you'll notice is the initializeBase method which is called from the constructor. This method, along with the two callBaseMethod methods are required in order to inherit the properties and methods of the base class. It is through those methods that the library implements class inheritence.

The initialize and dispose methods are provided by the base class, and are generally overridden, as I did in this control. They allow logic to be called when the class is initialized and right before it is disposed. Typically they are used to do things like add event handlers and initialize properties in the initialize method, and remove event handlers in the dispose method (preventing memory leaks). The other thing you'll notice is that the constructor accepts an argument which I've fittingly called element. The reason for this is that each client control is associated with a DOM element. When you create an instance of the control, you need to pass the element into the $create method (which in turn passes it to the class constructor), or there will be an error at runtime.

Control Properties 

Accessor(getter) and mutator(setter) methods are used in a similar way to properties in a language like C#. Although there is no way to declare a variable as private in JavaScript (it is technically possible to create variables that aren't accessible from outside an object, but that's a topic for another time), a common practice that is used to signify a private variable is adding an underscore to the front of its name. You will notice that all of the properties in the constructor are preceded by an underscore. When you use this technique, the framework recognizes the property as private and automatically uses the accessor or mutator methods to access that property. For example, consider the _selectedCssClass property of the control. The accessor and mutator methods for that property are:

get_selectedCssClass : function() {
    return this._selectedCssClass;
},
set_selectedCssClass : function(value) {
    this._selectedCssClass = value;
},

For every instance of the control that is created, you can set the _selectedCssClass property by passing the following object literal into the $create statement:

    { 'selectedCssClass' : 'yourCustomCssClassName' } 

Any properties that you have exposed using accessors and mutators can be set in that way.

Creating the Control in your Page 

I have laid out the basics of a control, so I'll quickly explain how to add it to the page before I fill in the details of this specific control. The method which accomplishes that is the $create statement (the $create statement is a shortcut for calling the Sys.Application.create method), and it is called during the init stage of the client page lifecycle. In my specific application, the $create statement for this control looks like this:

$create(PhotoLoc.ImageSelectorDiv, {
    'unselectedCssClass' : 'imageBoxHover',
    'selectedCssClass' : 'selected',
    'imageSelectedHandler' : imageSelected
}, 
{}, 
{},
$get('PhotoContainerInner'));

The first argument is the fully qualified name of the control. The second argument is the object literal in which you define the client properties. Notice that I have set two CSS classes: one for the unselected images, and one for the selected image. Although it looks like an ordinary object literal, the second object is actually a JSON object. For that reason, the name of the property is always passed as a string. You will also notice that it doesn't have the preceding underscore. The value of the property needs to be one of the accepted JSON values. These properties satisfy two of the requirements that I laid out at the beginning. The two CSS classes are used to provide a means for setting the visual style of the selected and unselected elements. The 'imageSelectedHandler' contains the name of a function which is used as an event handler for the 'imageSelected' event that is raised by the control whenever a new image is selected.

The third and fourth argument I won't talk about here, since they aren't used in this example, but you can find out more about them here. The final argument is a reference to the DOM element that the control will 'wrap', or be associated with. In this case it is a div, with an id of 'PhotoContainerInner'.

As I said, the $create statement is called during the init stage of the client page lifecycle. This event is accessed through the Sys.Application class. The following code adds a handler, onInit, for the page init event:

Sys.Application.add_init(onInit);

The $create statement would then be called from the onInit function, which would in turn create the control on the page. 

Public Methods

One of the requirements for this control was that it needed to provide a means for adding and removing images. This is implemented using public methods. You'll notice that the following method names aren't preceded by an underscore, meaning that they are intended to be public methods.

// Public Methods
addImage : function(image) {
    $addHandler(image, 'click', this._clickHandler);
    if(!image.id) {
        this._imageCounter++;
        image.id = "image" + this._imageCounter;
    }
    this._imageCollection[image.id] = image;
    this.get_element().appendChild(image);
    Sys.UI.DomElement.addCssClass(image, this._hoverCssClass);
},
    
removeSelectedImage : function() {
    this.get_element().removeChild(this._selectedImage);
    delete this._imageCollection[this._selectedImage.id];
    this._selectedImage = null;    
},
    
removeImage : function(image) {
    if(this._selectedImage == image) {
        this.removeSelectedImage();
    } else {
        this.get_element().removeChild(image);
        delete this._imageCollection[image.id];
    };       
},
clearImages : function() {
    this._imageCounter = 0;
    this._selectedImage = null;
    for(var image in this._imageCollection){
        this.get_element().removeChild(this._imageCollection[image]);
        delete this._imageCollection[image];
    };
},
getSelected : function() {
    return this._selectedImage;
},    
  

The question that I will now answer is "how do I access the control from my code?". That is an excellent question, as public methods aren't much good if you don't know where to find them. In short, the answer lies with the $find shortcut, provided by the library. It works just like the $get shortcut, except that it returns the control object, rather than a DOM element. Even though it returns the control, you still pass it the id of the associated DOM element. For my example, I would call the addImage method in the following way:

$find('PhotoContainerInner').addImage(pic); 

Details

You now know how to define and implement a custom client-side control. There are a few other concepts that I'll quickly cover before I finish. These won't be applicable in every control, but they are used here, and will be helpful in a lot of situations.

Looking at the class, you may have noticed the _clickHandler property. This property is used as a delegate to handle the internal click event. This is an implementation of another aspect of the Microsoft AJAX Library which will be familiar to anyone from a .NET background. The basic purpose of a delegate is to invoke a function which is used as an event handler. The reason this is useful has to do with scope. Generally, within an event handler, this refers to the element that hooked up the event. That is often not the behavior that you want. When building a class, it is often useful to have this continue to refer to the instantiated object. You can accomplish this by passing a delegate as the event handler, instead of a simple function.

A delegate is created using the Function.createDelegate method. The method accepts 2 arguments and returns a function. The first argument is the object that you wish to refer to as this within the event handler. The second argument is the function which will handle the event. So for this control, here is the delegate and the event handler function:

// Event Handler
_onImageClick : function(e) {
    if(e.target != this._selectedImage) { 
        if(this._selectedImage) {
            Sys.UI.DomElement.removeCssClass(this._selectedImage, this._selectedCssClass);
            Sys.UI.DomElement.addCssClass(this._selectedImage, this._hoverCssClass);
        };
        this._selectedImage = e.target;
        Sys.UI.DomElement.removeCssClass(this._selectedImage, this._hoverCssClass);
        Sys.UI.DomElement.addCssClass(this._selectedImage, this._selectedCssClass);
        this._raiseEvent('imageSelected', { event : e, image : this._selectedImage });
    };
},
// Delegate
_initializeClickHandler : function() {
    this._clickHandler = Function.createDelegate(this, this._onImageClick);
}, 

This allows us to handle the event with the the _onImageClick function, by way of the delegate, but still refer to the control object as this.

Another thing I'll mention is custom events. The third requirement that I had for the control is that it needed to provide an external event which fired whenever an image was selected. To accomplish that task I used the custom event functionality within the Microsoft AJAX Library to create an event called 'imageSelected' which is exposed through the control. Every component that inherits from Sys.Component (controls and behaviors both inherit from Sys.Component) has the ability to raise custom events. As that is a topic for an entire post, however, I'll just point you in the direction of somebody that has already explained it very well

The Finish Line

Building a client control, I realized mid-way through this article, is a subject that can't be thoroughly covered in such a short space. However, I hope that  the article, combined with the code, will give you a pretty good picture of what it takes to build a custom control. I created the control for use in a little side-project that I'm working on, so once it is live I will post the link here so you can see the control in action. Hopefully over the next little while I'll also do some posts which build on this topic and explain the process of creating custom extenders and script controls (I will, however, split them up into a series, rather than trying to tackle a whole topic in a single post, which will hopefully do it a bit more justice than I did this topic).

The following image is a screenshot of the control in a partially implemented state. The selected image has a thick green border, and the alert was called from the 'imageSelected' event handler:

screen shot image



Related posts

Comments

February 25. 2008 22:11

Cool mang. So is the registerClass trigger the class's instantiation and fire off the constructor?

Adam Kahtava

February 26. 2008 09:56

When you define a class in that way, you're actually pretty much just creating an ordinary JavaScript class, which you can instantiate and access in the same way as any standard JS class, with some extras which make it play nicer in an ASP.NET setting.

The main purpose of registerClass is threefold. It:
1) registers the type name as well as a few other things in the constructor(allowing you to do type-checking on instantiated objects)
2) let's you specify a base-class (taking care of automatically resolving the inheritance relationship)
3) let's you specify an interface

If you run in debug mode, checks are performed to make sure that you've made proper references to your base class and interfaces.

joel

February 26. 2008 11:48

Interesting... Just looking through some vanilla JS Prototype definitions used for instantiating classes (well pseudo classes, since JS is classless). It makes sense that the registerClass method augments the object with extra ASP.NET specific methods. I can't wait to read ASP.NET AJAX in Action. Smile

Adam Kahtava

Add comment


 

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 23. 2008 18:22