|
1 /* |
|
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
3 Code licensed under the BSD License: |
|
4 http://developer.yahoo.net/yui/license.txt |
|
5 version: 3.0.0 |
|
6 build: 1549 |
|
7 */ |
|
8 YUI.add('event-custom-complex', function(Y) { |
|
9 |
|
10 |
|
11 /** |
|
12 * Adds event facades, preventable default behavior, and bubbling. |
|
13 * events. |
|
14 * @module event-custom |
|
15 * @submodule event-custom-complex |
|
16 */ |
|
17 |
|
18 (function() { |
|
19 |
|
20 var FACADE, FACADE_KEYS, CEProto = Y.CustomEvent.prototype; |
|
21 |
|
22 /** |
|
23 * Wraps and protects a custom event for use when emitFacade is set to true. |
|
24 * Requires the event-custom-complex module |
|
25 * @class EventFacade |
|
26 * @param e {Event} the custom event |
|
27 * @param currentTarget {HTMLElement} the element the listener was attached to |
|
28 */ |
|
29 |
|
30 Y.EventFacade = function(e, currentTarget) { |
|
31 |
|
32 e = e || {}; |
|
33 |
|
34 /** |
|
35 * The arguments passed to fire |
|
36 * @property details |
|
37 * @type Array |
|
38 */ |
|
39 this.details = e.details; |
|
40 |
|
41 /** |
|
42 * The event type |
|
43 * @property type |
|
44 * @type string |
|
45 */ |
|
46 this.type = e.type; |
|
47 |
|
48 ////////////////////////////////////////////////////// |
|
49 |
|
50 /** |
|
51 * Node reference for the targeted eventtarget |
|
52 * @propery target |
|
53 * @type Node |
|
54 */ |
|
55 this.target = e.target; |
|
56 |
|
57 /** |
|
58 * Node reference for the element that the listener was attached to. |
|
59 * @propery currentTarget |
|
60 * @type Node |
|
61 */ |
|
62 this.currentTarget = currentTarget; |
|
63 |
|
64 /** |
|
65 * Node reference to the relatedTarget |
|
66 * @propery relatedTarget |
|
67 * @type Node |
|
68 */ |
|
69 this.relatedTarget = e.relatedTarget; |
|
70 |
|
71 /** |
|
72 * Stops the propagation to the next bubble target |
|
73 * @method stopPropagation |
|
74 */ |
|
75 this.stopPropagation = function() { |
|
76 e.stopPropagation(); |
|
77 }; |
|
78 |
|
79 /** |
|
80 * Stops the propagation to the next bubble target and |
|
81 * prevents any additional listeners from being exectued |
|
82 * on the current target. |
|
83 * @method stopImmediatePropagation |
|
84 */ |
|
85 this.stopImmediatePropagation = function() { |
|
86 e.stopImmediatePropagation(); |
|
87 }; |
|
88 |
|
89 /** |
|
90 * Prevents the event's default behavior |
|
91 * @method preventDefault |
|
92 */ |
|
93 this.preventDefault = function() { |
|
94 e.preventDefault(); |
|
95 }; |
|
96 |
|
97 /** |
|
98 * Stops the event propagation and prevents the default |
|
99 * event behavior. |
|
100 * @method halt |
|
101 * @param immediate {boolean} if true additional listeners |
|
102 * on the current target will not be executed |
|
103 */ |
|
104 this.halt = function(immediate) { |
|
105 e.halt(immediate); |
|
106 }; |
|
107 |
|
108 }; |
|
109 |
|
110 CEProto.fireComplex = function(args) { |
|
111 var es = Y.Env._eventstack, ef, q, queue, ce, ret, events; |
|
112 |
|
113 if (es) { |
|
114 // queue this event if the current item in the queue bubbles |
|
115 if (this.queuable && this.type != es.next.type) { |
|
116 this.log('queue ' + this.type); |
|
117 es.queue.push([this, args]); |
|
118 return true; |
|
119 } |
|
120 } else { |
|
121 Y.Env._eventstack = { |
|
122 // id of the first event in the stack |
|
123 id: this.id, |
|
124 next: this, |
|
125 silent: this.silent, |
|
126 stopped: 0, |
|
127 prevented: 0, |
|
128 queue: [] |
|
129 }; |
|
130 es = Y.Env._eventstack; |
|
131 } |
|
132 |
|
133 this.stopped = 0; |
|
134 this.prevented = 0; |
|
135 this.target = this.target || this.host; |
|
136 |
|
137 events = new Y.EventTarget({ |
|
138 fireOnce: true, |
|
139 context: this.host |
|
140 }); |
|
141 |
|
142 this.events = events; |
|
143 |
|
144 if (this.preventedFn) { |
|
145 events.on('prevented', this.preventedFn); |
|
146 } |
|
147 |
|
148 if (this.stoppedFn) { |
|
149 events.on('stopped', this.stoppedFn); |
|
150 } |
|
151 |
|
152 this.currentTarget = this.host || this.currentTarget; |
|
153 |
|
154 this.details = args.slice(); // original arguments in the details |
|
155 |
|
156 // this.log("Firing " + this + ", " + "args: " + args); |
|
157 this.log("Firing " + this.type); |
|
158 |
|
159 this._facade = null; // kill facade to eliminate stale properties |
|
160 |
|
161 ef = this._getFacade(args); |
|
162 |
|
163 if (Y.Lang.isObject(args[0])) { |
|
164 args[0] = ef; |
|
165 } else { |
|
166 args.unshift(ef); |
|
167 } |
|
168 |
|
169 if (this.hasSubscribers) { |
|
170 this._procSubs(Y.merge(this.subscribers), args, ef); |
|
171 } |
|
172 |
|
173 // bubble if this is hosted in an event target and propagation has not been stopped |
|
174 if (this.bubbles && this.host && this.host.bubble && !this.stopped) { |
|
175 es.stopped = 0; |
|
176 es.prevented = 0; |
|
177 ret = this.host.bubble(this); |
|
178 |
|
179 this.stopped = Math.max(this.stopped, es.stopped); |
|
180 this.prevented = Math.max(this.prevented, es.prevented); |
|
181 |
|
182 } |
|
183 |
|
184 // execute the default behavior if not prevented |
|
185 if (this.defaultFn && !this.prevented) { |
|
186 this.defaultFn.apply(this.host || this, args); |
|
187 } |
|
188 |
|
189 // broadcast listeners are fired as discreet events on the |
|
190 // YUI instance and potentially the YUI global. |
|
191 this._broadcast(args); |
|
192 |
|
193 // process after listeners. If the default behavior was |
|
194 // prevented, the after events don't fire. |
|
195 if (this.hasAfters && !this.prevented && this.stopped < 2) { |
|
196 this._procSubs(Y.merge(this.afters), args, ef); |
|
197 } |
|
198 |
|
199 if (es.id === this.id) { |
|
200 queue = es.queue; |
|
201 |
|
202 while (queue.length) { |
|
203 q = queue.pop(); |
|
204 ce = q[0]; |
|
205 es.stopped = 0; |
|
206 es.prevented = 0; |
|
207 // set up stack to allow the next item to be processed |
|
208 es.next = ce; |
|
209 ce.fire.apply(ce, q[1]); |
|
210 } |
|
211 |
|
212 Y.Env._eventstack = null; |
|
213 } |
|
214 |
|
215 return this.stopped ? false : true; |
|
216 }; |
|
217 |
|
218 CEProto._getFacade = function() { |
|
219 |
|
220 var ef = this._facade, o, o2, |
|
221 args = this.details; |
|
222 |
|
223 if (!ef) { |
|
224 ef = new Y.EventFacade(this, this.currentTarget); |
|
225 } |
|
226 |
|
227 // if the first argument is an object literal, apply the |
|
228 // properties to the event facade |
|
229 o = args && args[0]; |
|
230 |
|
231 if (Y.Lang.isObject(o, true)) { |
|
232 |
|
233 o2 = {}; |
|
234 |
|
235 // protect the event facade properties |
|
236 Y.mix(o2, ef, true, FACADE_KEYS); |
|
237 |
|
238 // mix the data |
|
239 Y.mix(ef, o, true); |
|
240 |
|
241 // restore ef |
|
242 Y.mix(ef, o2, true, FACADE_KEYS); |
|
243 } |
|
244 |
|
245 // update the details field with the arguments |
|
246 // ef.type = this.type; |
|
247 ef.details = this.details; |
|
248 ef.target = this.target; |
|
249 ef.currentTarget = this.currentTarget; |
|
250 ef.stopped = 0; |
|
251 ef.prevented = 0; |
|
252 |
|
253 this._facade = ef; |
|
254 |
|
255 return this._facade; |
|
256 }; |
|
257 |
|
258 /** |
|
259 * Stop propagation to bubble targets |
|
260 * @for CustomEvent |
|
261 * @method stopPropagation |
|
262 */ |
|
263 CEProto.stopPropagation = function() { |
|
264 this.stopped = 1; |
|
265 Y.Env._eventstack.stopped = 1; |
|
266 this.events.fire('stopped', this); |
|
267 }; |
|
268 |
|
269 /** |
|
270 * Stops propagation to bubble targets, and prevents any remaining |
|
271 * subscribers on the current target from executing. |
|
272 * @method stopImmediatePropagation |
|
273 */ |
|
274 CEProto.stopImmediatePropagation = function() { |
|
275 this.stopped = 2; |
|
276 Y.Env._eventstack.stopped = 2; |
|
277 this.events.fire('stopped', this); |
|
278 }; |
|
279 |
|
280 /** |
|
281 * Prevents the execution of this event's defaultFn |
|
282 * @method preventDefault |
|
283 */ |
|
284 CEProto.preventDefault = function() { |
|
285 if (this.preventable) { |
|
286 this.prevented = 1; |
|
287 Y.Env._eventstack.prevented = 1; |
|
288 this.events.fire('prevented', this); |
|
289 } |
|
290 }; |
|
291 |
|
292 /** |
|
293 * Stops the event propagation and prevents the default |
|
294 * event behavior. |
|
295 * @method halt |
|
296 * @param immediate {boolean} if true additional listeners |
|
297 * on the current target will not be executed |
|
298 */ |
|
299 CEProto.halt = function(immediate) { |
|
300 if (immediate) { |
|
301 this.stopImmediatePropagation(); |
|
302 } else { |
|
303 this.stopPropagation(); |
|
304 } |
|
305 this.preventDefault(); |
|
306 }; |
|
307 |
|
308 /** |
|
309 * Propagate an event. Requires the event-custom-complex module. |
|
310 * @method bubble |
|
311 * @param evt {Event.Custom} the custom event to propagate |
|
312 * @return {boolean} the aggregated return value from Event.Custom.fire |
|
313 * @for EventTarget |
|
314 */ |
|
315 Y.EventTarget.prototype.bubble = function(evt, args, target) { |
|
316 |
|
317 var targs = this._yuievt.targets, ret = true, |
|
318 t, type, ce, i, bc; |
|
319 |
|
320 if (!evt || ((!evt.stopped) && targs)) { |
|
321 |
|
322 for (i in targs) { |
|
323 if (targs.hasOwnProperty(i)) { |
|
324 t = targs[i]; |
|
325 type = evt && evt.type; |
|
326 ce = t.getEvent(type, true); |
|
327 |
|
328 // if this event was not published on the bubble target, |
|
329 // publish it with sensible default properties |
|
330 if (!ce) { |
|
331 |
|
332 if (t._yuievt.hasTargets) { |
|
333 t.bubble.call(t, evt, args, target); |
|
334 } |
|
335 |
|
336 } else { |
|
337 ce.target = target || (evt && evt.target) || this; |
|
338 ce.currentTarget = t; |
|
339 |
|
340 bc = ce.broadcast; |
|
341 ce.broadcast = false; |
|
342 ret = ret && ce.fire.apply(ce, args || evt.details); |
|
343 ce.broadcast = bc; |
|
344 |
|
345 // stopPropagation() was called |
|
346 if (ce.stopped) { |
|
347 break; |
|
348 } |
|
349 } |
|
350 } |
|
351 } |
|
352 } |
|
353 |
|
354 return ret; |
|
355 }; |
|
356 |
|
357 FACADE = new Y.EventFacade(); |
|
358 FACADE_KEYS = Y.Object.keys(FACADE); |
|
359 |
|
360 })(); |
|
361 |
|
362 |
|
363 }, '3.0.0' ,{requires:['event-custom-base']}); |