Object.Event Draft

This is an observer, observable implementation for Prototype which I'll be releasing in early june. Kick the tires, and tell me what crimes against JavaScript I've commited.

Control.Modal, Control.Tabs and Control.TextArea will all be re-written with this and bumped to 2.0.

This library is the fusion of a few ideas:

  • making any object respond to observe() just like an Element
  • making an interface similar to Ajax.Responders usable to other classes
  • providing a foundation for many to many object relationships in JavaScript MVC applications

if(typeof(Object.Event) == 'undefined'){
    var $stop = new Object();
    Object.Event = {
        extend: function(object){
            object.Responders = {
                _objectEventRespondersSetup: function(){
                    this._responders = this._responders || [];
                },
                register: function(responder){
                    this._objectEventRespondersSetup();
                    this._responders.push(responder);
                },
                unregister: function(responder){
                    this._objectEventRespondersSetup();
                    this._responders = this._responders.without(responder);
                }
            };
            object._objectEventSetup = object.prototype._objectEventSetup = function(event_name){
                this._observers = this._observers || {};
                this._observers[event_name] = this._observers[event_name] || [];
            };
            object.observe = object.prototype.observe = function(event_name,observer){
                this._objectEventSetup(event_name);
                if(!this._observers[event_name].include(observer))
                    this._observers[event_name].push(observer);
            };
            object.stopObserving = object.prototype.stopObserving = function(event_name,observer){
                this._objectEventSetup(event_name);
                this._observers[event_name] = this._observers[event_name].without(observer);
            };
            object.notify = object.prototype.notify = function(event_name){
                var collected_return_values = [];
                var arguments_for_observers = $A(arguments).slice(1);
                var arguments_for_responders = arguments_for_observers.clone();
                arguments_for_responders.unshift(this);
                this._objectEventSetup(event_name);
                try{
                    this._observers[event_name].each(function(observer){
                        collected_return_values.push(observer.apply(observer,arguments_for_observers) || null);
                    });
                    if((this.constructor && this.constructor.Responders) || this.Responders){
                        (this.constructor.Responders ? this.constructor.Responders : this.Responders)._responders.each(function(responder){
                            if(responder[event_name])
                                collected_return_values.push(responder[event_name].apply(responder,arguments_for_responders) || null);
                        });
                    }
                }catch(e){
                    if(e != $stop)
                        throw e;
                }
                return collected_return_values;
            };
        }
    }
}

Sample Life Cycle Callback Usage

The first obvious use case as stated above is in object lifecycles.


Control.Tabs = Class.create();
Object.Event.extend(Control.Tabs);
Object.extend(Control.Tabs,{
    setActiveTab: function(){
        this.notify('beforeChange');
        //tab changing logic here
        this.notify('afterChange');
    }
});
Control.Tabs.Responders.register({
    beforeChange: function(tabs_instance){
        //called for every tabs instance
    }
});
tabs = new Control.Tabs();
tabs.observe('beforeChange',function(){
    //called just for this one tabs instance
});

Usage as Foundation for JavaScript MVC

The talk I am giving in July at the AJAX experience will go into this in much greater depth, but the other use case is treating any internal event in your application as something that is observable. Among the key benefits:

  • very clean separation of concerns
  • you can add an unlimited amount of event handlers as the application grows
  • the code that pertains to a given object can all be written in the same place

For example, let's say we have a UserController object, and a number of things need to be displayed or hidden based on the login state:


UserController = {
    login: function(){
        //do login
        $('element1').show();
        $('element2').show();
    },
    logout: function(){
        //do logout
        $('element1').hide();
        $('element2').hide();
    }
};

If you treat the login and logout methods as events (the events can be called anything, the names don't need to match), you can seperate out the code that deals with the business logic, and the display logic.


UserController = {
    login: function(){
        //do login
        UserController.notify('login',user_data);
    },
    logout: function(){
        //do logout
        UserController.notify('logout');
    }
};
Object.Event.extend(UserController);
//elsewhere in your app
UserController.observe('login',function(){
    $('element1').show();
    $('element2').show();
});
UserController.observe('logout',function(){
    $('element1').hide();
    $('element2').hide();
});

For a 20 line script, this doesn't matter one bit. I'd gladly code it the first way. But if you're dealing with thousands of line of JS spread over multiple files, it's the only way to survive.

Posted May 24th, 2007 at 12:24 pm by Ryan in Programming

Replies to this Post

BTW, I'm still unsure of calling the notify method "notify". The other contenders:

  • createEvent
  • fireEvent
  • notifyObservers

Notify is nice and short, but UserController.notify() sounds like you are notifying the UserController, where you are actually notifying everything observing it.

Any thoughts?

Posted May 24th, 2007 at 12:27pm by ryan

Here are a couple of other contenders:

  • dispatchEvent();
  • dispatch();
  • broadcast();

I have used notify() in a crossWindow class once (to notify the parent or child window of changes)

I have used broadcast() in hooking in a less cool event system in my Classes.


foo.load = function(){
   //some code
   this.onLoad.broadcast();
};
Your text editor doesn't seem to handle selecting text and then clicking a button like "Bold", "JS" or "Quotes". It just applies the request 
to the entire field.value (took me a while to type this post)

Posted June 21st, 2007 at 8:00am by jdalton

Another reason I use broadcast is because I have instances "listening" or "ignoring" the broadcast().

Posted June 21st, 2007 at 8:16am by jdalton

Posted October 21st, 2007 at 5:26am by

Login or Register to Post