Question

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-jQuery 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?

Was it helpful?

Solution 4

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.

OTHER TIPS

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`

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.

Have you tried using live() binding method ?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top