Partager via


Creating Custom Non-Visual Client Components

This topic shows you how to create an AJAX non-visual client component in ASP.NET that derives from the client Sys.Component base class, and how to use the component in a page.

In this tutorial you will learn how to do the following:

  • Use the prototype design pattern to define a non-visual component class in ECMAScript (JavaScript).

  • Register a non-visual component as a class derived from the Component base class.

  • Initialize the base Component class of the non-visual component and invoke its methods.

  • Create properties that raise a change notification.

  • Use the component in a page and bind to the component's events.

The overview provides an example of a timer as a non-visual client component. The timer raises events that you can handle.

This topic focuses on client non-visual component-based objects. These components derive from Component and typically have no UI representation. There are two additional types of ASP.NET AJAX client component objects that extend basic component functionality: behaviors that derive from Sys.UI.Behavior, and controls that derive from Sys.UI.Control. The following table summarizes the differences between components, behaviors, and controls.

Client component object types

Summary

Components

  • Derive from the Component base class.

  • Typically have no UI representation, such as a timer component that raises events at intervals but is not visible on the page.

  • Have no associated DOM elements.

  • Encapsulate client code that is intended to be reusable across applications.

Behaviors

  • Derive from the Behavior base class, which extends the Component base class.

  • Extend the behavior of DOM elements, such as a watermarking behavior that can be attached to an existing text box.

  • Can create UI elements, although they do not typically modify the basic behavior of the DOM element that they are associated with.

  • Can be accessed directly from the DOM element through a custom attribute (expando). The attribute will have the name of the behavior if one was set; otherwise, it will have type name (not fully-qualified).

  • Do not require an association with another client object, such as a class derived from the Control or Behavior classes.

  • Can reference either a control or a non-control HTML element in their element property.

Controls

  • Derive from the Control base class, which extends the Component base class.

  • Represent a DOM element as a client object, typically changing the original DOM element's ordinary behavior to provide new functionality. For example, a menu control might read li items from a ul element as its source data, but not display a bulleted list.

  • Are accessed from the DOM element directly through the control expando.

Prerequisites

To run the example in this topic, you will need:

Creating Basic Functionality of a Non-visual Client Component

An ASP.NET AJAX non-visual client component encapsulates JavaScript code that is intended to be reusable across applications. An example of a non-visual component is a timer component that raises events at set intervals.

By deriving from the Component base class, your custom component automatically inherits the following features:

  • A cross-browser model for managing handler bindings to client object events.

  • Automatic registration of the component in the client application as a disposable object that implements the Sys.IDisposable interface.

  • The ability to raise notification events when properties are changed.

  • The ability to perform batch processing of component property settings. This is more efficient in script size and processing time than handling all logic in individual property get and set accessors.

  • An override of the Sys.Component.initialize method to initialize any properties and event listeners.

Implementing a Client Component Derived from the Component Class

To implement a custom client component derived from Component, you follow these steps:

  • Define a component class using the prototype design pattern.

  • Initialize the component's base Component instance.

  • Expose any property accessors, and optionally raise a propertyChanged notification event.

  • Override the dispose method to release resources, such as clearing event handlers.

The following sections provide details about implementation steps.

Defining a Component Class by Using the Prototype Design Pattern

An ASP.NET AJAX client class, which includes a component class, is defined in JavaScript using the prototype design pattern. To define a component class by using the prototype design pattern, you do the following:

  • Register the namespace for your component class.

  • Create the component's constructor function, and in the constructor function, define any private fields and set their initial values.

  • Define the component's prototype.

  • Register the component function as a class derived from Component.

For more information, see Creating a Client Component Class Using the Prototype Model.

Initializing the Base Class

In the component's constructor function, you invoke the inherited Type.initializeBase method to initialize the base type of your registered class. A non-visual component class is registered as a class that has a base type of Component. When the Component base class is initialized, its methods are available to the component, and it automatically registers the component as a disposable object with the AJAX-enabled ASP.NET application. For more information, see Sys.IDisposable Interface.

Any component class that derives from Component must initialize its base class from the constructor. You typically call initializeBase before any other code runs in the constructor. The following example shows the constructor function of a non-visual component that derives from Component.

Samples.SimpleComponent = function()
{
    Samples.SimpleComponent.initializeBase(this);
}

Defining Properties and Raising Property-change Notifications

You define properties in your component's class that page developers can get and set. An ASP.NET AJAX component that derives from Component inherits the Sys.Component.raisePropertyChanged method, which you call in order to raise a propertyChanged event for your component's properties. Page developers who use your component can then bind to these events. For more information, see Defining Custom Component Properties and Raising PropertyChanged Events.

Initializing Properties and Event Listeners

If your custom component must initialize any properties or event listeners, you should override the Sys.Component.initialize method in the component's prototype. For example, a non-visual component that derives from Component might assign a delegate to an event such as window.onFocus. As a final step, you call the base initialize method to enable the component's base class to complete initialization.

ASP.NET provides classes and methods to provide standard event management for components and for DOM elements. To manage your component's events, use the Sys.EventHandlerList class. For example, bind events by using the Sys.EventHandlerList.addHandler method, and release them by using the Sys.EventHandlerList.removeHandler method. For more information, see Sys.EventHandlerList Class.

To manage event handlers for DOM elements or for the window object, use the Sys.UI.DomEvent class. For example, you can bind and unbind event handlers by using the Sys.UI.DomEvent addHandler and Sys.UI.DomEvent removeHandler methods. For more information, see Sys.UI.DomEvent Class.

Releasing Resources

If your custom component must release resources before the component is disposed, override the dispose method and release the resources in the overridden method. This makes sure that the resources are released immediately before the component is disposed. Resources that should be released might include handlers for DOM events. By verifying that any possible circular references between DOM elements and the component object are removed, you make sure that the object can be removed from memory. For more information, see Releasing Component Resources.

Using a Non-visual Component in a Page

To use a custom client component in an ASP.NET AJAX application page, you do the following:

  • Register the component's script library in the Web page.

  • Create a component instance.

The following sections provide details about these steps.

Registering a Component's Script Library in the Web Page

You can register scripts required for a client control on the page with a ScriptManager control, either declaratively or programmatically. The following example shows the declarative markup for a ScriptManager control that registers a component script.

<form id="form1" runat="server">
  <asp:ScriptManager runat="server" ID="ScriptManager01">
    <scripts>
      <asp:ScriptReference path="DemoTimer.js" />
    </scripts>
  </asp:ScriptManager>
</form>

The asp:ScriptManager element contains an asp:ScriptReference element inside a scripts node. The path attribute of the asp:ScriptReference element references the path of the .js file (in the example, DemoTimer.js) that defines a component class. For more information, see Dynamically Assigning Script References and the ScriptManager class overview.

As an alternative to registering script files by using the ScriptManager control, you can manage the client components by using a custom server control that implements the IScriptControl interface. The custom server control can automatically register the required component scripts and expose declarative markup for setting component properties and event bindings. If you register scripts by using a custom server control, you can make it easier for a page developer to use your component. For more information, see the IScriptControl class overview.

Note

All standalone script files to be registered with the ScriptManager control must call the notifyScriptLoaded method to notify the application that the script has finished loading. Scripts that are embedded in an assembly should not call this method in most cases. For more information, see Sys.Application.notifyScriptLoaded Method.

Creating a Custom Component Instance

You instantiate a client component by calling the Sys.Component.create method or the $create shortcut. You pass parameters to the $create method to specify the component type. You also pass a JSON object that contains a required ID value and optional initial property values and optional event-handler bindings.

The following example shows how to instantiate a component instance by calling the $create method.

var app = Sys.Application;
app.add_init(applicationInitHandler);

function applicationInitHandler(sender, args) 
{
    $create(Demo.Timer, {enabled:true,id:"demoTimer1", interval:2000}, 
        {tick:OnTick}, null);
}

For more information, see Sys.Component.create Method and Sys.Component $create Method.

Creating the Custom Demo.Timer Component

In this section, you will create a custom client component named Demo.Timer that extends the Component base class, and then use the component in a page. Demo.Timer is a simple timer component that defines a tick event, exposes an enabled property and an interval property, and raises a change notification event for the interval property. A page developer who uses the Demo.Timer component can handle the tick event. The developer can also bind to the property-changed event and take action every time that the interval property is updated.

To create the code for the Demo.Timer component

  1. In the root directory of an AJAX-enabled ASP.NET Web application, create a file named DemoTimer.js.

  2. Add the following code to the file:

    Type.registerNamespace("Demo");
    
    Demo.Timer = function() {
        Demo.Timer.initializeBase(this);
    
        this._interval = 1000;
        this._enabled = false;
        this._timer = null;
    }
    
    Demo.Timer.prototype = {
        // OK to declare value types in the prototype
    
    
        get_interval: function() {
            /// <value type="Number">Interval in milliseconds</value>
            return this._interval;
        },
        set_interval: function(value) {
            if (this._interval !== value) {
                this._interval = value;
                this.raisePropertyChanged('interval');
    
                if (!this.get_isUpdating() && (this._timer !== null)) {
                    this._restartTimer();
                }
            }
        },
    
        get_enabled: function() {
            /// <value type="Boolean">True if timer is enabled, false if disabled.</value>
            return this._enabled;
        },
        set_enabled: function(value) {
            if (value !== this.get_enabled()) {
                this._enabled = value;
                this.raisePropertyChanged('enabled');
                if (!this.get_isUpdating()) {
                    if (value) {
                        this._startTimer();
                    }
                    else {
                        this._stopTimer();
                    }
                }
            }
        },
    
        // events
        add_tick: function(handler) {
            /// <summary>Adds a event handler for the tick event.</summary>
            /// <param name="handler" type="Function">The handler to add to the event.</param>
            this.get_events().addHandler("tick", handler);
        },
        remove_tick: function(handler) {
            /// <summary>Removes a event handler for the tick event.</summary>
            /// <param name="handler" type="Function">The handler to remove from the event.</param>
            this.get_events().removeHandler("tick", handler);
        },
    
        dispose: function() {
            // call set_enabled so the property changed event fires, for potentially attached listeners.
            this.set_enabled(false);
            // make sure it stopped so we aren't called after disposal
            this._stopTimer();
            // be sure to call base.dispose()
            Demo.Timer.callBaseMethod(this, 'dispose');
        },
    
        updated: function() {
            Demo.Timer.callBaseMethod(this, 'updated');
            // called after batch updates, this.beginUpdate(), this.endUpdate().
            if (this._enabled) {
                this._restartTimer();
            }
        },
    
        _timerCallback: function() {
            var handler = this.get_events().getHandler("tick");
            if (handler) {
                handler(this, Sys.EventArgs.Empty);
            }
        },
    
        _restartTimer: function() {
            this._stopTimer();
            this._startTimer();
        },
    
        _startTimer: function() {
            // save timer cookie for removal later
            this._timer = window.setInterval(Function.createDelegate(this, this._timerCallback), this._interval);
        },
    
        _stopTimer: function() {
            if(this._timer) {
                window.clearInterval(this._timer);
                this._timer = null;
            }
        }
    }
    
    Demo.Timer.registerClass('Demo.Timer', Sys.Component);
    
    // Since this script is not loaded by System.Web.Handlers.ScriptResourceHandler
    // invoke Sys.Application.notifyScriptLoaded to notify ScriptManager 
    // that this is the end of the script.
    if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
    

Code Discussion

The code registers the Demo namespace by calling the Type.registerNamespace method. It is recommended that you declare and initialize all private fields in the constructor, such as interval in this example. The constructor invokes the inherited initializeBase method so that the Component base class methods are available. The initialized base class in turn registers the Demo.Timer instance with the client application as a disposable object.

In the prototype, the code declares and initializes two public properties: interval and enabled. The property definitions include private fields to hold their values, and get and set accessors for each property. In the set accessor method for each public property, the code raises a propertyChanged event by invoking the raisePropertyChanged method. This event notifies page developers every time that the property is changed.

The add_tick and remove_tick methods enable a page developer to add and remove methods that will listen for the tick event. These methods in turn add or remove the specified handler through the component's Sys.EventHandlerList collection. The EventHandlerList object contains a collection of the component's event handlers through the inherited Sys.Component.events property. In the example, the code invokes the Sys.EventHandlerList.addHandler and Sys.EventHandlerList.removeHandler methods of the returned EventHandlerList object in order to add or remove the specified handler.

The Demo.Timer class overrides the base class's dispose method to update the enabled property and to indicate to consumers that the component has been disabled. The set accessor for the enabled property raises the propertyChanged event to send the notification. The code invokes the private _stopTimer method to stop tick events from being raised. Finally, the code calls the base dispose method to enable the application to release the component.

Using the Demo.Timer Component in a Web Page

Instances of ASP.NET AJAX client components in a page can be managed by a custom server control or by using client script in the Web page. In this section, you will learn how to create a component instance by using client script in a Web page.

To create a page to use the Demo.Timer component

  1. In the directory where you put the DemoTimer.js file, create a file named DemoTimer.aspx and add the following markup and code to it:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="https://www.w3.org/1999/xhtml">
    <head runat="server">
            <title>Demo Timer Component</title>
    </head>
    <body>
        <form id="form1" runat="server"> 
            <div>
                <asp:ScriptManager ID="ScriptManager1" runat="server">
                    <Scripts>
                        <asp:ScriptReference Path="DemoTimer.js"/>
                    </Scripts>
                </asp:ScriptManager>
    
                Timer Tick Count: <span id="result">0</span>
            </div>
    
            <script type="text/javascript">
    
                function OnTick(sender, args) {
                    var result = $get("result");
                    result.innerText = parseInt(result.innerText) + 1;
                }
    
                 var app = Sys.Application;
                 app.add_init(applicationInitHandler);
    
                 function applicationInitHandler(sender, args) {
                    // Create the DemoTimer component instance.  
                    // Set properties and bind events.
                    $create(Demo.Timer, 
                        {enabled:true,id:"demoTimer1",interval:2000}, 
                        {tick:OnTick}, null, null);
                }
    
            </script> 
        </form>
    </body>
    </html>
    
  2. In the same directory, create a file named TestDemoTimer.js and add the following code to it:

    function OnTick(sender, args) {
        var result = $get("result");
        result.innerText = parseInt(result.innerText) + 1;
    }
    
     var app = Sys.Application;
     app.add_init(applicationInitHandler);
    
     function applicationInitHandler(sender, args) {
        // Create the DemoTimer component instance.  
        // Set properties and bind events.
        $create(Demo.Timer, 
            {enabled:true,id:"demoTimer1",interval:2000}, 
            {tick:OnTick}, null, null);
    }
    

Code Discussion

The example page loads the TestDemoTimer.js by using JavaScript code that contains two functions, OnTick and applicationInitHandler. The OnTick function handles the Demo.Timer component's tick event and updates a counter value in a span HTML element.

The applicationInitHandler function is a handler for the app_init event. In the function, the Demo.Timer component is instantiated in client script by invoking the $create method, passing the following arguments:

  • The type argument is the Demo.Timer class that you created earlier.

  • The properties argument consists of a JSON object that contains the required component ID value followed by property name/value pairs that specify property names with initial values. For demonstration purposes, the interval property is initially set to 2000 milliseconds so that the timer will raise a tick event every 2 seconds. (In a production application, you would probably set the interval to a greater value to reduce network traffic.) The component's enabled property is set to true so that the timer will start immediately after it is instantiated.

  • The events argument contains an object that has event names paired with their handlers. In this case, the onTick handler is assigned to the tick event, which is defined in the page's script element.

The DemoTimer.aspx file is an ASP.NET Web page that hosts the component. In the page's ScriptManager control, the path attribute of the asp:ScriptReference element references the path of the DemoTimer.js file that defines the Demo.Timer component class.

See Also

Tasks

Dynamically Assigning Script References

Reference

Sys.Component Class

ScriptManager

Concepts

Using the ASP.NET UpdatePanel Control with Data-Bound Controls

Working with Partial-Page Rendering Events