View
14.583
Download
0
Category
Preview:
Citation preview
How CUSTOM EVENTS
will save the universe
Law of Demeter
“Each unit should have only limited knowledge
about other units.”
(we break this ruleall the time)
the Law of Demeter is about removing
unnecessary coupling
custom events can helpdecouple your code
Broadcaster and receiverdon’t have to knowabout one another…
… so there’s far less to mockin your unit tests
YOU DO HAVEUNIT TESTS, RIGHT?
How do custom events work?
$(document).observe('custom:event', function(event) { var customData = event.memo; // stuff}); $('troz').fire('custom:event', { foo: "bar" });
$(document).bind('customevent', function(event, customData) { // stuff}); $('#troz').trigger('customevent', [{ foo: "bar" }]);
$(document).addEvent('customevent', function(event, customData) { // stuff}); $('troz').fireEvent('customevent', { foo: "bar" });
BUT WHY?
Makes testing easier
(maybe you’ll do it now)
Why?
Rapid prototyping
Why?
More versatile than callbacks
Why?
Can be bubbled/canceled
Why?
Can handle exceptions properly(in theory)
Why?
(quoth Dean Edwards)
In Prototype,exceptions raised in one handler
won’t affect another handler
(is this a big deal? smart people disagree)
CASESTUDIES
#1
Case Studies
(script.aculo.us 2.0)
Case Studies
…as a “metronome” for effects
scripty2 uses custom events…
window.setTimeout(function() { document.fire('effect:heartbeat');}, 0);
document.observe('effect:heartbeat', advanceEffectByOneFrame);
Seems pointless…
…until you need to debug
Step through an animationframe by frame
document.observe('keydown', function(event) { if (event.keyCode === Event.KEY_RIGHT) { document.fire('effect:heartbeat'); }});
…to pass messagesbetween UI widgets
scripty2 uses custom events…
S2.UI.Menu
(used by other S2.UI components)
var menu = new S2.UI.Menu();menu.addChoice("Foo");menu.addChoice("Bar");someElement.insert(menu);menu.open();
menu.observe('ui:menu:selected', function(event) { console.log('user clicked on:', event.memo.element);});
S2.UI widgets act like elements when needed, so...
Easy to usein any context
Button with menu
ASIDE:
Custom events are cancelable
var event = $('troz').fire('custom:event');if (!event.stopped) performSomeDefaultAction();
document.observe('ui:menu:before:open', function(event) { event.stop();});
(prevent all menus from appearing)
…as hooks for debugging
scripty2 uses custom events…
“Why aren’t theseeffects queueing
like I expect?”
document.observe('effect:dequeued', function(event) { var queue = event.memo; console.log("Effects in queue:", queue.size());});
You get debuggingFOR FREE
#2
Case Studies
Mouse wheel
Case Studies
window.addEventListener('DOMMouseScroll', handler);window.onmousewheel = handler;
function handler(event) { var delta; if (event.wheelDelta) { delta = event.wheelDelta / 120; if (window.opera) delta = -delta; // (not a joke) } else if (event.detail) { delta = -event.detail / 3; }
// Do stuff with your stupid delta}
http://adomas.org/javascript-mouse-wheel/
Instead, do this:
window.addEventListener('DOMMouseScroll', handler);window.onmousewheel = handler;
function handler(event) { var delta; if (event.wheelDelta) { delta = event.wheelDelta / 120; if (window.opera) delta = -delta; // (not a joke) } else if (event.detail) { delta = -event.detail / 3; }
// Fire a custom event with the normalized delta. var result = event.target.fire('mouse:wheel', { delta: delta }); if (result.stopped) event.preventDefault();}
See also:
hashchange
#3
Case Studies
User idle state
Case Studies
credit: kangax
document.observe('state:idle', function() { turnOffBackgroundAjaxRequests();});
document.observe('state:active', function() { turnOnBackgroundAjaxRequests();});
http://perfectionkills.com/detect-idle-state-with-custom-events/
#4
Case Studies
Keyboard events
Case Studies
(function() { var KEYS = {}; KEYS[Event.KEY_ESC] = "esc"; KEYS[Event.KEY_UP] = "up"; KEYS[Event.KEY_DOWN] = "down"; KEYS[Event.KEY_LEFT] = "left"; KEYS[Event.KEY_RIGHT] = "right"; // ... and so on
function handler(event) { if (event.keyCode && KEYS[event.keyCode]) { event.element().fire("key:" + KEYS[event.keyCode], event); } }
document.observe("keydown", handler);})();
Then you’d be able to dosomething like this:
document.observe('key:left', function() { moveSlide(-1); });document.observe('key:right', function() { moveSlide( 1); });
#5
Case Studies
Data broadcasting
Server-sent events are awesome……but not universally supported
for browsers that support server-sent events
var eventSource = $('event_source');eventSource.observe('server-sent-event-name', function(event) { document.fire('data:received', event.data);});
for browsers that don’t
new Ajax.Request('/legacy/polling', { onComplete: function(request) { document.fire('data:received', request.responseJSON); }});
observer works with either approach
$(document).observe('data:received', function(event) { doStuffWithYourData(event.memo);});
…and your unit tests can fire dummy data
function testThatSomethingHappened() { document.fire('data:received', FIXTURE_DATA); assertSomething();}
FAQ
“What if my eventsaren’t DOM-related?”
FAQ
(meh)
Use the DOM anyway, I say
“Isn’t this overkill?”
FAQ
Yes, sometimes
“Aren’t events slow?”
FAQ
NO
Events aren’t slow;they’re synchronous
Events are onlyas slow as the handlers
attached to them
If performance is a concern, defer
window.setTimeout(function() { document.fire('costly:custom:event');}, 0);
Recommended