Chapter 8. Building AJAX Controls

After completing this chapter, you will

  • Understand the control architecture of the Microsoft AJAX Library.

  • Understand controls, events, and event-based control development.

  • Understand and utilize AJAX behaviors.

  • Utilize AJAX controls in your AJAX applications.

In Chapter 7, I introduced the Microsoft AJAX Library component implementation, in which components serviced by Sys.Application can be created and maintained by using event and property bindings through the Sys.Component.Create ($create) method. I also described how the component infrastructure supports event-driven programming and object disposal.

To provide component functionality that is tied to DOM elements, the AJAX library provides the base classes Sys.UI.Behavior and Sys.UI.Control, both of which inherit from Sys.Component. Behaviors are used to extend DOM elements with user interface functionality. Behaviors add functionality rather than replace it. Multiple behaviors can be added to a single DOM element, but the DOM element can be used by only a single control instance. A control can be used to replace a DOM element’s functionality. Finally, the Sys.UI.Control class provides a JavaScript programming interface for a DOM element through both static and instance methods.

Tip

Tip

Nothing ties the JavaScript Behavior and Control classes to AJAX architecture, but the JavaScript Behavior and Control classes fit into the AJAX architecture nicely by wrapping client-side functionality that can be easily integrated with Web service data sources.

In this chapter we’ll first look at the JavaScript Sys.UI.Behavior class and a behavior example that extends a DOM element with a simple tooltip. Next we’ll look at the Sys.UI.Control base class and an example that extends an input control with custom functionality. Finally, we’ll add more features and functionality to the case study knowledge base application by implementing a wiki control—a large-scale control implementation that ties control functionality to application components.

Figure 8-1 illustrates the role of controls in an AJAX application. They are central to the user interface and are based on the component system described in Chapter 7. In real-world applications you use discrete controls to build your own user interface library, as well as composite controls that implement major portions of functionality and call into the Web services back end.

The control infrastructure and the AJAX Control Toolkit are used to build custom frameworks.

Figure 8-1. The control infrastructure and the AJAX Control Toolkit are used to build custom frameworks.

More Information

More Information

The AJAX Control Toolkit is a collaboration between Microsoft and the ASP.NET AJAX community and contains a large variety of behavior and control implementations that are ready to use. I won’t cover the AJAX Control Toolkit in this book because my focus is on development using the technology platform, but you might find the control toolkit useful in your applications. You can download the AJAX Control Toolkit from http://codeplex.com/ajaxcontroltoolkit.

The Ajax Library Behavior Class

Behaviors are extensions to DOM elements that provide additional functionality for the user interface. Most often, however, an AJAX behavior doesn’t alter the core functionality of the DOM element it references. Behaviors can alter the user interface by adding cascading style sheet (CSS) styles and different mouse-over actions, or they might provide functionality that extends controls, such as the drop down list extender, a behavior in the AJAX Control Toolkit that adds an auto-complete drop-down list to an element on the basis of an XML data source.

The Sys.UI.Behavior JavaScript class is used to implement behaviors. It provides a simple programming model by which to extend the Component class with support for lightweight DOM extensions. Sys.UI.Behavior is a base class that is used to provide common client-side behavior. Because behaviors are components, the $create method is used to create behavior instances.

Tip

Tip

The Behavior class was originally written for server-side developers who wanted to extend server controls with JavaScript. You’ll see this in the MSDN documentation, but I’ll take a client-centric approach to behaviors here.

Behavior extends Component with the properties element, name, and id. By default, the name property is the name of the behavior type. For example, the behavior SOAjax.Behaviors. TooltipBehavior will have the value TooltipBehavior for its name property. The id property is generated by combining the name of the behavior with the DOM element’s ID.

The Behavior class is similar to the Control class, but with a few distinctions. While the Control class can be defined only once per DOM element, a behavior is just a wrapper for event handlers that are added to an element, and multiple behaviors can be added. The following code sample shows the constructor from the Behavior class. Notice that the behavior is stored as an array named _behaviors on the element. The base class removes its reference from the element’s _behaviors array in the dispose method.

// Behavior constructor, for reference only, implemented in the Microsoft AJAX Library:
Sys.UI.Behavior = function Sys$UI$Behavior(element) {
    Sys.UI.Behavior.initializeBase(this);
    this._element = element;
    var behaviors = element._behaviors;
    if (!behaviors)
        element._behaviors = [this];
    else
        behaviors[behaviors.length] = this;
}

Before we look at examples of behaviors, let’s examine how the Sys.UI.Behavior class is implemented in the AJAX library. The Behavior class is abstract and is only a base class for control libraries to implement. It provides common functionality for DOM-based behaviors. Table 8-1 lists properties of the Behavior class, where properties are defined by get_ and set_ methods. Sys.UI.Behavior simply adds properties to the Component base class that associate the behavior instance with a DOM element. It also defines static methods that let client code access the behavior. Methods of the Behavior class, including the inherited methods from Sys.Component, are defined in Table 8-2. The Behavior class also contains the events defined in Sys.Component, and these are defined in Table 8-3. Events are handled by using the add_ and remove_ methods for each event to add or remove event handlers. Finally, Sys.UI.Behavior defines three static methods that let client code access behaviors from DOM elements. These static methods are listed in Table 8-4.

Table 8-1. Behavior Class Properties

Name

Description

element

Gets the DOM element that the Behavior instance is based on (read-only).

id

Gets or sets the ID of the Behavior instance.

name

Gets or sets the name of the Behavior instance.

events

Returns an instance of the Sys.EventHandlerList for the component (read-only).

isInitialized

Returns true if the control is initialized; otherwise false (read-only).

isUpdating

Returns true if the control is currently updating itself; otherwise false (read-only).

Table 8-2. Behavior Class Methods

Name

Description

beginUpdate

Called to set isUpdating to true. Called from the beginning of the $create method (Sys.Component.create).

endUpdate

Called to set isUpdating to false. Called from the end of the $create method (Sys.Component.create). Calls initialize if isInitialized is false, and finally calls the updated method.

updated

Called when the component is updated. Use to implement any custom post-update or post-initialization logic.

initialize

Used to initialize the behavior.

dispose

Removes the behavior from the DOM element. Use this method to clean up any event handlers.

Table 8-3. Behavior Class Events

Name

Description

propertyChanged

Raised when a property is changed.

disposing

Raised when the control is being disposed.

Table 8-4. Static Methods of Sys.UI.Behavior

Name

Description

getBehaviorByName

Gets a behavior instance with the specified name for a particular DOM element.

getBehaviors

Gets all behavior instances for a particular DOM element.

getBehaviorsByType

Gets all behavior instances of the specified type for a particular DOM element.

Implementing Custom Behaviors with the AJAX Library

In the following examples, we’ll create the SOAjax.Behaviors.ToolTipBehavior class to add a simple tooltip to any DOM element. The tooltip is displayed when a user hovers the mouse over the element. To start creating a behavior, use the Ajax Client Behavior template in Visual Studio. The template creates the JavaScript namespace using the name of the Web application project it’s created in, but a simple search and replace can be used to get a good starting point. Example 8-1 shows a JavaScript class generated by using the AJAX Client Behavior template. After the initial code was generated, I replaced the default JavaScript namespace to use SOAjax.Behaviors.

Tip

Tip

Depending on the complexity of your behaviors, you might want to place them in a common file containing a library of behaviors and controls.

Tip

Tip

Code for this book is available online at http://www.microsoft.com/mspress/companion/9780735625914. The code for this chapter is in the file Chapter 8.zip.

Example 8-1. The AJAX Client Behavior template is used to begin behavior implementations (Web/Script/BehaviorTemplate.js).

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("SOAjax.Behaviors");

SOAjax.Behaviors.ToolTipBehavior = function(element) {
    SOAjax.Behaviors.ToolTipBehavior.initializeBase(this, [element]);
}
SOAjax.Behaviors.ToolTipBehavior.prototype = {
    initialize: function() {
        SOAjax.Behaviors.ToolTipBehavior.callBaseMethod(this, 'initialize'),
        // Add custom initialization here
    },
    dispose: function() {
        //Add custom dispose actions here
        SOAjax.Behaviors.ToolTipBehavior.callBaseMethod(this, 'dispose'),
    }
}
SOAjax.Behaviors.ToolTipBehavior.registerClass('SOAjax.Behaviors.ToolTipBehavior',
Sys.UI.Behavior);

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

As you can see from Example 8-1, a behavior is simple to create using the Visual Studio template. To implement the behavior, add properties and delegates to modify the DOM as you want. To keep the example simple, I created a single property toolTip that is used to set the text of the tooltip. Remember that AJAX library properties are defined by convention as get_ and set_ methods. Here’s the definition of the toolTip property:

SOAjax.Behaviors.TooltipBehavior.prototype = {
    _toolTipText: '',
    get_toolTip: function() {
        /// <value>Gets the tooltip text</value>
        return this._toolTipText;
    },
    set_toolTip: function(text) { this._toolTipText = text; },
    /* Code omitted for clarity.*/
}

To implement the tooltip, I created two handler methods in the prototype to handle events that trigger the tooltip’s visibility. The following code demonstrates the handlers I created. The _showTooltipHandler method (a private method, inferred by the naming convention) creates a tooltip element if needed. The additional methods, hide and show, are defined to expose methods that client code can call to manually hide or show the tooltip.

_showTooltipHandler: function(event) {
    var _toolTipText = this.get_toolTip();
    if (_toolTipText == null || _toolTipText.length == 0) return;

    if (this.toolTipElement == null) {
        var toolTipElement = document.createElement('DIV'),
        toolTipElement.style.zIndex = 999;
        document.body.appendChild(toolTipElement);
        var loc = Sys.UI.DomElement.getLocation(this.get_element());
        var bounds = Sys.UI.DomElement.getBounds(this.get_element());
        Sys.UI.DomElement.setLocation(
            toolTipElement, loc.x, loc.y + (bounds.height));
        Sys.UI.DomElement.setVisibilityMode(
            toolTipElement, Sys.UI.VisibilityMode.collapse);
        Sys.UI.DomElement.addCssClass(toolTipElement, 'ToolTip'),
        this.toolTipElement = toolTipElement;
    }
    this.toolTipElement.innerHTML = _toolTipText;
    Sys.UI.DomElement.setVisible(this.toolTipElement, true);
},

show: function() {
    this._showTooltipHandler(null);
},

_hideTooltipHandler: function(event) {
    if (this.toolTipElement != null)
        Sys.UI.DomElement.setVisible(this.toolTipElement, false);
},

hide: function() {
    this._hideTooltipHandler(null);
}

By convention, private fields (methods or properties) are prefixed with an underscore. In the sample code, I declared the handlers as private following this convention, and as a result these handlers won’t be shown by the Visual Studio IntelliSense engine outside the class. In this example, we’re creating the DOM element for the tooltip as a floating DIV, and we’re using static methods of Sys.UI.DomElement to position and style the element. Although we could make the CSS class another property, we’re defining the class ToolTip on the tooltip element that is created, which means the element can be styled by a CSS style definition that is external to the behavior implementation.

After you define the handler methods, the next step is to create delegates and add DOM handlers to call the delegates on DOM events. The following code sample implements delegates to reference the showTooltipHandler and hideTooltipHandler methods and uses $addHandler to add the handler to the DOM element. The delegates are added in the initialize method defined in the prototype:

initialize: function() {
    var element = this.get_element();

    this._showTooltipDelegate =
        Function.createDelegate(this, this._showTooltipHandler);

    this._hideTooltipDelegate =
        Function.createDelegate(this, this._hideTooltipHandler);

    $addHandler(element, 'mouseover', this._showTooltipDelegate);
    $addHandler(element, 'focus', this._showTooltipDelegate);
    $addHandler(element, 'mouseout', this._hideTooltipDelegate);
    $addHandler(element, 'blur', this._hideTooltipDelegate);

    SOAjax.Behaviors.TooltipBehavior.callBaseMethod(this, 'initialize'),
}

Whenever you add handlers, it’s important to remove the handlers later to ensure references aren’t left dangling, which can create memory leak problems or unexpected behavior. To remove the handlers, use the Behavior class’s dispose method. You must also write code in the dispose function that is safe if it is called repeatedly. The following code sample demonstrates a dispose method that cleans up references created through the initialize method shown previously.

dispose: function() {
    var element = this.get_element();
    if (element) {
        if (this._showTooltipDelegate) {
            $removeHandler(element, 'mouseover', this._showTooltipDelegate);
            $removeHandler(element, 'focus', this._showTooltipDelegate);
            delete this._showTooltipDelegate;
        }
        if (this._hideTooltipDelegate) {
            $removeHandler(element, 'mouseout', this._hideTooltipDelegate);
            $removeHandler(element, 'blur', this._hideTooltipDelegate);
            delete this._hideTooltipDelegate;
        }
    }
    if (this.toolTipElement)
        this.toolTipElement.parentNode.removeChild(this.toolTipElement);
    this.toolTipElement = null;
    SOAjax.Behaviors.TooltipBehavior.callBaseMethod(this, 'dispose'),
}

As you can see from the code samples, creating a behavior is a simple process with which you can implement powerful extensions to DOM elements. Simply define properties and events, and then implement delegates for event handlers within the initialize method. You might also want to provide additional methods that client code can use, such as the hide method in this example. Example 8-2 contains the full code sample for TooltipBehavior.

Example 8-2. The Behavior class can be used to define simple user interface enhancements for DOM elements (Web/script/tooltip.js).

/// <reference name="MicrosoftAjax.js"/>
// A common control library for SOAjax Sample Code

Type.registerNamespace('SOAjax.Behaviors'),

SOAjax.Behaviors.TooltipBehavior = function(element) {
    SOAjax.Behaviors.TooltipBehavior.initializeBase(this, [element]);
}
SOAjax.Behaviors.TooltipBehavior.prototype = {
    _showTooltipDelegate : null,
    _hideTooltipDelegate: null,

    _toolTipText: '',
    get_toolTip: function() {
        /// <value>Gets the tooltip text</value>
        return this._toolTipText;
    },
    set_toolTip: function(text) { this._toolTipText = text; },
    initialize: function() {
        var element = this.get_element();

        this._showTooltipDelegate =
            Function.createDelegate(this, this._showTooltipHandler);

        this._hideTooltipDelegate =
            Function.createDelegate(this, this._hideTooltipHandler);

        $addHandler(element, 'mouseover', this._showTooltipDelegate);
        $addHandler(element, 'focus', this._showTooltipDelegate);
        $addHandler(element, 'mouseout', this._hideTooltipDelegate);
        $addHandler(element, 'blur', this._hideTooltipDelegate);

        SOAjax.Behaviors.TooltipBehavior.callBaseMethod(this, 'initialize'),
    },

    _showTooltipHandler: function(event) {
        var _toolTipText = this.get_toolTip();
        if (_toolTipText == null || _toolTipText.length == 0) return;

        if (this.toolTipElement == null) {
            var toolTipElement = document.createElement('DIV'),
            toolTipElement.style.zIndex = 999;
            document.body.appendChild(toolTipElement);
            var loc = Sys.UI.DomElement.getLocation(this.get_element());
            var bounds = Sys.UI.DomElement.getBounds(this.get_element());
            Sys.UI.DomElement.setLocation(
                toolTipElement, loc.x, loc.y + (bounds.height));
            Sys.UI.DomElement.setVisibilityMode(
                toolTipElement, Sys.UI.VisibilityMode.collapse);
            Sys.UI.DomElement.addCssClass(toolTipElement, 'ToolTip'),
            this.toolTipElement = toolTipElement;
        }
        this.toolTipElement.innerHTML = _toolTipText;
        Sys.UI.DomElement.setVisible(this.toolTipElement, true);
    },

    _hideTooltipHandler: function(event) {
        if (this.toolTipElement != null)
            Sys.UI.DomElement.setVisible(this.toolTipElement, false);
    },

    hide: function() {
        this._hideTooltipHandler(null);
    },

    dispose: function() {
        var element = this.get_element();
        if (element) {
            if (this._showTooltipDelegate) {
                $removeHandler(element, 'mouseover', this._showTooltipDelegate);
                $removeHandler(element, 'focus', this._showTooltipDelegate);
                delete this._showTooltipDelegate;
            }
            if (this._hideTooltipDelegate) {
                $removeHandler(element, 'mouseout', this._hideTooltipDelegate);
                $removeHandler(element, 'blur', this._hideTooltipDelegate);
                delete this._hideTooltipDelegate;
            }
        }
        if (this.toolTipElement)
            this.toolTipElement.parentNode.removeChild(this.toolTipElement);
        this.toolTipElement = null;
        SOAjax.Behaviors.TooltipBehavior.callBaseMethod(this, 'dispose'),
    }
}
SOAjax.Behaviors.TooltipBehavior.registerClass(
    'SOAjax.Behaviors.TooltipBehavior', Sys.UI.Behavior);

Sys.Application.notifyScriptLoaded();

Because Behavior inherits from Component, the Sys.Component.create ($create) method is used to create the behavior. To recap, $create has the following interface:

$create(type, properties, events, references, element)

A Behavior instance is attached to a specific DOM element. One instance of the behavior is created and used per DOM element. Example 8-3 shows a simple test page for the tooltip behavior, with the behavior applied to both an INPUT and a DIV element.

Example 8-3. Behaviors are created and added to DOM elements with the $create method (Web/Tooltip.aspx).

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>SOAjax Controls Sample Page: Behaviors</title>
    <script type="text/javascript">
        function pageLoad() {
            var inputElement = $get('testInput'),
            var inputProperties = { toolTip:
              'Behaviors are flexible and can do all sorts of cool things.' };
            $create(SOAjax.Behaviors.TooltipBehavior,
                inputProperties, null, null, inputElement);

            var divElement = $get('testDiv'),
            var divProperties = { toolTip:
              'Behaviors can be added to any DOM element.'};
            $create(SOAjax.Behaviors.TooltipBehavior,
                divProperties, null, null, divElement);
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Script/Tooltip.js" />
            </Scripts>
        </asp:ScriptManager>
        <input type="text" id="testInput" maxlength="255" />
            <br />
            <br />
        <div id="testDiv">Mouse over me!!!</div>
    </form>
</body>
</html>

Behavior Class Static Methods

In addition to behavior component functionality, the Behavior class contains three methods for accessing behavior instances from an element. Sys.UI.Behavior.getBehaviorByName provides a way to get an instance of a typed behavior from an element. You can use this method to determine whether a behavior is already applied to an element. The Sys.UI.Behavior.getBehaviors method returns an array of all the behaviors of an element. Finally, the Sys.UI.Behavior.getBehaviorsByType method returns all behaviors that match the type specified for a given element. These static methods are shown in Example 8-4 for your reference.

Example 8-4. The Behavior class contains static methods for accessing behaviors (from MicrosoftAJAX.debug.js).

// Static Sys.UI.Behavior methods for accessing behaviors on a DOM element
// Defined in MicrosoftAJAX.js

Sys.UI.Behavior.getBehaviorByName =
        function Sys$UI$Behavior$getBehaviorByName(element, name) {
    var b = element[name];
    return (b && Sys.UI.Behavior.isInstanceOfType(b)) ? b : null;
}

Sys.UI.Behavior.getBehaviors = function Sys$UI$Behavior$getBehaviors(element) {
    if (!element._behaviors) return [];
    return Array.clone(element._behaviors);
}

Sys.UI.Behavior.getBehaviorsByType =
        function Sys$UI$Behavior$getBehaviorsByType(element, type) {
    var behaviors = element._behaviors;
    var results = [];
    if (behaviors) {
        for (var i = 0, l = behaviors.length; i < l; i++) {
            if (type.isInstanceOfType(behaviors[i])) {
                results[results.length] = behaviors[i];
            }
        }
    }
    return results;
}

Tip

Tip

An instance of Behavior is similar to an instance of Control, but a DOM element can have several behaviors attached to it but only one instance of Control.

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

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