Synthetic events are usually named abstractions that bind to existing DOM events to monitor user actions for specific patterns. However, at heart they are no more than a set of callbacks executed in response to various triggering methods in the DOM event system.
You can do all sorts of things with synthetic events, including:
Synthetic events hook into the subscription binding and unbinding methods. Specifically:
With the exception of a separate `detachDelegate()` method, the names used when defining synthetic events are the same as these basic methods.
``` Y.Event.define("tripleclick", { on: function (node, subscription, notifier) { // called in response to individual subscriptions }, delegate: function (node, subscription, notifier, filter) { // called in response to delegate subscriptions }, detach: function (node, subscription, notifier) { // called when individual subscriptions are detached in any way }, detachDelegate: function (node, subscription, notifier) { // called when delegate subscriptions are detached in any way } }); ```In addition to the subscribing Node, each method receives a subscription and a notifier. Use the subscription to store event handles or other data that may be needed by another method. Use `notifier.fire(e)` to dispatch the event to the callbacks that were bound to it.
``` Y.Event.define("tripleclick", { on: function (node, subscription, notifier) { var count = 0; subscription._handle = node.on("click", function (e) { if (++count === 3) { // Call notifier.fire(e) to execute subscribers. // Pass the triggering event facade to fire() notifier.fire(e); } else { ... } }); }, detach: function (node, subscription, notifier) { subscription._handle.detach(); }, delegate: function (node, subscription, notifier, filter) { ... }, detachDelegate: function (node, subscription, notifier) { ... } }); ```Subscribers to the synthetic event should receive a `DOMEventFacade`. The easiest way to provide one is to pass the triggering DOM event's facade to `notifier.fire(e)`. The facade's `e.type` will be updated to the name of the synth. You will also have the opportunity to add extra data to the event before dispatching to the subscription callbacks.
``` Y.Event.define('multiclick', { on: function (node, sub, notifier) { var count = 0, timer; sub._handle = node.on('click', function (e) { count++; if (timer) { timer.cancel(); } timer = Y.later(200, null, function () { e.clicks = count; count = 0; // subscribers will get e with e.type == 'multiclick' // and extra property e.clicks notifier.fire(e); }); }); }, ... }); ```The `delegate` function implementation takes an extra argument, the `filter` that was passed node.delegate(type, callback, HERE)
. It's your responsibility to make sense of this filter for your event.
Typically, it is just passed along to a `node.delegate(...)` call against another event, deferring the filtration to the core `delegate()` method.
``` Y.Event.define("tripleclick", { on: function (node, subscription, notifier) { ... }, detach: function (node, subscription, notifier) { ... }, delegate: function (node, subscription, notifier, filter) { var activeNode = null, count = 0, timer; subscription._handle = node.delegate("click", function (e) { if (timer) { timer.cancel(); } if (this !== activeNode) { activeNode = this; count = 0; } if (++count === 3) { // Call notifier.fire(e) just as with `on` notifier.fire(e); } else { timer = Y.later(300, null, function () { count = 0; }); } }, filter); // filter is passed on to the underlying `delegate()` call }, detachDelegate: function (node, subscription, notifier) { subscription._handle.detach(); } }); ```Supply a `processArgs` method in the event definition to support a custom subscription signature. The method receives two arguments:
If this method is supplied, it
The same `processArgs` method is used by both `on` and `delegate`, but there are various signatures to account for. The easiest way to accept extra arguments is to require them from index 3 in the argument list. It's also best to limit the number of extra arguments to one and require an object literal to allow for future changes.
``` // for an event that takes one extra param processArgs: function (args, isDelegate) { var extra = args[3]; // remove the extra arguments from the array args.splice(3,1); return extra; } // for an event that takes three extra args processArgs: function (args, isDelegate) { return args.splice(3,3); } ```Requiring extra params start at index 3 of the `args` array results in the following subscription signatures:
``` var extraConfig = { ... }; // Third argument for node.on() and node.delegate node.on('extraArgEvent', callback, extraConfig, thisOverride, arg...); node.delegate('extraArgEvent', callback, extraConfig, filter, thisOverride, arg...); // Fourth argument for Y.on() and Y.delegate Y.on('extraArgEvent', callback, targetSelector, extraConfig, thisOverride, arg...); Y.delegate('extraArgEvent', callback, parentSelector, extraConfig, filter, thisOverride, arg...); ```For some custom signatures, the placement of the extra argument for implementers using `Y.on()` or `Y.delegate()` may look awkward. Sometimes you can support extras at other indexes if you can reliably tell that the argument is not part of the extended signature for `on(...)` or `delegate(...)`. See the source for the "hover" event for an example of supporting multiple signatures.
The return value of `processArgs` is assigned to `subscription._extras` for the `on` and `delegate` definition methods.
``` Y.Event.define('multiclick', { processArgs: function (args, isDelegate) { // The args list will look like this coming in: // [ type, callback, node, (extras...), [filter,] thisObj, arg0...argN ] return args.splice(3,1)[1] || {}; }, // Custom subscription signatures don't change the params of on/delegate on: function (node, sub, notifier) { var clicks = 0, // data returned from processArgs is available at sub._extras min = sub._extras.minClicks || 3, max = sub._extras.maxClicks || 10, timer; sub._handle = node.on('click', function (e) { if (timer) { timer.cancel(); } if (++clicks === max) { e.clicks = clicks; notifier.fire(e); } else { timer = Y.later(200, null, function () { if (clicks > min) { e.clicks = count; notifier.fire(e); } count = 0; }); } }); }, ... }); ```Usage of this synthetic event then expects a third argument as a configuration object with `minClicks` and `maxClicks` properties.
``` node.on('multiclick', obj.method, { minClicks: 5, maxClicks: 8 }, obj); // extra args are supplied before the delegate filter container.delegate('multiclick', doSomething, { minClicks: 3, maxClicks: 55 }, '.clickable'); ```If the only difference between your `on` and `delegate` definitions is which method is used to bind to the supporting events, then you can propably get away with defining `delegate` and aliasing it to `on` (and so with `detach` and `detachDelegate`). See the source for the "hover" event for an example of this approach.