/** * @extends Ext.ux.event.Driver * This class manages the playback of an array of "event descriptors". For details on the * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class. * * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call * {@link #method-start}. Like so: * * var player = Ext.create('Ext.ux.event.Player', { * eventQueue: [ ... ], * speed: 2, // play at 2x speed * listeners: { * stop: function() { * player = null; // all done * } * } * }); * * player.start(); * * A more complex use would be to incorporate keyframe generation after playing certain * events. * * var player = Ext.create('Ext.ux.event.Player', { * eventQueue: [ ... ], * keyFrameEvents: { * click: true * }, * listeners: { * stop: function() { * // play has completed... probably time for another keyframe... * player = null; * }, * keyframe: onKeyFrame * } * }); * * player.start(); * * If a keyframe can be handled immediately (synchronously), the listener would be: * * function onKeyFrame () { * handleKeyFrame(); * } * * If the keyframe event is always handled asynchronously, then the event listener is only * a bit more: * * function onKeyFrame (p, eventDescriptor) { * eventDescriptor.defer(); // pause event playback... * * handleKeyFrame(function() { * eventDescriptor.finish(); // ...resume event playback * }); * } * * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps * differently by browser), a slightly more complex listener is required. * * function onKeyFrame (p, eventDescriptor) { * var async; * * handleKeyFrame(function() { * // either this callback is being called immediately by handleKeyFrame (in * // which case async is undefined) or it is being called later (in which case * // async will be true). * * if (async) { * eventDescriptor.finish(); * } * else { * async = false; * } * }); * * // either the callback was called (and async is now false) or it was not * // called (and async remains undefined). * * if (async !== false) { * eventDescriptor.defer(); * async = true; // let the callback know that we have gone async * } * } */Ext.define('Ext.ux.event.Player', function(Player) {/* eslint-disable indent, vars-on-top, one-var */var defaults = {}, mouseEvents = {}, keyEvents = {}, doc, // HTML events supported uiEvents = {}, // events that bubble by default bubbleEvents = { // scroll: 1, resize: 1, reset: 1, submit: 1, change: 1, select: 1, error: 1, abort: 1 }; Ext.each([ 'click', 'dblclick', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 'mousemove' ], function(type) { bubbleEvents[type] = defaults[type] = mouseEvents[type] = { bubbles: true, cancelable: (type !== "mousemove"), // mousemove cannot be cancelled detail: 1, screenX: 0, screenY: 0, clientX: 0, clientY: 0, ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, button: 0 }; }); Ext.each([ 'keydown', 'keyup', 'keypress' ], function(type) { bubbleEvents[type] = defaults[type] = keyEvents[type] = { bubbles: true, cancelable: true, ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, keyCode: 0, charCode: 0 }; }); Ext.each([ 'blur', 'change', 'focus', 'resize', 'scroll', 'select' ], function(type) { defaults[type] = uiEvents[type] = { bubbles: (type in bubbleEvents), cancelable: false, detail: 1 }; }); var inputSpecialKeys = { 8: function(target, start, end) { // backspace: 8, if (start < end) { target.value = target.value.substring(0, start) + target.value.substring(end); } else if (start > 0) { target.value = target.value.substring(0, --start) + target.value.substring(end); } this.setTextSelection(target, start, start); }, 46: function(target, start, end) { // delete: 46 if (start < end) { target.value = target.value.substring(0, start) + target.value.substring(end); } else if (start < target.value.length - 1) { target.value = target.value.substring(0, start) + target.value.substring(start + 1); } this.setTextSelection(target, start, start); } }; return { extend: 'Ext.ux.event.Driver', /** * @cfg {Array} eventQueue The event queue to playback. This must be provided before * the {@link #method-start} method is called. */ /** * @cfg {Object} keyFrameEvents An object that describes the events that should generate * keyframe events. For example, `{ click: true }` would generate keyframe events after * each `click` event. */ keyFrameEvents: { click: true }, /** * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false * to ignore animations. Default is true. */ pauseForAnimations: true, /** * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the * recorded speed). A value of 2 would playback at 2x speed. */ speed: 1.0, stallTime: 0, _inputSpecialKeys: { INPUT: inputSpecialKeys, TEXTAREA: Ext.apply({ // 13: function(target, start, end) { // enter: 8, // TODO ? // } }, inputSpecialKeys) }, tagPathRegEx: /(\w+)(?:\[(\d+)\])?/, xpathRe: /^[#~](?:[a-z][-a-z0-9_]*)(?:\/[a-z]+)*$/i, /** * @event beforeplay * Fires before an event is played. * @param {Ext.ux.event.Player} this * @param {Object} eventDescriptor The event descriptor about to be played. */ /** * @event keyframe * Fires when this player reaches a keyframe. Typically, this is after events * like `click` are injected and any resulting animations have been completed. * @param {Ext.ux.event.Player} this * @param {Object} eventDescriptor The keyframe event descriptor. */ constructor: function(config) { var me = this; me.callParent(arguments); me.timerFn = function() { me.onTick(); }; me.attachTo = me.attachTo || window; me.counter = 0; doc = me.attachTo.document; }, /** * Returns the element given is XPath-like description. * @param {String} xpath The XPath-like description of the element. * @return {HTMLElement} */ getElementFromXPath: function(xpath) { var me = this, parts = xpath.split('/'), regex = me.tagPathRegEx, i, n, m, count, tag, child, el = me.attachTo.document; el = (parts[0] === '~') ? el.body : el.getElementById(parts[0].substring(1)); // remove '#' for (i = 1, n = parts.length; el && i < n; ++i) { m = regex.exec(parts[i]); count = m[2] ? parseInt(m[2], 10) : 1; tag = m[1].toUpperCase(); for (child = el.firstChild; child; child = child.nextSibling) { if (child.tagName === tag) { if (count === 1) { break; } --count; } } el = child; } return el; }, locateElement: function(locator) { var cmp, cq, dq, el, parts; if (this.xpathRe.test(locator)) { el = this.getElementFromXPath(locator); } else { parts = locator.split('=>'); cq = Ext.String.trim(parts[0]); dq = Ext.String.trim(parts[1]); if (cq) { cmp = Ext.first(cq); el = cmp && cmp.el; } else { el = Ext.getBody(); } if (dq && el) { el = Ext.query(dq, true, el); el = el && el[0]; } } return el; }, // Moving across a line break only counts as moving one character in a TextRange, whereas // a line break in the textarea value is two characters. This function corrects for that // by converting a text offset into a range character offset by subtracting one character // for every line break in the textarea prior to the offset offsetToRangeCharacterMove: function(el, offset) { return offset - (el.value.slice(0, offset).split("\r\n").length - 1); }, setTextSelection: function(el, startOffset, endOffset) { var range, startCharMove; // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js if (startOffset < 0) { startOffset += el.value.length; } if (endOffset == null) { endOffset = startOffset; } if (endOffset < 0) { endOffset += el.value.length; } if (typeof el.selectionStart === "number") { el.selectionStart = startOffset; el.selectionEnd = endOffset; } else { range = el.createTextRange(); startCharMove = this.offsetToRangeCharacterMove(el, startOffset); range.collapse(true); if (startOffset === endOffset) { range.move("character", startCharMove); } else { range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset)); range.moveStart("character", startCharMove); } range.select(); } }, getTimeIndex: function() { var t = this.getTimestamp() - this.stallTime; return t * this.speed; }, makeToken: function(eventDescriptor, signal) { var me = this, t0; eventDescriptor[signal] = true; eventDescriptor.defer = function() { eventDescriptor[signal] = false; t0 = me.getTime(); }; eventDescriptor.finish = function() { eventDescriptor[signal] = true; me.stallTime += me.getTime() - t0; me.schedule(); }; }, /** * This method is called after an event has been played to prepare for the next event. * @param {Object} eventDescriptor The descriptor of the event just played. */ nextEvent: function(eventDescriptor) { var me = this, index = ++me.queueIndex; // keyframe events are inserted after a keyFrameEvent is played. if (me.keyFrameEvents[eventDescriptor.type]) { Ext.Array.insert(me.eventQueue, index, [ { keyframe: true, ts: eventDescriptor.ts } ]); } }, /** * This method returns the event descriptor at the front of the queue. This does not * dequeue the event. Repeated calls return the same object (until {@link #nextEvent} * is called). */ peekEvent: function() { var ev = this.eventQueue[this.queueIndex] || null; if (ev && ev.seq === undefined) { ev.seq = this.counter++; } return ev; }, /** * Replaces an event in the queue with an array of events. This is often used to roll * up a multi-step pseudo-event and expand it just-in-time to be played. The process * for doing this in a derived class would be this: * * Ext.define('My.Player', { * extend: 'Ext.ux.event.Player', * * peekEvent: function() { * var event = this.callParent(); * * if (event.multiStepSpecial) { * this.replaceEvent(null, [ * ... expand to actual events * ]); * * event = this.callParent(); // get the new next event * } * * return event; * } * }); * * This method ensures that the `beforeplay` hook (if any) from the replaced event is * placed on the first new event and the `afterplay` hook (if any) is placed on the * last new event. * * @param {Number} index The queue index to replace. Pass `null` to replace the event * at the current `queueIndex`. * @param {Event[]} events The array of events with which to replace the specified * event. */ replaceEvent: function(index, events) { for (var t, i = 0, n = events.length; i < n; ++i) { if (i) { t = events[i - 1]; delete t.afterplay; delete t.screenshot; delete events[i].beforeplay; } } Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events); }, /** * This method dequeues and injects events until it has arrived at the time index. If * no events are ready (based on the time index), this method does nothing. * @return {Boolean} True if there is more to do; false if not (at least for now). */ processEvents: function() { var me = this, animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items, eventDescriptor; while ((eventDescriptor = me.peekEvent()) !== null) { if (animations && animations.getCount()) { return true; } if (eventDescriptor.keyframe) { if (!me.processKeyFrame(eventDescriptor)) { return false; } me.nextEvent(eventDescriptor); } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) { me.nextEvent(eventDescriptor); } else { return true; } } me.stop(); return false; }, /** * This method is called when a keyframe is reached. This will fire the keyframe event. * If the keyframe has been handled, true is returned. Otherwise, false is returned. * @param {Object} eventDescriptor The event descriptor of the keyframe. * @return {Boolean} True if the keyframe was handled, false if not. */ processKeyFrame: function(eventDescriptor) { var me = this; // only fire keyframe event (and setup the eventDescriptor) once... if (!eventDescriptor.defer) { me.makeToken(eventDescriptor, 'done'); me.fireEvent('keyframe', me, eventDescriptor); } return eventDescriptor.done; }, /** * Called to inject the given event on the specified target. * @param {HTMLElement} target The target of the event. * @param {Object} event The event to inject. The properties of this object should be * those of standard DOM events but vary based on the `type` property. For details on * event types and their properties, see the class documentation. */ injectEvent: function(target, event) { var me = this, type = event.type, options = Ext.apply({}, event, defaults[type]), handler; if (type === 'type') { handler = me._inputSpecialKeys[target.tagName]; if (handler) { return me.injectTypeInputEvent(target, event, handler); } return me.injectTypeEvent(target, event); } if (type === 'focus' && target.focus) { target.focus(); return true; } if (type === 'blur' && target.blur) { target.blur(); return true; } if (type === 'scroll') { target.scrollLeft = event.pos[0]; target.scrollTop = event.pos[1]; return true; } if (type === 'mduclick') { return me.injectEvent(target, Ext.applyIf({ type: 'mousedown' }, event)) && me.injectEvent(target, Ext.applyIf({ type: 'mouseup' }, event)) && me.injectEvent(target, Ext.applyIf({ type: 'click' }, event)); } if (mouseEvents[type]) { return Player.injectMouseEvent(target, options, me.attachTo); } if (keyEvents[type]) { return Player.injectKeyEvent(target, options, me.attachTo); } if (uiEvents[type]) { return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail); } return false; }, injectTypeEvent: function(target, event) { var me = this, text = event.text, xlat = [], ch, chUp, i, n, upper; if (text) { delete event.text; upper = text.toUpperCase(); for (i = 0, n = text.length; i < n; ++i) { ch = text.charCodeAt(i); chUp = upper.charCodeAt(i); xlat.push( Ext.applyIf({ type: 'keydown', charCode: chUp, keyCode: chUp }, event), Ext.applyIf({ type: 'keypress', charCode: ch, keyCode: ch }, event), Ext.applyIf({ type: 'keyup', charCode: chUp, keyCode: chUp }, event) ); } } else { xlat.push( Ext.applyIf({ type: 'keydown', charCode: event.keyCode }, event), Ext.applyIf({ type: 'keyup', charCode: event.keyCode }, event) ); } for (i = 0, n = xlat.length; i < n; ++i) { me.injectEvent(target, xlat[i]); } return true; }, injectTypeInputEvent: function(target, event, handler) { var me = this, text = event.text, sel, n; if (handler) { sel = me.getTextSelection(target); if (text) { n = sel[0]; target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]); n += text.length; me.setTextSelection(target, n, n); } else { if (!(handler = handler[event.keyCode])) { // no handler for the special key for this element if ('caret' in event) { me.setTextSelection(target, event.caret, event.caret); } else if (event.selection) { me.setTextSelection(target, event.selection[0], event.selection[1]); } return me.injectTypeEvent(target, event); } handler.call(this, target, sel[0], sel[1]); return true; } } return true; }, playEvent: function(eventDescriptor) { var me = this, target = me.locateElement(eventDescriptor.target), now = me.getTimeIndex(), timeout = eventDescriptor.timeout, event; if (eventDescriptor.startedAt === undefined) { eventDescriptor.startedAt = now; } if (!target) { // not present (yet)... wait for element present... if (timeout !== null) { timeout = timeout || 3e4; // 30 sec if (now - eventDescriptor.startedAt > timeout) { me.playEventHook(eventDescriptor, 'timeout', 'onEventTimeout'); } } return false; } if (!me.playEventHook(eventDescriptor, 'beforeplay')) { return false; } if (!eventDescriptor.injected) { eventDescriptor.injected = true; event = me.translateEvent(eventDescriptor, target); me.injectEvent(target, event); } return me.playEventHook(eventDescriptor, 'afterplay'); }, playEventHook: function(eventDescriptor, hookName, hookHandler) { var me = this, doneName = hookName + '.done', firedName = hookName + '.fired', hook = hookHandler || eventDescriptor[hookName]; if (hook && !eventDescriptor[doneName]) { if (!eventDescriptor[firedName]) { eventDescriptor[firedName] = true; me.makeToken(eventDescriptor, doneName); if (me.eventScope && Ext.isString(hook)) { hook = me.eventScope[hook]; } if (hook) { hook.call(me.eventScope || me, eventDescriptor); } } return false; } return true; }, schedule: function() { var me = this; if (!me.timer) { me.timer = Ext.defer(me.timerFn, 10); } }, _translateAcross: [ 'type', 'button', 'charCode', 'keyCode', 'caret', 'pos', 'text', 'selection' ], translateEvent: function(eventDescriptor, target) { var me = this, event = {}, modKeys = eventDescriptor.modKeys || '', names = me._translateAcross, i = names.length, name, xy; while (i--) { name = names[i]; if (name in eventDescriptor) { event[name] = eventDescriptor[name]; } } event.altKey = modKeys.indexOf('A') > 0; event.ctrlKey = modKeys.indexOf('C') > 0; event.metaKey = modKeys.indexOf('M') > 0; event.shiftKey = modKeys.indexOf('S') > 0; if (target && 'x' in eventDescriptor) { xy = Ext.fly(target).getXY(); xy[0] += eventDescriptor.x; xy[1] += eventDescriptor.y; } else if ('x' in eventDescriptor) { xy = [ eventDescriptor.x, eventDescriptor.y ]; } else if ('px' in eventDescriptor) { xy = [ eventDescriptor.px, eventDescriptor.py ]; } if (xy) { event.clientX = event.screenX = xy[0]; event.clientY = event.screenY = xy[1]; } if (eventDescriptor.key) { event.keyCode = me.specialKeysByName[eventDescriptor.key]; } if (eventDescriptor.type === 'wheel') { if ('onwheel' in me.attachTo.document) { event.wheelX = eventDescriptor.dx; event.wheelY = eventDescriptor.dy; } else { event.type = 'mousewheel'; event.wheelDeltaX = -40 * eventDescriptor.dx; event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy; } } return event; }, //--------------------------------- // Driver overrides onStart: function() { var me = this; me.queueIndex = 0; me.schedule(); }, onStop: function() { var me = this; if (me.timer) { Ext.undefer(me.timer); me.timer = null; } }, //--------------------------------- onTick: function() { var me = this; me.timer = null; if (me.processEvents()) { me.schedule(); } }, statics: { ieButtonCodeMap: { 0: 1, 1: 4, 2: 2 }, /** * Injects a key event using the given event information to populate the event * object. * * **Note:** `keydown` causes Safari 2.x to crash. * * @param {HTMLElement} target The target of the given event. * @param {Object} options Object object containing all of the event injection * options. * @param {String} options.type The type of event to fire. This can be any one of * the following: `keyup`, `keydown` and `keypress`. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up. * DOM Level 3 specifies that all key events bubble by default. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled * using `preventDefault`. DOM Level 3 specifies that all key events can be * cancelled. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is * pressed while the event is firing. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is * pressed while the event is firing. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is * pressed while the event is firing. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is * pressed while the event is firing. * @param {Number} [options.keyCode=0] The code for the key that is in use. * @param {Number} [options.charCode=0] The Unicode code for the character * associated with the key being used. * @param {Window} [view=window] The view containing the target. This is typically * the window object. * @private */ injectKeyEvent: function(target, options, view) { var type = options.type, customEvent = null; if (type === 'textevent') { type = 'keypress'; } view = view || window; // check for DOM-compliant browsers first if (doc.createEvent) { try { customEvent = doc.createEvent("KeyEvents"); // Interesting problem: Firefox implemented a non-standard // version of initKeyEvent() based on DOM Level 2 specs. // Key event was removed from DOM Level 2 and re-introduced // in DOM Level 3 with a different interface. Firefox is the // only browser with any implementation of Key Events, so for // now, assume it's Firefox if the above line doesn't error. // @TODO: Decipher between Firefox's implementation and a correct one. customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); } catch (ex) { // If it got here, that means key events aren't officially supported. // Safari/WebKit is a real problem now. WebKit 522 won't let you // set keyCode, charCode, or other properties if you use a // UIEvent, so we first must try to create a generic event. The // fun part is that this will throw an error on Safari 2.x. The // end result is that we need another try...catch statement just to // deal with this mess. try { // try to create generic event - will fail in Safari 2.x customEvent = doc.createEvent("Events"); } catch (uierror) { // the above failed, so create a UIEvent for Safari 2.x customEvent = doc.createEvent("UIEvents"); } finally { customEvent.initEvent(type, options.bubbles, options.cancelable); customEvent.view = view; customEvent.altKey = options.altKey; customEvent.ctrlKey = options.ctrlKey; customEvent.shiftKey = options.shiftKey; customEvent.metaKey = options.metaKey; customEvent.keyCode = options.keyCode; customEvent.charCode = options.charCode; } } target.dispatchEvent(customEvent); } else if (doc.createEventObject) { // IE customEvent = doc.createEventObject(); customEvent.bubbles = options.bubbles; customEvent.cancelable = options.cancelable; customEvent.view = view; customEvent.ctrlKey = options.ctrlKey; customEvent.altKey = options.altKey; customEvent.shiftKey = options.shiftKey; customEvent.metaKey = options.metaKey; // IE doesn't support charCode explicitly. CharCode should // take precedence over any keyCode value for accurate // representation. customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode; target.fireEvent("on" + type, customEvent); } else { return false; } return true; }, /** * Injects a mouse event using the given event information to populate the event * object. * * @param {HTMLElement} target The target of the given event. * @param {Object} options Object object containing all of the event injection * options. * @param {String} options.type The type of event to fire. This can be any one of * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`, * `mouseover` and `mousemove`. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up. * DOM Level 2 specifies that all mouse events bubble by default. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled * using `preventDefault`. DOM Level 2 specifies that all mouse events except * `mousemove` can be cancelled. This defaults to `false` for `mousemove`. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is * pressed while the event is firing. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is * pressed while the event is firing. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is * pressed while the event is firing. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is * pressed while the event is firing. * @param {Number} [options.detail=1] The number of times the mouse button has * been used. * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point * the event occurred. * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point * the event occurred. * @param {Number} [options.clientX=0] The x-coordinate on the client at which point * the event occurred. * @param {Number} [options.clientY=0] The y-coordinate on the client at which point * the event occurred. * @param {Number} [options.button=0] The button being pressed while the event is * executing. The value should be 0 for the primary mouse button (typically the * left button), 1 for the tertiary mouse button (typically the middle button), * and 2 for the secondary mouse button (typically the right button). * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this * is the element that the mouse has moved to. For `mouseover` events, this is * the element that the mouse has moved from. This argument is ignored for all * other events. * @param {Window} [view=window] The view containing the target. This is typically * the window object. * @private */ injectMouseEvent: function(target, options, view) { var type = options.type, customEvent = null; view = view || window; // check for DOM-compliant browsers first if (doc.createEvent) { customEvent = doc.createEvent("MouseEvents"); // Safari 2.x (WebKit 418) still doesn't implement initMouseEvent() if (customEvent.initMouseEvent) { customEvent.initMouseEvent(type, options.bubbles, options.cancelable, view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget); } else { // Safari // the closest thing available in Safari 2.x is UIEvents customEvent = doc.createEvent("UIEvents"); customEvent.initEvent(type, options.bubbles, options.cancelable); customEvent.view = view; customEvent.detail = options.detail; customEvent.screenX = options.screenX; customEvent.screenY = options.screenY; customEvent.clientX = options.clientX; customEvent.clientY = options.clientY; customEvent.ctrlKey = options.ctrlKey; customEvent.altKey = options.altKey; customEvent.metaKey = options.metaKey; customEvent.shiftKey = options.shiftKey; customEvent.button = options.button; customEvent.relatedTarget = options.relatedTarget; } /* * Check to see if relatedTarget has been assigned. Firefox * versions less than 2.0 don't allow it to be assigned via * initMouseEvent() and the property is readonly after event * creation, so in order to keep YAHOO.util.getRelatedTarget() * working, assign to the IE proprietary toElement property * for mouseout event and fromElement property for mouseover * event. */ if (options.relatedTarget && !customEvent.relatedTarget) { if (type === "mouseout") { customEvent.toElement = options.relatedTarget; } else if (type === "mouseover") { customEvent.fromElement = options.relatedTarget; } } target.dispatchEvent(customEvent); } else if (doc.createEventObject) { // IE customEvent = doc.createEventObject(); customEvent.bubbles = options.bubbles; customEvent.cancelable = options.cancelable; customEvent.view = view; customEvent.detail = options.detail; customEvent.screenX = options.screenX; customEvent.screenY = options.screenY; customEvent.clientX = options.clientX; customEvent.clientY = options.clientY; customEvent.ctrlKey = options.ctrlKey; customEvent.altKey = options.altKey; customEvent.metaKey = options.metaKey; customEvent.shiftKey = options.shiftKey; customEvent.button = Player.ieButtonCodeMap[options.button] || 0; /* * Have to use relatedTarget because IE won't allow assignment * to toElement or fromElement on generic events. This keeps * YAHOO.util.customEvent.getRelatedTarget() functional. */ customEvent.relatedTarget = options.relatedTarget; target.fireEvent('on' + type, customEvent); } else { return false; } return true; }, /** * Injects a UI event using the given event information to populate the event * object. * * @param {HTMLElement} target The target of the given event. * @param {Object} options * @param {String} options.type The type of event to fire. This can be any one of * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`, * `mouseover` and `mousemove`. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up. * DOM Level 2 specifies that all mouse events bubble by default. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled * using `preventDefault`. DOM Level 2 specifies that all mouse events except * `mousemove` can be canceled. This defaults to `false` for `mousemove`. * @param {Number} [options.detail=1] The number of times the mouse button has been * used. * @param {Window} [view=window] The view containing the target. This is typically * the window object. * @private */ injectUIEvent: function(target, options, view) { var customEvent = null; view = view || window; // check for DOM-compliant browsers first if (doc.createEvent) { // just a generic UI Event object is needed customEvent = doc.createEvent("UIEvents"); customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail); target.dispatchEvent(customEvent); } else if (doc.createEventObject) { // IE customEvent = doc.createEventObject(); customEvent.bubbles = options.bubbles; customEvent.cancelable = options.cancelable; customEvent.view = view; customEvent.detail = options.detail; target.fireEvent("on" + options.type, customEvent); } else { return false; } return true; } } // statics};});