From a03498c7908e8527f65cc3e27dc2741366e339ba Mon Sep 17 00:00:00 2001 From: Patrick Steele-Idem Date: Fri, 12 Aug 2016 11:31:28 -0600 Subject: [PATCH] Fixes #153 - Bubbling event listeners are attached too late Attach bubbling event listeners lazily when initializing widgets for the first time. --- lib/event-delegation.js | 74 +++++++++++++++++++++++++++++++++++++ lib/index-browser.js | 67 --------------------------------- lib/init-widgets-browser.js | 9 +++++ 3 files changed, 83 insertions(+), 67 deletions(-) create mode 100644 lib/event-delegation.js diff --git a/lib/event-delegation.js b/lib/event-delegation.js new file mode 100644 index 0000000..bc235ac --- /dev/null +++ b/lib/event-delegation.js @@ -0,0 +1,74 @@ +var _addEventListener = require('./addEventListener'); +var updateManager = require('./update-manager'); + +var attachBubbleEventListeners = function() { + var body = document.body; + // Here's where we handle event delegation using our own mechanism + // for delegating events. For each event that we have white-listed + // as supporting bubble, we will attach a listener to the root + // document.body element. When we get notified of a triggered event, + // we again walk up the tree starting at the target associated + // with the event to find any mappings for event. Each mapping + // is from a DOM event type to a method of a widget. + require('./bubble').forEach(function addBubbleHandler(eventType) { + _addEventListener(body, eventType, function(event) { + var propagationStopped = false; + + // Monkey-patch to fix #97 + var oldStopPropagation = event.stopPropagation; + + event.stopPropagation = function() { + oldStopPropagation.call(event); + propagationStopped = true; + }; + + updateManager.batchUpdate(function() { + var curNode = event.target; + if (!curNode) { + return; + } + + // Search up the tree looking DOM events mapped to target + // widget methods + var attrName = 'data-w-on' + eventType; + var targetMethod; + var targetWidget; + + // Attributes will have the following form: + // w-on="|" + + do { + if ((targetMethod = curNode.getAttribute(attrName))) { + var separator = targetMethod.lastIndexOf('|'); + var targetWidgetId = targetMethod.substring(separator+1); + targetWidget = document.getElementById(targetWidgetId).__widget; + + if (!targetWidget) { + throw new Error('Widget not found: ' + targetWidgetId); + } + targetMethod = targetMethod.substring(0, separator); + + var targetFunc = targetWidget[targetMethod]; + if (!targetFunc) { + throw new Error('Method not found on widget ' + targetWidget.id + ': ' + targetMethod); + } + + // Invoke the widget method + targetWidget[targetMethod](event, curNode); + if (propagationStopped) { + break; + } + } + } while((curNode = curNode.parentNode) && curNode.getAttribute); + }); + }); + }); +}; + +exports.init = function() { + if (attachBubbleEventListeners) { + // Only attach event listeners once... + attachBubbleEventListeners(); + attachBubbleEventListeners = null; // This is a one time thing + } +}; \ No newline at end of file diff --git a/lib/index-browser.js b/lib/index-browser.js index fc6a980..98323ce 100644 --- a/lib/index-browser.js +++ b/lib/index-browser.js @@ -18,7 +18,6 @@ var ready = require('raptor-dom').ready; var EMPTY_OBJ = {}; var Widget = require('./Widget'); var initWidgets = require('./init-widgets'); -var _addEventListener = require('./addEventListener'); var raptorRenderer = require('raptor-renderer'); var updateManager = require('./update-manager'); @@ -73,8 +72,6 @@ raptorPubsub } }); - - exports.initWidgets = window.$markoWidgets = function(ids) { initWidgets.initServerRendered(ids); }; @@ -91,70 +88,6 @@ if (!jquery) { exports.$ = jquery; -ready(function() { - var body = document.body; - // Here's where we handle event delegation using our own mechanism - // for delegating events. For each event that we have white-listed - // as supporting bubble, we will attach a listener to the root - // document.body element. When we get notified of a triggered event, - // we again walk up the tree starting at the target associated - // with the event to find any mappings for event. Each mapping - // is from a DOM event type to a method of a widget. - require('./bubble').forEach(function addBubbleHandler(eventType) { - _addEventListener(body, eventType, function(event) { - var propagationStopped = false; - - // Monkey-patch to fix #97 - var oldStopPropagation = event.stopPropagation; - - event.stopPropagation = function() { - oldStopPropagation.call(event); - propagationStopped = true; - }; - - updateManager.batchUpdate(function() { - var curNode = event.target; - if (!curNode) { - return; - } - - // Search up the tree looking DOM events mapped to target - // widget methods - var attrName = 'data-w-on' + eventType; - var targetMethod; - var targetWidget; - - // Attributes will have the following form: - // w-on="|" - - do { - if ((targetMethod = curNode.getAttribute(attrName))) { - var separator = targetMethod.lastIndexOf('|'); - var targetWidgetId = targetMethod.substring(separator+1); - targetWidget = document.getElementById(targetWidgetId).__widget; - - if (!targetWidget) { - throw new Error('Widget not found: ' + targetWidgetId); - } - targetMethod = targetMethod.substring(0, separator); - - var targetFunc = targetWidget[targetMethod]; - if (!targetFunc) { - throw new Error('Method not found on widget ' + targetWidget.id + ': ' + targetMethod); - } - - // Invoke the widget method - targetWidget[targetMethod](event, curNode); - if (propagationStopped) { - break; - } - } - } while((curNode = curNode.parentNode) && curNode.getAttribute); - }); - }); - }); -}); - exports.registerWidget = require('./registry').register; exports.makeRenderable = exports.renderable = raptorRenderer.renderable; exports.render = raptorRenderer.render; diff --git a/lib/init-widgets-browser.js b/lib/init-widgets-browser.js index 9f13750..381fe18 100644 --- a/lib/init-widgets-browser.js +++ b/lib/init-widgets-browser.js @@ -23,6 +23,7 @@ var ready = require('raptor-dom').ready; var _addEventListener = require('./addEventListener'); var registry = require('./registry'); var warp10Finalize = require('warp10/finalize'); +var eventDelegation = require('./event-delegation'); function invokeWidgetEventHandler(widget, targetMethodName, args) { var method = widget[targetMethodName]; @@ -258,6 +259,10 @@ function initWidgetFromEl(el, state, config) { // Create a helper function handle recursion function initClientRendered(widgetDefs, document) { + // Ensure that event handlers to handle delegating events are + // always attached before initializing any widgets + eventDelegation.init(); + document = document || window.document; for (var i=0,len=widgetDefs.length; i