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
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<style scoped>
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff position: relative;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith margin-left: 100px;
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff position: absolute;
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff height: 150px;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith width: 200px;
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff height: 210px;
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff width: 180px;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith position: absolute;
af2a34339f1f67253636b38975ef84b03a553264Jeff Conniff outline-style: none;
b137e0059e04e5168cb5a731ee7b05e06ab89bb2Jeff Conniff opacity: 0.5;
b137e0059e04e5168cb5a731ee7b05e06ab89bb2Jeff Conniff filter: alpha(opacity=50);
b137e0059e04e5168cb5a731ee7b05e06ab89bb2Jeff Conniff .yui3-focused {
b137e0059e04e5168cb5a731ee7b05e06ab89bb2Jeff Conniff filter: alpha(opacity=100);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith left: 125px;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith #demo input {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith margin-left: 4em;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith #demo label {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith font-size: 87%;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith color: #555;
ef72940b6b98893cd35e5e70b9f233c07d5209cfJeff Conniff margin-top: 150px;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<div class="intro">
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith This example will illustrate how to use the synthetic event creation
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith API. We'll create an `arrow` event that fires in response
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith to the user pressing the arrow keys (up, down, left, right) and adds a
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith `direction` property to the generated event.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <p>Subscribing to this new event will look like this:</p>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith node.on("arrow", onArrowHandler);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Support will also be added for delegation, allowing a single subscriber
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith from a node higher up the DOM tree, to listen for the new event
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith emanating from its descendant elements.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith containerNode.delegate("arrow", onArrowHandler, ".robot");
819e90d415ed17d59af3a247b2ad9d6feb0c21b5Luke Smith<div class="example yui3-skin-sam">
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith {{>synth-example-source}}
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<h2>`on`, `fire`, and `detach`</h2>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith The three interesting moments in the lifecycle of a DOM event subscription
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <li>The event is subscribed to</li>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <li>The event is fired</li>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <li>The event is unsubscribed from</li>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Create a new synthetic DOM event with `Y.Event.define( <em>name</em>,
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <em>config</em> )`. Define the implementation logic for the
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith `on` and `detach` moments in the configuration.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Typically the condition triggering the event firing is set up in the
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith on: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // what happens when a subscription is made
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // if (condition) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith notifier.fire(); // subscribers executed
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith detach: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // what happens when a subscription is removed
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith In the case of arrow handling, the trigger is simply a key event with a
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith `keyCode` between 37 and 40. There are a few browser quirks with arrow
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith handling that warrant listening to `keydown` for some browsers and
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith `keypress` for others, so we'll take care of that transparently for `arrow`
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith subscribers.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith on: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith var directions = {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith 39: 'right',
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Webkit and IE repeat keydown when you hold down arrow keys.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Opera links keypress to page scroll; others keydown.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Firefox prevents page scroll via preventDefault() on either
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // keydown or keypress.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Bummer to sniff, but can't test the repeating behavior, and a
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // feature test for the scrolling would more than double the code size.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith var eventName = (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress';
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // To make detaching the associated DOM event easy, store the detach
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // handle from the DOM subscription on the synthethic subscription
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith sub._detacher = node.on(eventName, function (e) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Only notify subscribers if one of the arrow keys was pressed
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith if (directions[e.keyCode]) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Add the extra property
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Firing the notifier event executes the arrow subscribers
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Pass along the key event, which will be renamed "arrow"
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith detach: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Detach the key event subscription using the stored detach handle
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<h2>Add Delegation Support</h2>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Since the `arrow` event is simply a filtered `keydown` or `keypress` event,
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith no special handling needs to be done for delegate subscriptions. We will
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith extract the key event handler and use it for both `on("arrow", ...)` and
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith `delegate("arrow", ...)` subscriptions.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Webkit and IE repeat keydown when you hold down arrow keys.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Opera links keypress to page scroll; others keydown.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Firefox prevents page scroll via preventDefault() on either
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // keydown or keypress.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith '37': 'left',
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith '39': 'right',
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith '40': 'down'
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith _keyHandler: function (e, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith if (this._keys[e.keyCode]) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith on: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Use the extended subscription signature to set the 'this' object
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // in the callback and pass the notifier as a second parameter to
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // _keyHandler
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith sub._detacher = node.on(this._event, this._keyHandler,
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith this, notifier);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith detach: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Note the delegate handler receives a fourth parameter, the filter
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // passed (e.g.) container.delegate('click', callback, '.HERE');
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // The filter could be either a string or a function.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith delegate: function (node, sub, notifier, filter) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith sub._delegateDetacher = node.delegate(this._event, this._keyHandler,
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith filter, this, notifier);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // Delegate uses a separate detach function to facilitate undoing more
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // complex wiring created in the delegate logic above. Not needed here.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith detachDelegate: function (node, sub, notifier) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<h2>Use it</h2>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Subscribe to the new event or detach the event as you would any other DOM
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smithfunction move(e) {
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // to prevent page scrolling
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith // See full code listing to show the data set up
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith case 'up': xy.y -= 10; break;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith case 'down': xy.y += 10; break;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith case 'left': xy.x -= 10; break;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith case 'right': xy.x += 10; break;
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith top : (xy.y + 'px'),
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith left: (xy.x + 'px'),
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith duration: .2
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith// Subscribe using node.on("arrow", ...);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke SmithY.one("#A").on("arrow", move),
8966f18bc05afef70f40b84ee4179f0df30e219dLuke SmithY.one("#B").on("arrow", move)
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith// OR using container.delegate("arrow", ...);
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smithsubs = Y.one('#demo').delegate('arrow', move, '.robot');
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<h2>Bonus Step: to the Gallery!</h2>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith Synthetic events are perfect candidates for Gallery modules. There are a
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith number already hosted there, and there are plenty of UI interaction
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith patterns that would benefit from being encapsulated in synthetic
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith The `arrow` event in this example is also
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <a href="http://yuilibrary.com/gallery/show/event-arrow">in the gallery</a>,
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith but with additional functionality. Check out
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith <a href="https://github.com/lsmith/yui3-gallery/blob/master/build/gallery-event-arrow/gallery-event-arrow-debug.js">its source</a>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith to see what you can do with synthetic events.
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith<h2>Full Code Listing</h2>
8966f18bc05afef70f40b84ee4179f0df30e219dLuke Smith{{>synth-example-source}}