See Joel Program

July 31, 2008

A Client Event Pool in JavaScript

Filed under: ASP.NET AJAX — Joel Rumerman @ 9:26 pm

One of the most useful programming concepts that my company has created over the past 16 months is also one of the simplest things that I’ve ever coded. It’s a client-side event pool that lives on the browser and that you interact with through JavaScript.

It’s purpose is simple: to allow arbitrary code to execute when an event is raised. You can think of it as a simple observer pattern implementation.

(You can download the source code here.)

To see just how simple it is, take a look the definition of the ClientEventPool … all 22 lines of it.

_ClientEventPool = function() {
    _ClientEventPool.initializeBase(this, null);
};
_ClientEventPool.prototype = {
    initialize: function() {
        _ClientEventPool.callBaseMethod(this, 'initialize');
        this._localEvents = this.get_events();
    },
    addEvent: function(eventName, handler) {
        this._localEvents.addHandler(eventName, handler);
    },
    removeEvent: function(eventName, handler) {
        this._localEvents.removeHandler(eventName, handler);
    },
    raiseEvent: function(eventName, sender, args) {
        var h = this._localEvents.getHandler(eventName);
        if (h) {
            h(sender, args);
        }
    }
};
_ClientEventPool.registerClass("_ClientEventPool", Sys.Component);
window.ClientEventPool = $create(_ClientEventPool, {"id":"ClientEventPool"}, null, null, null);

To use the event pool, I can simply register a handler for the event, and then raise the event like so.

ClientEventPool.addEvent("Test", function(sender, args) { alert("hello!"); });
ClientEventPool.raiseEvent("Test", this, Sys.EventArgs.Empty);

(In case you’re interested, the event pool relies upon ASP.NET AJAX Sys.Component’s .NET-style eventing mechanism, but in reality, using Sys.Component was just a convenience. Because Functions in JavaScript are nothing more than objects, executing functions that are assigned to properties, is really easy and is the concept of .NET delegates, are built into the language. This object could have easily been created without using anything from ASP.NET AJAX.)

(Shameless above-the-fold plug: If you’re a control developer who adds AJAX functionality to their server controls, you might be interested in my new book: Advanced ASP.NET AJAX Server Controls for .NET 3.5. Also, there’s a preview chapter in CoDe magazine.)

Let’s see what else the ClientEventPool can do for us. It really pays off when we we’re working with two different client components that really don’t know about each other.

Creating a couple arbitrary components, AirlineMiles which shows the text the user entered into the UserMileageInput textbox. (I know, I stink at real-world examples.) As the user enters the text into the textbox, I want to the AirlineMiles label to update dynamically, but I don’t want the components to have pointers to each other to make that happen.

Okay, first the client code …

/// <reference name="MicrosoftAjax.js"/>
AirlineMiles = function(element)
{
    AirlineMiles.initializeBase(this, [element]);
    this._milesChangedDelegate = null;
};
AirlineMiles.prototype = {
    initialize: function() {
        AirlineMiles.callBaseMethod(this, 'initialize');
        this._milesChangedDelegate = Function.createDelegate(this, this._milesChanged);
        ClientEventPool.addEvent("MilesChanged", this._milesChangedDelegate);
    },
    dispose: function() {
        AirlineMiles.callBaseMethod(this, 'dispose');
        if (this._milesChangedDelegate !== null) {
            ClientEventPool.removeEvent("MilesChanged", this._milesChangedDelegate);
            this._milesChangedDelegate = null;
        }
    },

    _milesChanged: function(sender, args) {
        this.get_element().innerHTML += args.get_miles();
    }
}
AirlineMiles.registerClass("AirlineMiles", Sys.UI.Control);

(The UserMileageInput client class)

UserMileageInput = function(element) {
    UserMileageInput.initializeBase(this, [element]);
    this._keyPressDelegate = null;
};
UserMileageInput.prototype = {
    initialize: function() {
        UserMileageInput.callBaseMethod(this, 'initialize');
        this._keyPressDelegate = Function.createDelegate(this, this._keyPress);
        $addHandler(this.get_element(), "keypress", this._keyPressDelegate);
    },
    dispose: function() {
        UserMileageInput.callBaseMethod(this, 'dispose');
        if (this._keyPressDelegate !== null) {
            $removeHandler(this.get_element(), "keypress", this._keyPressDelegate);
            this._keyPressDelegate = null;
        }
    },

    _keyPress: function(e) {
        ClientEventPool.raiseEvent("MilesChanged", this, new MileageEventArgs(String.fromCharCode(e.charCode)));
    }
};
UserMileageInput.registerClass("UserMileageInput", Sys.UI.Control);

Reading through the code, we can see that the AirlineMiles component adds a handler to the ClientEventPool for the “MilesChanged” event in initialize and in its handler it reads the get_miles method off the args class (we’ll show that in a second). In the UserMileageInput class, we raise the “MilesChanged” event when the user presses a key. Pretty simple. The only remaining part is the MileageEventArgs class which we create when we raise the event and pass through to the event handler. It’s a simple EventArgs class and is displayed below.

MileageEventArgs = function(miles)
{
    MileageEventArgs.initializeBase(this, null);
    this._miles = miles;
};
MileageEventArgs.prototype = {
    get_miles: function() { return this._miles; }
}
MileageEventArgs.registerClass("MileageEventArgs", Sys.EventArgs);

When we wire these client components to their .NET controls and place them on the page

(AirlineMiles.cs)

[assembly: WebResource("ControlLib.AirlineMiles.js", "text/javascript")]
namespace ControlLib
{
    public class AirlineMiles : Label, IScriptControl
    {
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            ScriptManager.GetCurrent(this.Page).RegisterScriptControl<AirlineMiles>(this);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
            base.Render(writer);
        }

        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            yield return new ScriptControlDescriptor("AirlineMiles", this.ClientID);
        }

        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            yield return new ScriptReference("ControlLib.AirlineMiles.js", typeof(AirlineMiles).Assembly.FullName);
        }
    }
}
(UserMileageInput.cs)
[assembly: WebResource("ControlLib.UserMileageInput.js", "text/javascript")]
namespace ControlLib
{
    public class UserMileageInput : TextBox, IScriptControl
    {
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            ScriptManager.GetCurrent(this.Page).RegisterScriptControl<UserMileageInput>(this);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            ScriptManager.GetCurrent(this.Page).RegisterScriptDescriptors(this);
            base.Render(writer);
        }

        public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor scd = new ScriptControlDescriptor("UserMileageInput", this.ClientID);
            yield return scd;
        }

        public IEnumerable<ScriptReference> GetScriptReferences()
        {
            yield return new ScriptReference("ControlLib.UserMileageInput.js", typeof(UserMileageInput).Assembly.FullName);
        }
    }
}
(AirlineMiles.aspx)

    <form id="form1" runat="server">
    <asp:ScriptManager ID="SM1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/ClientEventPool.js" />
        </Scripts>
    </asp:ScriptManager>
    <CtrlLib:AirlineMiles ID="lblAirlineMiles" runat="server" />
    <CtrlLib:UserMileageInput ID="txtUserInput" runat="server" />
   </form>

we can see how nice the Client Event Pool makes communication between objects. The screen shot below shows the text after it’s been updated a few times.

UpdatedMiles

So that’s the Client Event Pool.

About these ads

4 Comments »

  1. It’s interesting to see how this is done in 22 lines thanks to the Microsoft Library…
    This looks very much like the OpenAjax hub: http://www.openajax.org/member/wiki/OpenAjax_Hub_1.0_Specification_PublishSubscribe
    The hub comes as a simple open-source library that is compatible with most (if not all) popular libraries (including Microsoft).

    Comment by Bertrand Le Roy — August 1, 2008 @ 9:55 am

  2. Yes, the MS AJAX library def. helped us write the feature quickly and with some assurance that it would work right. :-).

    The OpenAjax hub spec really is similar to what we did. It wouldn’t be that hard to add the wildcard capabilities either, but I’m not sure I understand the feature’s usefulness. How many times am I going to subscribe the same event handler for all events coming Foo.Bar.*?? Seems an odd use case.

    Comment by Joel Rumerman — August 1, 2008 @ 10:10 am

  3. [...] II Scene I: I go to one of my trusty controls (AirlinesMiles.cs from my Client Event Pool example) and start to poke around at how I’m going to get the control’s script files to be part [...]

    Pingback by .NET 3.5 SP1 Doesn’t Provide Composite Script Registration from an IScriptControl (out-of-the-box) « See Joel Program — August 19, 2008 @ 9:43 pm

  4. [...] Now that we’ve got our handler wired up, we need to create a mechanism to call it. For performance purposes, we want to call the handler in a way that doesn’t affect our application’s performance (perceived or real) and we don’t want to call it too often. Rather, we want to call it once a minute or something like that. We’re going to wrap this functionality in a custom component. In the custom component we’re going to reply on a Client EventPool event to talk to our component rather than call into it directly. This way, we don’t really need to have a link to the component. First, for posterity, let’s redefine our Client EventPool. (For more information on the Client EventPool, see this post.) [...]

    Pingback by Maintaining ASP.NET Session State in an Ajax Application « See Joel Program — November 10, 2008 @ 9:44 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Silver is the New Black Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: