]> git.proxmox.com Git - extjs.git/blame - extjs/packages/ux/src/event/Player.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / ux / src / event / Player.js
CommitLineData
6527f429
DM
1/**\r
2 * @extends Ext.ux.event.Driver\r
3 * This class manages the playback of an array of "event descriptors". For details on the\r
4 * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the\r
5 * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.\r
6 * \r
7 * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call\r
8 * {@link #method-start}. Like so:\r
9 *\r
10 * var player = Ext.create('Ext.ux.event.Player', {\r
11 * eventQueue: [ ... ],\r
12 * speed: 2, // play at 2x speed\r
13 * listeners: {\r
14 * stop: function () {\r
15 * player = null; // all done\r
16 * }\r
17 * }\r
18 * });\r
19 *\r
20 * player.start();\r
21 *\r
22 * A more complex use would be to incorporate keyframe generation after playing certain\r
23 * events.\r
24 *\r
25 * var player = Ext.create('Ext.ux.event.Player', {\r
26 * eventQueue: [ ... ],\r
27 * keyFrameEvents: {\r
28 * click: true\r
29 * },\r
30 * listeners: {\r
31 * stop: function () {\r
32 * // play has completed... probably time for another keyframe...\r
33 * player = null;\r
34 * },\r
35 * keyframe: onKeyFrame\r
36 * }\r
37 * });\r
38 *\r
39 * player.start();\r
40 *\r
41 * If a keyframe can be handled immediately (synchronously), the listener would be:\r
42 *\r
43 * function onKeyFrame () {\r
44 * handleKeyFrame();\r
45 * }\r
46 *\r
47 * If the keyframe event is always handled asynchronously, then the event listener is only\r
48 * a bit more:\r
49 *\r
50 * function onKeyFrame (p, eventDescriptor) {\r
51 * eventDescriptor.defer(); // pause event playback...\r
52 *\r
53 * handleKeyFrame(function () {\r
54 * eventDescriptor.finish(); // ...resume event playback\r
55 * });\r
56 * }\r
57 *\r
58 * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps\r
59 * differently by browser), a slightly more complex listener is required.\r
60 *\r
61 * function onKeyFrame (p, eventDescriptor) {\r
62 * var async;\r
63 *\r
64 * handleKeyFrame(function () {\r
65 * // either this callback is being called immediately by handleKeyFrame (in\r
66 * // which case async is undefined) or it is being called later (in which case\r
67 * // async will be true).\r
68 *\r
69 * if (async) {\r
70 * eventDescriptor.finish();\r
71 * } else {\r
72 * async = false;\r
73 * }\r
74 * });\r
75 *\r
76 * // either the callback was called (and async is now false) or it was not\r
77 * // called (and async remains undefined).\r
78 *\r
79 * if (async !== false) {\r
80 * eventDescriptor.defer();\r
81 * async = true; // let the callback know that we have gone async\r
82 * }\r
83 * }\r
84 */\r
85Ext.define('Ext.ux.event.Player', function (Player) {\r
86var defaults = {},\r
87 mouseEvents = {},\r
88 keyEvents = {},\r
89 doc,\r
90 \r
91 //HTML events supported\r
92 uiEvents = {},\r
93 \r
94 //events that bubble by default\r
95 bubbleEvents = {\r
96 //scroll: 1,\r
97 resize: 1,\r
98 reset: 1,\r
99 submit: 1,\r
100 change: 1,\r
101 select: 1,\r
102 error: 1,\r
103 abort: 1\r
104 };\r
105\r
106Ext.each([ 'click', 'dblclick', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 'mousemove' ],\r
107 function (type) {\r
108 bubbleEvents[type] = defaults[type] = mouseEvents[type] = {\r
109 bubbles: true,\r
110 cancelable: (type != "mousemove"), // mousemove cannot be cancelled\r
111 detail: 1,\r
112 screenX: 0,\r
113 screenY: 0,\r
114 clientX: 0,\r
115 clientY: 0,\r
116 ctrlKey: false,\r
117 altKey: false,\r
118 shiftKey: false,\r
119 metaKey: false,\r
120 button: 0\r
121 };\r
122 });\r
123\r
124Ext.each([ 'keydown', 'keyup', 'keypress' ],\r
125 function (type) {\r
126 bubbleEvents[type] = defaults[type] = keyEvents[type] = {\r
127 bubbles: true,\r
128 cancelable: true,\r
129 ctrlKey: false,\r
130 altKey: false,\r
131 shiftKey: false,\r
132 metaKey: false,\r
133 keyCode: 0,\r
134 charCode: 0\r
135 };\r
136 });\r
137\r
138Ext.each([ 'blur', 'change', 'focus', 'resize', 'scroll', 'select' ],\r
139 function (type) {\r
140 defaults[type] = uiEvents[type] = {\r
141 bubbles: (type in bubbleEvents),\r
142 cancelable: false,\r
143 detail: 1\r
144 };\r
145 });\r
146\r
147var inputSpecialKeys = {\r
148 8: function (target, start, end) { // backspace: 8,\r
149 if (start < end) {\r
150 target.value = target.value.substring(0, start) +\r
151 target.value.substring(end);\r
152 } else if (start > 0) {\r
153 target.value = target.value.substring(0, --start) +\r
154 target.value.substring(end);\r
155 }\r
156 this.setTextSelection(target, start, start);\r
157 },\r
158 46: function (target, start, end) { // delete: 46\r
159 if (start < end) {\r
160 target.value = target.value.substring(0, start) +\r
161 target.value.substring(end);\r
162 } else if (start < target.value.length - 1) {\r
163 target.value = target.value.substring(0, start) +\r
164 target.value.substring(start+1);\r
165 }\r
166 this.setTextSelection(target, start, start);\r
167 }\r
168 };\r
169\r
170return {\r
171 extend: 'Ext.ux.event.Driver',\r
172\r
173 /**\r
174 * @cfg {Array} eventQueue The event queue to playback. This must be provided before\r
175 * the {@link #method-start} method is called.\r
176 */\r
177\r
178 /**\r
179 * @cfg {Object} keyFrameEvents An object that describes the events that should generate\r
180 * keyframe events. For example, `{ click: true }` would generate keyframe events after\r
181 * each `click` event.\r
182 */\r
183 keyFrameEvents: {\r
184 click: true\r
185 },\r
186\r
187 /**\r
188 * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false\r
189 * to ignore animations. Default is true.\r
190 */\r
191 pauseForAnimations: true,\r
192\r
193 /**\r
194 * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the\r
195 * recorded speed). A value of 2 would playback at 2x speed.\r
196 */\r
197 speed: 1.0,\r
198\r
199 stallTime: 0,\r
200 \r
201 _inputSpecialKeys: {\r
202 INPUT: inputSpecialKeys,\r
203\r
204 TEXTAREA: Ext.apply({\r
205 //13: function (target, start, end) { // enter: 8,\r
206 //TODO ?\r
207 //}\r
208 }, inputSpecialKeys)\r
209 },\r
210\r
211 tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,\r
212 \r
213 /**\r
214 * @event beforeplay\r
215 * Fires before an event is played.\r
216 * @param {Ext.ux.event.Player} this\r
217 * @param {Object} eventDescriptor The event descriptor about to be played.\r
218 */\r
219\r
220 /**\r
221 * @event keyframe\r
222 * Fires when this player reaches a keyframe. Typically, this is after events\r
223 * like `click` are injected and any resulting animations have been completed.\r
224 * @param {Ext.ux.event.Player} this\r
225 * @param {Object} eventDescriptor The keyframe event descriptor.\r
226 */\r
227\r
228 constructor: function (config) {\r
229 var me = this;\r
230 \r
231 me.callParent(arguments);\r
232\r
233 me.timerFn = function () {\r
234 me.onTick();\r
235 };\r
236 me.attachTo = me.attachTo || window;\r
237\r
238 doc = me.attachTo.document;\r
239 },\r
240\r
241 /**\r
242 * Returns the element given is XPath-like description.\r
243 * @param {String} xpath The XPath-like description of the element.\r
244 * @return {HTMLElement}\r
245 */\r
246 getElementFromXPath: function (xpath) {\r
247 var me = this,\r
248 parts = xpath.split('/'),\r
249 regex = me.tagPathRegEx,\r
250 i, n, m, count, tag, child,\r
251 el = me.attachTo.document;\r
252\r
253 el = (parts[0] == '~') ? el.body\r
254 : el.getElementById(parts[0].substring(1)); // remove '#'\r
255\r
256 for (i = 1, n = parts.length; el && i < n; ++i) {\r
257 m = regex.exec(parts[i]);\r
258 count = m[2] ? parseInt(m[2], 10) : 1;\r
259 tag = m[1].toUpperCase();\r
260\r
261 for (child = el.firstChild; child; child = child.nextSibling) {\r
262 if (child.tagName == tag) {\r
263 if (count == 1) {\r
264 break;\r
265 }\r
266 --count;\r
267 }\r
268 }\r
269\r
270 el = child;\r
271 }\r
272\r
273 return el;\r
274 },\r
275\r
276 // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in\r
277 // the textarea value is two characters. This function corrects for that by converting a text offset into a\r
278 // range character offset by subtracting one character for every line break in the textarea prior to the\r
279 // offset\r
280 offsetToRangeCharacterMove: function(el, offset) {\r
281 return offset - (el.value.slice(0, offset).split("\r\n").length - 1);\r
282 },\r
283\r
284 setTextSelection: function (el, startOffset, endOffset) {\r
285 // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js\r
286 if (startOffset < 0) {\r
287 startOffset += el.value.length;\r
288 }\r
289 if (endOffset == null) {\r
290 endOffset = startOffset;\r
291 }\r
292 if (endOffset < 0) {\r
293 endOffset += el.value.length;\r
294 }\r
295\r
296 if (typeof el.selectionStart === "number") {\r
297 el.selectionStart = startOffset;\r
298 el.selectionEnd = endOffset;\r
299 } else {\r
300 var range = el.createTextRange();\r
301 var startCharMove = this.offsetToRangeCharacterMove(el, startOffset);\r
302\r
303 range.collapse(true);\r
304\r
305 if (startOffset == endOffset) {\r
306 range.move("character", startCharMove);\r
307 } else {\r
308 range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));\r
309 range.moveStart("character", startCharMove);\r
310 }\r
311\r
312 range.select();\r
313 }\r
314 },\r
315\r
316 getTimeIndex: function () {\r
317 var t = this.getTimestamp() - this.stallTime;\r
318 return t * this.speed;\r
319 },\r
320\r
321 makeToken: function (eventDescriptor, signal) {\r
322 var me = this,\r
323 t0;\r
324\r
325 eventDescriptor[signal] = true;\r
326\r
327 eventDescriptor.defer = function () {\r
328 eventDescriptor[signal] = false;\r
329 t0 = me.getTime();\r
330 };\r
331\r
332 eventDescriptor.finish = function () {\r
333 eventDescriptor[signal] = true;\r
334 me.stallTime += me.getTime() - t0;\r
335\r
336 me.schedule();\r
337 };\r
338 },\r
339\r
340 /**\r
341 * This method is called after an event has been played to prepare for the next event.\r
342 * @param {Object} eventDescriptor The descriptor of the event just played.\r
343 */\r
344 nextEvent: function (eventDescriptor) {\r
345 var me = this,\r
346 index = ++me.queueIndex;\r
347\r
348 // keyframe events are inserted after a keyFrameEvent is played.\r
349 if (me.keyFrameEvents[eventDescriptor.type]) {\r
350 Ext.Array.insert(me.eventQueue, index, [\r
351 {keyframe: true, ts: eventDescriptor.ts}\r
352 ]);\r
353 }\r
354 },\r
355\r
356 /**\r
357 * This method returns the event descriptor at the front of the queue. This does not\r
358 * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}\r
359 * is called).\r
360 */\r
361 peekEvent: function () {\r
362 return this.eventQueue[this.queueIndex] || null;\r
363 },\r
364\r
365 /**\r
366 * Replaces an event in the queue with an array of events. This is often used to roll\r
367 * up a multi-step pseudo-event and expand it just-in-time to be played. The process\r
368 * for doing this in a derived class would be this:\r
369 * \r
370 * Ext.define('My.Player', {\r
371 * extend: 'Ext.ux.event.Player',\r
372 *\r
373 * peekEvent: function () {\r
374 * var event = this.callParent();\r
375 *\r
376 * if (event.multiStepSpecial) {\r
377 * this.replaceEvent(null, [\r
378 * ... expand to actual events\r
379 * ]);\r
380 *\r
381 * event = this.callParent(); // get the new next event\r
382 * }\r
383 *\r
384 * return event;\r
385 * }\r
386 * });\r
387 * \r
388 * This method ensures that the `beforeplay` hook (if any) from the replaced event is\r
389 * placed on the first new event and the `afterplay` hook (if any) is placed on the\r
390 * last new event.\r
391 * \r
392 * @param {Number} index The queue index to replace. Pass `null` to replace the event\r
393 * at the current `queueIndex`.\r
394 * @param {Event[]} events The array of events with which to replace the specified\r
395 * event.\r
396 */\r
397 replaceEvent: function (index, events) {\r
398 for (var t, i = 0, n = events.length; i < n; ++i) {\r
399 if (i) {\r
400 t = events[i-1];\r
401 delete t.afterplay;\r
402 delete t.screenshot;\r
403\r
404 delete events[i].beforeplay;\r
405 }\r
406 }\r
407\r
408 Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index,\r
409 1, events);\r
410 },\r
411\r
412 /**\r
413 * This method dequeues and injects events until it has arrived at the time index. If\r
414 * no events are ready (based on the time index), this method does nothing.\r
415 * @return {Boolean} True if there is more to do; false if not (at least for now).\r
416 */\r
417 processEvents: function () {\r
418 var me = this,\r
419 animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,\r
420 eventDescriptor;\r
421\r
422 while ((eventDescriptor = me.peekEvent()) !== null) {\r
423 if (animations && animations.getCount()) {\r
424 return true;\r
425 }\r
426 \r
427 if (eventDescriptor.keyframe) {\r
428 if (!me.processKeyFrame(eventDescriptor)) {\r
429 return false;\r
430 }\r
431 me.nextEvent(eventDescriptor);\r
432 } else if (eventDescriptor.ts <= me.getTimeIndex() &&\r
433 me.fireEvent('beforeplay', me, eventDescriptor) !== false &&\r
434 me.playEvent(eventDescriptor)) {\r
435 me.nextEvent(eventDescriptor);\r
436 } else {\r
437 return true;\r
438 }\r
439 }\r
440\r
441 me.stop();\r
442 return false;\r
443 },\r
444\r
445 /**\r
446 * This method is called when a keyframe is reached. This will fire the keyframe event.\r
447 * If the keyframe has been handled, true is returned. Otherwise, false is returned.\r
448 * @param {Object} eventDescriptor The event descriptor of the keyframe.\r
449 * @return {Boolean} True if the keyframe was handled, false if not.\r
450 */\r
451 processKeyFrame: function (eventDescriptor) {\r
452 var me = this;\r
453\r
454 // only fire keyframe event (and setup the eventDescriptor) once...\r
455 if (!eventDescriptor.defer) {\r
456 me.makeToken(eventDescriptor, 'done');\r
457 me.fireEvent('keyframe', me, eventDescriptor);\r
458 }\r
459\r
460 return eventDescriptor.done;\r
461 },\r
462\r
463 /**\r
464 * Called to inject the given event on the specified target.\r
465 * @param {HTMLElement} target The target of the event.\r
466 * @param {Object} event The event to inject. The properties of this object should be\r
467 * those of standard DOM events but vary based on the `type` property. For details on\r
468 * event types and their properties, see the class documentation.\r
469 */\r
470 injectEvent: function (target, event) {\r
471 var me = this,\r
472 type = event.type,\r
473 options = Ext.apply({}, event, defaults[type]),\r
474 handler;\r
475\r
476 if (type === 'type') {\r
477 handler = me._inputSpecialKeys[target.tagName];\r
478 if (handler) {\r
479 return me.injectTypeInputEvent(target, event, handler);\r
480 }\r
481 return me.injectTypeEvent(target, event);\r
482 }\r
483 if (type === 'focus' && target.focus) {\r
484 target.focus();\r
485 return true;\r
486 }\r
487 if (type === 'blur' && target.blur) {\r
488 target.blur();\r
489 return true;\r
490 }\r
491 if (type === 'scroll') {\r
492 target.scrollLeft = event.pos[0];\r
493 target.scrollTop = event.pos[1];\r
494 return true;\r
495 }\r
496 if (type === 'mduclick') {\r
497 return me.injectEvent(target, Ext.applyIf({ type: 'mousedown' }, event)) &&\r
498 me.injectEvent(target, Ext.applyIf({ type: 'mouseup' }, event)) &&\r
499 me.injectEvent(target, Ext.applyIf({ type: 'click' }, event));\r
500 }\r
501\r
502 if (mouseEvents[type]){\r
503 return Player.injectMouseEvent(target, options, me.attachTo);\r
504 }\r
505\r
506 if (keyEvents[type]){\r
507 return Player.injectKeyEvent(target, options, me.attachTo);\r
508 }\r
509\r
510 if (uiEvents[type]){\r
511 return Player.injectUIEvent(target, type,\r
512 options.bubbles,\r
513 options.cancelable,\r
514 options.view || me.attachTo,\r
515 options.detail);\r
516 }\r
517\r
518 return false;\r
519 },\r
520\r
521 injectTypeEvent: function (target, event) {\r
522 var me = this,\r
523 text = event.text,\r
524 xlat = [],\r
525 ch, chUp, i, n, sel, upper, isInput;\r
526\r
527 if (text) {\r
528 delete event.text;\r
529 upper = text.toUpperCase();\r
530\r
531 for (i = 0, n = text.length; i < n; ++i) {\r
532 ch = text.charCodeAt(i);\r
533 chUp = upper.charCodeAt(i);\r
534\r
535 xlat.push(\r
536 Ext.applyIf({type: 'keydown', charCode: chUp, keyCode: chUp}, event),\r
537 Ext.applyIf({type: 'keypress', charCode: ch, keyCode: ch}, event),\r
538 Ext.applyIf({type: 'keyup', charCode: chUp, keyCode: chUp}, event)\r
539 );\r
540 }\r
541 } else {\r
542 xlat.push(\r
543 Ext.applyIf({type: 'keydown', charCode: event.keyCode}, event),\r
544 Ext.applyIf({type: 'keyup', charCode: event.keyCode}, event)\r
545 );\r
546 }\r
547\r
548 for (i = 0, n = xlat.length; i < n; ++i) {\r
549 me.injectEvent(target, xlat[i]);\r
550 }\r
551\r
552 return true;\r
553 },\r
554\r
555 injectTypeInputEvent: function (target, event, handler) {\r
556 var me = this,\r
557 text = event.text,\r
558 sel, n;\r
559\r
560 if (handler) {\r
561 sel = me.getTextSelection(target);\r
562\r
563 if (text) {\r
564 n = sel[0];\r
565 target.value = target.value.substring(0, n) + text +\r
566 target.value.substring(sel[1]);\r
567 n += text.length;\r
568 me.setTextSelection(target, n, n);\r
569 } else {\r
570 if (!(handler = handler[event.keyCode])) {\r
571 // no handler for the special key for this element\r
572 if ('caret' in event) {\r
573 me.setTextSelection(target, event.caret, event.caret);\r
574 } else if (event.selection) {\r
575 me.setTextSelection(target, event.selection[0], event.selection[1]);\r
576 }\r
577 return me.injectTypeEvent(target, event);\r
578 }\r
579 handler.call(this, target, sel[0], sel[1]);\r
580 return true;\r
581 }\r
582 }\r
583\r
584 return true;\r
585 },\r
586\r
587 playEvent: function (eventDescriptor) {\r
588 var me = this,\r
589 target = me.getElementFromXPath(eventDescriptor.target),\r
590 event;\r
591\r
592 if (!target) {\r
593 // not present (yet)... wait for element present...\r
594 // TODO - need a timeout here\r
595 return false;\r
596 }\r
597\r
598 if (!me.playEventHook(eventDescriptor, 'beforeplay')) {\r
599 return false;\r
600 }\r
601\r
602 if (!eventDescriptor.injected) {\r
603 eventDescriptor.injected = true;\r
604 event = me.translateEvent(eventDescriptor, target);\r
605 me.injectEvent(target, event);\r
606 }\r
607\r
608 return me.playEventHook(eventDescriptor, 'afterplay');\r
609 },\r
610\r
611 playEventHook: function (eventDescriptor, hookName) {\r
612 var me = this,\r
613 doneName = hookName + '.done',\r
614 firedName = hookName + '.fired',\r
615 hook = eventDescriptor[hookName];\r
616\r
617 if (hook && !eventDescriptor[doneName]) {\r
618 if (!eventDescriptor[firedName]) {\r
619 eventDescriptor[firedName] = true;\r
620 me.makeToken(eventDescriptor, doneName);\r
621\r
622 if (me.eventScope && Ext.isString(hook)) {\r
623 hook = me.eventScope[hook];\r
624 }\r
625\r
626 if (hook) {\r
627 hook.call(me.eventScope || me, eventDescriptor);\r
628 }\r
629 }\r
630 return false;\r
631 }\r
632\r
633 return true;\r
634 },\r
635\r
636 schedule: function () {\r
637 var me = this;\r
638 if (!me.timer) {\r
639 me.timer = setTimeout(me.timerFn, 10);\r
640 }\r
641 },\r
642\r
643 _translateAcross: [\r
644 'type',\r
645 'button',\r
646 'charCode',\r
647 'keyCode',\r
648 'caret',\r
649 'pos',\r
650 'text',\r
651 'selection'\r
652 ],\r
653\r
654 translateEvent: function (eventDescriptor, target) {\r
655 var me = this,\r
656 event = {},\r
657 modKeys = eventDescriptor.modKeys || '',\r
658 names = me._translateAcross,\r
659 i = names.length,\r
660 name, xy;\r
661\r
662 while (i--) {\r
663 name = names[i];\r
664 if (name in eventDescriptor) {\r
665 event[name] = eventDescriptor[name];\r
666 }\r
667 }\r
668\r
669 event.altKey = modKeys.indexOf('A') > 0;\r
670 event.ctrlKey = modKeys.indexOf('C') > 0;\r
671 event.metaKey = modKeys.indexOf('M') > 0;\r
672 event.shiftKey = modKeys.indexOf('S') > 0;\r
673\r
674 if (target && 'x' in eventDescriptor) {\r
675 xy = Ext.fly(target).getXY();\r
676 xy[0] += eventDescriptor.x;\r
677 xy[1] += eventDescriptor.y;\r
678 } else if ('x' in eventDescriptor) {\r
679 xy = [ eventDescriptor.x, eventDescriptor.y ];\r
680 } else if ('px' in eventDescriptor) {\r
681 xy = [ eventDescriptor.px, eventDescriptor.py ];\r
682 }\r
683\r
684 if (xy) {\r
685 event.clientX = event.screenX = xy[0];\r
686 event.clientY = event.screenY = xy[1];\r
687 }\r
688\r
689 if (eventDescriptor.key) {\r
690 event.keyCode = me.specialKeysByName[eventDescriptor.key];\r
691 }\r
692\r
693 if (eventDescriptor.type === 'wheel') {\r
694 if ('onwheel' in me.attachTo.document) {\r
695 event.wheelX = eventDescriptor.dx;\r
696 event.wheelY = eventDescriptor.dy;\r
697 } else {\r
698 event.type = 'mousewheel';\r
699 event.wheelDeltaX = -40 * eventDescriptor.dx;\r
700 event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;\r
701 }\r
702 }\r
703 \r
704 return event;\r
705 },\r
706\r
707 //---------------------------------\r
708 // Driver overrides\r
709\r
710 onStart: function () {\r
711 var me = this;\r
712\r
713 me.queueIndex = 0;\r
714 me.schedule();\r
715 },\r
716\r
717 onStop: function () {\r
718 var me = this;\r
719\r
720 if (me.timer) {\r
721 clearTimeout(me.timer);\r
722 me.timer = null;\r
723 }\r
724 },\r
725\r
726 //---------------------------------\r
727\r
728 onTick: function () {\r
729 var me = this;\r
730\r
731 me.timer = null;\r
732\r
733 if (me.processEvents()) {\r
734 me.schedule();\r
735 }\r
736 },\r
737\r
738 statics: {\r
739 ieButtonCodeMap: {\r
740 0: 1,\r
741 1: 4,\r
742 2: 2\r
743 },\r
744\r
745 /**\r
746 * Injects a key event using the given event information to populate the event\r
747 * object.\r
748 * \r
749 * **Note:** `keydown` causes Safari 2.x to crash.\r
750 * \r
751 * @param {HTMLElement} target The target of the given event.\r
752 * @param {Object} options Object object containing all of the event injection\r
753 * options.\r
754 * @param {String} options.type The type of event to fire. This can be any one of\r
755 * the following: `keyup`, `keydown` and `keypress`.\r
756 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.\r
757 * DOM Level 3 specifies that all key events bubble by default.\r
758 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled\r
759 * using `preventDefault`. DOM Level 3 specifies that all key events can be\r
760 * cancelled.\r
761 * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is\r
762 * pressed while the event is firing.\r
763 * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is\r
764 * pressed while the event is firing.\r
765 * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is\r
766 * pressed while the event is firing.\r
767 * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is\r
768 * pressed while the event is firing.\r
769 * @param {Number} [options.keyCode=0] The code for the key that is in use.\r
770 * @param {Number} [options.charCode=0] The Unicode code for the character \r
771 * associated with the key being used.\r
772 * @param {Window} [view=window] The view containing the target. This is typically\r
773 * the window object.\r
774 * @private\r
775 */\r
776 injectKeyEvent: function (target, options, view) {\r
777 var type = options.type,\r
778 customEvent = null;\r
779\r
780 if (type === 'textevent') {\r
781 type = 'keypress';\r
782 }\r
783 view = view || window;\r
784\r
785 //check for DOM-compliant browsers first\r
786 if (doc.createEvent){\r
787 try {\r
788 customEvent = doc.createEvent("KeyEvents");\r
789\r
790 // Interesting problem: Firefox implemented a non-standard\r
791 // version of initKeyEvent() based on DOM Level 2 specs.\r
792 // Key event was removed from DOM Level 2 and re-introduced\r
793 // in DOM Level 3 with a different interface. Firefox is the\r
794 // only browser with any implementation of Key Events, so for\r
795 // now, assume it's Firefox if the above line doesn't error.\r
796\r
797 // @TODO: Decipher between Firefox's implementation and a correct one.\r
798 customEvent.initKeyEvent(type, options.bubbles, options.cancelable,\r
799 view, options.ctrlKey, options.altKey, options.shiftKey,\r
800 options.metaKey, options.keyCode, options.charCode);\r
801\r
802 } catch (ex) {\r
803 // If it got here, that means key events aren't officially supported. \r
804 // Safari/WebKit is a real problem now. WebKit 522 won't let you\r
805 // set keyCode, charCode, or other properties if you use a\r
806 // UIEvent, so we first must try to create a generic event. The\r
807 // fun part is that this will throw an error on Safari 2.x. The\r
808 // end result is that we need another try...catch statement just to\r
809 // deal with this mess.\r
810\r
811 try {\r
812 //try to create generic event - will fail in Safari 2.x\r
813 customEvent = doc.createEvent("Events");\r
814\r
815 } catch (uierror) {\r
816 //the above failed, so create a UIEvent for Safari 2.x\r
817 customEvent = doc.createEvent("UIEvents");\r
818\r
819 } finally {\r
820 customEvent.initEvent(type, options.bubbles, options.cancelable);\r
821\r
822 customEvent.view = view;\r
823 customEvent.altKey = options.altKey;\r
824 customEvent.ctrlKey = options.ctrlKey;\r
825 customEvent.shiftKey = options.shiftKey;\r
826 customEvent.metaKey = options.metaKey;\r
827 customEvent.keyCode = options.keyCode;\r
828 customEvent.charCode = options.charCode;\r
829 } \r
830 }\r
831\r
832 target.dispatchEvent(customEvent);\r
833\r
834 } else if (doc.createEventObject) { //IE\r
835 customEvent = doc.createEventObject();\r
836\r
837 customEvent.bubbles = options.bubbles;\r
838 customEvent.cancelable = options.cancelable;\r
839 customEvent.view = view;\r
840 customEvent.ctrlKey = options.ctrlKey;\r
841 customEvent.altKey = options.altKey;\r
842 customEvent.shiftKey = options.shiftKey;\r
843 customEvent.metaKey = options.metaKey;\r
844\r
845 // IE doesn't support charCode explicitly. CharCode should\r
846 // take precedence over any keyCode value for accurate\r
847 // representation.\r
848\r
849 customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;\r
850\r
851 target.fireEvent("on" + type, customEvent); \r
852\r
853 } else {\r
854 return false;\r
855 }\r
856\r
857 return true;\r
858 },\r
859\r
860 /**\r
861 * Injects a mouse event using the given event information to populate the event\r
862 * object.\r
863 *\r
864 * @param {HTMLElement} target The target of the given event.\r
865 * @param {Object} options Object object containing all of the event injection\r
866 * options.\r
867 * @param {String} options.type The type of event to fire. This can be any one of\r
868 * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,\r
869 * `mouseover` and `mousemove`.\r
870 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.\r
871 * DOM Level 2 specifies that all mouse events bubble by default.\r
872 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled\r
873 * using `preventDefault`. DOM Level 2 specifies that all mouse events except\r
874 * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.\r
875 * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is\r
876 * pressed while the event is firing.\r
877 * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is\r
878 * pressed while the event is firing.\r
879 * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is\r
880 * pressed while the event is firing.\r
881 * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is\r
882 * pressed while the event is firing.\r
883 * @param {Number} [options.detail=1] The number of times the mouse button has \r
884 * been used.\r
885 * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point\r
886 * the event occurred.\r
887 * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point\r
888 * the event occurred.\r
889 * @param {Number} [options.clientX=0] The x-coordinate on the client at which point\r
890 * the event occurred.\r
891 * @param {Number} [options.clientY=0] The y-coordinate on the client at which point\r
892 * the event occurred.\r
893 * @param {Number} [options.button=0] The button being pressed while the event is\r
894 * executing. The value should be 0 for the primary mouse button (typically the\r
895 * left button), 1 for the tertiary mouse button (typically the middle button),\r
896 * and 2 for the secondary mouse button (typically the right button).\r
897 * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this\r
898 * is the element that the mouse has moved to. For `mouseover` events, this is\r
899 * the element that the mouse has moved from. This argument is ignored for all\r
900 * other events.\r
901 * @param {Window} [view=window] The view containing the target. This is typically\r
902 * the window object.\r
903 * @private\r
904 */\r
905 injectMouseEvent: function (target, options, view) {\r
906 var type = options.type,\r
907 customEvent = null;\r
908\r
909 view = view || window;\r
910\r
911 //check for DOM-compliant browsers first\r
912 if (doc.createEvent){\r
913 customEvent = doc.createEvent("MouseEvents");\r
914\r
915 //Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()\r
916 if (customEvent.initMouseEvent){\r
917 customEvent.initMouseEvent(type, options.bubbles, options.cancelable,\r
918 view, options.detail, options.screenX, options.screenY,\r
919 options.clientX, options.clientY, options.ctrlKey,\r
920 options.altKey, options.shiftKey, options.metaKey,\r
921 options.button, options.relatedTarget);\r
922 } else { //Safari\r
923 //the closest thing available in Safari 2.x is UIEvents\r
924 customEvent = doc.createEvent("UIEvents");\r
925\r
926 customEvent.initEvent(type, options.bubbles, options.cancelable);\r
927\r
928 customEvent.view = view;\r
929 customEvent.detail = options.detail;\r
930 customEvent.screenX = options.screenX;\r
931 customEvent.screenY = options.screenY;\r
932 customEvent.clientX = options.clientX;\r
933 customEvent.clientY = options.clientY;\r
934 customEvent.ctrlKey = options.ctrlKey;\r
935 customEvent.altKey = options.altKey;\r
936 customEvent.metaKey = options.metaKey;\r
937 customEvent.shiftKey = options.shiftKey;\r
938 customEvent.button = options.button;\r
939 customEvent.relatedTarget = options.relatedTarget;\r
940 }\r
941\r
942 /*\r
943 * Check to see if relatedTarget has been assigned. Firefox\r
944 * versions less than 2.0 don't allow it to be assigned via\r
945 * initMouseEvent() and the property is readonly after event\r
946 * creation, so in order to keep YAHOO.util.getRelatedTarget()\r
947 * working, assign to the IE proprietary toElement property\r
948 * for mouseout event and fromElement property for mouseover\r
949 * event.\r
950 */\r
951 if (options.relatedTarget && !customEvent.relatedTarget){\r
952 if (type == "mouseout"){\r
953 customEvent.toElement = options.relatedTarget;\r
954 } else if (type == "mouseover"){\r
955 customEvent.fromElement = options.relatedTarget;\r
956 }\r
957 }\r
958\r
959 target.dispatchEvent(customEvent);\r
960\r
961 } else if (doc.createEventObject) { //IE\r
962 customEvent = doc.createEventObject();\r
963\r
964 customEvent.bubbles = options.bubbles;\r
965 customEvent.cancelable = options.cancelable;\r
966 customEvent.view = view;\r
967 customEvent.detail = options.detail;\r
968 customEvent.screenX = options.screenX;\r
969 customEvent.screenY = options.screenY;\r
970 customEvent.clientX = options.clientX;\r
971 customEvent.clientY = options.clientY;\r
972 customEvent.ctrlKey = options.ctrlKey;\r
973 customEvent.altKey = options.altKey;\r
974 customEvent.metaKey = options.metaKey;\r
975 customEvent.shiftKey = options.shiftKey;\r
976 customEvent.button = Player.ieButtonCodeMap[options.button] || 0;\r
977\r
978 /*\r
979 * Have to use relatedTarget because IE won't allow assignment\r
980 * to toElement or fromElement on generic events. This keeps\r
981 * YAHOO.util.customEvent.getRelatedTarget() functional.\r
982 */\r
983 customEvent.relatedTarget = options.relatedTarget;\r
984\r
985 target.fireEvent('on' + type, customEvent);\r
986 } else {\r
987 return false;\r
988 }\r
989\r
990 return true;\r
991 },\r
992\r
993 /**\r
994 * Injects a UI event using the given event information to populate the event\r
995 * object.\r
996 * \r
997 * @param {HTMLElement} target The target of the given event.\r
998 * @param {Object} options\r
999 * @param {String} options.type The type of event to fire. This can be any one of\r
1000 * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,\r
1001 * `mouseover` and `mousemove`.\r
1002 * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.\r
1003 * DOM Level 2 specifies that all mouse events bubble by default.\r
1004 * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled\r
1005 * using `preventDefault`. DOM Level 2 specifies that all mouse events except\r
1006 * `mousemove` can be canceled. This defaults to `false` for `mousemove`.\r
1007 * @param {Number} [options.detail=1] The number of times the mouse button has been\r
1008 * used.\r
1009 * @param {Window} [view=window] The view containing the target. This is typically\r
1010 * the window object.\r
1011 * @private\r
1012 */\r
1013 injectUIEvent: function (target, options, view) {\r
1014 var customEvent = null; \r
1015\r
1016 view = view || window;\r
1017\r
1018 //check for DOM-compliant browsers first\r
1019 if (doc.createEvent){\r
1020 //just a generic UI Event object is needed\r
1021 customEvent = doc.createEvent("UIEvents");\r
1022\r
1023 customEvent.initUIEvent(options.type, options.bubbles, options.cancelable,\r
1024 view, options.detail);\r
1025\r
1026 target.dispatchEvent(customEvent);\r
1027\r
1028 } else if (doc.createEventObject){ //IE\r
1029 customEvent = doc.createEventObject();\r
1030\r
1031 customEvent.bubbles = options.bubbles;\r
1032 customEvent.cancelable = options.cancelable;\r
1033 customEvent.view = view;\r
1034 customEvent.detail = options.detail;\r
1035\r
1036 target.fireEvent("on" + options.type, customEvent);\r
1037\r
1038 } else {\r
1039 return false;\r
1040 }\r
1041\r
1042 return true;\r
1043 }\r
1044 } // statics\r
1045};\r
1046});\r