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:
focus
and blur
)
gesturemovestart
and family)
hover
)
flick
or
key
)
clickoutside
)
konami
)
Synthetic events hook into the subscription binding and unbinding methods. Specifically:
node.on("eventName", ...)
, Y.on("eventName", ...)
, and familynode.delegate("eventName", ...)
or Y.delegate("eventName", ...)
node.detach(...)
or subscription.detach()
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:
true
if the subscription is being made through
delegate(...)
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.