From 08b99df781612092393b1359f820c4a371de8a83 Mon Sep 17 00:00:00 2001 From: Kir Belevich Date: Thu, 23 Jan 2014 17:16:31 +0200 Subject: [PATCH 1/2] update jquery__event_type_pointer; add jquery__event_type_pointerpressrelease --- .jscs.json | 4 +- .../_type/jquery__event_type_pointer.js | 735 +++++++++++------- ...ry__event_type_pointerpressrelease.deps.js | 7 + .../jquery__event_type_pointerpressrelease.js | 195 +++++ 4 files changed, 671 insertions(+), 270 deletions(-) create mode 100644 common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js create mode 100644 common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js diff --git a/.jscs.json b/.jscs.json index 719d8db09..77423a96d 100644 --- a/.jscs.json +++ b/.jscs.json @@ -15,6 +15,8 @@ ".bem/**", "common.blocks/vow/**", "common.blocks/ecma/__array/ecma__array.spec.js", - "touch.blocks/jquery/__event/_type/jquery__event_type_pointerclick.js" + "touch.blocks/jquery/__event/_type/jquery__event_type_pointerclick.js", + "common.blocks/jquery/__event/_type/jquery__event_type_pointer.js", + "common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js" ] } diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointer.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointer.js index 96a0b75f6..7ac2100e3 100644 --- a/common.blocks/jquery/__event/_type/jquery__event_type_pointer.js +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointer.js @@ -1,127 +1,94 @@ /** - * Basic polyfill for "Pointer Events" W3C Candidate Recommendation - * with addition of custom pointerpress/pointerrelease events. - * - * @see http://www.w3.org/TR/pointerevents/ - * @see https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html - * @see https://dvcs.w3.org/hg/webevents/raw-file/default/touchevents.html - * @see http://msdn.microsoft.com/en-US/library/ie/hh673557.aspx - * @see http://www.benalman.com/news/2010/03/jquery-special-events/ - * @see http://api.jquery.com/category/events/event-object/ + * Basic polyfill for Pointer Events W3C Specification. * * @modules pointerevents * * @author Kir Belevich * @copyright Kir Belevich 2013 * @license MIT - * @version 0.1.0 + * @version 0.5.2 */ modules.define('jquery', function(provide, $) { -// nothing to do -if(window.navigator.pointerEnabled) { - provide($); - return; -} - -// current events type and aliases -var current; - -// touch -// https://github.com/ariya/phantomjs/issues/10375 -if('ontouchstart' in window && !('_phantom' in window)) { - current = { - type : 'touch', - enter : 'touchstart', - over : 'touchstart', - down : 'touchstart', - move : 'touchmove', - up : 'touchend', - out : 'touchend', - leave : 'touchend', - cancel : 'touchcancel' - }; -// msPointer -} else if(window.navigator.msPointerEnabled) { - current = { - type : 'mspointer', - enter : 'mouseenter', // :( - over : 'MSPointerOver', - down : 'MSPointerDown', - move : 'MSPointerMove', - up : 'MSPointerUp', - out : 'MSPointerOut', - leave : 'mouseleave', // :( - cancel : 'MSPointerCancel' - }; -// mouse -} else { - current = { - type : 'mouse', - enter : 'mouseenter', - over : 'mouseover', - down : 'mousedown', - move : 'mousemove', - up : 'mouseup', - out : 'mouseout', - leave : 'mouseleave' - }; -} - -var isTouch = current.type === 'touch', - isMouse = current.type === 'mouse'; +/* + http://www.w3.org/TR/pointerevents/ + https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html + https://dvcs.w3.org/hg/webevents/raw-file/default/touchevents.html + http://msdn.microsoft.com/en-US/library/ie/hh673557.aspx + http://www.benalman.com/news/2010/03/jquery-special-events/ + http://api.jquery.com/category/events/event-object/ +*/ + +var win = window, + doc = win.document, + binds = { + mouse: { + enter: 'mouseenter', + over: 'mouseover', + down: 'mousedown', + move: 'mousemove', + up: 'mouseup', + out: 'mouseout', + leave: 'mouseleave' + }, -/** - * Mutate an argument event to PointerEvent. - * - * @param {Object} e current event - * @param {String} type new pointerevent type - */ -function PointerEvent(e, type) { - e.type = type; - // do not do anything with multiple touch-events because of gestures - if(!(type === 'touch' && e.originalEvent.changedTouches.length > 1)) { - normalizeToJQueryEvent(e); - extendToPointerEvent(e); - $.extend(this, e); - } -} + touch: { + enter: 'touchstart', + over: 'touchstart', + down: 'touchstart', + move: 'touchmove', + up: 'touchend', + out: 'touchend', + leave: 'touchend', + cancel: 'touchcancel' + }, -/** - * Dispatch current event. - * - * @param {Element} target target element - */ -PointerEvent.prototype.dispatch = function(target) { - this.type && ($.event.handle || $.event.dispatch).call(target, this); - return this; -}; + mspointer: { + over: 'MSPointerOver', + down: 'MSPointerDown', + move: 'MSPointerMove', + up: 'MSPointerUp', + out: 'MSPointerOut', + cancel: 'MSPointerCancel' + } + }; /** - * Normalize only touch-event to jQuery event interface. + * Normalize touch-event by keeping all the + * possible properties normalized by jQuery. * * @see http://api.jquery.com/category/events/event-object/ * - * @param {Object} e input event + * @param {Object} e event */ -function normalizeToJQueryEvent(e) { - if(!isTouch) return; - - var touchPoint = e.originalEvent.changedTouches[0]; - - // keep all the properties normalized by jQuery - e.clientX = touchPoint.clientX; - e.clientY = touchPoint.clientY; - e.pageX = touchPoint.pageX; - e.pageY = touchPoint.pageY; - e.screenX = touchPoint.screenX; - e.screenY = touchPoint.screenY; - e.layerX = e.originalEvent.layerX; - e.layerY = e.originalEvent.layerY; - e.offsetX = e.layerX - e.currentTarget.offsetLeft; - e.offsetY = e.layerY - e.currentTarget.offsetTop; - e.target = touchPoint.target; - e.identifier = touchPoint.identifier; +function normalizeTouchEvent(e) { + + if(e.pointerType === 'touch') { + + e.originalEvent = e.originalEvent || e; + + // multitouch + if(e.originalEvent.touches.length > 1) { + e.multitouch = true; + return; + } + + var touchPoint = e.originalEvent.changedTouches[0]; + + // keep all the properties normalized by jQuery + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + e.pageX = touchPoint.pageX; + e.pageY = touchPoint.pageY; + e.screenX = touchPoint.screenX; + e.screenY = touchPoint.screenY; + e.layerX = e.originalEvent.layerX; + e.layerY = e.originalEvent.layerY; + e.offsetX = e.layerX - e.target.offsetLeft; + e.offsetY = e.layerY - e.target.offsetTop; + e.identifier = touchPoint.identifier; + } + } /** @@ -129,231 +96,461 @@ function normalizeToJQueryEvent(e) { * * @see https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html#pointer-events-and-interfaces * @see https://dvcs.w3.org/hg/webevents/raw-file/default/touchevents.html - * @param {Object} e input event + * + * @param {object} e event */ function extendToPointerEvent(e) { + + /*eslint complexity:0*/ e.width = e.width || - e.webkitRadiusX || - e.radiusX || - 0; + e.webkitRadiusX || + e.radiusX || + 0; e.height = e.width || - e.webkitRadiusY || - e.radiusY || - 0; + e.webkitRadiusY || + e.radiusY || + 0; // TODO: stupid Android somehow could send "force" > 1 ;( e.pressure = e.pressure || - e.mozPressure || - e.webkitForce || - e.force || - e.which && 0.5 || - 0; + e.mozPressure || + e.webkitForce || + e.force || + e.which && 0.5 || + 0; e.tiltX = e.tiltX || 0; e.tiltY = e.tiltY || 0; - e.pointerType = e.pointerType || current.type; - // https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html#the-primary-pointer + switch(e.pointerType) { + case 2: e.pointerType = 'touch'; break; + case 3: e.pointerType = 'pen'; break; + case 4: e.pointerType = 'mouse'; break; + default: e.pointerType = e.pointerType; + } + e.isPrimary = true; - // "1" is always for mouse, need to +2 for touch which can start from "0" - e.pointerId = e.identifier? e.identifier + 2 : 1; + // "1" is always for mouse, so +2 because of touch can start from 0 + e.pointerId = e.identifier ? e.identifier + 2 : 1; + } -function addSpecialEvent(eventType, extend) { - var pointerEventType = 'pointer' + eventType, - handlerFn = 'handler' + (isTouch? 'Touch' : 'NonTouch'), - specialEvent = $.event.special[pointerEventType] = { - setup : function() { - $(this).on(current[eventType], specialEvent.handler); +/** + * Mutate an event to PointerEvent. + * + * @param {object} e current event object + * @param {string} type future pointerevent type + */ +function PointerEvent(e, type) { + + extendToPointerEvent(e); + normalizeTouchEvent(e); + e.type = type; + + $.extend(this, e); + +} + +// export PointerEvent class +$.PointerEvent = PointerEvent; + +// nothing to do in IE11 for today +if(win.navigator.pointerEnabled) { + provide($); + return; +} + +/** + * Simple nextTick polyfill. + * + * @see http://jsperf.com/settimeout-vs-nexttick-polyfill + * + * @return {Function} + */ +function nextTick(callback) { + + var msgName = 'nextTick-polyfill', + timeouts = []; + + if(win.nextTick) { + return win.nextTick(callback); + } + + if(!win.postMessage || win.ActiveXObject) { + return setTimeout(callback, 0); + } + + win.addEventListener('message', function(e){ + if(e.source === win && e.data === msgName) { + if(e.stopPropagation) { + e.stopPropagation(); + } + + if(timeouts.length) { + timeouts.shift()(); + } + } + }, false); + + timeouts.push(callback); + win.postMessage(msgName, '*'); + +} + +/** + * Create new $.event.special wrapper with some default behavior. + * + * @param {string} type event type + * @param {object} toExtend object to extend default wrapper + */ +function addPointerEvent(type, toExtend) { + + var eventName = 'pointer' + type, + pointerevent, + + eventSpecial = $.event.special[eventName] = { + // bind + setup: function() { + $(this) + .on(binds.mouse[type], eventSpecial.mouseHandler) + .on(binds.touch[type], eventSpecial.touchHandler) + .on(binds.mspointer[type], eventSpecial.msHandler); }, - teardown : function() { - $(this).off(current[eventType], specialEvent.handler); + // unbind + teardown: function() { + $(this) + .off(binds.mouse[type], eventSpecial.mouseHandler) + .off(binds.touch[type], eventSpecial.touchHandler) + .off(binds.mspointer[type], eventSpecial.msHandler); }, - handler : function() { - specialEvent[handlerFn].apply(this, arguments); + // mouse + mouseHandler: function(e) { + // do not duplicate PointerEvent if + // touch/mspointer is already processed + if(!eventSpecial._noMouse) { + e.pointerType = 4; + pointerevent = new PointerEvent(e, eventName); + $(e.currentTarget).triggerHandler(pointerevent); + } + + // clear the "processed" key right after + // current event and all the bubblings + nextTick(function() { + eventSpecial._noMouse = false; + }); }, - handlerTouch : function(e) { - var pointerEvent = new PointerEvent(e, pointerEventType); - pointerEvent.dispatch(pointerEvent.target); + // touch + touchHandler: function(e) { + // stop mouse events handling + eventSpecial._noMouse = true; + + e.pointerType = 2; + pointerevent = new PointerEvent(e, eventName); + + $(e.currentTarget).triggerHandler(pointerevent); }, - handlerNonTouch : function(e) { - new PointerEvent(e, pointerEventType).dispatch(this); + // mspointer + msHandler: function(e) { + // stop mouse events handling + eventSpecial._noMouse = true; + + pointerevent = new PointerEvent(e, eventName); + $(e.target).trigger(pointerevent); } }; - extend && $.extend(specialEvent, extend(specialEvent, pointerEventType)); -} + // extend this $.event.special wrapper + if(toExtend) { + $.extend(eventSpecial, toExtend({ + event: eventSpecial, + name: eventName, + type: type + })); + } -function extendHandlerTouchByElement(_, pointerEventType) { - return { - handlerTouch : function(e) { - var pointerEvent = new PointerEvent(e, pointerEventType), - target = document.elementFromPoint(pointerEvent.clientX, pointerEvent.clientY); - pointerEvent.dispatch(target); - } - }; } -function pressAndReleaseHandlerStub(specialEvent, pointerEventType) { - var eventTypeForMouse = current[pointerEventType === 'pointerpress'? 'down' : 'up']; +/** + * Object to extend $.event.special to touchmove-based events. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function touchmoveBased(params) { + + var event = params.event, + type = params.type; + return { - setup : function() { - isMouse? - $(this).on(eventTypeForMouse, specialEvent.handlerMouse) : - $(this) - .on(current.down, specialEvent.handlerNonMouseDown) - .on(current.move, specialEvent.handlerNonMouseMove) - .on(current.up, specialEvent.handlerNonMouseUp); + // bind + setup: function() { + $(this) + .on(binds.mouse[type], event.mouseHandler) + .on(binds.touch[type], event.touchHandler) + .on(binds.touch.down, event.touchDownHandler) + .on(binds.mspointer[type], event.msHandler); + + if(type !== 'move') { + $(this).on(binds.touch.move, event.touchMoveHandler); + } }, - teardown : function() { - isMouse? - $(this).off(eventTypeForMouse, specialEvent.handlerMouse) : - $(this) - .off(current.down, specialEvent.handlerNonMouseDown) - .off(current.move, specialEvent.handlerNonMouseMove) - .off(current.up, specialEvent.handlerNonMouseUp); - }, + // unbind + teardown: function() { + $(this) + .off(binds.mouse[type], event.mouseHandler) + .off(binds.touch[type], event.touchHandler) + .off(binds.touch.down, event.touchDownHandler) + .off(binds.mspointer[type], event.msHandler); - handlerNonMouseMove : function(e) { - var data = specialEvent.data; - if(Math.abs(e.clientX - data.clientX) > 5 || - Math.abs(e.clientY - data.clientY) > 5) { - data.move = true; + if(type !== 'move') { + $(this).off(binds.touch.move, event.touchMoveHandler); } }, - handlerMouse : function(e) { - // only left mouse button - e.which === 1 && new PointerEvent(e, pointerEventType).dispatch(this); + touchDownHandler: function(e) { + // stop mouse events handling + event._noMouse = true; + // save initial target + event._target = e.target; } }; -} -addSpecialEvent('enter'); -addSpecialEvent('over'); -addSpecialEvent('down'); -addSpecialEvent('up', extendHandlerTouchByElement); -addSpecialEvent('out', extendHandlerTouchByElement); -addSpecialEvent('leave', extendHandlerTouchByElement); -addSpecialEvent('move', function(specialEvent) { - return { - setup : function() { - isTouch && $(this).on(current.down, specialEvent.downHandler); - $(this).on(current.move, specialEvent.moveHandler); - }, +} - teardown : function() { - isTouch && $(this).off(current.down, specialEvent.downHandler); - $(this).off(current.move, specialEvent.moveHandler); - }, +/** + * Object to extend $.event.special to pointerenter. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendToEnter(params) { + + return $.extend(touchmoveBased(params), { + touchMoveHandler: function(e) { + e.pointerType = 2; + + var pointerevent = new PointerEvent(e, params.name), + targetFromPoint = doc.elementFromPoint( + pointerevent.clientX, + pointerevent.clientY + ), + target = params.event._target; + + // new target + if(target !== targetFromPoint) { + // fix simulated event targets + pointerevent.relatedTarget = pointerevent.target; + pointerevent.target = pointerevent.targetFromPoint; + + // inner target + if(target.contains(targetFromPoint)) { + $(targetFromPoint).triggerHandler(pointerevent); + // truly new target + } else if(!targetFromPoint.contains(target)) { + $(targetFromPoint).trigger(pointerevent); + } - downHandler : function(e) { - var pointerEvent = new PointerEvent(e, 'pointerdown'); - specialEvent.target = pointerEvent.target; - }, + // targetFromPoint -> target + params.event._target = targetFromPoint; + } + } + }); - moveHandler : function(e) { - var pointerEvent = new PointerEvent(e, 'pointermove'); - if(isTouch) { - var newTarget = document.elementFromPoint(pointerEvent.clientX, pointerEvent.clientY), - currentTarget = specialEvent.target; +} - pointerEvent.dispatch(currentTarget); +/** + * Object to extend $.event.special to pointerover. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendToOver(params) { - if(currentTarget !== newTarget) { - // out current target - pointerEvent = new PointerEvent(e, 'pointerout'); - pointerEvent.dispatch(currentTarget); + return $.extend(touchmoveBased(params), { + touchMoveHandler: function(e) { + e.pointerType = 2; - // new target is not a child of the current -> leave current target - if(!currentTarget.contains(newTarget)) { - pointerEvent = new PointerEvent(e, 'pointerleave'); - pointerEvent.dispatch(currentTarget); - } + var pointerevent = new PointerEvent(e, params.name), + targetFromPoint = doc.elementFromPoint( + pointerevent.clientX, + pointerevent.clientY + ), + target = params.event._target; - // new target is not the parent of the current -> leave new target - if(!newTarget.contains(currentTarget)) { - pointerEvent = new PointerEvent(e, 'pointerenter'); - pointerEvent.dispatch(newTarget); - } + // new target + if(target !== targetFromPoint) { + // fix simulated event targets + pointerevent.relatedTarget = pointerevent.target; + pointerevent.target = pointerevent.targetFromPoint; - // over new target - pointerEvent = new PointerEvent(e, 'pointerover'); - pointerEvent.dispatch(newTarget); + $(targetFromPoint).trigger(pointerevent); - // new target -> current target - specialEvent.target = newTarget; - } - } else { - pointerEvent.dispatch(this); + // targetFromPoint -> target + params.event._target = targetFromPoint; } } + }); + +} + +/** + * Object to extend $.event.special touchHandler with "target from point". + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendWithTargetFromPoint(params) { + + return { + touchHandler: function(e) { + // stop mouse events handling + params.event._noMouse = true; + + e.pointerType = 2; + + var pointerevent = new PointerEvent(e, params.name), + targetFromPoint = doc.elementFromPoint( + pointerevent.clientX, + pointerevent.clientY + ); + + // fix simulated event targets + pointerevent.relatedTarget = pointerevent.target; + pointerevent.target = pointerevent.targetFromPoint; + + $(targetFromPoint).triggerHandler(pointerevent); + } }; -}); -addSpecialEvent('press', function(specialEvent, pointerEventType) { +} + +/** + * Object to extend $.event.special to pointerout. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendToOut(params) { + return $.extend( - pressAndReleaseHandlerStub(specialEvent, pointerEventType), + touchmoveBased(params), + extendWithTargetFromPoint(params), { - handlerNonMouseDown : function(e) { - specialEvent.data = { - timer : (function() { - return setTimeout(function() { - if(!specialEvent.data.move) { - var pointerevent = new PointerEvent(e, pointerEventType); - pointerevent.dispatch(pointerevent.target); - } - }, 80); - })(), - clientX : e.clientX, - clientY : e.clientY - }; - }, - - handlerNonMouseUp : function() { - clearTimeout(specialEvent.data.timer); - delete specialEvent.data; + touchMoveHandler: function(e) { + e.pointerType = 2; + + var pointerevent = new PointerEvent(e, params.name), + targetFromPoint = doc.elementFromPoint( + pointerevent.clientX, + pointerevent.clientY + ), + target = params.event._target; + + // new target + if(target !== targetFromPoint) { + $(target).trigger(pointerevent); + + // targetFromPoint -> target + params.event._target = targetFromPoint; + } } - }); -}); + } + ); + +} + +/** + * Object to extend $.event.special to pointerleave. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendToLeave(params) { -addSpecialEvent('release', function(specialEvent, pointerEventType) { return $.extend( - pressAndReleaseHandlerStub(specialEvent, pointerEventType), + touchmoveBased(params), + extendWithTargetFromPoint(params), { - handlerNonMouseDown : function(e) { - var data = specialEvent.data = { - timer : (function() { - return setTimeout(function() { - data.move || (data.pressed = true); - }, 80); - })(), - clientX : e.clientX, - clientY : e.clientY - }; - }, - - handlerNonMouseUp : function(e) { - clearTimeout(specialEvent.data.timer); + touchMoveHandler: function(e) { + e.pointerType = 2; + + var pointerevent = new PointerEvent(e, params.name), + targetFromPoint = doc.elementFromPoint( + pointerevent.clientX, + pointerevent.clientY + ), + target = params.event._target; + + // new target + if(target !== targetFromPoint) { + if(targetFromPoint.contains(target)) { + $(target).triggerHandler(pointerevent); + } else { + $(e.currentTarget).triggerHandler(pointerevent); + } - if(specialEvent.data.pressed) { - var pointerEvent = new PointerEvent(e, pointerEventType), - target = document.elementFromPoint(pointerEvent.clientX, pointerEvent.clientY); - pointerEvent.dispatch(target); + // targetFromPoint -> target + params.event._target = targetFromPoint; } - - delete specialEvent.data; } - }); -}); + } + ); + +} + +/** + * Object to extend $.event.special to pointermove. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendToMove(params) { + + return $.extend( + touchmoveBased(params), + extendWithTargetFromPoint(params) + ); + +} + +// init pointer events +addPointerEvent('enter', extendToEnter); +addPointerEvent('over', extendToOver); +addPointerEvent('down'); +addPointerEvent('move', extendToMove); +addPointerEvent('up', extendWithTargetFromPoint); +addPointerEvent('out', extendToOut); +addPointerEvent('leave', extendToLeave); +addPointerEvent('cancel'); provide($); -}); \ No newline at end of file +}); diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js new file mode 100644 index 000000000..256cb03ab --- /dev/null +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js @@ -0,0 +1,7 @@ +({ + mustDeps : { + block : 'jquery', elems : { + elem : 'event', mods : { type : 'pointerpressrelease' } + } + } +}) diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js new file mode 100644 index 000000000..82226dc8a --- /dev/null +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js @@ -0,0 +1,195 @@ +/** + * Additional pointerpress and pointerrelease events on top of + * jquery-pointerevents. Goal – to prevent an accidental pressed + * states when you just move your finger through the element on + * touch devices. + * + * @modules pointerpressrelease + * + * @author Kir Belevich + * @copyright Kir Belevich 2013 + * @license MIT + * @version 0.1.0 + */ +modules.define('jquery', function(provide, $) { + +// nothing to do without jquery-ppinterevents +if(!('PointerEvent' in $)) { + provide($); + return; +} + +/** + * Create new $.event.special wrapper with some default behavior. + * + * @param {string} type event type + * @param {object} toExtend object to extend default wrapper + */ +function addPointerEvent(type, toExtend) { + + var eventName = 'pointer' + type, + + eventSpecial = $.event.special[eventName] = { + // bind + setup: function() { + $(this).on({ + pointerdown: eventSpecial.handlerDown, + pointermove: eventSpecial.handlerMove, + pointerup: eventSpecial.handlerUp + }); + }, + + // unbind + teardown: function() { + $(this).off({ + pointerdown: eventSpecial.handlerDown, + pointermove: eventSpecial.handlerMove, + pointerup: eventSpecial.handlerUp + }); + }, + + handlerMove: function(e) { + + if(e.pointerType === 'touch') { + var data = eventSpecial.data; + + // if there is a touch move + if( + data && + (Math.abs(e.clientX - data.clientX) > 5 || + Math.abs(e.clientY - data.clientY) > 5) + ) { + // save that + data.move = true; + } + } + } + }; + + // extend this $.event.special wrapper + if(toExtend) { + $.extend(eventSpecial, toExtend({ + event: eventSpecial, + name: eventName, + type: type + })); + } + +} + +/** + * Object to extend $.event.special to handle pointerpress. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendPointerPress(params) { + + var data = params.event.data; + + return { + handlerDown: function(e) { + var target = e.target, + pointerevent; + + // touch + if(e.pointerType === 'touch') { + data = { + timer: (function() { + // if there was no touchmove in 80ms – trigger pointerpress + return setTimeout(function() { + if(data && !data.move) { + pointerevent = new $.PointerEvent(e, params.name); + $(e.currentTarget).triggerHandler(pointerevent); + } + }, 80); + })(), + clientX: e.clientX, + clientY: e.clientY + }; + // mouse – only left button + } else if(e.which === 1) { + pointerevent = new $.PointerEvent(e, params.name); + $(target).trigger(pointerevent); + } + }, + + handlerUp: function(e) { + if(e.pointerType === 'touch') { + if(data) { + clearTimeout(data.timer); + } + data = null; + } + } + }; + +} + +/** + * Object to extend $.event.special to handle pointerpress. + * + * @param {object} params + * @param {object} params.event event object + * @param {string} params.name event name + * @param {string} params.type event type + * @return {object} + */ +function extendPointerRelease(params) { + + var data = params.event.data; + + return { + handlerDown: function(e) { + var target = e.target, + pointerevent; + + // touch + if(e.pointerType === 'touch') { + data = { + timer: (function() { + // if there was no touchmove in 80ms – trigger pointerpress + return setTimeout(function() { + if(data && !data.move) { + data.pressed = true; + } + }, 80); + })(), + clientX: e.clientX, + clientY: e.clientY + }; + // mouse – only left button + } else if(e.which === 1) { + pointerevent = new $.PointerEvent(e, params.name); + $(target).trigger(pointerevent); + } + }, + + handlerUp: function(e) { + if(e.pointerType === 'touch') { + if(data && data.pressed) { + var pointerevent = new $.PointerEvent(e, params.name); + $(e.target).trigger(pointerevent); + } + + if(data) { + clearTimeout(data.timer); + } + + data = null; + } + } + }; + +} + +// init pointer events +addPointerEvent('press', extendPointerPress); +addPointerEvent('release', extendPointerRelease); + +provide($); + +}); From c54a6b0e41ffc62958e3732e3cefbecba6ca9cde Mon Sep 17 00:00:00 2001 From: Kir Belevich Date: Fri, 24 Jan 2014 14:10:15 +0200 Subject: [PATCH 2/2] fix jquery__event_type_pointerpressrelease, update tests --- ...ry__event_type_pointerpressrelease.deps.js | 2 +- .../jquery__event_type_pointerpressrelease.js | 15 +++++---- ...vent_type_pointerpressrelease.ru.title.txt | 1 + .../_type/jquery__event_type_pointer.spec.js | 20 ------------ ...ry__event_type_pointerpressrelease.spec.js | 32 +++++++++++++++++++ 5 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.ru.title.txt create mode 100644 desktop.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.spec.js diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js index 256cb03ab..0cd476c54 100644 --- a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.deps.js @@ -1,7 +1,7 @@ ({ mustDeps : { block : 'jquery', elems : { - elem : 'event', mods : { type : 'pointerpressrelease' } + elem : 'event', mods : { type : 'pointer' } } } }) diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js index 82226dc8a..8929fc5eb 100644 --- a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.js @@ -113,7 +113,7 @@ function extendPointerPress(params) { // mouse – only left button } else if(e.which === 1) { pointerevent = new $.PointerEvent(e, params.name); - $(target).trigger(pointerevent); + $(e.currentTarget).triggerHandler(pointerevent); } }, @@ -161,17 +161,16 @@ function extendPointerRelease(params) { clientX: e.clientX, clientY: e.clientY }; - // mouse – only left button - } else if(e.which === 1) { - pointerevent = new $.PointerEvent(e, params.name); - $(target).trigger(pointerevent); } }, handlerUp: function(e) { + var pointerevent; + + // touch if(e.pointerType === 'touch') { if(data && data.pressed) { - var pointerevent = new $.PointerEvent(e, params.name); + pointerevent = new $.PointerEvent(e, params.name); $(e.target).trigger(pointerevent); } @@ -180,6 +179,10 @@ function extendPointerRelease(params) { } data = null; + // mouse – only left button + } else if(e.which === 1) { + pointerevent = new $.PointerEvent(e, params.name); + $(e.currentTarget).triggerHandler(pointerevent); } } }; diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.ru.title.txt b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.ru.title.txt new file mode 100644 index 000000000..c7e1750a6 --- /dev/null +++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.ru.title.txt @@ -0,0 +1 @@ +Плагин, реализующий дополнительные события pointerpress и pointerrelease на основе Pointer Events diff --git a/desktop.blocks/jquery/__event/_type/jquery__event_type_pointer.spec.js b/desktop.blocks/jquery/__event/_type/jquery__event_type_pointer.spec.js index a7e356cb9..7b2ab62d2 100644 --- a/desktop.blocks/jquery/__event/_type/jquery__event_type_pointer.spec.js +++ b/desktop.blocks/jquery/__event/_type/jquery__event_type_pointer.spec.js @@ -86,26 +86,6 @@ describe('jquery__event_type_pointer', function() { spy.should.have.been.calledOnce; }); - it('should trigger "pointerpress" event on "mousedown"', function() { - var spy = sinon.spy(), - e = $.Event('mousedown', { which : 1 }), - elem = $('
'); - - elem.on('pointerpress', spy).trigger(e); - - spy.should.have.been.calledOnce; - }); - - it('should trigger "pointerrelease" event on "mouseup"', function() { - var spy = sinon.spy(), - e = $.Event('mouseup', { which : 1 }), - elem = $('
'); - - elem.on('pointerrelease', spy).trigger(e); - - spy.should.have.been.calledOnce; - }); - it('should successfully unbind from aliased events', function() { var spy = sinon.spy(), elem = $('
'); diff --git a/desktop.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.spec.js b/desktop.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.spec.js new file mode 100644 index 000000000..61dd5175f --- /dev/null +++ b/desktop.blocks/jquery/__event/_type/jquery__event_type_pointerpressrelease.spec.js @@ -0,0 +1,32 @@ +modules.define( + 'spec', + ['jquery', 'sinon'], + function(provide, $, sinon) { + +describe('jquery__event_type_pointerpressrelease', function() { + + it('should trigger "pointerpress" event on "mousedown"', function() { + var spy = sinon.spy(), + e = $.Event('mousedown', { which : 1 }), + elem = $('
'); + + elem.on('pointerpress', spy).trigger(e); + + spy.should.have.been.calledOnce; + }); + + it('should trigger "pointerrelease" event on "mouseup"', function() { + var spy = sinon.spy(), + e = $.Event('mouseup', { which : 1 }), + elem = $('
'); + + elem.on('pointerrelease', spy).trigger(e); + + spy.should.have.been.calledOnce; + }); + +}); + +provide(); + +});