views:

46

answers:

4

I've done a few hours of additional research but I still can't find a good pattern to what I would think is a common problem.

I'm working on a .Net MVC2 app. Our app is made up of a main page then a number of partial views (included pages) each one of the partial views corresponds to a widget consisting of an HTML structure and JS-jQury guts

Some of our widgets have rich interactions with each other so of course we are using the jQuery's event binding something to the effect of this:

Data Summary Widget

$(document).bind("DataUpdated", summaryobject.updateTotals());

Data Manipulation Widget

$(document).trigger("DataUpdated");

The problem we are having is that as a user navigates to parts of the applications these widgets (HTML/DOM) are removed from the page and replace with the new set of partial views. When a user navigates back the HTML (visual representation) is reloaded along with the JQuery binding which creates a double binding.

So far my solutions seem lame to me.

(1) bind to the DOM object the binding is for:

$("#summaryobject").bind("DataUpdated", summaryobject.updateTotals());

The problem with this is that then my triggering widget needs to know what DOM bject to trigger on: $("#summaryobject") which kind of defeats the purpose.

(2) Create a EventBus object to tract who bound which events and only allow events to be bound once. The problem I am having is when storing/binding the events I can't track who created it so I can't unregister it if I need to....perhaps unregistered events isn't event necessary.

What patterns are others using to manage custom events?

+1  A: 

There are two good solutions to your problem (that I can see).

  1. Remove all of your bind calls and replace them with a single call to delegate or live and set up a listener which listens for all of events that occur in your application and routes them to the appropriate places. (In other words, create a Controller).

  2. Use an EventBus that allows events to be unsubscribed as well as subscribed. The simplest way I've seen to do this with a EventBus is Peter Michaux's Eventing script.


/* 
Extremely loosely coupled custom events
Source: 
http://peter.michaux.ca/articles/anonymous-events-extremely-loose-coupling
*/

EventManager = {};

EventManager.events = {};

EventManager.subscribe = function( event_name, subscriber, method_name ) {
    var event = this.events[event_name];
    if ( ! event ) {
        event = this.events[event_name] = [];
    }
    event.push( { s: subscriber, m: method_name } );
    return this;
};

EventManager.unsubscribe = function( event_name, subscriber ) {
    var event = this.events[event_name];
    if ( ! event ) {
        return;
    }
    for ( var i = event.length - 1; i >= 0; i-- ) {
        if ( event[i].s === subscriber ) {
            event.splice( i, 1 );
        }
    }
    return this;
};

EventManager.unsubscribe_all = function( event_name ) {
    delete this.events[event_name];
    return this;
};

EventManager.fire = function( event_name ) {
    var event = this.events[event_name];
    if ( ! event ) {
        return this;
    }
    for ( var i = event.length - 1; i >= 0; i-- ) {
        subscription = event[i];
        subscription.s[subscription.m].apply( subscription.s, arguments );
    }
    return this;
};

An example of using this second method would be:

EventManager.subscribe("DataUpdated", summaryobject, "updateTotals");
// ... at some point later on ...
EventManager.fire("DataUpdated");
// Fires `updateTotals` with `this` bound to `summaryobject`
Sean Vieira
So this basically is only using the jQuery binding for calling the event manager? The event manager itself is a more sophisticated version of the jQuery built-in event binding? I don't quite understand the fire method yet but I think i know what its doing.
Chris
@Chris -- option #2 is actually completely independent of any javascript library. It creates a central registry of events and allows anything at all that knows about it to add "listeners" for events. Whenever `fire` is called it checks to see if the registry (`EventManager.events`) contains a key by the expected name. If it does, it runs over the array and calls each of the functions in the event array.
Sean Vieira
Oh I'm seeing it more clearly now. I actually don't really see how this solves the problem of double binding. This just basically recreates the Jquery eventbus. jQuery provides this same functionality with bind/unbind. I also don't see the benefit of live for custom bindings. Well I see a benefit but not one that solve this. I think my problem is really a overall design of how the application works. We need to be smarter with how we load our JS objects and do our bindings. I have a temporary solution for this but we'll have to do a major refactor to make it right. I'll ponder this though
Chris
FYI I'm probably missing the point of the .live method. I'll play with it a bit
Chris
A: 

You could try a couple of things, like:

  1. Namespacing your event types. This way, while not creating a binding between two specific view instances, you can create a generalized relationship between types of views. From your example above:

    Data Summary Widget A

    $(document).bind("DataUpdated.Statistics", summaryobject.updateTotals());
    

    Data Manipulation Widget A

    $(document).trigger("DataUpdated.Statistics");
    

    Check out the documentation in here: http://api.jquery.com/bind/

  2. Unloading the event handlers whenever a widget or group of them is unloaded, prior to loading the new DOM / handlers.

Ioannis Karadimas
The triggering widget still needs to call "DataUpdated.Statistics" which tightly couples the two just the same as binding to the specific DOM object.
Chris
A: 

Have you tried using live() binding method ?

Arnaud F.
Live looks pretty cool however from my initial look over it seem like live is great for non custom events. However my customer events are bound to JS objects that stay registered.
Chris
sorry I didn't post that last comment. but anyways just to elaborate. live wouldn't solve the problem but i think it might be a better way than just bind.
Chris
A: 

I ended up doing this for now:

under my pagemanager object I wrote this method

function PageManager() {
    this.eventBusRegistry = [];

    ///<summary>Registers Event if not already bound</summary>
    this.registerForEvent = function (eventName, eventOwner, functionToBind) {
        if (this.eventBusRegistry[eventName + eventOwner] == null) {
            this.eventBusRegistry[eventName + eventOwner] = "BOUND";
            $(document).bind(eventName, functionToBind);
        }
    }
}

For my widgets class I did this.

   ///<summary>Widget</summary>
    function Widget(inWidgetName) {

        //the data you wish to update.
        this.data = {};
        //name of widget
        this.widgetName = inWidgetName;
    }

    Widget.prototype.registerForEvent = function (eventName, functiontoBind) {
       //Session is the instance of PageManager
        Session.registerForEvent(eventName, this.widgetName, functiontoBind);
    };

For any instance of a widget you call registerForEvent to bind events.

This is far from a perfect solution. Our design will still have memory leaks in that we don't unregister our JS objects. However, our objects are overwritten each time they are loaded so worse case is that every object in the application can be registered once, even if they are not used.

However, binding a method to an event binds to the document object which is not overwritten or course. So this code will stop us from rebinding. We still have a bit of a sloppy setup however it's ramifications should be minimized.

Chris