From 2c40de62befea284c23a0d59af840d087bb7fba3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 21 Dec 2012 08:47:29 -0800 Subject: [PATCH] Add iD.behavior.drag `iD.behavior.drag` is like `d3.behavior.drag`, with the following differences: * The `origin` function is expected to return an [x, y] tuple rather than an {x, y} object. * The events are `start`, `move`, and `end`. (https://github.com/mbostock/d3/issues/563) * The `start` event is not dispatched until the first cursor movement occurs. (https://github.com/mbostock/d3/pull/368) * The `move` event has a `loc` and `dxdy` [x, y] tuple properties rather than `x`, `y`, `dx`, and `dy` properties. * The `end` event is not dispatched if no movement occurs. * An `off` function is available that unbinds the drag's internal event handlers. * Delegation is supported via the `delegate` function. --- Makefile | 2 + index.html | 3 + js/id/behavior.js | 1 + js/id/behavior/drag.js | 151 +++++++++++++++++++++++++++++++++++ js/id/renderer/background.js | 2 +- js/id/renderer/map.js | 2 +- js/id/util.js | 22 ++++- test/index.html | 3 + 8 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 js/id/behavior.js create mode 100644 js/id/behavior/drag.js diff --git a/Makefile b/Makefile index fcf898b54..b6962d37b 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,8 @@ all: \ js/id/util.js \ js/id/actions.js \ js/id/actions/*.js \ + js/id/behavior.js \ + js/id/behavior/*.js \ js/id/modes.js \ js/id/modes/*.js \ js/id/controller/*.js \ diff --git a/index.html b/index.html index afa192755..26b7f2673 100644 --- a/index.html +++ b/index.html @@ -62,6 +62,9 @@ + + + diff --git a/js/id/behavior.js b/js/id/behavior.js new file mode 100644 index 000000000..c0801afa3 --- /dev/null +++ b/js/id/behavior.js @@ -0,0 +1 @@ +iD.behavior = {}; diff --git a/js/id/behavior/drag.js b/js/id/behavior/drag.js new file mode 100644 index 000000000..dc947582c --- /dev/null +++ b/js/id/behavior/drag.js @@ -0,0 +1,151 @@ +/* + `iD.behavior.drag` is like `d3.behavior.drag`, with the following differences: + + * The `origin` function is expected to return an [x, y] tuple rather than an + {x, y} object. + * The events are `start`, `move`, and `end`. + (https://github.com/mbostock/d3/issues/563) + * The `start` event is not dispatched until the first cursor movement occurs. + (https://github.com/mbostock/d3/pull/368) + * The `move` event has a `loc` and `dxdy` [x, y] tuple properties rather + than `x`, `y`, `dx`, and `dy` properties. + * The `end` event is not dispatched if no movement occurs. + * An `off` function is available that unbinds the drag's internal event handlers. + * Delegation is supported via the `delegate` function. + + */ +iD.behavior.drag = function () { + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + } + + var event = d3.dispatch("start", "move", "end"), + origin = null, + selector = ''; + + event.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = drag; + d3.event = e1; + event[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + + function mousedown() { + var target = this, + event_ = event.of(target, arguments), + eventTarget = d3.event.target, + touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, + offset, + origin_ = point(), + moved = 0; + + var w = d3.select(window) + .on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove) + .on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + + if (origin) { + offset = origin.apply(target, arguments); + offset = [ offset[0] - origin_[0], offset[1] - origin_[1] ]; + } else { + offset = [ 0, 0 ]; + } + + if (touchId == null) d3_eventCancel(); + + function point() { + var p = target.parentNode; + return touchId != null ? d3.touches(p).filter(function (p) { + return p.identifier === touchId; + })[0] : d3.mouse(p); + } + + function dragmove() { + if (!target.parentNode) return dragend(); + + var p = point(), + dx = p[0] - origin_[0], + dy = p[1] - origin_[1]; + + if (!moved) { + event_({ + type: "start" + }); + } + + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + + event_({ + type: "move", + loc: [p[0] + offset[0], p[1] + offset[1]], + dxdy: [dx, dy] + }); + } + + function dragend() { + if (moved) { + event_({ + type: "end" + }); + + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + + w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null) + .on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null); + } + + function click() { + d3_eventCancel(); + w.on("click.drag", null); + } + } + + function drag(selection) { + var matchesSelector = iD.util.prefixDOMProperty('matchesSelector'), + delegate = mousedown; + + if (selector) { + delegate = function() { + var root = this, + target = d3.event.target; + for (; target && target !== root; target = target.parentNode) { + if (target[matchesSelector](selector)) { + return mousedown.call(target, target.__data__); + } + } + } + } + + selection.on("mousedown.drag" + selector, delegate) + .on("touchstart.drag" + selector, delegate); + } + + drag.off = function(selection) { + selection.on("mousedown.drag" + selector, null) + .on("touchstart.drag" + selector, null); + }; + + drag.delegate = function(_) { + if (!arguments.length) return selector; + selector = _; + return drag; + }; + + drag.origin = function (_) { + if (!arguments.length) return origin; + origin = _; + return drag; + }; + + return d3.rebind(drag, event, "on"); +}; diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 24da773d6..54803980f 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -2,7 +2,7 @@ iD.Background = function() { var tile = d3.geo.tile(), projection, cache = {}, - transformProp = iD.util.prefixProperty('Transform'), + transformProp = iD.util.prefixCSSProperty('Transform'), source = d3.functor(''); function atZoom(t, distance) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a86b92de4..8d7cadbb3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -20,7 +20,7 @@ iD.Map = function() { class_fill = iD.Style.styleClasses('stroke'), class_area = iD.Style.styleClasses('area'), class_casing = iD.Style.styleClasses('casing'), - transformProp = iD.util.prefixProperty('Transform'), + transformProp = iD.util.prefixCSSProperty('Transform'), support3d = iD.util.support3d(), supersurface, surface, defs, tilegroup, r, g, alength; diff --git a/js/id/util.js b/js/id/util.js index ea478b7f8..fb377bf9c 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -56,7 +56,25 @@ iD.util.qsString = function(obj) { }).join('&'); }; -iD.util.prefixProperty = function(property) { +iD.util.prefixDOMProperty = function(property) { + var prefixes = ['webkit', 'ms', 'moz', 'o'], + i = -1, + n = prefixes.length, + s = document.body; + + if (property in s) + return property; + + property = property.substr(0, 1).toUpperCase() + property.substr(1); + + while (++i < n) + if (prefixes[i] + property in s) + return prefixes[i] + property; + + return false; +}; + +iD.util.prefixCSSProperty = function(property) { var prefixes = ['webkit', 'ms', 'Moz', 'O'], i = -1, n = prefixes.length, @@ -74,7 +92,7 @@ iD.util.prefixProperty = function(property) { iD.util.support3d = function() { // test for translate3d support. Based on https://gist.github.com/3794226 by lorenzopolidori and webinista - var transformProp = iD.util.prefixProperty('Transform'); + var transformProp = iD.util.prefixCSSProperty('Transform'); var el = document.createElement('div'), has3d = false; document.body.insertBefore(el, null); diff --git a/test/index.html b/test/index.html index cdff46673..2ff91d1c7 100644 --- a/test/index.html +++ b/test/index.html @@ -61,6 +61,9 @@ + + +