Skip to content

Commit

Permalink
Fixes #153 - Bubbling event listeners are attached too late
Browse files Browse the repository at this point in the history
Attach bubbling event listeners lazily when initializing widgets for the first time.
  • Loading branch information
patrick-steele-idem committed Aug 12, 2016
1 parent 0491bb6 commit a03498c
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 67 deletions.
74 changes: 74 additions & 0 deletions lib/event-delegation.js
Original file line number Diff line number Diff line change
@@ -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<event_type>="<target_method>|<widget_id>"

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
}
};
67 changes: 0 additions & 67 deletions lib/index-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -73,8 +72,6 @@ raptorPubsub
}
});



exports.initWidgets = window.$markoWidgets = function(ids) {
initWidgets.initServerRendered(ids);
};
Expand All @@ -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<event_type>="<target_method>|<widget_id>"

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;
Expand Down
9 changes: 9 additions & 0 deletions lib/init-widgets-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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<len; i++) {
var widgetDef = widgetDefs[i];
Expand Down Expand Up @@ -311,6 +316,10 @@ exports.initClientRendered = initClientRendered;
* }
*/
exports.initServerRendered = function(dataIds) {
// Ensure that event handlers to handle delegating events are
// always attached before initializing any widgets
eventDelegation.init();

var stateStore;
var configStore;

Expand Down

0 comments on commit a03498c

Please sign in to comment.