/** * @class ST.event.Event * @private */ST.event.Event = ST.define({ normalizedType: { mousewheel: 'wheel' }, constructor: function(event, targets, time) { var me = this, self = me.self, type = event.type, target = event.target, keyTracker = ST.___keyTracker, i, n, origin, pointerType, t, touch, xy, matchedKey; time = time || +(event.timeStamp || new Date()); if (!keyTracker) { ST.___keyTracker = keyTracker = { keydown: {}, keyup: {} }; } ST.apply(me, { browserEvent: event, target: target, targets: [], type: me.normalizedType[type] || self.MSToPointer[type] || type, button: event.button, buttons: event.buttons, shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey, // Mac "command" key altKey: event.altKey, charCode: event.charCode, keyCode: event.keyCode, key: event.key, time: time, pageX: event.pageX, pageY: event.pageY, detail: event.detail, pointerId: event.pointerId, isPrimary: event.isPrimary, recorderId: event.recorderId }); if (self.mouseEvents[type] || self.clickEvents[type]) { pointerType = 'mouse'; } else if (self.pointerEvents[type] || self.msPointerEvents[type]) { pointerType = self.pointerTypes[event.pointerType]; } else if (self.touchEvents[type]) { pointerType = 'touch'; } if (pointerType) { me.pointerType = pointerType; } if (type === 'scroll') { me.scrollPosition = ST.fly(target).getScroll(); } if (me.isMouse() || me.isPointer()) { xy = me.getXY(); } else if (me.isTouch()) { // TODO: ORION-42 - support multi-touch recording touch = me.browserEvent.changedTouches[0]; xy = [ Math.round(touch.pageX), Math.round(touch.pageY) ]; } if (event.prereq) { me.prereq = event.prereq.slice(); } // Locators produce targets[] as pairs of [el, locator] so convert // them to event subsets. for (i = 0, n = targets.length; i < n; ++i) { me.targets[i] = t = { target: targets[i][1] // the locator }; if (xy) { origin = ST.fly(targets[i][0]).getXY(); // origin of the target el t.x = xy[0] - origin[0]; t.y = xy[1] - origin[1]; } if (me.isKey()) { if (me.caret == null) { me.caret = ST.fly(targets[i][0]).getCaret(true); } if (me.type === 'keydown') { keyTracker.keydown[me.keyCode] = { caret: me.caret }; } else if (me.type === 'keyup') { keyTracker.keyup[me.keyCode] = true; } if ([16, 17, 18, 20].indexOf(me.keyCode) === -1 && me.caret != null) { if (me.type === 'keydown' && me.altKey && me.ctrlKey) { me.altGr = true; } else if (me.type === 'keyup') { matchedKey = keyTracker.keydown[me.keyCode]; if (matchedKey) { try { var value = targets[i][0].value.substr(matchedKey.caret, 1); if (value !== me.key) { me.actualKey = value; me.caret = matchedKey.caret; me.altGr = true; } } catch (e) {} } } } } } }, getXY: function() { var me = this, xy = me.xy; if (!xy) { xy = me.xy = [me.pageX, me.pageY]; //<feature legacyBrowser> var x = xy[0], browserEvent, doc, docEl, body; // pageX/pageY not available (undefined, not null), use clientX/clientY instead if (!x && x !== 0) { browserEvent = me.browserEvent; doc = document; docEl = doc.documentElement; body = doc.body; xy[0] = browserEvent.clientX + (docEl && docEl.scrollLeft || body && body.scrollLeft || 0) - (docEl && docEl.clientLeft || body && body.clientLeft || 0); xy[1] = browserEvent.clientY + (docEl && docEl.scrollTop || body && body.scrollTop || 0) - (docEl && docEl.clientTop || body && body.clientTop || 0); } //</feature> } // Some browsers will return coordinates which are overprecise (long decimal number) xy[0] = parseInt(xy[0]); xy[1] = parseInt(xy[1]); return xy; }, getKey: function(){ var me = this, keyCode = me.keyCode, charCode = me.charCode, KeyMap = ST.KeyMap, key; if (me.type === 'keypress') { // keypress events are the only events that have a charCode if (charCode === undefined) { // IE9 and earlier have the character code in the keyCode property charCode = keyCode; } if (charCode === 0) { // firefox fires keypress events for function keys with charCode = 0 // If this is the case we want the key name (F1, F2 etc) key = KeyMap.keys[keyCode]; } else { key = KeyMap.specialKeys[charCode] || String.fromCharCode(charCode); } } else { if (me.shiftKey) { key = KeyMap.shiftKeys[keyCode]; } if (!key) { key = KeyMap.keys[keyCode]; } } return key || null; }, getWheelDeltas: function() { var deltas = { x: 0 }, browserEvent = this.browserEvent; if (this.browserEvent.type === 'wheel') { // Current FireFox (technically IE9+ if we use addEventListener but // checking document.onwheel does not detect this) deltas.x = browserEvent.deltaX; deltas.y = browserEvent.deltaY; } else if (typeof browserEvent.wheelDeltaX === 'number') { // new WebKit has both X & Y deltas.x = -1/40 * browserEvent.wheelDeltaX; deltas.y = -1/40 * browserEvent.wheelDeltaY; } else if (browserEvent.wheelDelta) { // old WebKit and IE deltas.y = -1/40 * browserEvent.wheelDelta; } else if (browserEvent.detail) { // Old Gecko deltas.y = browserEvent.detail; } return deltas; }, toJSON: function() { var me = this, self = me.self, type = me.type, browserEvent = me.browserEvent, targets = me.targets, data = { type: type, targets: targets }, isPointer = me.isPointer(), scrollPosition = me.scrollPosition, button = me.button, buttons = me.buttons, altKey = me.altKey, altGr = me.altGr, actualKey = me.actualKey, ctrlKey = me.ctrlKey, shiftKey = me.shiftKey, metaKey = me.metaKey, KeyMap = ST.KeyMap, keyCode = me.keyCode, key = me.key, caret = me.caret, prereq = me.prereq, reverseKeyCode, touch, wheelDeltas; if (isPointer || me.isMouse()) { // only serialize non-zero values for button and buttons since 0 // is the default playback value for both properties if (button) { data.button = button; } if (buttons && (!self.downEvents[type] || (buttons !== self.buttonToButtons[button]))) { // When the mouse/pointer is "down", only serialize the buttons property // if there are multiple buttons pressed. data.buttons = buttons; } if (isPointer) { data.identifier = me.pointerId; data.pointerType = me.pointerType; // TODO: multi-touch //data.isPrimary = me.isPrimary } } else if (me.isTouch()) { // TODO: ORION-42 - support multi-touch recording touch = browserEvent.changedTouches[0]; data.identifier = touch.identifier; } ST.apply(data, targets[0]); // targets = [{ target: '@foo', x: 10, y: 10 }, ...] if (self.detailEvents[type]) { data.detail = me.detail; } // only encode true values for altKey/shiftKey/ctrlKey/metaKey since they default to false if (altKey) { data.altKey = altKey; } if (altGr) { data.altGr = altGr; } if (ctrlKey) { data.ctrlKey = ctrlKey; } if (shiftKey) { data.shiftKey = shiftKey; } if (metaKey) { data.metaKey = metaKey; } if (me.isKey()) { if (!me.key) { me.key = key = me.getKey(); } if (caret != null) { data.caret = caret; } if (key) { data.key = key; data.actualKey = actualKey || key; data.actualKeyCode = keyCode; // The reverse key map (used for playback) can only contain one keyCode // per character. If we detect that playback will default to a different // keyCode than the one we have here, lets add the keyCode to the serialized // event as well so that the user has the option to preserve that info // during playback. (Only needed for keydown/up since keypress does not // have a key code) if (type !== 'keypress') { if (shiftKey) { reverseKeyCode = KeyMap.reverseShiftKeys[key]; } if (!reverseKeyCode) { reverseKeyCode = KeyMap.reverseKeys[key]; } if (reverseKeyCode !== keyCode) { data.keyCode = keyCode; } else if (['Multiply', 'Add', 'Subtract', 'Decimal', 'Divide'].indexOf(key) !== -1) { // Convert numpad key names (on Internet Explorer) to keyCode and key value equivalents data.keyCode = reverseKeyCode; data.key = KeyMap.keys[reverseKeyCode]; data.actualKeyCode = reverseKeyCode; data.actualKey = KeyMap.keys[reverseKeyCode]; } } } else { data.keyCode = keyCode; } } if (type === 'scroll') { data.pos = [scrollPosition.x, scrollPosition.y]; } if (type === 'wheel') { wheelDeltas = me.getWheelDeltas(); data.deltaX = wheelDeltas.x; data.deltaY = wheelDeltas.y; } if (prereq) { data.prereq = prereq.slice(); } return data; }, serialize: function () { var me = this, memento = { }, keys = [ 'targets', 'type', 'button', 'buttons', 'generatedSelector', 'shiftKey', 'ctrlKey', 'metaKey', 'altKey', 'charCode', 'keyCode', 'key', 'timeStamp', 'recorderId', 'time', 'pageX', 'pageY', 'detail', 'pointerId', 'isPrimary', 'webElement', 'x', 'y' ], len = keys.length, key, i, wheelDeltas; for (i=0; i<len; i++) { key = keys[i]; if (typeof me[key] !== 'undefined' && me[key]) { // skip undefined and falsey memento[key] = me[key]; } } if (me.type === 'wheel') { wheelDeltas = me.getWheelDeltas(); memento.deltaX = wheelDeltas.x; memento.deltaY = wheelDeltas.y; } return memento; }, isMouse: function() { var self = this.self, type = this.type; return !!(self.mouseEvents[type] || self.clickEvents[type] || (type === 'wheel')); }, isPointer: function() { return !!(this.self.pointerEvents[this.type] || this.self.msPointerEvents[this.type]); }, isTouch: function() { return !!this.self.touchEvents[this.type]; }, isKey: function() { return !!this.self.keyEvents[this.type]; }, statics: { mouseEvents: { mousedown: 1, mousemove: 1, mouseup: 1, mouseover: 1, mouseout: 1, mouseenter: 1, mouseleave: 1 }, clickEvents: { click: 1, dblclick: 1, rightclick: 1 }, pointerEvents: { pointerdown: 1, pointermove: 1, pointerup: 1, pointercancel: 1, pointerover: 1, pointerout: 1, pointerenter: 1, pointerleave: 1 }, msPointerEvents: { MSPointerDown: 1, MSPointerMove: 1, MSPointerUp: 1, MSPointerCancel: 1, MSPointerOver: 1, MSPointerOut: 1, MSPointerEnter: 1, MSPointerLeave: 1 }, touchEvents: { touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1 }, keyEvents: { keydown: 1, keyup: 1, keypress: 1 }, focusEvents: { focus: 1, blur: 1 }, movementEvents: { mousemove: 1, touchmove: 1, pointermove: 1, MSPointerMove: 1 }, pointerToMS: { pointerdown: 'MSPointerDown', pointermove: 'MSPointerMove', pointerup: 'MSPointerUp', pointercancel: 'MSPointerCancel', pointerover: 'MSPointerOver', pointerout: 'MSPointerOut', pointerenter: 'MSPointerEnter', pointerleave: 'MSPointerLeave' }, MSToPointer: { MSPointerDown: 'pointerdown', MSPointerMove: 'pointermove', MSPointerUp: 'pointerup', MSPointerCancel: 'pointercancel', MSPointerOver: 'pointerover', MSPointerOut: 'pointerout', MSPointerEnter: 'pointerenter', MSPointerLeave: 'pointerleave' }, mouseToPointer: { mousedown: 'pointerdown', mousemove: 'pointermove', mouseup: 'pointerup', mouseover: 'pointerover', mouseout: 'pointerout', mouseenter: 'pointerenter', mouseleave: 'pointerleave' }, pointerToMouse: { pointerdown: 'mousedown', pointermove: 'mousemove', pointerup: 'mouseup', pointerover: 'mouseover', pointerout: 'mouseout', pointerenter: 'mouseenter', pointerleave: 'mouseleave' }, touchToPointer: { touchstart: 'pointerdown', touchmove: 'pointermove', touchend: 'pointerup', touchcancel: 'pointercancel' }, pointerToTouch: { pointerdown: 'touchstart', pointermove: 'touchmove', pointerup: 'touchend', pointercancel: 'touchcancel' }, touchToMouse: { touchstart: 'mousedown', touchmove: 'mousemove', touchend: 'mouseup' }, mouseToTouch: { mousedown: 'touchstart', mousemove: 'touchmove', mouseup: 'touchend' }, // msPointerTypes in IE10 are numbers, in the w3c spec they are strings. // this map allows us to normalize the pointerType for an event // http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType // http://msdn.microsoft.com/en-us/library/ie/hh772359(v=vs.85).aspx pointerTypes: { 2: 'touch', 3: 'pen', 4: 'mouse', touch: 'touch', pen: 'pen', mouse: 'mouse' }, msPointerTypes: { touch: 2, pen: 3, mouse: 4 }, downEvents: { mousedown: 1, pointerdown: 1, MSPointerDown: 1 }, // events for which the "detail" property can be non-zero // https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail detailEvents: { mousedown: 1, mouseup: 1, click: 1, dblclick: 1, rightclick: 1 }, buttonToButtons: { 0: 1, 1: 4, 2: 2 }, buttonsToButton: { 1: 0, 4: 1, 2: 2 } }});