Allow any mouse handler to return true in order to enforce browser default.

Relates to #686
This commit is contained in:
Jürg Lehni 2016-01-13 19:04:03 +01:00
parent 0780a87429
commit 00a7588a3a
5 changed files with 58 additions and 26 deletions

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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}

View file

@ -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) {