Building ASP.NET AJAX Controls (Pt 2 - Components)

Links to the other posts in this series 

In Part 1 I looked at what an ASP.NET AJAX control is and the basic building blocks of such controls, the Sys.Component, Sys.UI.Behavior and Sys.UI.Control types. Let's look at what these types offer us:

Picture1

Sys.Component is the base class for the other two types and implements (amongst other things):

  • Sys.IDisposable (I implement a dispose() method to clean up resources)
  • Sys.INotifyDisposing (I have a disposing event you can register an interest in)
  • Sys.INotifyPropertychange (I have a propertyChanged event you can register an interest in)
  • An events property of type EventHandlerList used to maintain a list of event handlers mapped to the component's events
  • An id property used to identify the component
  • A raisePropertyChanged() method used to raise the propertyChanged event
  • Sys.UI.Control adds an element property that represents the associated DOM element
  • Sys.UI.Behavior adds an element property and a name property (as you can have multiple behaviors associated with a single DOM element you need a way to uniquely identify them)

Let's take a look at a definition of a component. Conveniently, in Visual Studio 2008 / ASP.NET 3.5 there is a new item template for both a behavior and a control:

Picture2

If I opt for a behavior, this is what I get (NB to change this or a control definition to a component is a trivial task):

 Type.registerNamespace("WebSite1");

WebSite1.ClientBehavior = function(element) {
    WebSite1.ClientBehavior.initializeBase(this, [element]);
}

WebSite1.ClientBehavior.prototype = {
    initialize: function() {
        WebSite1.ClientBehavior.callBaseMethod(this, 'initialize');
        
        // Add custom initialization here
    },
    dispose: function() {        
        //Add custom dispose actions here
        WebSite1.ClientBehavior.callBaseMethod(this, 'dispose');
    }
}
WebSite1.ClientBehavior.registerClass('WebSite1.ClientBehavior', Sys.UI.Behavior);

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

If you're well acquainted with JavaScript this probably looks pretty familiar. If not, it may look a little daunting. Let me try and explain. Firstly, the ASP.NET AJAX client framework allows me to register namespaces just as I can in .NET. We can then break down out component definition into 3 parts; constructor, class definition and registration.

You can think of the Website1.ClientBehavior assignment as the equivalent of a .NET constructor. Each time we create an instance of our ClientBehavior class that anonymous function code will be executed giving us an opportunity to perform any actions required (including initialising the base class with a call to initializeBase()).

The JavaScript prototype model is used to allow us to create a definition that can be re-used or cloned multiple times. Every object in JavaScript has a prototype object that it inherits from. Defining properties on the ClientBehavior's prototype object ensures these properties are shared across all instances. Defining methods here makes them available to every instance of our component. If we define a field in the prototype that value is also shared across all instances; not necessarily what we want. If we want an instance variable, we should define it in the constructor and expose it via property getters and setters in the prototype. We'll see all this in action shortly.

Finally we register our new type with ASP.NET AJAX via the call to registerClass() specifying the base type (in this case Sys.UI.Behavior). The last line of code ensures that we notify the ScriptManager that we're done so it can carry on with its next action.

Now this all sounds a bit complicated but in essence, if you start with the template it's pretty straightforward to modify to achieve what you want. I should say this all holds true for ASP.NET 2.0 with the ASP.NET AJAX Extensions as well (though you'll need to manually copy the templates from somewhere as I don't think you get the new item templates in Visual Studio).

Let's take the template above and modify it to be a basic Sys.Component that helps us to see the component lifecycle:

 Type.registerNamespace("DevWeek");

DevWeek.SimpleComponent = function() {
    DevWeek.SimpleComponent.initializeBase(this);
    alert('Simple Component Constructor');
}

DevWeek.SimpleComponent.prototype = {

    initialize: function() {
        DevWeek.SimpleComponent.callBaseMethod(this, 'initialize');
        
        // Add custom initialization here
        alert('Simple Component Initialising');
    },
    
    dispose: function() {        
        //Add custom dispose actions here
        alert('Simple Component Disposing');
        DevWeek.SimpleComponent.callBaseMethod(this, 'dispose');
    }
}
DevWeek.SimpleComponent.registerClass('DevWeek.SimpleComponent', Sys.Component);

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

Notice there are a few alerts() in there which will hopefully help us to understand what's going on. But before we get there we need to know how to create and use one of these things. Well there's nothing stopping you creating an instance provided you perform the right sequence of steps but there is a create() helper method on the Sys.Component type (with a $create shortcut) that does the hard work for you.

So to create an instance of our new DevWeek.SimpleComponent class all I need to do is make a call to $create during Sys.Application.init (the initialisation event of the Sys.Application object). In our case this would be something as simple as:

 Sys.Application.add_init(pageInit);

function pageInit() {
    $create(DevWeek.SimpleComponent,    // class
        {},                             // properties
        {},                             // events
        {},                             // references
        null);                          // associated element
}

ie we add a handler to the Sys.Application.init event and in there call $create passing the type of component we want to create as the first parameter. Other parameters on $create allow us to set property values, hook up event handlers, add references to other components and associate our component with a DOM element on the page (to be used with behaviors and controls).

Sys.Component.create() does a number of things:

  • It creates an instance of the specified component
  • It "wires up" properties, events etc as specified in the parameters
  • It calls the initialise() method on the component
  • It adds the component to the Sys.Application object which hosts and tracks client components created in the page. This provides an easy way to access the component again (we'll get to that) and also ensures that the dispose() method on the component is called when the page is unloaded.

If we create a simple aspx page as follows

 <%@ Page Language="C#" %>

<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Simple Component</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/Component.js" />
        </Scripts>
    </asp:ScriptManager>

    <script type="text/javascript">

    Sys.Application.add_init(pageInit);

    function pageInit() {
        $create(DevWeek.SimpleComponent,    // class
            {},                             // properties
            {},                             // events
            {},                             // references
            null);                          // associated element
    }

    </script>

    </form>
</body>
</html>

and save our component definition in a file called Component.js then when we run the page we'll see two alerts "Simple Component Constructor" (we're in the constructor code) and "Simple Component Initialising" (in the initialise method). Navigate away from the page or close the browser and the "Simple Component Disposing" (in the dispose method) alert will pop-up.

Next on the list is accessing the components we've created as well as adding properties and events. We're at the stage now where we can start to use my control from the launch session to illustrate some of these concepts and ultimately build them into an ASP.NET AJAX server control you can just drop onto an aspx page.

Technorati Tags: asp.net,ajax.visual studio