diff --git a/packages/ui/backend.js b/packages/ui/backend.js index 469e3d6849..d7a27d7b99 100644 --- a/packages/ui/backend.js +++ b/packages/ui/backend.js @@ -34,6 +34,7 @@ if (Meteor.isClient) { }, // `selector` is non-null. `type` is one type (but // may be in backend-specific form, e.g. have namespaces). + // Order fired must be order bound. delegateEvents: function (elem, type, selector, handler) { $(elem).on(type, selector, handler); }, diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index e9526f37a8..a6988c7cf5 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -881,19 +881,20 @@ var HandlerRec = function (elem, type, selector, handler, $ui) { } }; -HandlerRec.prototype.setup = function () { - if (this.mode === EVENT_MODE_TBD) { +HandlerRec.prototype.bind = function () { + if (this.mode !== EVENT_MODE_BUBBLING) { DomBackend.bindEventCapturer( this.elem, this.type, this.capturingHandler); } - DomBackend.delegateEvents( - this.elem, this.type, - this.selector || '*', this.delegatedHandler); + if (this.mode !== EVENT_MODE_CAPTURING) + DomBackend.delegateEvents( + this.elem, this.type, + this.selector || '*', this.delegatedHandler); }; -HandlerRec.prototype.teardown = function () { +HandlerRec.prototype.unbind = function () { if (this.mode !== EVENT_MODE_BUBBLING) DomBackend.unbindEventCapturer(this.elem, this.type, this.capturingHandler); @@ -903,6 +904,7 @@ HandlerRec.prototype.teardown = function () { this.delegatedHandler); }; + // XXX could write the form of arguments for this function // in several different ways, including simply as an event map. DomRange.prototype.on = function (events, selector, handler) { @@ -940,10 +942,28 @@ DomRange.prototype.on = function (events, selector, handler) { info = eventDict[type] = {}; info.handlers = []; } + var handlerList = info.handlers; var handlerRec = new HandlerRec( parentNode, type, selector, handler, this.component); - handlerRec.setup(); - info.handlers.push(handlerRec); + handlerRec.bind(); + handlerList.push(handlerRec); + // move handlers of enclosing ranges to end + for (var r = (this.owner && this.owner.dom); + r; r = (r.owner && r.owner.dom)) { + // r is an enclosing DomRange + for (var j = 0, Nj = handlerList.length; + j < Nj; j++) { + var h = handlerList[j]; + if (h.$ui && h.$ui.dom === r) { + h.unbind(); + h.bind(); + handlerList.splice(j, 1); // remove handlerList[j] + handlerList.push(h); + j--; // account for removed handler + Nj--; // don't visit appended handlers + } + } + } } }; diff --git a/packages/ui/domrange_tests.js b/packages/ui/domrange_tests.js index 1758cde8f8..b5776780ae 100644 --- a/packages/ui/domrange_tests.js +++ b/packages/ui/domrange_tests.js @@ -896,6 +896,42 @@ Tinytest.add("ui - DomRange - events in tables", function (test) { }); }); +Tinytest.add("ui - DomRange - nested event order", function (test) { + inDocument(new DomRange, function (r) { + var a = new DomRange; + var b = new DomRange; + var c = new DomRange; + var d = new DomRange; + r.add(a); + a.add(b); + b.add(c); + c.add(d); + var div = document.createElement("DIV"); + d.add(div); + + var buf = []; + var appender = function (str) { + return function (evt) { + buf.push(str); + }; + }; + + b.on('click', 'div', appender("B")); + a.on('click', 'div', appender("A")); + d.on('click', appender("D")); + c.on('click', 'div', appender("C")); + test.equal(buf, []); + div.click(); + test.equal(buf, ['D', 'C', 'B', 'A']); + buf.length = 0; + + b.on('click', appender("B2")); + d.on('click', 'div', appender("D2")); + div.click(); + test.equal(buf, ['D', 'D2', 'C', 'B', 'B2', 'A']); + }); +}); + // TO TEST STILL: // - external remove element // - double-add, double-remove