From 2a3f5040e62f493aab459879c3572be96637b89b Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 19 Feb 2021 14:34:45 -0800 Subject: [PATCH] Move bind/unbind events functions to new file, with example usage. --- core/blockly.js | 156 ++----------------------------------- core/bubble.js | 25 +++--- core/event_handling.js | 173 +++++++++++++++++++++++++++++++++++++++++ core/workspace_svg.js | 18 +++-- 4 files changed, 202 insertions(+), 170 deletions(-) create mode 100644 core/event_handling.js diff --git a/core/blockly.js b/core/blockly.js index 55aa241d7..d1b987180 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -17,6 +17,7 @@ goog.provide('Blockly'); goog.require('Blockly.constants'); +goog.require('Blockly.eventHandling'); goog.require('Blockly.Events'); goog.require('Blockly.Events.Ui'); goog.require('Blockly.Events.UiBase'); @@ -104,13 +105,6 @@ Blockly.cache3dSupported_ = null; */ Blockly.parentContainer = null; -/** - * Blockly opaque event data used to unbind events when using - * `Blockly.bindEvent_` and `Blockly.bindEventWithChecks_`. - * @typedef {!Array.} - */ -Blockly.EventData; - /** * Returns the dimensions of the specified SVG image. * @param {!SVGElement} svg SVG image. @@ -388,149 +382,6 @@ Blockly.defineBlocksWithJsonArray = function(jsonArray) { } }; -/** - * Bind an event to a function call. When calling the function, verifies that - * it belongs to the touch stream that is currently being processed, and splits - * multitouch events into multiple events as needed. - * @param {!EventTarget} node Node upon which to listen. - * @param {string} name Event name to listen to (e.g. 'mousedown'). - * @param {Object} thisObject The value of 'this' in the function. - * @param {!Function} func Function to call when event is triggered. - * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event - * should not block execution of other event handlers on this touch or - * other simultaneous touches. False by default. - * @param {boolean=} opt_noPreventDefault True if triggering on this event - * should prevent the default handler. False by default. If - * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be - * provided. - * @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_. - */ -Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, - opt_noCaptureIdentifier, opt_noPreventDefault) { - var handled = false; - var wrapFunc = function(e) { - var captureIdentifier = !opt_noCaptureIdentifier; - // Handle each touch point separately. If the event was a mouse event, this - // will hand back an array with one element, which we're fine handling. - var events = Blockly.Touch.splitEventByTouches(e); - for (var i = 0, event; (event = events[i]); i++) { - if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) { - continue; - } - Blockly.Touch.setClientFromTouch(event); - if (thisObject) { - func.call(thisObject, event); - } else { - func(event); - } - handled = true; - } - }; - - var bindData = []; - if (Blockly.utils.global['PointerEvent'] && - (name in Blockly.Touch.TOUCH_MAP)) { - for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { - node.addEventListener(type, wrapFunc, false); - bindData.push([node, type, wrapFunc]); - } - } else { - node.addEventListener(name, wrapFunc, false); - bindData.push([node, name, wrapFunc]); - - // Add equivalent touch event. - if (name in Blockly.Touch.TOUCH_MAP) { - var touchWrapFunc = function(e) { - wrapFunc(e); - // Calling preventDefault stops the browser from scrolling/zooming the - // page. - var preventDef = !opt_noPreventDefault; - if (handled && preventDef) { - e.preventDefault(); - } - }; - for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { - node.addEventListener(type, touchWrapFunc, false); - bindData.push([node, type, touchWrapFunc]); - } - } - } - return bindData; -}; - - -/** - * Bind an event to a function call. Handles multitouch events by using the - * coordinates of the first changed touch, and doesn't do any safety checks for - * simultaneous event processing. In most cases prefer is to use - * `Blockly.bindEventWithChecks_`. - * @param {!EventTarget} node Node upon which to listen. - * @param {string} name Event name to listen to (e.g. 'mousedown'). - * @param {Object} thisObject The value of 'this' in the function. - * @param {!Function} func Function to call when event is triggered. - * @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_. - */ -Blockly.bindEvent_ = function(node, name, thisObject, func) { - var wrapFunc = function(e) { - if (thisObject) { - func.call(thisObject, e); - } else { - func(e); - } - }; - - var bindData = []; - if (Blockly.utils.global['PointerEvent'] && - (name in Blockly.Touch.TOUCH_MAP)) { - for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { - node.addEventListener(type, wrapFunc, false); - bindData.push([node, type, wrapFunc]); - } - } else { - node.addEventListener(name, wrapFunc, false); - bindData.push([node, name, wrapFunc]); - - // Add equivalent touch event. - if (name in Blockly.Touch.TOUCH_MAP) { - var touchWrapFunc = function(e) { - // Punt on multitouch events. - if (e.changedTouches && e.changedTouches.length == 1) { - // Map the touch event's properties to the event. - var touchPoint = e.changedTouches[0]; - e.clientX = touchPoint.clientX; - e.clientY = touchPoint.clientY; - } - wrapFunc(e); - - // Stop the browser from scrolling/zooming the page. - e.preventDefault(); - }; - for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { - node.addEventListener(type, touchWrapFunc, false); - bindData.push([node, type, touchWrapFunc]); - } - } - } - return bindData; -}; - -/** - * Unbind one or more events event from a function call. - * @param {!Array.} bindData Opaque data from bindEvent_. - * This list is emptied during the course of calling this function. - * @return {!Function} The function call. - */ -Blockly.unbindEvent_ = function(bindData) { - while (bindData.length) { - var bindDatum = bindData.pop(); - var node = bindDatum[0]; - var name = bindDatum[1]; - var func = bindDatum[2]; - node.removeEventListener(name, func, false); - } - return func; -}; - /** * Is the given string a number (includes negative and decimals). * @param {string} str Input string. @@ -632,3 +483,8 @@ Blockly.checkBlockColourConstant_ = function( Blockly.setParentContainer = function(container) { Blockly.parentContainer = container; }; + +/** Aliases. */ +Blockly.bindEvent_ = Blockly.eventHandling.bind; +Blockly.unbindEvent_ = Blockly.eventHandling.unbind; +Blockly.bindEventWithChecks_ = Blockly.eventHandling.checkAndBind; diff --git a/core/bubble.js b/core/bubble.js index e597214c3..fd696014c 100644 --- a/core/bubble.js +++ b/core/bubble.js @@ -12,6 +12,7 @@ goog.provide('Blockly.Bubble'); +goog.require('Blockly.eventHandling'); goog.require('Blockly.Scrollbar'); goog.require('Blockly.Touch'); goog.require('Blockly.utils'); @@ -66,14 +67,14 @@ Blockly.Bubble = function( /** * Mouse down on bubbleBack_ event data. - * @type {?Blockly.EventData} + * @type {?Blockly.eventHandling.Data} * @private */ this.onMouseDownBubbleWrapper_ = null; /** * Mouse down on resizeGroup_ event data. - * @type {?Blockly.EventData} + * @type {?Blockly.eventHandling.Data} * @private */ this.onMouseDownResizeWrapper_ = null; @@ -137,14 +138,14 @@ Blockly.Bubble.ANCHOR_RADIUS = 8; /** * Mouse up event data. - * @type {?Blockly.EventData} + * @type {?Blockly.eventHandling.Data} * @private */ Blockly.Bubble.onMouseUpWrapper_ = null; /** * Mouse move event data. - * @type {?Blockly.EventData} + * @type {?Blockly.eventHandling.Data} * @private */ Blockly.Bubble.onMouseMoveWrapper_ = null; @@ -155,11 +156,11 @@ Blockly.Bubble.onMouseMoveWrapper_ = null; */ Blockly.Bubble.unbindDragEvents_ = function() { if (Blockly.Bubble.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_); + Blockly.eventHandling.unbind(Blockly.Bubble.onMouseUpWrapper_); Blockly.Bubble.onMouseUpWrapper_ = null; } if (Blockly.Bubble.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_); + Blockly.eventHandling.unbind(Blockly.Bubble.onMouseMoveWrapper_); Blockly.Bubble.onMouseMoveWrapper_ = null; } }; @@ -299,10 +300,10 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { } if (!this.workspace_.options.readOnly) { - this.onMouseDownBubbleWrapper_ = Blockly.bindEventWithChecks_( + this.onMouseDownBubbleWrapper_ = Blockly.eventHandling.checkAndBind( this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); if (this.resizeGroup_) { - this.onMouseDownResizeWrapper_ = Blockly.bindEventWithChecks_( + this.onMouseDownResizeWrapper_ = Blockly.eventHandling.checkAndBind( this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); } } @@ -387,9 +388,9 @@ Blockly.Bubble.prototype.resizeMouseDown_ = function(e) { new Blockly.utils.Coordinate( this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_( + Blockly.Bubble.onMouseUpWrapper_ = Blockly.eventHandling.checkAndBind( document, 'mouseup', this, Blockly.Bubble.bubbleMouseUp_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_( + Blockly.Bubble.onMouseMoveWrapper_ = Blockly.eventHandling.checkAndBind( document, 'mousemove', this, this.resizeMouseMove_); Blockly.hideChaff(); // This event has been handled. No need to bubble up to the document. @@ -821,10 +822,10 @@ Blockly.Bubble.prototype.setColour = function(hexColour) { */ Blockly.Bubble.prototype.dispose = function() { if (this.onMouseDownBubbleWrapper_) { - Blockly.unbindEvent_(this.onMouseDownBubbleWrapper_); + Blockly.eventHandling.unbind(this.onMouseDownBubbleWrapper_); } if (this.onMouseDownResizeWrapper_) { - Blockly.unbindEvent_(this.onMouseDownResizeWrapper_); + Blockly.eventHandling.unbind(this.onMouseDownResizeWrapper_); } Blockly.Bubble.unbindDragEvents_(); Blockly.utils.dom.removeNode(this.bubbleGroup_); diff --git a/core/event_handling.js b/core/event_handling.js new file mode 100644 index 000000000..fa9183bce --- /dev/null +++ b/core/event_handling.js @@ -0,0 +1,173 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Browser event handling. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.eventHandling'); + +goog.require('Blockly.Touch'); + + +/** + * Blockly opaque event data used to unbind events when using + * `Blockly.eventHandling.bindEvent_` and + * `Blockly.eventHandling.bindEventWithChecks_`. + * @typedef {!Array.} + */ +Blockly.eventHandling.Data; + +/** + * Bind an event to a function call. When calling the function, verifies that + * it belongs to the touch stream that is currently being processed, and splits + * multitouch events into multiple events as needed. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event + * should not block execution of other event handlers on this touch or + * other simultaneous touches. False by default. + * @param {boolean=} opt_noPreventDefault True if triggering on this event + * should prevent the default handler. False by default. If + * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be + * provided. + * @return {!Blockly.eventHandling.Data} Opaque data that can be passed to + * unbindEvent_. + * @public + */ +Blockly.eventHandling.checkAndBind = function( + node, name, thisObject, func, opt_noCaptureIdentifier, + opt_noPreventDefault) { + var handled = false; + var wrapFunc = function(e) { + var captureIdentifier = !opt_noCaptureIdentifier; + // Handle each touch point separately. If the event was a mouse event, this + // will hand back an array with one element, which we're fine handling. + var events = Blockly.Touch.splitEventByTouches(e); + for (var i = 0, event; (event = events[i]); i++) { + if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) { + continue; + } + Blockly.Touch.setClientFromTouch(event); + if (thisObject) { + func.call(thisObject, event); + } else { + func(event); + } + handled = true; + } + }; + + var bindData = []; + if (Blockly.utils.global['PointerEvent'] && + (name in Blockly.Touch.TOUCH_MAP)) { + for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { + node.addEventListener(type, wrapFunc, false); + bindData.push([node, type, wrapFunc]); + } + } else { + node.addEventListener(name, wrapFunc, false); + bindData.push([node, name, wrapFunc]); + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + wrapFunc(e); + // Calling preventDefault stops the browser from scrolling/zooming the + // page. + var preventDef = !opt_noPreventDefault; + if (handled && preventDef) { + e.preventDefault(); + } + }; + for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); + } + } + } + return bindData; +}; + + +/** + * Bind an event to a function call. Handles multitouch events by using the + * coordinates of the first changed touch, and doesn't do any safety checks for + * simultaneous event processing. In most cases prefer is to use + * `Blockly.bindEventWithChecks_`. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!Blockly.eventHandling.Data} Opaque data that can be passed to + * unbindEvent_. + * @public + */ +Blockly.eventHandling.bind = function(node, name, thisObject, func) { + var wrapFunc = function(e) { + if (thisObject) { + func.call(thisObject, e); + } else { + func(e); + } + }; + + var bindData = []; + if (Blockly.utils.global['PointerEvent'] && + (name in Blockly.Touch.TOUCH_MAP)) { + for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { + node.addEventListener(type, wrapFunc, false); + bindData.push([node, type, wrapFunc]); + } + } else { + node.addEventListener(name, wrapFunc, false); + bindData.push([node, name, wrapFunc]); + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + // Punt on multitouch events. + if (e.changedTouches && e.changedTouches.length == 1) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } + wrapFunc(e); + + // Stop the browser from scrolling/zooming the page. + e.preventDefault(); + }; + for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); + } + } + } + return bindData; +}; + +/** + * Unbind one or more events event from a function call. + * @param {!Blockly.eventHandling.Data} bindData Opaque data from bindEvent_. + * This list is emptied during the course of calling this function. + * @return {!Function} The function call. + * @public + */ +Blockly.eventHandling.unbind = function(bindData) { + while (bindData.length) { + var bindDatum = bindData.pop(); + var node = bindDatum[0]; + var name = bindDatum[1]; + var func = bindDatum[2]; + node.removeEventListener(name, func, false); + } + return func; +}; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index fda7eeba7..009d07c82 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -17,6 +17,7 @@ goog.require('Blockly.blockRendering'); goog.require('Blockly.ConnectionDB'); goog.require('Blockly.constants'); goog.require('Blockly.ContextMenuRegistry'); +goog.require('Blockly.eventHandling'); goog.require('Blockly.Events'); goog.require('Blockly.Events.BlockCreate'); goog.require('Blockly.Events.ThemeChange'); @@ -221,8 +222,8 @@ Blockly.utils.object.inherits(Blockly.WorkspaceSvg, Blockly.Workspace); /** * A wrapper function called when a resize event occurs. - * You can pass the result to `unbindEvent_`. - * @type {Array.} + * You can pass the result to `eventHandling.unbind`. + * @type {?Blockly.eventHandling.Data} * @private */ Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null; @@ -753,7 +754,8 @@ Blockly.WorkspaceSvg.prototype.getBlockCanvas = function() { /** * Save resize handler data so we can delete it later in dispose. - * @param {!Array.} handler Data that can be passed to unbindEvent_. + * @param {!Blockly.eventHandling.Data} handler Data that can be passed to + * eventHandling.unbind. */ Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) { this.resizeHandlerWrapper_ = handler; @@ -807,10 +809,10 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { {'class': 'blocklyBubbleCanvas'}, this.svgGroup_); if (!this.isFlyout) { - Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this, - this.onMouseDown_, false, true); - Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, - this.onMouseWheel_); + Blockly.eventHandling.checkAndBind( + this.svgGroup_, 'mousedown', this, this.onMouseDown_, false, true); + Blockly.eventHandling.checkAndBind( + this.svgGroup_, 'wheel', this, this.onMouseWheel_); } // Determine if there needs to be a category tree, or a simple list of @@ -917,7 +919,7 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { } } if (this.resizeHandlerWrapper_) { - Blockly.unbindEvent_(this.resizeHandlerWrapper_); + Blockly.eventHandling.unbind(this.resizeHandlerWrapper_); this.resizeHandlerWrapper_ = null; } };