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:
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:
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.
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