From b557c65460cce44c89064b1e17f2ec992b1798f9 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 29 Aug 2013 19:52:03 -0700 Subject: [PATCH] start of Meteor UI Events --- packages/ui/domrange.js | 65 +++++++++++++++++++++++++++++++++++ packages/ui/domrange_tests.js | 52 ++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index 22be68fff9..612c61b5a4 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -650,4 +650,69 @@ var moveWithOwnersIntoTbody = function (range) { return tbody; }; +///// EVENTS + +// 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) { + var parentNode = this.parentNode(); + if (! parentNode) + // if we're not in the DOM, silently fail. + return; + + var eventTypes = []; + events.replace(/[^ /]+/g, function (e) { + eventTypes.push(e); + }); + + if (! handler && (typeof selector === 'function')) { + // omitted `selector` + handler = selector; + selector = null; + } else if (! selector) { + // take `""` to `null` + selector = null; + } + + for (var i = 0, N = eventTypes.length; i < N; i++) { + var type = eventTypes[i]; + + var eventDict = parentNode.$_uievents; + if (! eventDict) + eventDict = (parentNode.$_uievents = {}); + + var info = eventDict[type]; + if (! info) { + info = eventDict[type] = {}; + info.handlers = []; + } + var handlerRecord = { + elem: parentNode, + type: type, + selector: selector, + $ui: this.component, + handler: handler + }; + // It's important that lowLevelHandler be a different + // instance for each handlerRecord, because its identity + // is used to remove it. Capture handlerRecord in a + // closure so that we have access to it, even when + // the var changes, and so we don't pull in the rest of + // the stack frame. + handlerRecord.lowLevelHandler = (function (h) { + return function (evt) { + if ((! selector) && evt.currentTarget !== evt.target) + // no selector means only fire on target + return; + return h.handler.call(h.$ui, evt); + }; + })(handlerRecord); + + info.handlers.push(handlerRecord); + + $(parentNode).on(type, selector || '*', + handlerRecord.lowLevelHandler); + } +}; + UI.DomRange = DomRange; \ No newline at end of file diff --git a/packages/ui/domrange_tests.js b/packages/ui/domrange_tests.js index a0a21f60c4..f8501008b9 100644 --- a/packages/ui/domrange_tests.js +++ b/packages/ui/domrange_tests.js @@ -1,5 +1,6 @@ var DomRange = UI.DomRange; +var parseHTML = UI.DomBackend.parseHTML; // fake component; DomRange host var Comp = function (which) { @@ -16,6 +17,18 @@ var isEndMarker = function (n) { return (n.$ui && n === n.$ui.dom.end); }; +var inDocument = function (range, func) { + var onscreen = document.createElement("DIV"); + onscreen.style.display = 'none'; + document.body.appendChild(onscreen); + DomRange.insert(range.component, onscreen); + try { + func(range); + } finally { + document.body.removeChild(onscreen); + } +}; + Tinytest.add("ui - DomRange - basic", function (test) { var r = new DomRange; r.which = 'R'; @@ -593,6 +606,45 @@ Tinytest.add("ui - DomRange - tables", function (test) { test.equal(tbody.childNodes[4].nodeName, 'TR'); }); +Tinytest.add("ui - DomRange - basic events", function (test) { + // test.equal doesn't work on arrays of DOM nodes, so + // we need this. It's `===` that descends recursively + // into any arrays. + var arrayEqual = function (a, b) { + test.equal(_.isArray(a), _.isArray(b)); + if (_.isArray(a)) { + test.equal(a.length, b.length); + for (var i = 0; i < a.length; i++) { + arrayEqual(a[i], b[i]); + } + } else { + test.isTrue(a[i] === b[i]); + } + }; + + var htmlRange = function (html) { + var r = new DomRange; + _.each(parseHTML(html), function (node) { + r.add(node); + }); + return r; + }; + + inDocument( + htmlRange("Foo"), + function (r) { + var buf = []; + + r.on('click', 'span', function (evt) { + buf.push([evt.type, evt.target, evt.currentTarget]); + }); + + arrayEqual(buf, []); + var span = r.elements()[0]; + span.click(); + arrayEqual(buf, [['click', span, span]]); + }); +}); // TO TEST STILL: // - external remove element