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:

The hooks

Synthetic events hook into the subscription binding and unbinding methods. Specifically:

  1. `node.on("eventName", ...)`, `Y.on("eventName", ...)`, and family
  2. `node.delegate("eventName", ...)` or `Y.delegate("eventName", ...)`
  3. `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 } }); ```

Subscriptions and Notifiers

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); }); }); }, ... }); ```

Delegation support

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(); } }); ```

Extra Arguments

Supply a `processArgs` method in the event definition to support a custom subscription signature. The method receives two arguments:

  1. an array of the subscription arguments for analysis
  2. a boolean `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'); ```

A Tip to Make Your Synth Definition Smaller

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.