1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<div class="intro">
<p>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.</p>
<p>You can do all sorts of things with synthetic events, including:</p>
<ul>
<li>
redefine native DOM events that behave inconsistently across
browsers (e.g. <a href="focus.html">`focus` and `blur`</a>)
</li>
<li>
provide abstract events that attach to different DOM events based on
the environment (e.g. <a href="touch.html#move">`gesturemovestart`
and family</a>)
</li>
<li>
create events with different subscription signatures (e.g.
<a href="hover.html">`hover`</a>)
</li>
<li>
create configurable events that only execute subscribers when
criteria passed during subscription are met (e.g.
<a href="touch.html#flick">`flick`</a> or
<a href="key.html">`key`</a>)
</li>
<li>
create events that encapsulate common UX patterns (e.g.
<a href="outside.html">`clickoutside`</a>)
</li>
<li>
create fun little easter eggs (e.g. <a
href="http://yuilibrary.com/gallery/show/event-konami">`konami`</a>)
</li>
<li>and more...</li>
</ul>
</div>
<h2>The hooks</h2>
<p>Synthetic events hook into the subscription binding and unbinding methods. Specifically:</p>
<ol>
<li>`node.on("eventName", ...)`, `Y.on("eventName", ...)`, <a href="index.html#one-time-subscriptions">and family</a></li>
<li>`node.delegate("eventName", ...)` or `Y.delegate("eventName", ...)`</li>
<li>`node.detach(...)` or `subscription.detach()`</li>
</ol>
<p>With the exception of a separate `detachDelegate()` method, the names used when defining synthetic events are the same as these basic methods.</p>
```
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
}
});
```
<h2>Subscriptions and Notifiers</h2>
<p>In addition to the subscribing Node, each method receives a
<em>subscription</em> and a <em>notifier</em>. Use the <em>subscription</em>
to store event handles or other data that may be needed by another method. Use
<em>`notifier.fire(e)`</em> to dispatch the event to the callbacks that were
bound to it.</p>
```
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) {
},
delegate: function (node, subscription, notifier, filter) { ... },
detachDelegate: function (node, subscription, notifier) { ... }
});
```
<p>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.</p>
```
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);
});
});
},
...
});
```
<h2>Delegation support</h2>
<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>
<p>Typically, it is just passed along to a `node.delegate(...)` call against another event, deferring the filtration to the core `delegate()` method.</p>
```
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) {
}
});
```
<h2>Extra Arguments</h2>
<p>Supply a `processArgs` method in the event definition to support a custom
subscription signature. The method receives two arguments:</p>
<ol>
<li>an array of the subscription arguments for analysis</li>
<li>
a boolean `true` if the subscription is being made through
`delegate(...)`
</li>
</ol>
<p>If this method is supplied, it</p>
<ul>
<li>
<strong>MUST</strong> remove the extra arguments from the arg array
that is passed in, and
</li>
<li>
<strong>SHOULD</strong> return the extra data relevant to the
subscription.
</li>
</ul>
<p>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.</p>
```
// 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);
}
```
<p>Requiring extra params start at index 3 of the `args` array results in the
following subscription signatures:</p>
```
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...);
```
<p>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
<a href="index.html#binding-this-and-additional-callback-arguments">the extended
signature for `on(...)` or `delegate(...)`</a>. See the <a
href="{{apiDocs}}/files/event_js_hover.js.html">source for the "hover"
event</a> for an example of supporting multiple signatures.</p>
<p>The return value of `processArgs` is assigned to `subscription._extras` for the `on` and `delegate` definition methods.</p>
```
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;
});
}
});
},
...
});
```
<p>Usage of this synthetic event then expects a third argument as a
configuration object with `minClicks` and `maxClicks` properties.</p>
```
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');
```
<h2>A Tip to Make Your Synth Definition Smaller</h2>
<p>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
<a href="{{apiDocs}}/files/event_js_hover.js.html">source for the "hover"
event</a> for an example of this approach.</p>