I got a lot of great
help yesterday on my calendar questions. Makes me want to post another
contribution :-)
This one is called
EventPublisher. This was packaged also in Marco Jaeger's recent post with his
excellent Dialogs (a la Windows), but now I'll post it by itself in case someone
missed it, and also give a little explanation for ya'll.
This class allows
you to very easily fire and subscribe to events from any object. It allows you
to attach event handlers both synchronously and asynchronously, attach any
number of event handlers to a specific event, and pass any arguments to those
handlers that you want. The file is attached (sans the ".js" extension to get
past my company's firewall, just re-add that once you have the file, replacing
the ".txt").
USAGE:
Let's say you have
an object called BouncingBall, and you'd like to fire notification events every
time the ball bounces. To enable this, just extend BouncingBall to inherit from
EventPublisher, and then use the fireEvent method...
Object.extend(Object.extend(BouncingBall.prototype,
EventPublisher.prototype),
{
initialize: function(color)
{
this.color = color;
...other setup stuff and some code the makes the ball
start bouncing...
},
_bounce: function(howHigh)
{
//...fire the onBounce event with no arguments...
this.fireEvent( "onBounce" );
//...or fire it with some
arguments...
this.fireEvent( "onBounce", {ball: this, howHigh:
howHigh} );
...make the ball bounce here...
}
});
Then let's say you
have a class called Floor that tracks all your BouncingBalls, and needs to know
when they bounce so it can push back on them (you know, for every action there
is an equal and opposite reaction)...
Floor.prototype
=
{
initialize: function(balls)
{
this.balls = [];
//if an initial balls collection was passed in, loop
through it, adding each ball separately in order to setup the event
handlers...
if (balls)
$A(balls).each(function(b) { this.addBall(b); }.bind(this));
},
addBall: function(ball)
{
this.balls.push(ball);
// set up the event handler for this ball
(synchronously, so no 3rd parameter)...
ball.attachEventHandler( "onBounce",
this.ballBounce.bind(this) );
// ...or set it up so it executes asynchronously (pass in "true" as the 3rd
parameter)
// obviously a real floor can handle many balls bouncing at the same time, so
this is probably the way to go
ball.attachEventHandler( "onBounce", this.ballBounce.bind(this), true
);
},
ballBounce: function(args)
{
// assuming arguments are passed (the 2nd fireEvent is
used from the 1st example)...
// special physics... red balls bounce twice as high :-)
if (args.ball.color == "red")
this.doTheBounceStuff(args.ball, args.howHigh * 2);
else
this.doTheBounceStuff(args.ball, args.howHigh);
},
doTheBounceStuff: function(ball, howHigh)
{
...push back on the ball to make a bunch of dead
physicists proud...
}
};
Please let me know
if you have any questions about this class... Most of the other methods should
be pretty self-explanatory though. You should be able to drop it in and be on
your way to a consistent, robust event model for all your objects :-)
Enjoy!
Sincerely,
Ryan Gahl
Design
Engineer
Camtronics Medical Systems (an Emageon Company)
262-369-3251
The information transmitted in this electronic mail is intended only for the person or entity to which it is addressed and may contain confidential, proprietary, and/or privileged material. Any review, retransmission, dissemination or other use of, or taking of any action in reliance upon, this information by persons or entities other than the intended recipient is prohibited. If you received this in error, please contact the sender and delete the material from all computers.
///// EventPublisher Class ///////////////////////////////////////////////////////// // // The EventPublisher class allows objects to fire events (and other objects to // subscribe handlers to those events). The events can be fired either // synchronously or asynchonously (depending on how the handlers register themselves), // and may pass optional arguments to the handlers.
EventPublisher = Class.create(); EventPublisher.prototype = { initialize: function() { }, // pass the asynch flag (true/false) as the 3rd argument, or omit it to default to false attachEventHandler: function(eventName, handler) { // using an event cache array to track all handlers for proper cleanup if (this.allEvents == null) this.allEvents = new Array(); // loop through the event cache to prevent adding duplicates var len = this.allEvents.length; var foundEvent = false; for (var i = 0; i < len; i++) { if (this.allEvents[i] == eventName) { foundEvent = true; break; } } if (!foundEvent) this.allEvents.push(eventName); eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions if (this[eventName] == null) this[eventName] = new Array(); //create a custom object containing the handler method and the asynch flag var asynchVar = arguments.length > 2 ? arguments[1] : false; var handlerObj = { method: handler, asynch: asynchVar }; this[eventName].push(handlerObj); }, // Removes a single handler from a specific event removeEventHandler: function(eventName, handler) { eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions if (this[eventName] != null) this[eventName] = this[eventName].reject(function(obj) { return obj.method == handler; }); }, // Removes all handlers from a single event clearEventHandlers: function(eventName) { eventName = eventName + "_evt"; // appending _evt to event name to avoid collisions this[eventName] = null; }, // Removes all handlers from ALL events clearAllEventHandlers: function() { if (this.allEvents) { var len = this.allEvents.length; for (var i = 0; i < len; i++) { this.clearEventHandlers(this.allEvents[i]); } } }, //to pass arguments to the handlers, include a 2nd argument (anonymous object that can contain whatever you want) fireEvent: function(eventName) { var evtName = eventName + "_evt"; // appending _evt to event name to avoid collisions if (this[evtName] != null) { var len = this[evtName].length; //optimization for (var i = 0; i < len; i++) { try { if (arguments.length > 1) { if (this[evtName][i].asynch) { //using a double closure to maintain "this" scope and pass all arguments properly (not sure if this is overkill or not) var eventArgs = arguments[1]; var eventHandler = function(evt, index, args) { this[evt][index].method(args); }.bind(this); var eventHandlerPointer = function() { eventHandler(evtName, i, eventArgs); }.bind(this); setTimeout(eventHandlerPointer, 1); } else this[evtName][i].method(arguments[1]); } else { if (this[evtName][i].asynch) { var eventHandler = this[evtName][i].method; setTimeout(eventHandler, 1); } else if (this && this[evtName] && this[evtName][i] && this[evtName][i].method) this[evtName][i].method(); //this[evtName][i].method(); } } catch (e) { if (this.id) { alert("ERROR: error in " + this.id + ".fireEvent():\n\n" + e.message); } else { alert("ERROR: error in [unknown object].fireEvent():\n\n" + e.message); } } } } } };
_______________________________________________ Rails-spinoffs mailing list Rails-spinoffs@lists.rubyonrails.org http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs