11.1. 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-oriented 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-oriented characteristics that can be used to build new types, extend existing types, and in general create a reusable 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 uses standard .NET terminology and refers 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.

11.1.1. 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.

MethodDescription
callBaseMethodReturns 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.
implementsInterfaceDetermines if a type implements a specific interface. Returns a Boolean value.
inheritsFromDetermines if a type inherits from a specific base type. Returns a Boolean value.
initializeBaseInitializes the base type of a derived type. A call to the initializeBase() method is made in a control's constructor.
registerClassRegisters 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().
registerInterfaceRegisters an interface with the ASP.NET AJAX client-side framework.
registerNamespaceRegisters 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 namespace, 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, so you don't have to write the entire control from scratch. The control that you 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.

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().

Figure 11-1. Figure 11-1

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

NOTE

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 Web site. The individual code listings that follow represent only a portion of the overall control's code and will not run on their own.

11.1.2. 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 method. 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.

11.1.3. 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 namespace that it should be placed in and defining an anonymous JavaScript function as shown in Listing 11-1.

Example 11-1. Defining a Client-Side Class's Namespace and Constructor
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;
}

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 square brackets, [ ], are used. The array 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 that 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.

11.1.4. 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.

Example 11-2. Defining properties using the prototype design pattern
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;
}

NOTE

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 are 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 followed by a colon that separates the name from the actual functionality. Each property and method is then separated by a comma.

Example 11-3. Using the prototype design pattern with JSON syntax
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;
    }
}

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.

Example 11-4. Defining properties in a separate location
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;
}

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.

11.1.5. 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. For example, to define a getter and setter for a property named Albums that reads and writes to and 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.

Property NameDescription
AlbumsGets and sets an array of Albums objects.
AlbumDivOverCssClassGets and sets the CSS class name to use when a user hovers over an album in the control.
AlbumDivOutCssClassGets and sets the CSS class name to use when a user moves the mouse out of an album in the control.
AlbumTitleCssClassGets and sets the CSS class name to use for an individual album title.
AlbumArtistCssClassGets and sets the CSS class name to use for an album artist.
SongTitleCssClassGets and sets the CSS class name to use for album songs.
ShowSearchBoxGets and sets a Boolean value that is used by the AlbumViewer control to determine if an album search textbox should be shown or not.
ServicePathGets and sets the path to the album Web service. The service is used to retrieve album data dynamically.
ServiceMethodGets and sets the WebMethod to call on the album Web Serviceservice.

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.

Example 11-5. Defining AlbumViewer properties using the prototype design pattern
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
}

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 in the MSDN Library). 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.

11.1.6. 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 handlers. The AlbumViewer control defines the initialize method within the prototype section of the code shown in Listing 11-6.

Example 11-6. Creating an initialize method using the prototype design pattern
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 textbox
        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
}

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 to an event handler using the delegate
$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 button 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 initialize 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.

11.1.7. 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.

Method NameDescription
initializeInitializes the control as well as the base class.
disposeCleans up resources used by the control such as event handlers.
set_dataBinds 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.
_getAlbumNodeRetrieves a specific album object within the AlbumViewer control.
_invokeWebServiceCalls a Web service based upon values supplied by the ServicePath and ServiceMethod properties.
_onButtonClickHandles the search button's click event.
_onClickHandles the click event for album divs in the control.
_onMouseOutHandles the mouseout event for album divs in the control.
_onMouseOverHandles the mouseover event for album divs in the control.
_onMethodCompleteProcesses data returned from a Web service call and binds the data by calling set_data.
_onMethodErrorHandles errors returned from calls made to a Web service.
_onPropertyChangedHandles property changed events that occur as the ServicePath and ServiceMethod property values change.
_setStyleUsed 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-side 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.

NOTE

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.

Example 11-7. Defining album and song classes using JavaScript
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();

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 document.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.

Example 11-8. Defining the set_data method
set_data: function(albums) {
    var e = this.get_element();
    //Clear any albums already showing
    while (this._albumContainer.childNodes.length > 0) {
        var child = this._albumContainer.childNodes[0];
        this._albumContainer.removeChild(child);
    }

    //Handle case where no albums are available to bind
    if (albums == null) {
        var noRecords = document.createElement("SPAN");
        noRecords.style.cssText = "font-weight:bold;";
        noRecords.innerHTML = "No albums found.";
        this._albumContainer.appendChild(noRecords);
        return;
    }

    //Loop through albums
    for (var i=0; i<albums.length; i++) {
        var album = albums[i];

        //Create Album Div
        var id = this.get_element().id + "_Album" + i.toString();
        var albumDiv = document.createElement("DIV");
        albumDiv.id = id;
        albumDiv.style.clear = "both";
        albumDiv.className = this._AlbumDivOutCssClass;
        this._albumContainer.appendChild(albumDiv);

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

        //Create album title
        var albumTitle = document.createElement("SPAN");
        albumTitle.className = this._AlbumTitleCssClass;
        albumTitle.innerHTML = album.Title;
        albumDiv.appendChild(albumTitle);

         //Create album artist
        var albumArtist = document.createElement("SPAN");
        albumArtist.className = this._AlbumArtistCssClass;

albumArtist.innerHTML = "&nbsp;-&nbsp;" + album.Artist;
        albumDiv.appendChild(albumArtist);

        //Create child content div
        childrenDiv = document.createElement("DIV");
        childrenDiv.id = id + "_Children";
        childrenDiv.style.display = "none";

        albumDiv.appendChild(childrenDiv);

        //Create Image div
        var albumPicDiv = document.createElement("DIV");
        var img = document.createElement("IMG");
        img.src = album.ImageUrl;
        albumPicDiv.style.paddingTop = "5px";
        albumPicDiv.appendChild(img);
        childrenDiv.appendChild(albumPicDiv);

        //Create songs div
        var albumSongsDiv = document.createElement("DIV");
        var ul = document.createElement("UL");
        albumSongsDiv.appendChild(ul);

        //Loop through songs
        var songs = album.Songs;
        for (var j=0;j<songs.length;j++) {
            var li = document.createElement("LI");
            li.className = this._SongTitleCssClass;
            li.innerHTML = songs[j].Title;
            ul.appendChild(li);
        }
        childrenDiv.appendChild(albumSongsDiv);
    }
}

Listing 11-9 shows how the AlbumViewer control's private methods are used to handle events, locate album div nodes, assign CSS styles, and call Web services. Each method is prefixed with an underscore character to convey that it's private. Several of the methods such as _onMouseOver and _onMouseOut handle style changes to album objects as the end user moves his or her mouse or clicks. Others perform more complex functionality such as calling Web services and processing results or errors.

Example 11-9. Defining Album class private methods
_onMouseOver : function(evt) {
     this._setStyle(evt,this._AlbumDivOverCssClass);
 },

 _onMouseOut : function(evt) {
    this._setStyle(evt,this._AlbumDivOutCssClass);
 },

_setStyle: function(evt,className) {
    evt.stopPropagation();
    this._getAlbumNode(evt.target).className = className;
    return true;
 },

 _getAlbumNode: function(node) {
    var baseID = this.get_element().id + "_Album";
    var currElem = node;

    while (!(currElem.id.startsWith(baseID) && !currElem.id.endsWith
                  ("_Children")))
    {
        if (currElem.parentNode) {
            currElem = currElem.parentNode;
        } else {
            break;
        }
    }
    return currElem;
 },

 _onClick : function(evt) {
    var parent = this._getAlbumNode(evt.target);
    var child = $get(parent.id + "_Children");
    if (child.style.display == "block") {
        child.style.display = "none";
    } else {
        child.style.display = "block";
    }
 },

 _onButtonClick : function(evt) {
    var searchText = $get(this.get_element().id + "_txtAlbumSearch").value;
    this._invokeWebService(searchText);
 },

_onMethodComplete : function(result, userContext, methodName) {
    //Remove status label
    this._removeStatusLabel();
    // Bind returned data
    this.set_data(result);
},

_onMethodError : function(webServiceError, userContext, methodName) {
    // Call failed
    this._removeStatusLabel();
    if (webServiceError.get_timedOut()) {
        alert("Web Service call timed out.");
    } else {
        alert("Error calling Web Service: " +
          webServiceError.get_statusCode() + " " + webServiceError.get_message());
    }

},

_removeStatusLabel: function() {
    if (this._StatusLabel) {
        this.get_element().removeChild(this._StatusLabel);
        this._StatusLabel = null;
    }
},

_invokeWebService : function(searchText) {

    //Add status label in case operation takes a while
    this._StatusLabel = document.createElement("DIV");
    this._StatusLabel.style.cssText = "font-weight:bold;color:Navy;";
    this._StatusLabel.innerHTML = "Calling Albums Web Service....";
    this.get_element().appendChild(this._StatusLabel);
    Sys.Net.WebServiceProxy.invoke(this._ServicePath, this._ServiceMethod, false,
        { prefixText:searchText, count:10 },
        Function.createDelegate(this, this._onMethodComplete),
        Function.createDelegate(this, this._onMethodError));
},

_onPropertyChanged : function(sender,args) {
    var propname = args.get_propertyName();
    if (propname == "ServicePath" || propname === "ServiceMethod") {
        var searchText = null;
        if (this._ShowSearchBox) {
            $get(this.get_element().id + "_txtAlbumSearch").value;
        }
        this._invokeWebService(searchText);
    }
}

The _onPropertyChanged method, shown in Listing 11-9, handles propertychanged events raised by calling the raisePropertyChanged method discussed in the "Defining Properties" section of this chapter. Recall that _onPropertyChanged is defined as the event handler by calling the add_propertyChanged method in the AlbumViewer control's initialization phase shown in Listing 11-6.

When the ServicePath or ServiceMethod properties change, _onPropertyChanged is automatically called and used to invoke an album data Web service. This allows fresh data to be loaded into the control in cases where the consumer of the control wants to dynamically change information about the Web service. How do you actually call a Web service in a custom control when the location of the service hasn't been assigned to the ScriptManager's Services property, though? Let's take a closer look.

11.1.7.1. Calling Web Services with a Custom Control

The AlbumViewer control's set_data method can receive data from a client-side script or from a .NET Web service. Calling a Web service can be a tricky process, as different browsers that have different XmlHttp objects must be taken into account. Fortunately, the ASP.NET AJAX script library has cross-browser functionality built in that makes it straightforward to make Web service calls and retrieve data without worrying about the user agent.

The _invokeWebService method shown in Listing 11-9 calls an album viewer service for the AlbumViewer control (the album Web service is available in the download code for this chapter). The method accepts a single parameter that represents the album title the end user would like to search on. When called, _invokeWebService adds a div tag to the AlbumViewer control to notify the end user that the search has begun. Because the call is made asynchronously, this provides a nice visual clue for the user so that he or she is aware that the request is being processed. The script library's Sys.Net.WebServiceProxy class is then used to make the Web service request by calling its invoke method:

Sys.Net.WebServiceProxy.invoke(this._ServicePath, this._ServiceMethod,
  false, { prefixText:searchText, count:10 },
  Function.createDelegate(this, this._onMethodComplete),
  Function.createDelegate(this, this._onMethodError));

The invoke method works across all major browsers and creates a JavaScript proxy, calls the Web service, and processes the results. Several parameters can be passed to invoke, including the following:

  • The path to the Web service and the name of the WebMethod to call

  • Whether the call should be made using HttpGet

  • The parameter data to be passed to the service (parameter data is passed using JSON)

  • A delegate that points to the callback method that handles the response that is received (_onMethodComplete)

  • A delegate that points to an error handler callback method (_onMethodError)

Once the Web service is called and a response is returned, _onMethodComplete is used to handle the response data. This method accepts one or more Album objects returned from the Web service, any context data associated with the call, and the name of the WebMethod that was called:

_onMethodComplete : function(result, userContext, methodName) {
    //Remove status label
    this._removeStatusLabel();
    // Bind returned data
    this.set_data(result);
}

The album data returned from the Web service is bound to the AlbumViewer control by calling the set_data method discussed earlier.

Web service calls can fail when the service is unavailable or when invalid data is passed to or from the service. Fortunately, the Sys.Net.WebServiceProxy.invoke method used to call the service allows a callback method to be specified that handles errors. The _onMethodError method handles any errors returned by the Web service or errors generated if an invalid service path or WebMethod name was used.

_onMethodError : function(webServiceError, userContext, methodName) {
    // Call failed
    this._removeStatusLabel();
    if (webServiceError.get_timedOut()) {
        alert("Web Service call timed out.");
    } else {
        alert("Error calling Web Service: " +

webServiceError.get_statusCode() + " " +
          webServiceError.get_message());
    }
}

If an error occurs and the callback method is invoked, a Sys.Net.WebServiceError object is passed as the first parameter, along with call context data and the name of the WebMethod that was initially called. Because the AlbumViewer control can't display album data when a Web service error occurs, it displays an alert to the user that contains the status code of the error as well as the error message.

Before displaying error data, a check is made to ensure that the call didn't time out by calling get_timedOut. If the call returns true, the service was unavailable. The following table lists the properties of the Sys.Net.WebServiceError class.

Property NameDescription
exceptionGets the type of exception that occurred. A string is returned.
messageGets the error message associated with the current error.
statusCodeGets the status code of the current HTTP response.
stackTraceGets the stack trace returned by the server.
timedOutGets a Boolean value that indicates if the Web service call timed out.

11.1.8. Disposing of Control Resources

Because JavaScript is a garbage-collected language, many complexities associated with cleaning up fields, arrays, and other objects are greatly simplified and typically don't require any code to be written by a developer. However, it's important to note that specific types of resources must be explicitly cleaned up to avoid memory leaks caused by circular references. DOM event handlers used in a control, as well as processes (such as timers) that may be used internally, should be explicitly cleaned up in your custom controls.

By deriving from base classes such as Sys.Component and Sys.UI.Control, access to a method named dispose is immediately available in custom controls that can be overridden. The AlbumViewer control uses the dispose method shown in Listing 11-10 to cleanup event handlers and call its base class's dispose method.

Example 11-10. Defining a dispose Method
dispose: function() {
    var e = this.get_element();
    this.remove_propertyChanged(this._propertyChanged);
    if (this._ShowSearchBox) $removeHandler($get(e.id +
      "_btnAlbumSearch"),"click",this._btnClickHandler);
    //Remove event handlers
    for (var i=0;i<e.childNodes.length;i++) {
        var albumDiv = e.childNodes[i];
        $clearHandlers(albumDiv);
    }

this._overHandler = null;
    this._outHandler = null;
    this._clickHandler = null;
    this._btnClickHandler = null;
    this._propertyChanged = null;
    Wrox.ASPAJAX.Samples.AlbumViewer.callBaseMethod(this, "dispose");
}

When all event handlers attached to a given DOM element need to be removed, you can call the ASP.NET AJAX script library's $clearHandlers method. When a specific event handler needs to be removed the $removeHandler method can be called. Property changed event handlers can be removed by calling the remove_propertyChanged method.

After dispose is called and event handlers are cleaned up, a call is made to the base Sys.UI.Control class's dispose method via callBaseMethod, which raises a disposing event and unregisters the control from the application. Removal occurs as a result of the following calls made in the Sys.Component class's dispose method:

Sys.Application.unregisterDisposableObject(this);
Sys.Application.removeComponent(this);

While you don't have to override the dispose() method on every client-side object or control used in an ASP.NET AJAX application, it's important to override it in cases where event handlers or processes are used by a control and need to be cleaned up to prevent memory issues.

11.1.9. Registering a Custom Control Class

Now that you've seen how to create a custom control's constructor, properties, and methods, let's examine how the control can inherit from a base class and register itself with the ASP.NET AJAX framework. Although you can't use the VB.NET Inherits keyword or the C# colon character to handle inheritance, you can call the registerClass method available to all ASP.NET AJAX types through the Type class.

The registerClass method accepts three parameters. The first parameter is a string containing the fully qualified name of the class to register with the framework. The parameter value should include the namespace and class name. The second parameter represents the base class that the control should inherit from. The third parameter specifies one or more interfaces that the control implements. This parameter is optional. Following is an example of using registerClass to register the AlbumViewer control. Notice that the control derives from Sys.UI.Control.

Wrox.ASPAJAX.Samples.AlbumViewer.registerClass(
  "Wrox.ASPAJAX.Samples.AlbumViewer", Sys.UI.Control);

As with any client-side script used by the ASP.NET AJAX framework, the AlbumViewer control adds the following line of code at the bottom the script file to notify the framework when the entire script has been loaded and is ready to be used:

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

11.1.10. Creating a Client-Side Control Instance

Once a custom client-side control has been written, it needs to be instantiated by an application. To do this, the control's script must be referenced by the page either by using the standard script tag or by declaring it in the ScriptManager's Scripts property. Once the control script is available for use in a page, the control can be instantiated by using the ASP.NET AJAX script library's $create method. $create is an alias for the Sys.Component class's create method.

The $create method accepts several different parameters as shown in the following table.

ParameterDescription
typeThe type of component or control that should be created.
propertiesA JSON object containing properties and values that should be passed to the control. This parameter is optional.
eventsA JSON object containing events and associated handlers defined through delegates. This parameter is optional.
referencesA JSON object containing references to other control properties.
elementThe DOM element that the control uses as its parent container. Output rendered by the control will be placed in this element. This parameter is optional.

Listing 11-11 (AlbumViewerClientBinding.aspx in the sample code) shows how the $create method can be used to instantiate a new instance of the AlbumViewer control and hook it to a div container. When the pageLoad event handler is called, two new Album objects are created and added to an array. The $create method is then used to instantiate the AlbumViewer control and attach it to a div element named divAlbums. Initial CSS property values used by the control, as well as the Album objects to bind, are passed into $create using a JSON object. Because the control's initialize method calls set_data, the two albums are iterated through and added into the parent container automatically as the control initializes and loads.

Example 11-11. AlbumViewerClientBinding.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Album Viewer: Client-side Control</title>
    <link href="Style/Styles.css" rel="stylesheet" type="text/css" />

    <script type="text/javascript">
        function pageLoad()
        {
            //Create albums
            var album = new Wrox.ASPAJAX.Samples.Album();

album.set_Artist("Incubus");
            album.set_Title("Light Grenades");
            album.set_ImageUrl("Images/Incubus_Light_Grenades.jpg");
            album.addSong(new Wrox.ASPAJAX.Samples.Song(1,"QuickSand"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(2,
             "A Kiss to Send Us Off"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(3,"Dig"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(4,"Anna Molly"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(5,"Love Hurts"));

            album.addSong(new Wrox.ASPAJAX.Samples.Song(6,"Light Grenades"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(7,"Earth to Bella Pt 1"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(8,"Oil and Water"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(9,"Diamonds and Coal"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(10,"Rogues"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(11,"Paper Shoes"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(12,"Earth to Bella Pt 1"));
            album.addSong(new Wrox.ASPAJAX.Samples.Song(13,"Pendulous Threads"));

            var album2 = new Wrox.ASPAJAX.Samples.Album();
            album2.set_Artist("The Killers");
            album2.set_Title("Sam's Town");
            album2.set_ImageUrl("Images/The_Killers_Sams_Town.jpg");
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(1,"Sam's Town"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(2,"Enterlude"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(3,"When You Were Young"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(4,"Bling"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(5,"For Reasons Unknown"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(6,"Read My Mind"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(7,"Uncle Johnny"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(8,"Bones"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(9,"My List"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(10,"The River is Wild"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(11,
             "Why Do I Keep Counting"));
            album2.addSong(new Wrox.ASPAJAX.Samples.Song(12,"Exitlude"));

            var albums = [];
            Array.add(albums,album);
            Array.add(albums,album2);

            //Create AlbumViewer object and pass in albums
            $create(Wrox.ASPAJAX.Samples.AlbumViewer,
                {Albums: albums,AlbumDivOverCssClass: "AlbumDivOver",
                 AlbumDivOutCssClass: "AlbumDivOut",
                 AlbumTitleCssClass: "AlbumTitle",
                 AlbumArtistCssClass: "AlbumArtist",
                 SongTitleCssClass: "SongTitle"},
                null, //Events and handlers
                null, //References to other control properties
                $get("divAlbums"));  //parent container DOM element

}
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager id="smgr" runat="Server">
        <Scripts>
            <asp:ScriptReference Path="~/Scripts/Album.js" />
            <asp:ScriptReference Path="~/Scripts/AlbumViewerStandAlone.js" />

        </Scripts>
    </asp:ScriptManager>
    <h2>Album Viewer</h2>
    <div id="divAlbums" style="width:500px;">
    </div>
    </form>
</body>
</html>

A Web service can also automatically be called when an AlbumViewer control is created to retrieve album data, rather than creating the album objects in the page as in Listing 11-11. This is done by passing the path to the Web service along with the name of the WebMethod to call in the JSON object that contains properties and values. An example of using the $create method to pass ServicePath and ServiceMethod property values to the control is shown in Listing 11-12 (AlbumViewerWSBinding.aspx in the sample code). The ShowSearchBox property is also set to a value of true so that the album search textbox and button controls are created and shown. The output generated by the control is shown in Figure 11-1.

Example 11-12. AlbumViewerWSBinding.aspx
function pageLoad()
{
     $create(Wrox.ASPAJAX.Samples.AlbumViewer,
        {ShowSearchBox: true,AlbumDivOverCssClass: "AlbumDivOver",
         AlbumDivOutCssClass: "AlbumDivOut",AlbumTitleCssClass: "AlbumTitle",
         AlbumArtistCssClass: "AlbumArtist",SongTitleCssClass: "SongTitle",
         ServicePath: "AlbumService.asmx",ServiceMethod: "GetAlbums"},
        null,
        null,
        $get("divAlbums"));
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset