diff --git a/dom-renderables/DOMElement.js b/dom-renderables/DOMElement.js index 87fe71ea..d8e13776 100644 --- a/dom-renderables/DOMElement.js +++ b/dom-renderables/DOMElement.js @@ -288,17 +288,17 @@ DOMElement.prototype.onOpacityChange = function onOpacityChange(opacity) { * Method to be invoked by the node as soon as a new UIEvent is being added. * This results into an `ADD_EVENT_LISTENER` command being sent. * - * @param {String} UIEvent UIEvent to be subscribed to (e.g. `click`) + * @param {String} uiEvent uiEvent to be subscribed to (e.g. `click`) * * @return {undefined} undefined */ -DOMElement.prototype.onAddUIEvent = function onAddUIEvent(UIEvent) { - if (this._UIEvents.indexOf(UIEvent) === -1) { - this._subscribe(UIEvent); - this._UIEvents.push(UIEvent); +DOMElement.prototype.onAddUIEvent = function onAddUIEvent(uiEvent) { + if (this._UIEvents.indexOf(uiEvent) === -1) { + this._subscribe(uiEvent); + this._UIEvents.push(uiEvent); } else if (this._inDraw) { - this._subscribe(UIEvent); + this._subscribe(uiEvent); } return this; }; @@ -309,13 +309,50 @@ DOMElement.prototype.onAddUIEvent = function onAddUIEvent(UIEvent) { * @method * @private * - * @param {String} UIEvent Event type (e.g. `click`) + * @param {String} uiEvent Event type (e.g. `click`) * * @return {undefined} undefined */ -DOMElement.prototype._subscribe = function _subscribe (UIEvent) { +DOMElement.prototype._subscribe = function _subscribe (uiEvent) { if (this._initialized) { - this._changeQueue.push('SUBSCRIBE', UIEvent, true); + this._changeQueue.push('SUBSCRIBE', uiEvent); + } + if (!this._requestingUpdate) this._requestUpdate(); +}; + +/** + * When running in a worker, the browser's default action for specific events + * can't be prevented on a case by case basis (via `e.preventDefault()`). + * Instead this function should be used to register an event to be prevented by + * default. + * + * @method + * + * @param {String} uiEvent UI Event (e.g. wheel) for which to prevent the + * browser's default action (e.g. form submission, + * scrolling) + * @return {undefined} undefined + */ +DOMElement.prototype.preventDefault = function preventDefault (uiEvent) { + if (this._initialized) { + this._changeQueue.push('PREVENT_DEFAULT', uiEvent); + } + if (!this._requestingUpdate) this._requestUpdate(); +}; + +/** + * Opposite of {@link DOMElement#preventDefault}. No longer prevent the + * browser's default action on subsequent events of this type. + * + * @method + * + * @param {type} uiEvent UI Event previously registered using + * {@link DOMElement#preventDefault}. + * @return {undefined} undefined + */ +DOMElement.prototype.allowDefault = function allowDefault (uiEvent) { + if (this._initialized) { + this._changeQueue.push('ALLOW_DEFAULT', uiEvent); } if (!this._requestingUpdate) this._requestUpdate(); }; diff --git a/dom-renderers/DOMRenderer.js b/dom-renderers/DOMRenderer.js index 8e17af52..de313300 100644 --- a/dom-renderers/DOMRenderer.js +++ b/dom-renderers/DOMRenderer.js @@ -104,16 +104,80 @@ function DOMRenderer (element, selector, compositor) { * * @return {undefined} undefined */ -DOMRenderer.prototype.subscribe = function subscribe(type, preventDefault) { - // TODO preventDefault should be a separate command +DOMRenderer.prototype.subscribe = function subscribe(type) { this._assertTargetLoaded(); - - this._target.preventDefault[type] = preventDefault; + this._listen(type); this._target.subscribe[type] = true; +}; + +/** + * Unsubscribes from all events that are of the specified type. + * + * @method + * + * @param {String} type Event type to unsubscribe from. + * @return {undefined} undefined + */ +DOMRenderer.prototype.unsubscribe = function unsubscribe(type) { + this._assertTargetLoaded(); + this._listen(type); + this._target.subscribe[type] = false; +}; + +/** + * Used to preventDefault if an event of the specified type is being emitted on + * the currently loaded target. + * + * @method + * + * @param {String} type The type of events that should be prevented. + * @return {undefined} undefined + */ +DOMRenderer.prototype.preventDefault = function preventDefault(type) { + this._assertTargetLoaded(); + this._listen(type); + this._target.preventDefault[type] = true; +}; + +/** + * Used to undo a previous call to preventDefault. No longer `preventDefault` + * for this event on the loaded target. + * + * @method + * @private + * + * @param {String} type The event type that should no longer be affected by + * `preventDefault`. + * @return {undefined} undefined + */ +DOMRenderer.prototype.allowDefault = function allowDefault(type) { + this._assertTargetLoaded(); + this._listen(type); + this._target.preventDefault[type] = false; +}; + +/** + * Internal helper function used for adding an event listener for the the + * currently loaded ElementCache. + * + * If the event can be delegated as specified in the {@link EventMap}, the + * bound {@link _triggerEvent} function will be added as a listener on the + * root element. Otherwise, the listener will be added directly to the target + * element. + * + * @private + * @method + * + * @param {String} type The event type to listen to (e.g. click). + * @return {undefined} undefined + */ +DOMRenderer.prototype._listen = function _listen(type) { + this._assertTargetLoaded(); if ( !this._target.listeners[type] && !this._root.listeners[type] ) { + // FIXME Add to content DIV if available var target = eventMap[type][1] ? this._root : this._target; target.listeners[type] = this._boundTriggerEvent; target.element.addEventListener(type, this._boundTriggerEvent); @@ -143,18 +207,18 @@ DOMRenderer.prototype._triggerEvent = function _triggerEvent(ev) { var path = evPath[i].dataset.faPath; if (!path) continue; + // Optionally preventDefault. This needs forther consideration and + // should be optional. Eventually this should be a separate command/ + // method. + if (this._elements[path].preventDefault[ev.type]) { + ev.preventDefault(); + } + // Stop further event propogation and path traversal as soon as the // first ElementCache subscribing for the emitted event has been found. if (this._elements[path] && this._elements[path].subscribe[ev.type]) { ev.stopPropagation(); - // Optionally preventDefault. This needs forther consideration and - // should be optional. Eventually this should be a separate command/ - // method. - if (this._elements[path].preventDefault[ev.type]) { - ev.preventDefault(); - } - var NormalizedEventConstructor = eventMap[ev.type][0]; // Finally send the event to the Worker Thread through the diff --git a/renderers/Context.js b/renderers/Context.js index 8d2ff259..a566febe 100644 --- a/renderers/Context.js +++ b/renderers/Context.js @@ -224,7 +224,22 @@ Context.prototype.receive = function receive(path, commands, iterator) { case 'SUBSCRIBE': if (this.WebGLRenderer) this.WebGLRenderer.getOrSetCutout(path); - this.DOMRenderer.subscribe(commands[++localIterator], commands[++localIterator]); + this.DOMRenderer.subscribe(commands[++localIterator]); + break; + + case 'UNSUBSCRIBE': + if (this.WebGLRenderer) this.WebGLRenderer.getOrSetCutout(path); + this.DOMRenderer.unsubscribe(commands[++localIterator]); + break; + + case 'PREVENT_DEFAULT': + if (this.WebGLRenderer) this.WebGLRenderer.getOrSetCutout(path); + this.DOMRenderer.preventDefault(commands[++localIterator]); + break; + + case 'ALLOW_DEFAULT': + if (this.WebGLRenderer) this.WebGLRenderer.getOrSetCutout(path); + this.DOMRenderer.allowDefault(commands[++localIterator]); break; case 'GL_SET_DRAW_OPTIONS':