Skip to content

Commit

Permalink
Support dijit/Tree DnD on mobile, and support touch.over, touch.out, …
Browse files Browse the repository at this point in the history
…touch.enter, and touch.leave synthetic events from dojo/touch module. Also fixed touch.move to behave like mousemove.

I refactored the touch.over/touch.out code to work by firing synthetic events when it that the user drags over a different node than before.   

About touch.move: Previously, on(node, touch.move, func) on mobile would only trigger func if the user started their drag inside of the specified node.   This did not match the behavior of on(node, touch.move, fun) on desktop, which was to fire events whenever the mouse was moved over the specified node, regardless of where the mouse movement started.   DnD at least was depending on the onmousemove-like behavior.

Refs #14185 !strict.

git-svn-id: http://svn.dojotoolkit.org/src/dojo/trunk@28381 560b804f-0ae3-0310-86f3-f6aa0a117693
  • Loading branch information
wkeese committed Apr 19, 2012
1 parent f883b2f commit 2b82a97
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 74 deletions.
51 changes: 2 additions & 49 deletions dnd/Container.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,52 +75,6 @@ dojo.dnd.Item = function(){
}
=====*/

// Setup synthetic "touchover" and "touchout" events to handle mouseout/mouseover plus their touch equivalents.
// Probably this code should eventually be moved to dojo/touch.
// TODO: should touchend cause touchout event on the currently "hovered" node?
var touchover = "mouseover", touchout = "mouseout";
if(has("touch")){
// Keep track of the currently hovered node. This code tries to fire a touchover event on touchstart,
// but that part doesn't work because Selector::onMouseDown() getsc called before this code does,
// but expects that there was already a mouseenter (aka touchover) event. Also, Selector::onMouseDown() calls
// evt.stop() preventing this code from even seeing the event.
var hoveredNode, // currently hovered node
tracker = new Evented(); // emits events when hovered node changes
ready(function(){
on(win.doc, "touchstart, touchmove", function(evt){
var newHoverNode = win.doc.elementFromPoint(
evt.pageX - win.body().parentNode.scrollLeft,
evt.pageY - win.body().parentNode.scrollTop
);
if(newHoverNode != hoveredNode){
tracker.emit("hoverNode", hoveredNode, hoveredNode = newHoverNode);
}
});
});

touchover = function(myNode, listener){
return tracker.on("hoverNode", function(oldVal, newVal){
if(dom.isDescendant(newVal, myNode)){
return listener.call(myNode, {
type: "touch.over",
target: newVal,
relatedTarget: oldVal
});
}
});
};
touchout = function(myNode, listener){
return tracker.on("hoverNode", function(oldVal, newVal){
if(dom.isDescendant(oldVal, myNode)){
return listener.call(myNode, {
type: "touch.out",
target: oldVal,
relatedTarget: newVal
});
}
});
};
}

var Container = declare("dojo.dnd.Container", Evented, {
// summary:
Expand Down Expand Up @@ -173,9 +127,8 @@ var Container = declare("dojo.dnd.Container", Evented, {

// set up events
this.events = [
on(this.node, touchover, lang.hitch(this, "onMouseOver")),
on(this.node, "touchstart", lang.hitch(this, "onMouseOver")), // because on mobile there's no mouseover equivalent before the touchpress event
on(this.node, touchout, lang.hitch(this, "onMouseOut")),
on(this.node, touch.over, lang.hitch(this, "onMouseOver")),
on(this.node, touch.out, lang.hitch(this, "onMouseOut")),
// cancel text selection and text dragging
on(this.node, "dragstart", lang.hitch(this, "onSelectStart")),
on(this.node, "selectstart", lang.hitch(this, "onSelectStart"))
Expand Down
6 changes: 4 additions & 2 deletions dnd/Selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,10 @@ var Selector = declare("dojo.dnd.Selector", Container, {
onOutEvent: function(){
// summary:
// this function is called once, when mouse is out of our container
this.onmousemoveEvent.remove();
delete this.onmousemoveEvent;
if(this.onmousemoveEvent){
this.onmousemoveEvent.remove();
delete this.onmousemoveEvent;
}
},
_removeSelection: function(){
// summary:
Expand Down
1 change: 1 addition & 0 deletions mouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ define(["./_base/kernel", "./on", "./has", "./dom", "./_base/window"], function(
return handler;
}
return {
_eventHandler: eventHandler, // for dojo/touch
enter: eventHandler("mouseover"),
leave: eventHandler("mouseout"),
isLeft: mouseButtons.isLeft,
Expand Down
75 changes: 57 additions & 18 deletions tests/test_touch.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@
border: 1px solid #7FB0DB;
background-color: #7FB0DB;
}
#log {
#innertest {
border: 1px solid white;
width: 100px;
heigh: 75px;
background-color: white;
}
#current, #log {
width: 300px;
height: 200px;
float: left;
}
#dohDiv{
display: none;
Expand All @@ -22,20 +29,26 @@
<script type="text/javascript" src="../dojo.js" djConfig="parseOnLoad: true"></script>
<script>
require([
"dojo/_base/html",
"dojo/_base/array",
"dojo/dom",
"dojo/_base/event",
"dojo/_base/lang",
"dojo/ready",
"dojo/touch",
"dojo/on",
"dojo/has",
"dojo/dom-style",
"doh/runner"
], function(html, evt, ready, touch, on, has, domStyle, doh){
], function(array, dom, evt, lang, ready, touch, on, has, domStyle, doh){
ready(function(){
var action = function(e){
evt.stop(e);
html.byId("log").innerHTML = "";
var info = "[Touch Event]: " + e.type + "<br/> ------ Event Properties: ------<br/>";
var action = function(comment, e){
// summary:
// Callback to display into when events fire
// Detailed log of the most recent event:

dom.byId("current").innerHTML = "Most recent event:";
var info = "[Touch Event]: " + e.type + " on " + comment +
"<br/> ------ Event Properties: ------<br/>";
for(var i in e){
if(i == "touches" || i == "targetTouches" || i == "changedTouches"){
info += i + ": " + e[i].length + "<br/>";
Expand All @@ -45,17 +58,30 @@
}
}
}
html.byId("log").innerHTML += info + "<br/>";
dom.byId("current").innerHTML += info + "<br/>";

// This is a log of all events, most recent first:
dom.byId("log").innerHTML = comment + "{type:" +
e.type + ", target:" + (e.target.id||e.target.tagName) +
"}<br/>" + dom.byId("log").innerHTML;
};

var node = html.byId("test");

//1. should work well on PC and touch devices
on(node, touch.press, action);
on(node, touch.move, action);
on(node, touch.release, action);
on(node, touch.cancel, action);
var node = dom.byId("test"),
innerNode = dom.byId("innertest");

//1. should work well on PC and touch devices
array.forEach(["test", "innertest"], function(name){
for(var event in touch){
if(event != "move"){
on(dom.byId(name), touch[event], lang.hitch(null, action, "on("+name+", touch."+event+")-->"));
}
}
});

on(document, "touchmove", function(evt){
// Prevent scrolling since it interferes with testing touchover/touchout on "test" and "innertest"
evt.preventDefault();
});

// //2. should work well across touch devices
// on(node, "touchstart", action);
Expand All @@ -67,13 +93,20 @@
//3. we can also isolate mouse/touch handlers
on(node, "touchend", function(){alert("isolated touchend handler");});
on(node, "mouseup", function(){alert("isolated mouseup handler");});
/*
on(node, "touchmove", function(e){
dom.byId("log").innerHTML = 'on(node,"touchmove")-->{type:' +
e.type + ", target:" + (e.target.id||e.target.tagName) +
"}<br/>" + dom.byId("log").innerHTML;
});
*/

//================================= DoH tests - only running on desktop ======================
if(has("touch")){
//FIXME - DoH not supported on touch device
return;
}
var dohDiv = html.byId('dohDiv');
var dohDiv = dom.byId('dohDiv');
domStyle.set(dohDiv, {display: 'block'});

function setObj(obj, e){
Expand Down Expand Up @@ -136,9 +169,15 @@
</script>
</head>
<body>
<div id="test"></div>
<div id="test">
test
<div id="innertest">
inner test
</div>
</div>
<div id="current"></div>
<div id="log"></div>
<br/>
<br style="clear:both"/>
<div id="dohDiv">doh</div>
</body>
</html>
121 changes: 116 additions & 5 deletions touch.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
define(["./_base/kernel", "./on", "./has", "./mouse"], function(dojo, on, has, mouse){
define(["./_base/kernel", "./_base/lang", "./aspect", "./dom", "./on", "./has", "./mouse", "./ready", "./_base/window"],
function(dojo, lang, aspect, dom, on, has, mouse, ready, win){

// module:
// dojo/touch

Expand Down Expand Up @@ -69,21 +71,130 @@ define(["./_base/kernel", "./on", "./has", "./mouse"], function(dojo, on, has, m
// returns:
// A handle which will be used to remove the listener by handle.remove()
}
over: function(node, listener){
// summary:
// Register a listener to 'mouseover' or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
out: function(node, listener){
// summary:
// Register a listener to 'mouseout' or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
enter: function(node, listener){
// summary:
// Register a listener to mouse.enter or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
},
leave: function(node, listener){
// summary:
// Register a listener to mouse.leave or touch equivalent for the given node
// node: Dom
// Target node to listen to
// listener: Function
// Callback function
// returns:
// A handle which will be used to remove the listener by handle.remove()
}
};
=====*/


var touch = has("touch");

var touchmove, hoveredNode;

if(touch){
ready(function(){
// Keep track of currently hovered node
hoveredNode = win.body(); // currently hovered node

win.doc.addEventListener("touchstart", function(evt){
// Precede touchstart event with touch.over event. DnD depends on this.
// Use addEventListener(cb, true) to run cb before any touchstart handlers on node run,
// and to ensure this code runs even if the listener on the node does event.stop().
var oldNode = hoveredNode;
hoveredNode = evt.target;
on.emit(oldNode, "dojotouchout", {
target: oldNode,
relatedTarget: hoveredNode,
bubbles: true
});
on.emit(hoveredNode, "dojotouchover", {
target: hoveredNode,
relatedTarget: oldNode,
bubbles: true
});
}, true);

// Fire synthetic touchover and touchout events on nodes since the browser won't do it natively.
on(win.doc, "touchmove", function(evt){
var oldNode = hoveredNode;
hoveredNode = win.doc.elementFromPoint(
evt.pageX - win.body().parentNode.scrollLeft,
evt.pageY - win.body().parentNode.scrollTop
);
if(oldNode !== hoveredNode){
on.emit(oldNode, "dojotouchout", {
target: oldNode,
relatedTarget: hoveredNode,
bubbles: true
});

on.emit(hoveredNode, "dojotouchover", {
target: hoveredNode,
relatedTarget: oldNode,
bubbles: true
});
}
});
});

// Define synthetic touchmove event that unlike the native touchmove, fires for the node the finger is
// currently dragging over rather than the node where the touch started.
touchmove = function(node, listener){
return on(win.doc, "touchmove", function(evt){
if(node === win.doc || dom.isDescendant(hoveredNode, node)){
listener.call(this, lang.mixin({}, evt, {
target: hoveredNode
}));
}
});
};
}


function _handle(/*String - press | move | release | cancel*/type){
return function(node, listener){//called by on(), see dojo.on
return on(node, type, listener);
};
}
var touch = has("touch");
//device neutral events - dojo.touch.press|move|release|cancel

//device neutral events - dojo.touch.press|move|release|cancel/over/out
dojo.touch = {
press: _handle(touch ? "touchstart": "mousedown"),
move: _handle(touch ? "touchmove": "mousemove"),
move: touch ? touchmove :_handle("mousemove"),
release: _handle(touch ? "touchend": "mouseup"),
cancel: touch ? _handle("touchcancel") : mouse.leave
cancel: touch ? _handle("touchcancel") : mouse.leave,
over: _handle(touch ? "dojotouchover": "mouseover"),
out: _handle(touch ? "dojotouchout": "mouseout"),
enter: mouse._eventHandler(touch ? "dojotouchover" : "mouseover"),
leave: mouse._eventHandler(touch ? "dojotouchout" : "mouseout")
};
return dojo.touch;
});

0 comments on commit 2b82a97

Please sign in to comment.