From 00a7588a3afe6be494ec205f103ff9ae6b83cbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 13 Jan 2016 19:04:03 +0100 Subject: [PATCH] Allow any mouse handler to return true in order to enforce browser default. Relates to #686 --- src/core/Emitter.js | 16 ++++++++++++---- src/dom/DomEvent.js | 16 ++++++++++++---- src/event/Event.js | 2 ++ src/tool/Tool.js | 25 +++++++++++++++++-------- src/view/View.js | 25 +++++++++++++++---------- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/core/Emitter.js b/src/core/Emitter.js index ff8438cc..db4c53eb 100644 --- a/src/core/Emitter.js +++ b/src/core/Emitter.js @@ -74,8 +74,9 @@ var Emitter = { }); }, + emit: function(type, event) { - // Returns true if fired, false otherwise + // Returns true if any events were emitted, false otherwise. var handlers = this._callbacks && this._callbacks[type]; if (!handlers) return false; @@ -84,12 +85,19 @@ var Emitter = { // won't throw us off track here: handlers = handlers.slice(); for (var i = 0, l = handlers.length; i < l; i++) { - // When the handler function returns false, prevent the default - // behavior and stop propagation of the event by calling stop() - if (handlers[i].apply(this, args) === false) { + var res = handlers[i].apply(this, args); + // Look at the handler's return value to decide how to propagate: + if (res === false) { + // If it returns false, prevent the default behavior and stop + // propagation of the event by calling stop() if (event && event.stop) event.stop(); + // Stop propagation right now! break; + } else if (res === true) { + // If it return true, remember that one handler wants to enforce + // the browser's default behavior. This is handled later. + event._enforced = true; } } return true; diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index b83d3310..8352e721 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -17,13 +17,21 @@ */ var DomEvent = /** @lends DomEvent */{ add: function(el, events) { - for (var type in events) - el.addEventListener(type, events[type], false); + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.addEventListener(parts[i], func, false); + } }, remove: function(el, events) { - for (var type in events) - el.removeEventListener(type, events[type], false); + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.removeEventListener(parts[i], func, false); + } }, getPoint: function(event) { diff --git a/src/event/Event.js b/src/event/Event.js index d450221e..68b13f57 100644 --- a/src/event/Event.js +++ b/src/event/Event.js @@ -26,6 +26,8 @@ var Event = Base.extend(/** @lends Event# */{ prevented: false, stopped: false, + // Internal flag indicating whether the default shall be enforced. + _enforced: false, /** * Cancels the event if it is cancelable, without stopping further diff --git a/src/tool/Tool.js b/src/tool/Tool.js index 8a2ed36c..6b79061b 100644 --- a/src/tool/Tool.js +++ b/src/tool/Tool.js @@ -282,7 +282,9 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{ /** * Private method to handle tool-events. * - * @return true if at least one event handler was called, false otherwise. + * @return {@true if the default event should be prevented}. This is if at + * least one event handler was called and none of the called handlers + * wants to enforce the default. */ _handleEvent: function(type, event, point) { // Update global reference to this scope. @@ -299,12 +301,13 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{ // case it is shorter than maxDistance, as this would produce weird // results. matchMaxDistance controls this. matchMaxDistance = false, - called = false, + called = false, // Has at least one handler been called? + enforced = false, // Does a handler want to enforce the default? tool = this, mouse = {}; - // Create a simple lookup object to quickly check for different - // mouse event types. - mouse[type.substr(5)] = true; + // Create a simple lookup object to quickly check for different + // mouse event types. + mouse[type.substr(5)] = true; function update(start, minDistance, maxDistance) { var toolPoint = tool._point, @@ -349,8 +352,14 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{ } function emit() { - called = tool.responds(type) && - tool.emit(type, new ToolEvent(tool, type, event)) || called; + if (tool.responds(type)) { + var toolEvent = new ToolEvent(tool, type, event); + if (tool.emit(type, toolEvent)) { + called = true; + if (toolEvent._enforced) + enforced = true; + } + } } if (mouse.down) { @@ -378,7 +387,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{ } } } - return called; + return called && !enforced; } /** * {@grouptitle Event Handling} diff --git a/src/view/View.js b/src/view/View.js index cff92879..fa90de33 100644 --- a/src/view/View.js +++ b/src/view/View.js @@ -733,10 +733,10 @@ new function() { // Injection scope for mouse events on the browser var eventType = type === 'mousemove' && mouseDown ? 'mousedrag' : type, project = paper.project, tool = view._scope.tool, - called = false; + prevent = false; function handle(obj) { - called = obj._handleEvent(eventType, event, point) || called; + prevent = obj._handleEvent(eventType, event, point) || prevent; } if (!point) @@ -752,7 +752,7 @@ new function() { // Injection scope for mouse events on the browser handle(tool); // Prevent default if at least one mouse event handler was called, to // prevent scrolling on touch devices. - if (called) + if (prevent) event.preventDefault(); // In the end we always call update(), which only updates the view if // anything has changed in the above calls. @@ -869,7 +869,8 @@ new function() { // Injection scope for mouse events on the browser * with support for bubbling (event-propagation). */ - var called = false, // Keep track of whether at least one handler was called + var called = false, // Has at least one handler been called? + enforced = false, // Does a handler want to enforce the default? // Event fallbacks for "virutal" events, e.g. if an item doesn't respond // to doubleclick, fall back to click: fallbacks = { @@ -905,6 +906,8 @@ new function() { // Injection scope for mouse events on the browser } if (obj.emit(type, mouseEvent)) { called = true; + if (mouseEvent._enforced) + enforced = true; // Bail out if propagation is stopped if (mouseEvent.stopped) return true; @@ -928,9 +931,10 @@ new function() { // Injection scope for mouse events on the browser // Returns true if event was stopped, false otherwise, whether handler was // called or not! function emitEvents(view, item, type, event, point, prevPoint) { - // Set called to false, so it will reflect if the following calls to - // emitEvent() have at least called one handler. - called = false; + // Set enforced and called to false, so it will reflect if the following + // calls to emitEvent() have called a handler, and if at least one of + // the handlers wants to enforce default. + called = enforced = false; // First handle the drag-item and its parents, through bubbling. return (dragItem && emitEvent(dragItem, type, event, point, prevPoint) @@ -977,8 +981,9 @@ new function() { // Injection scope for mouse events on the browser /** * Private method to handle view and item events. * - * @return true if at least one event handler was called, false - * otherwise. + * @return {@true if the default event should be prevented}. This is if + * at least one event handler was called and none of the called + * handlers wants to enforce the default. */ _handleEvent: function(type, event, point) { // Run the hit-test first @@ -1047,7 +1052,7 @@ new function() { // Injection scope for mouse events on the browser } } lastPoint = point; - return called; + return called && !enforced; }, _installEvent: function(type) {