Skip to content

Commit

Permalink
pointer events prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrebnov committed Jun 14, 2013
1 parent 5e274c5 commit a2e3ddf
Show file tree
Hide file tree
Showing 4 changed files with 451 additions and 0 deletions.
1 change: 1 addition & 0 deletions has.js
Expand Up @@ -99,6 +99,7 @@ define(["require", "module"], function(require, module){
// Common application level tests
has.add("dom-addeventlistener", !!document.addEventListener);
has.add("touch", "ontouchstart" in document || window.navigator.msMaxTouchPoints > 0);
has.add("pointer", !!window.navigator.msPointerEnabled);

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

Typically here we would kill two birds with one stone by returning the event name prefix, something like "MSPointer" for IE10 and "Pointer" for IE11, and null for older browsers.

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Updated

// I don't know if any of these tests are really correct, just a rough guess
has.add("device-width", screen.availWidth || innerWidth);

Expand Down
178 changes: 178 additions & 0 deletions pointer.js
@@ -0,0 +1,178 @@
define(["./_base/kernel", "./aspect", "./dom", "./dom-class", "./_base/lang", "./on", "./has", "./mouse", "./domReady", "./_base/window"],
function(dojo, aspect, dom, domClass, lang, on, has, mouse, domReady, win){

// module:
// dojo/pointer

var hasTouch = has("touch"),
hasPointer = has("pointer"),
lastTouch,
mouseEventFireThreshold = 1000,

POINTER_TYPE_TOUCH = "touch",
POINTER_TYPE_PEN = "pen",
POINTER_TYPE_MOUSE = "mouse",

bindEvent = function(preferredEventName, mouseType, touchType, pointerType) {

pointerType = browserSpecificEventName(pointerType);

// Returns synthetic event that listens for pointer or both the specified mouse event and specified touch event.
// But ignore fake mouse events that were generated due to the user touching the screen.
if (hasPointer) {
// Pointer events are designed to handle both mouse and touch in a uniform way,
// so just use that regardless of hasTouch.
return function(node, listener) {
// TODO We may want to normalize Pointer events too since there could be a difference in comparison to the spec;
// for example in IE10 MSPointer.pointerType is int instead of string as per specification.
return on(node, pointerType, listener);
}
}

if (hasTouch) {
return function(node, listener) {
var touchHandle = on(node, touchType, function(evt) {
var self = this;
lastTouch = (new Date()).getTime();

normalizeEvent(evt, POINTER_TYPE_TOUCH, preferredEventName).forEach(function(e){
listener.call(self, e);
})

}),
mouseHandle = on(node, mouseType, function(evt) {
if (!lastTouch || (new Date()).getTime() > lastTouch + mouseEventFireThreshold) {
listener.call(this, normalizeEvent(evt, POINTER_TYPE_MOUSE, preferredEventName)[0]);
}
});
return {

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

bindEvent() returns a function that's called every time the app does something like on(node, pointer.down, callback). That means that on webkit, every call to on(node, pointer.down, callback) will setup two listeners, one for mousedown and one for touchstart.

I had envisioned the opposite design, where on webkit, on(node, pointer.down, callback) simply does on(node, 'dojopointerdown', callback).

To enable that, dojo/pointer would set up one mousedown and one touchstart listener at the root of the document, in capturing phase (rather than bubbling phase), and together those listeners fire a synthetic dojopointerdown event with a normalized event object. In the case of multi-touch it would fire multiple dojopointerdown events, one for each finger.

What do you think of that design?

Support for IE is a different matter; since MSPointerDown already more-or-less matches the standard, perhaps the document level listeners are unneeded and on(node, pointer.down, callback) should do on(node, 'MSPointerDown', callback).

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Will follow up by email

remove: function() {
touchHandle.remove();
mouseHandle.remove();
}
};
};
}

// no Pointer or Touch support
// Avoid creating listeners for touch events on performance sensitive older browsers like IE6
return function(node, listener) {
return on(node, mouseType, function(evt) {
listener.call(this, normalizeEvent(evt, POINTER_TYPE_MOUSE, preferredEventName)[0]);
});
}

},

/**
* Converts given Mouse or Touch event to a Pointer event.
*
* In case of Touch event returns separate Pointer event for each touch (event.changedTouches container).
*
* @param {Event} originalEvent TODO
* @param {Enum} [eventType] TODO
* @return {Array} A list of Pointer events.
*/
normalizeEvent = function (originalEvent, eventType, preferredEventName) {
// defines extra properties for normalized events (use default values)
var pointerProperties = {"width" : 0, "height" : 0, "pressure" : 0, "tiltX" : 0, "tiltY" : 0,
"type" : preferredEventName, "pointerType" : eventType, "isPrimary" : true, "POINTER_TYPE_TOUCH" : POINTER_TYPE_TOUCH,
"POINTER_TYPE_PEN" : POINTER_TYPE_PEN, "POINTER_TYPE_MOUSE" : POINTER_TYPE_MOUSE},
normalizedEvents = [];

if (eventType === POINTER_TYPE_MOUSE) {

// Mouse is required to have a pointerId of 1
pointerProperties.pointerId = 1;

normalizedEvents.push(PointerEvent(originalEvent, pointerProperties));

} else if (eventType === POINTER_TYPE_TOUCH) {

for(var i=0; i<originalEvent.changedTouches.length; i++) {

var touch = originalEvent.changedTouches[i],
touchProperties = {"isPrimary": touch === originalEvent.touches[0],
"pointerId" : touch.identifier + 2, // Touch identifiers can start at 0 so we add 2 to the touch identifier for compatibility.

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

@seb-pereira - you mentioned before that the code didn't support pointerID, but it seems to, on the line above, right?

"pageX" : touch.pageX, "pageY" : touch.pageY,
"clientX" : touch.clientX, "clientY" : touch.clientY,
"screenX" : touch.screenX, "screenY" : touch.screenY,
"currentTarget" : touch.target, "target" : touch.target};

var pointerPropertiesForTouch = lang.mixin(touchProperties, pointerProperties);

normalizedEvents.push(PointerEvent(originalEvent, pointerPropertiesForTouch));
};
}




return normalizedEvents;
},

/**
* Creates Pointer event from a given original event and properties table.
*
* @param {Event} originalEvent TODO
* @param {Object} [properties] Dictionary of initial event properties.
* @return {Event} A new Pointer event initialized with properties from `properties`.
*/
PointerEvent = function(originalEvent, properties) {

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

It's confusing (to me) that you camel case PointerEvent like it's a class name but you can't (or don't) call new PointerEvent. How about renaming the function to makePointerEvent()?

I also personally prefer declaring functions like function foo() instead of foo = function() but I guess that's a matter of taste.

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Thank you, updated

var pointerEvent = lang.delegate(properties, originalEvent);

// override default event type
pointerEvent.type = properties.type;

var buttons = pointerEvent.buttons;
if (buttons === undefined) {
switch (pointerEvent.which) {
case 1: buttons = 1; break;
case 2: buttons = 4; break;
case 3: buttons = 2; break;
default: buttons = 0;
}

Object.defineProperty(pointerEvent, 'buttons', {get: function(){ return buttons }, enumerable: true});
}

// use 0.5 for down state and 0 for up state.
pointerEvent.pressure = pointerEvent.pressure || (pointerEvent.buttons ? 0.5 : 0);

return pointerEvent;

},

browserSpecificEventName = function (eventName) {
if(has("ie") == 10) {

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

This would be better as a functional test. Not technically better, but it avoids people getting on a soapbox preaching about the evils of browser sniffing.

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Updated

return "MS" + eventName;
}

return eventName;
}


//device touch model agnostic events - pointer.down|move|up|cancel|over|out|enter|leave
var pointer = {
down: bindEvent("pointer.down","mousedown", "touchstart", "PointerDown"),
move: bindEvent("pointer.move", "mousemove", "touchmove", "PointerMove"),
up: bindEvent("pointer.up", "mouseup", "touchend", "PointerUp"),
cancel: bindEvent("pointer.cancel", mouse.leave, "touchcancel", "PointerCancel"),
over: bindEvent("pointer.over", "mouseover", "touchover", "PointerOver"),
out: bindEvent("pointer.out", "mouseout", "touchout", "PointerOut"),
enter: bindEvent("pointer.enter", "mouseover","touchover", "PointerOver"),
leave: bindEvent("pointer.leave", "mouseout", "touchout", "PointerOut")

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

I'm confused by these event names like "pointer.down", why does it have a dot in it?

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Fixed

};

has("extend-dojo") && (dojo.pointer = pointer);

This comment has been minimized.

Copy link
@wkeese

wkeese Jul 10, 2013

We won't extend the dojo object in 2.0, so this line shouldn't be here.

This comment has been minimized.

Copy link
@sgrebnov

sgrebnov Jul 16, 2013

Author Owner

Updated


return pointer;


// TODO
// #0 create new pointer event from MouseEvent
// #1 add dojoClick support and the rest dojo specific functionality
// #2 review enter and leave events
// comment/refactor

});
101 changes: 101 additions & 0 deletions tests/test_pointer.html
@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width,maximum-scale=1,minimum-scale=1,user-scalable=no" />
<title>Dojo Pointer events Testing</title>
<style type="text/css">

html, body, #main {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
}

#main > div {
width: 100%;
}

#touchpad {
height: 100px;
text-align: center;
background-color: #eee;
}

#log {
height: 500px;
background-color: rgb(3, 38, 38);
color: white;
overflow: hidden;
overflow: scroll;
}

.box {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: box;

-ms-flex-direction: column;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
box-orien: vertical;
}

.flex1 {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}

</style>
<script type="text/javascript" src="../dojo.js" data-dojo-config="async: true"></script>
<script>
require([
"dojo/dom",
"dojo/pointer",
"dojo/on",
"dojo/has",
"dojo/domReady!"
], function(dom, pointer, on, has){
var touchpad = dom.byId("touchpad"),
events = [pointer.down, pointer.move, pointer.up, pointer.cancel,
pointer.over, pointer.out, pointer.enter, pointer.leave],

onTouchPadEvent = function(event) {
var logEntry = {
event: event.type,
pointerType: event.pointerType,
pointerId: event.pointerId,
clientX: event.clientX,
clientY: event.clientY,
buttons: event.buttons,
target: event.target ? event.target.id : null
}

var msg = JSON.stringify(logEntry) + "<br/>",
log = dom.byId("log");

log.innerHTML = msg + log.innerHTML;
};

events.forEach(function(event) {
on(touchpad, event, onTouchPadEvent);
});

});



</script>
</head>
<body>
<div id="main" class="box">
<div id="touchpad">Touch here!</div>
<div id="log" class="flex1"></div>
</div>
</body>
</html>

0 comments on commit a2e3ddf

Please sign in to comment.