synths.mustache revision 2e7441169df0fcc324f2d44947beba7d237f5a37
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<div class="intro">
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <p>Synthetic events are usually named abstractions that bind to existing
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith DOM events to monitor user actions for specific patterns. However, at
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith heart they are no more than a set of callbacks executed in response to
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith various triggering methods in the DOM event system.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <p>You can do all sorts of things with synthetic events, including:</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith redefine native DOM events that behave inconsistently across
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith browsers (e.g. <a href="focus.html">`focus` and `blur`</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith provide abstract events that attach to different DOM events based on
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith the environment (e.g. <a href="touch.html#move">`gesturemovestart`
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith and family</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith create events with different subscription signatures (e.g.
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <a href="hover.html">`hover`</a> or
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <a href="touch.html#move">`gesturemovestart`</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith create configurable events that only execute subscribers when
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith criteria passed during subscription are met (e.g.
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <a href="touch.html#flick">`flick`</a> or
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <a href="key.html">`key`</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith create events that encapsulate common UX patterns (e.g.
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <a href="outside.html">`clickoutside`</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith create fun little easter eggs (e.g. <a
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith href="http://yuilibrary.com/gallery/show/event-konami">`konami`</a>)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <li>and more...</li>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h2>The hooks</h2>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>Synthetic events hook into the subscription binding and unbinding methods. Specifically:</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <li>`node.on("eventName", ...)`, `Y.on("eventName", ...)`, <a href="index.html#one-time-subscriptions">and family</a></li>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <li>`node.delegate("eventName", ...)` or `Y.delegate("eventName", ...)`</li>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <li>`node.detach(...)` or `subscription.detach()`</li>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>With the exception of a separate `detachDelegate()` method, the names used when defining synthetic events are the same as these basic methods.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke SmithY.Event.define("tripleclick", {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith on: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // called in response to individual subscriptions
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith delegate: function (node, subscription, notifier, filter) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // called in response to delegate subscriptions
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detach: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // called when individual subscriptions are detached in any way
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detachDelegate: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // called when delegate subscriptions are detached in any way
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h2>Subscriptions and Notifiers</h2>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>In addition to the subscribing Node, each method receives a
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<em>subscription</em> and a <em>notifier</em>. Use the <em>subscription</em>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithto store event handles or other data that may be needed by another method. Use
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smiththe <em>notifier</em> to signal that subscribed callbacks should be
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithexecuted.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke SmithY.Event.define("tripleclick", {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith on: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith var count = 0;
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith subscription._handle = node.on("click", function (e) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (++count === 3) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // Call notifier.fire(e) to execute subscribers.
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // Pass the triggering event facade to fire()
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detach: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith delegate: function (node, subscription, notifier, filter) { ... },
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detachDelegate: function (node, subscription, notifier) { ... }
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>Subscribers to the synthetic event should receive a `DOMEventFacade`. The
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smitheasiest way to provide one is to pass the triggering DOM event's facade to
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith`notifier.fire(e)`. The facade's `e.type` will be updated to the name of the
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithsynth. You will also have the opportunity to add extra data to the event
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithbefore dispatching to the subscription callbacks.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke SmithY.Event.define('multiclick', {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith on: function (node, sub, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith var count = 0,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith sub._handle = node.on('click', function (e) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (timer) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith timer = Y.later(200, null, function () {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith notifier.fire(e); // subscribers will get e with e.clicks
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h2>Delegation support</h2>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>The `delegate` function implementation takes an extra argument, the `filter` that was passed <code>node.delegate(type, callback, <em>HERE</em>)</code>. It's your responsibility to make sense of this filter for your event.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>Typically, it is just passed along to a `node.delegate(...)` call against another event, deferring the filtration to the core `delegate()` method.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke SmithY.Event.define("tripleclick", {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith on: function (node, subscription, notifier) { ... },
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detach: function (node, subscription, notifier) { ... },
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith delegate: function (node, subscription, notifier, filter) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith var activeNode = null,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith subscription._handle = node.delegate("click", function (e) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (timer) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (this !== activeNode) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith activeNode = this;
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (++count === 3) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // Call notifier.fire(e) just as with `on`
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith timer = Y.later(300, null, function () {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith detachDelegate: function (node, subscription, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h2>Extra arguments</h2>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>Supply a `processArgs` method in the event definition to accept additional
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smitharguments. If this method is supplied, it</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <strong>MUST</strong> remove the extra arguments from the arg array
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith that is passed in, and
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith <strong>SHOULD</strong> return the extra data relevant to the
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith subscription.
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>The same `processArgs` method is used by both `on` and `delegate`. A second
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithargument to `processArgs` indicates that the subscription is being made through
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith`delegate`. Because `node.on(...)` and `node.delegate(...)` signatures differ,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithit is easiest to expect custom arguments starting from at index 3 of the
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithsubscription arguments array.</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke SmithY.Event.define('multiclick', {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith processArgs: function (args, isDelegate) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // The args list will look like this coming in:
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // [ type, callback, node, (extras...), [filter], thisObj, arg0...argN ]
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith var extras = args[3];
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // The args array going out MUST look like this:
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // [ type, callback, node, [filter], thisObj, arg0...argN ]
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // this will be assigned to subscription._extras;
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith return extras;
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith on: function (node, sub, notifier) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith var clicks = 0,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith // data returned from processArgs is available at sub._extras
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith sub._handle = node.on('click', function (e) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (timer) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (++clicks === max) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith timer = Y.later(200, null, function () {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith if (clicks > min) {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith// Usage would look like this (extra config object as third arg)
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith minClicks: 5,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith maxClicks: 8
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith// extra args are supplied before the delegate filter
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smithcontainer.delegate('multiclick', doSomething, {
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith minClicks: 3,
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith maxClicks: 55
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith}, '.clickable');
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h2>Tips and Tricks</h2>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h4>Alias `delegate` to `on` and `detachDelegate` to `detach`</h4>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h4>Don't store state in `node.setData(...)` for `delegate`</h4>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<p>It might be tempting</p>
2e7441169df0fcc324f2d44947beba7d237f5a37Luke Smith<h4>Use raw events for performance</h4>