Building Client-Side ASP.NET AJAX Controls

Building Client-Side ASP.NET AJAX Controls

JavaScript files can be written using several different techniques. A great majority of scripts on the Internet consist of multiple functions thrown into one or more files used to perform client-side functionality. While these types of unstructured scripts certainly get the job done, they don’t encapsulate related functions into containers for better code reuse and organization. Relatively few scripts rely on object-ori-ented techniques where JavaScript classes are defined using the JavaScript function and prototype design pattern.

Although JavaScript is not a true object-oriented language, it is capable of emulating several object-ori-ented characteristics that can be used to build new types, extend existing types, and in general create a reuseable client-side object library. Building custom AJAX controls requires understanding how to leverage encapsulation features built into the JavaScript language that can be used to create classes. It also requires an understanding of how the ASP.NET AJAX script library extends JavaScript’s default set of features.

Terminology between JavaScript and VB.NET or C# objects is surprisingly similar. Functionality can be grouped into classes, although JavaScript doesn’t technically support the concept of a class and doesn’t use the class keyword. However, JavaScript does support organizing related functionality into container objects much like VB.NET and C# do. Both JavaScript and .NET “classes” can expose fields, properties, and methods, although methods are typically referred to as “functions” in JavaScript, as they are defined using the function keyword. To keep things consistent, the remainder of this chapter will use standard .NET terminology and refer to actions a JavaScript object performs as methods rather than functions. It will also refer to variables as fields and characteristics/attributes of an object as properties.

Extending JavaScript

The ASP.NET AJAX script library extends JavaScript in several ways by allowing namespaces, classes, enumerations, and even interfaces to be defined and used on the client side. These extensions provide a familiar framework that resembles the server-side environment most ASP.NET developers are accustomed to working with and using. As a result, ASP.NET and JavaScript developers alike can create custom ASP.NET AJAX controls rather quickly once a few core concepts are understood.

One of the key classes involved in extending JavaScript’s existing functionality is the ASP.NET AJAX Type class. The Type class exposes several different methods that are useful when building custom controls as shown in the following table.

Open table as spreadsheet

Method

Description

callBaseMethod

Returns a reference to a base class’s method for the specified instance. Typically used in a control’s initialize() method and dispose() method when building custom client-side controls.

implementsInterface

Determines if a type implements a specific interface. Returns a Boolean value.

InheritsFrom

Determines if a type inherits from a specific base type. Returns a Boolean value.

initializeBase

Initializes the base type of a derived type. A call to the initializeBase() method is made in a control’s constructor.

registerClass

Registers a specific type with the ASP.NET AJAX client-side framework. A base class and one or more interfaces can optionally be defined when calling registerClass().

registerInterface

Registers an interface with the ASP.NET AJAX client-side framework.

registerNamespace

Registers a namespace with the ASP.NET AJAX client-side framework.

When building a custom AJAX control, you use the Type class within client-side code to define a names-pace, register a class, and possibly even an interface or enumeration with the ASP.NET AJAX framework. You’ll also use the Type class when a custom control needs to inherit functionality from an existing class in the ASP.NET AJAX script library. Every client-side control used in an ASP.NET AJAX application is considered to be a “type,” just as any VB.NET or C# class built using the .NET framework is considered to be a “type.” As a result, methods exposed by the ASP.NET AJAX Type class are available on all AJAX controls that you create.

In addition to the Type class, the ASP.NET AJAX script library provides several different classes that can serve as the foundation for a custom control depending upon the control’s intended purpose. Three main categories of custom controls can be built, including behaviors, visual controls, and nonvisual controls. In cases where you’d like to extend an existing DOM element’s behavior (to add additional style information to a button or textbox, for instance), the Sys.UI.Behavior class can be inherited from to create a custom behavior control. While controls that derive from Sys.UI.Behavior extend the overall behavior of a DOM element, they don’t modify the original purpose or intent of the control. When visual controls need to be created that modify the original intent of a DOM element and extend the element’s functionality, the Sys.UI.Control class can be used as the base class for the control. Nonvisual controls that don’t extend the behavior of a DOM element or extend its overall functionality (a timer for example) should derive from the Sys.Component base class.

The custom control used throughout this chapter derives from the Sys.UI.Control class, as it extends the functionality of a DOM element and adds significant functionality to it. Inheriting from Sys.UI.Control provides a solid starting foundation that can be built upon rather than writing the entire control from scratch. The control that we’ll develop here is named AlbumViewer and allows users to view one or more albums, and to search through them using asynchronous calls made to a .NET web service. Figure 11-1 shows the output generated by the AlbumViewer control.

Image from book
Figure 11-1

To create the AlbumViewer control, a prescribed set of steps can be followed. These steps are fairly standard and should be followed when building ASP.NET AJAX client-side controls in general.

  1. Register a namespace - Register the namespace for the control using the Type class’s registerNamespace() method.

  2. Create a constructor - Create the control’s constructor and pass in the DOM element that represents the container for the control as a parameter. Initialize the base class by calling the initializeBase() method and define any fields that will be used by the control to store data.

  3. Prototype properties and methods - Follow the prototype design pattern to define properties and methods exposed by the control. Properties and methods are defined using JSON notation.

  4. Override initialize() - Within the prototype section of the control, initialize the control by overriding the initialize() method. Within initialize you can dynamically assign values to fields defined in the constructor, create delegates, handle events, plus more.

  5. Override dispose() - Within the prototype section of the control, override the dispose() method to allow objects such as delegates used by the control to be cleaned up properly.

  6. Register the control and inherit from a base class - Register the control with the ASP.NET AJAX client-side object model by using the Type class’s registerClass() method. Pass the appropriate base class to derive from as a parameter to registerClass().

Take a look at how a custom AlbumViewer control can be created and used in an ASP.NET AJAX application.

Tip 

All of the code that follows for the AlbumViewer client-side control can be found in the Scripts/ AlbumViewerStandAlone.js file or in the AlbumViewer class library project available in the code download for this book on the WROX website. The individual code listings that follow represent only a portion of the overall control’s code and will not run on their own.

Registering a Control Namespace

When creating custom client-side controls, you’ll want to define a namespace for each control to resolve any possible naming conflicts that may arise in an application. The AlbumViewer control is placed in a Wrox.ASPAJAX.Samples namespace and registered by using the Type class’s registerNamespace() function. Namespaces can contain other nested namespaces, classes, interfaces, enumerations, and even delegates and should be defined at the beginning of a control’s code:

Type.registerNamespace("Wrox.ASPAJAX.Samples");

Note that no End Namespace keywords (as in VB.NET) or start and end brackets (as in C#) are used to define where an ASP.NET AJAX client-side namespace starts and ends. Instead, the namespace is referenced directly by a given control, as shown next.

Creating a Control Constructor

Once the namespace for a control is registered using the Type class, the control itself can be defined using the JavaScript "function" keyword. This is done by prefixing the control’s name with the names-pace that it should be placed in and defining an anonymous JavaScript function as shown in Listing 11-1.

Listing 11-1
Image from book

Type.registerNamespace("Wrox.ASPJAX.Samples");

Wrox.ASPAJAX.Samples.AlbumViewer = function(element) {
    // Initialize base class
    Wrox.ASPAJAX.Samples.AlbumViewer.initializeBase(this,[element]);
    this._AlbumDivOverCssClass = null;
    this._AlbumDivOutCssClass = null;
    this._AlbumTitleCssClass = null;
    this._AlbumArtistCssClass = null;
    this._SongTitleCssClass = null;
    this._Albums = [];
    this._ServicePath = null;
    this._ServiceMethod = null;
    this._StatusLabel = null;
    this._ShowSearchBox = false;
    this._albumContainer = null;

    //Delegate definitions
    this._overHandler = null;
    this._outHandler = null;
    this._clickHandler = null;
    this._btnClickHandler = null;
    this._propertyChanged = null;
}   
Image from book

The code in Listing 11-1 associates the namespace of Wrox.ASPAJAX.Samples with the AlbumViewer control and creates a constructor for the control by defining a new anonymous JavaScript function. Like VB.NET and C# constructors, JavaScript constructors can accept initialization parameters. The AlbumViewer constructor shown in Listing 11-1 accepts a DOM element object representing the parent container that the control will use. The AlbumViewer control uses a div DOM element as its parent container.

The first line of code inside of the AlbumViewer control’s constructor makes a call to the Type class’s initializeBase() method (recall that all controls are considered types and have access to the Type class’s methods) to initialize the control’s base class. Because the AlbumViewer control is a visual control that adds content to a div element, it inherits from the Sys.UI.Control base class provided by the ASP.NET AJAX script library. The call to initializeBase() instantiates the Sys.UI.Control class. You’ll learn more about how the AlbumViewer class inherits from Sys.UI.Control later in the chapter.

The initializeBase() method accepts two parameters. The first parameter represents the object instance to initialize the base class for (the AlbumViewer control, in this case). You’ll normally use the JavaScript this keyword for the parameter value. The second parameter accepts an array, which is why the [ and ] characters are used, that can be passed to the base constructor as it initializes. Listing 11-1 passes the container element of the AlbumViewer control as the parameter value. Because the control uses a div element as its container, the div is passed directly to the base class’s constructor.

Once the base class has been initialized, fields used by the AlbumViewer control are defined. Each field is prefixed with the this keyword, which represents the current class (the this keyword is much like the Me keyword VB.NET developers use and analogous to the this keyword in C#). Because the fields shown in Listing 11-1 are private, their names are prefixed by the underscore character. This character isn’t required and has no actual significance; it is a standard naming convention used in the ASP.NET AJAX script library for private members of a class. As a general rule, all object and array fields used by a control should be defined in the control’s constructor so that the field instances are unique to all control instances and not shared across instances.

The AlbumViewer class defines several fields in its constructor such as this._AlbumTitleCssClass and this._AlbumArtistCssClass that are used to store the CSS class applied to various child elements within the control. It also defines an array named this._Albums using the shortcut []JSON notation. this._Albums is used to store album information used by the control. Other fields such as this._ServicePath and this._ServiceMethod are used to store information about the web service that provides album, artist, and song information to the control. The handler fields shown (this._overHandler, this._outHandler, and so on) represent delegates used by the control that route data from events to event handlers.

Using the Prototype Design Pattern with JSON

JavaScript has long supported the ability to extend objects by using the prototype property. By using it, JavaScript objects can inherit from base types and add additional properties and methods. The ASP.NET AJAX Library uses the prototype property and a pattern referred to as the “prototype design pattern” to extend existing objects and create new ones. Listing 11-2 shows how the prototype property can be used to define a Person object and expose two property getters and setters, as well as a method.

Listing 11-2
Image from book

function Person() {
    this._FirstName = null;
    this._LastName = null;
}

Person.prototype.get_FirstName = function() {
      return this._FirstName;
}

Person.prototype.set_FirstName = function(value) {
      this._FirstName = value;
}

Person.prototype.get_LastName = function() {
      return this._LastName;
}

Person.prototype.set_LastName = function(value) {
      this._LastName = value;
}
Person.prototype.getFullName = function() {
      return this._FirstName + " " + this._LastName;
}
Image from book
Tip 

JavaScript doesn’t officially support the concept of property getters and setters as in VB.NET or C#. However, getters and setters can be emulated by writing the corresponding methods. For example, get_FirstName represents the get block while set_FirstName represents the set block. Both of these are officially methods in .NET terms and functions in JavaScript terms but are typically treated as properties, as they get and set the value of a field.

Usually, there is a one-to-one relationship between a prototype property and a function definition, as shown in Listing 11-2. If four methods need to be added to a custom control, the prototype property is used four times. An exception to this rule would be for objects that define their properties and methods directly in the constructor, which is not a practice followed by the ASP.NET AJAX script library. Properties and methods added directly into the constructor get duplicated each time a new object instance is created. However, by using the prototype property, properties and methods are shared across multiple object instances resulting in less bloat.

Although the code shown in Listing 11-2 is fairly standard, ASP.NET AJAX client-side controls use the prototype design pattern with JSON notation to define properties and methods rather than using the prototype property for each property or method. Listing 11-3 shows how the Person class shown in Listing 11-2 can be refactored using the prototype design pattern with JSON. As you look through the code, notice that all properties and methods are wrapped within a single prototype statement. Property and method names are following by a colon that separates the name from the actual functionality. Each property and method is then separated by a comma.

Listing 11-3
Image from book

function Person() {
    this._FirstName = null;
    this._LastName = null;
}

Person.prototype = {
    get_FirstName: function() {
       return this._FirstName;
    },

    set_FirstName: function(value) {
       this._FirstName = value;
    },

    get_LastName: function() {
       return this._LastName;
    },

    set_LastName: function(value) {
       this._LastName = value;
    },

    getFullName: function() {
       return this._FirstName + " " + this._LastName;
    }
}
Image from book

The functionality for a property or method can also be defined in a separate location rather than inline, as shown in Listing 11-4. The ASP.NET AJAX script library uses this technique quite frequently to define client-side classes.

Listing 11-4
Image from book

function Person() {
    this._FirstName = null;
    this._LastName = null;
}

Person.prototype = {
    get_FirstName: Person$get_FirstName,

    set_FirstName: Person$set_FirstName,

    get_LastName: Person$get_LastName,

    set_LastName: Person$set_LastName,

    getFullName: Person$getFullName
}

function Person$get_FirstName() {
    return this._FirstName;
}

function Person$set_FirstName(value) {
    this._FirstName = value;
}

function Person$get_LastName() {
    return this._LastName;
}

function Person$set_LastName(value) {
    this._LastName = value;
}

function Person$getFullName() {
    return this._FirstName + " " + this._LastName;
}
Image from book

Now that you’ve seen the fundamentals of building constructors and using the prototype design pattern, let’s take a look at some of the properties defined in the AlbumViewer control.

Defining Control Properties

Properties in client-side controls work in the same way as properties in server-side controls. However, the way properties are defined in client-side controls is quite different because JavaScript doesn’t support the .NET property syntax that formally defines get and set blocks. As an example, to define a getter and setter for a property named Albums that reads and writes from a private field named this._Albums, the following code could be written using the prototype design pattern and JSON:

get_Albums: function() {
    return this._Albums;
},

set_Albums: function(value) {
    this._Albums = value;
}

The get block normally found in VB.NET or C# classes is simulated by adding get to the front of the property name while the set block uses set_. Any keywords can be used, but get_ and set_ are used throughout the ASP.NET AJAX script library and are also used in the AlbumViewer control for the sake of consistency.

The AlbumViewer control defines several different properties used to control CSS styles, searching capabilities, and web service calls. Each property acts as a gateway to private fields defined in the control’s constructor shown in Listing 11-1. A list of properties exposed by the AlbumViewer control is shown in the following table. Keep in mind that although these properties are really JavaScript functions prefixed by get_ and set_ keywords, their only purpose is to get and set data, so we’ll refer to them as properties to stay consistent with VB.NET and C# property definitions. However, the class consumer needs to use a function syntax when calling the getter or setter, as there is no explicit property syntax support in JavaScript.

Open table as spreadsheet

Property Name

Description

Albums

Gets and sets an array of Albums objects.

AlbumDivOverCssClass

Gets and sets the CSS class name to use when a user hovers over an album in the control.

AlbumDivOutCssClass

Gets and sets the CSS class name to use when a user moves the mouse out of an album in the control.

AlbumTitleCssClass

Gets and sets the CSS class name to use for an individual album title.

AlbumArtistCssClass

Gets and sets the CSS class name to use for an album artist.

SongTitleCssClass

Gets and sets the CSS class name to use for album songs.

ShowSearchBox

Gets and sets a Boolean value that is used by the AlbumViewer control to determine if an album search textbox should be shown or not.

ServicePath

Gets and sets the path to the album web service. The service is used to retrieve album data dynamically.

ServiceMethod

Gets and sets the Web Method to call on the album web service.

Listing 11-5 shows how the properties in the previous table are defined and used within the AlbumViewer control’s prototype definition. Each property has an associated getter and setter used to access the associated field variable defined in the control’s constructor.

Listing 11-5
Image from book

Wrox.ASPAJAX.Samples.AlbumViewer.prototype = {
    get_Albums: function() {
        return this._Albums;
    },

    set_Albums: function(value) {
        this._Albums = value;
    },

    get_AlbumDivOverCssClass: function() {

    return this._AlbumDivOverCssClass;
},

set_AlbumDivOverCssClass: function(value) {
    this._AlbumDivOverCssClass = value;
},

get_AlbumDivOutCssClass: function() {
    return this._AlbumDivOutCssClass;
},

set_AlbumDivOutCssClass: function(value) {
    this._AlbumDivOutCssClass = value;
},

get_AlbumTitleCssClass: function() {
    return this._AlbumTitleCssClass;
},

set_AlbumTitleCssClass: function(value) {
    this._AlbumTitleCssClass = value;
},

get_AlbumArtistCssClass: function() {
    return this._AlbumArtistCssClass;
},

set_AlbumArtistCssClass: function(value) {
    this._AlbumArtistCssClass = value;
},

get_SongTitleCssClass: function() {
    return this._SongTitleCssClass;
},

set_SongTitleCssClass: function(value) {
    this._SongTitleCssClass = value;
},

get_ShowSearchBox: function() {
    return this._ShowSearchBox;
},

set_ShowSearchBox: function(value) {
    this._ShowSearchBox = value;
},

get_ServicePath : function() {
    return this._ServicePath;
},

set_ServicePath : function(value) {
    if (this._ServicePath != value) {
        this._ServicePath = value;
            this.raisePropertyChanged('ServicePath');
        }
    },

    get_ServiceMethod : function() {
        return this._ServiceMethod;
    },

    set_ServiceMethod : function(value) {
        if (this._ServiceMethod != value) {
            this._ServiceMethod = value;
            this.raisePropertyChanged('ServiceMethod');
        }
    }

    //More code follows for methods and event handlers
}
Image from book

The majority of the properties shown in Listing 11-5 simply read and write from private fields. While the ServicePath and ServiceMethod properties follow this same pattern, the setter for each of these properties adds a call to a method named raisePropertyChanged(). Calling this method is useful in situations where a control needs to be notified when a property changes so that it can act upon the change. This is a simple analog of the .NET event mechanism. The AlbumViewer control requires notification if the ServicePath or ServiceMethod properties are changed by the consumer of the control so that it can retrieve fresh album data.

You may wonder where the raisePropertyChanged() method is defined. Fortunately, it’s not something you have to code manually. The AlbumViewer control derives from the Sys.UI.Control class, which in turn derives from the Sys.Component class. The Sys.Component class defines the raisePropertyChanged() method as one of its members (for a complete listing of the Control class’s properties and methods refer to the ASP.NET AJAX documentation). When raisePropertyChanged() is called, an event is raised that can be handled by an event handler. The AlbumViewer control handles the property changed event as the ServicePath and ServiceMethod properties change and uses the two property values to call the appropriate web service to retrieve new album data and load it into the control. Additional information about handling events such as the one raised by raisePropertyChanged() is covered in the next section.

Initializing a Control and Handling Events

The Sys.Component class mentioned earlier defines an initialize() method that’s useful when a control is first initialized. When initialize() is called, a property named isInitialized is set to true by Sys.Component through a call to set_isInitialized(). This property is referenced by other classes in the ASP.NET AJAX script library such as Sys.UI.Behavior to determine if a control has been initialized or not.

Custom controls such as the AlbumViewer control can override the initialize() method and use it to initialize a base class, initialize properties or field values, define delegates, and hook events to event han-dlers. The AlbumViewer control defines the initialize() method within the prototype section of the code shown in Listing 11-6.

Listing 11-6
Image from book

Wrox.ASPAJAX.Samples.AlbumViewer.prototype = {

    //Property definitions go here

    initialize : function() {
        var e = this.get_element();
        e.style.className = this.get_AlbumDivOutCssClass();

        //Initialize Delegates
        this._overHandler = Function.createDelegate(this, this._onMouseOver);
        this._outHandler = Function.createDelegate(this, this._onMouseOut);
        this._clickHandler = Function.createDelegate(this, this._onClick);   
        this._btnClickHandler = Function.createDelegate(this,this._onButtonClick); 
        this._propertyChanged = 
          Function.createDelegate(this,this._onPropertyChanged);
        this.add_propertyChanged(this._propertyChanged);

        //Create search text box
        if (this._ShowSearchBox) {
            var lblText = document.createElement("SPAN");
            lblText.style.cssText = "font-family:arial;font-weight:bold;" +
              "font-size:8pt;";
            lblText.innerHTML = "Album Name: ";

            var txtAlbum = document.createElement("INPUT");
            txtAlbum.setAttribute("type","text");
            txtAlbum.setAttribute("id",e.id + "_txtAlbumSearch");
            txtAlbum.style.cssText = "width:150px;";

            var btnAlbumSearch = document.createElement("INPUT");
            btnAlbumSearch.setAttribute("type","button");
            btnAlbumSearch.id = e.id + "_btnAlbumSearch";
            btnAlbumSearch.value = "Search";
            $addHandler(btnAlbumSearch,"click",this._btnClickHandler);

            e.appendChild(lblText);
            e.appendChild(txtAlbum);
            e.appendChild(btnAlbumSearch);
            e.appendChild(document.createElement("P"));        
        }

        //Create div that will hold albums
        this._albumContainer = document.createElement("DIV");
        this._albumContainer.id = e.id + "_AlbumContainer";      
        e.appendChild(this._albumContainer); 

        Wrox.ASPAJAX.Samples.AlbumViewer.callBaseMethod(this, 'initialize'); 

        //Bind data if albums have been assigned
        if (this._ServicePath && this._ServiceMethod) {                    
            // Call the web service
            this._invokeWebService(null);
        } else if (this._Albums != null) {
            this.set_data(this._Albums);
        }
    },

    //Additional methods go here
}
Image from book

When the AlbumViewer control’s initialize() method is called, several tasks are performed. First, a reference to the DOM element used by the control is made by calling get_element(). The element property is inherited from Sys.UI.Control and is referenced any time the AlbumViewer control needs to access the DOM element container. Once the DOM element is accessed, the CSS class to apply to the element is assigned by reading from the AlbumDivOutCssClass property. This class defines the overall style applied to the control’s parent container.

Next, several delegates are created that point to various event handler methods. Delegates in ASP.NET AJAX client-side controls perform the same overall function as delegates used in VB.NET or C#: They pass data raised by an event to an event handler. Client-side delegates are created by calling the Function class’s createDelegate() method and passing the object context that the delegate applies to, as well as the name of the event handler method that the delegate should direct data to. The delegates created in the AlbumViewer control are used to route data when mouseover, mouseout, click, and propertychanged events are raised.

The delegate instances returned from calling Function.createDelegate() are assigned to private fields defined in the control’s constructor because several of them are reused throughout the control and need to be globally accessible. For example, the following code creates a delegate that is responsible for routing mouseover event data to an event handler named _onMouseOver. A reference to the delegate is stored in the this._overHandler field.

this._overHandler = Function.createDelegate(this, this._onMouseOver);

Property changed events raised by the AlbumViewer control are routed to an event handler by calling the add_propertyChanged() method. This method routes data raised when the raisePropertyChanged() method discussed earlier is called to an event handler so that the change can be handled by the control. It accepts the name of the event handler method to call or a delegate instance that points to the method.

Once a delegate is created, it can be used to hook an event directly to an event handler using the ASP.NET AJAX $addHandler method. VB.NET developers will be quite comfortable using the $addHandler() method, as it’s similar in functionality to the VB.NET AddHandler keyword. $addHandler() is a shortcut to the Sys.UI.DomEvent class’s addHandler() method. It accepts several parameters, including the object that raises the event, the name of the event that will be raised, and the delegate that should be used to route event data to an event handler. Listing 11-6 contains an example of using the $addHandler method to hook up a button control’s click event to a delegate named this._btnClickHandler:

//Define Delegate
this._btnClickHandler = 
    Function.createDelegate(this,this._onButtonClick);

//Hook button's click event
$addHandler(btnAlbumSearch,"click",this._btnClickHandler);

In addition to $addHandler(), the ASP.NET AJAX script library provides an $addHandlers() method that allows multiple event handlers to be defined for a DOM element with a single statement. For example, if you need to attach the mouseover, mouseout, and click events of a DOM element named albumDiv to event handlers, you could use $addHandlers() in the following way:

$addHandlers(albumDiv, 
   {"mouseover" : this._overHandler,
    "mouseout" : this._outHandler,
    "click" : this._clickHandler },
  this);

The second parameter passed to $addHandlers is a JSON object containing the different event names and related delegates that should be attached to the DOM element.

The initialize() method shown in Listing 11-6 is also used to add several controls into the AlbumViewer control’s parent container element. Because album data is being displayed, an end user may want to search for one or more albums based upon title. Searching is controlled by data stored in the this._ShowTextBox field of the control. When the field value is true, a span tag, textbox, and but-ton are added to the AlbumViewer control’s div container. End users can then use the controls to perform a search. Adding controls is accomplished by calling document.createElement() and passing in the name of the element object that should be created. Once element objects are created, they’re added to the appropriate parent container by calling its appendChild() method.

The final two tasks performed by the AlbumViewer control’s initialize() method are calling the base Sys.UI.Control class’s initialize() method and binding album data to the control. The Type class’s callBaseMethod() method calls the initialize() method of the base class. This method accepts the current object instance as well as the name of the method to call on the base class. Although not used by the AlbumViewer control, callBaseMethod() returns a reference to the base class’s ini tialize() method.

Now that you’ve been exposed to the initialize() method, let’s move on to a few other methods used by the AlbumViewer control to bind data, call web services, and perform additional functionality.

Defining Control Methods

The AlbumViewer control provides several ways for an end user to interact with the control to view albums, artists, and songs. Users can search for albums using all or part of the album name, highlight albums as they move their mouse, and click albums to view more data about an album. Each of these interactions requires event handlers to be written to handle the performed action. In addition to supporting user interactions, the AlbumViewer supports calling a web service to retrieve album data as well as data binding. All of this functionality is handled by methods defined within the AlbumViewer control.

Methods used by the control are shown in the following table. Methods that start with an underscore are considered private, although JavaScript doesn’t truly support access modifiers such as public or private.

Open table as spreadsheet

Method Name

Description

initialize

Initializes the control as well as the base class.

dispose

Cleans up resources used by the control such as event handlers.

set_data

Binds album data supplied by the client or by a web service to the control. Performs the bulk of the control’s functionality. This method is similar in purpose to the DataBind() method found on ASP.NET server controls.

_getAlbumNode

Retrieves a specific album object within the AlbumViewer control.

_invokeWebService

Calls a web service based upon values supplied by the ServicePath and ServiceMethod properties.

_onButtonClick

Handles the search button’s click event.

_onClick

Handles the click event for album divs in the control.

_onMouseOut

Handles the mouseout event for album divs in the control.

_onMouseOver

Handles the mouseover event for album divs in the control.

_onMethodComplete

Processes data returned from a web service call and binds the data by calling set_data().

_onMethodError

Handles errors returned from calls made to a web service.

_onPropertyChanged

Handles property changed events that occur as the ServicePath and ServiceMethod property values change.

_setStyle

Used to apply a CSS style to an album div as the user interacts with the AlbumViewer control.

The AlbumViewer control’s set_data() method performs the bulk of the work done by the control. It accepts a single parameter that contains the Album objects to bind to the control and display to the end user. Although Album objects aren’t the focus of this chapter, it’s important to note that many of the same steps followed when building a custom control are also applied to building custom client-side classes. Listing 11-7 shows a representation of the Album and Song classes consumed and bound by the set_data() method. These classes are used when a client script creates objects and passes them to the control’s set_data() method. When the AlbumViewer control calls a web service, the JSON Album objects returned from the service are used instead.

Tip 

The fields defined in the Song and Album class constructors aren’t prefixed with an underscore character (to simulate a private field) so that the objects are more in line with JSON objects returned from web service calls that have properties defined such as Title, Artist, ImageUrl, and Songs for the Album class. By matching up the Album class shown in Listing 11-7 with the objects exposed by the web service, the AlbumViewer control can work in a flexible manner with both types of objects.

Listing 11-7
Image from book

Type.registerNamespace("Wrox.ASPAJAX.Samples");

//#  Song Object #
Wrox.ASPAJAX.Samples.Song = function(trackNumber,title)
{
    // Initialize as a base class.
    Wrox.ASPAJAX.Samples.Song.initializeBase(this);
    this.Title = title;
    this.TrackNumber = trackNumber;
}  

//Define Album properties
Wrox.ASPAJAX.Samples.Song.prototype = {
    initialize: function() {
       Wrox.ASPAJAX.Samples.Song.callBaseMethod(this,"initialize");
    },

    get_Title: function() {
        /// <value type="String"></value>
        if (arguments.length !== 0) throw Error.parameterCount();
        return this.Title;
    },

    set_Title: function(value) {
        var e = Function._validateParams(arguments, 
          [{name: "value", type: String}]);
        if (e) throw e;

        this.Title = value;
    },

    get_TrackNumber: function() {
        /// <value type="String"></value>
        if (arguments.length !== 0) throw Error.parameterCount();
        return this.TrackNumber;
    },

    set_TrackNumber: function(value) {
        var e = Function._validateParams(arguments, 
          [{name: "value", type: String}]);
        if (e) throw e;
        this.TrackNumber = value;
    },

    dispose: function() {
        this.Title = null;
        this.TrackNumber = null;
        Wrox.ASPAJAX.Samples.Song.callBaseMethod(this, "dispose");
    }
}

Wrox.ASPAJAX.Samples.Song.registerClass("Wrox.ASPAJAX.Samples.Song", 
  Sys.Component, Sys.IDisposable);


//#  Album Object #
Wrox.ASPAJAX.Samples.Album = function()
{
    // Initialize as a base class.
    Wrox.ASPAJAX.Samples.Album.initializeBase(this);
    this.Title;
    this.Artist;
    this.ImageUrl;
    this.Songs = [];
}  

//Define Album properties
Wrox.ASPAJAX.Samples.Album.prototype = {
    initialize: function() {
       Wrox.ASPAJAX.Samples.Album.callBaseMethod(this,"initialize");
    },

    get_Title: function() {
        return this.Title;
    },

    set_Title: function(value) {
        /// <value type="String"></value>
        this.Title = value;
    },

    get_ImageUrl: function() {
        return this.ImageUrl;
    },

    set_ImageUrl: function(value) {
        /// <value type="String"></value>
        this.ImageUrl = value;
    },

    get_Artist: function() {
        return this.Artist;
    },

    set_Artist: function(value) {
        /// <value type="String"></value>
        this.Artist = value;
    },

    addSong: function(song)
    {
        /// <value type="Wrox.ASPAJAX.Samples.Song"></value>
        if (Object.getTypeName(song) != "Wrox.ASPAJAX.Samples.Song") 
        {
            var e = Error.argumentType("song", Object.getType(song),
              Wrox.ASPAJAX.Samples.Song,"Wrox.ASPAJAX.Samples.Song required!");
            e.popStackFrame();
            throw e;
        }
        Array.add(this.Songs,song);
    },

    removeSong: function(song)
    {
        /// <value type="Wrox.ASPAJAX.Samples.Song"></value>
        Array.remove(this.Songs,song);
    },

    get_Songs: function()
    {
        return this.Songs;
    },

    rateSong: function(song,rating)
    {
        throw Error.notImplemented("rateSong() has not yet been implemented");  
    },

    dispose: function() {
        this.Title = null;
        this.Artist = null;
        this.Songs = null;
        this.ImageUrl = null;
        Wrox.ASPAJAX.Samples.Album.callBaseMethod(this, "dispose");
    }
}

Wrox.ASPAJAX.Samples.Album.registerClass("Wrox.ASPAJAX.Samples.Album", 
  Sys.Component, Sys.IDisposable);

//Added to satisfy new notifyScriptLoaded() requirement
if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
Image from book

Looking through the code in Listing 11-7, you’ll see that the Album class has several properties, including Title, Artist, Songs, and ImageUrl. These properties are used by the AlbumViewer control to create new div element objects that are used to display album data. The AlbumViewer control’s set_data() method is responsible for iterating through Album and Song objects passed to the control by a page or by calls made to a web service and binding those objects to the control. It’s also responsible for clearing existing albums that are displayed when a user performs a new search and assigning styles to newly created DOM element objects.

The Album object binding process creates new div element objects as well as several others using docu- ment.createElement() and adds the div objects to the AlbumViewer control’s parent div container. set_data() also handles hooking up mouseout, mouseover, and click events for each album div object created to event handlers by using the $addHandlers() method discussed earlier. Listing 11-8 shows the complete code for the set_data() method.

Listing 11-8
Image from book