diff --git a/packages/liveui/liveevents.js b/packages/liveui/liveevents.js index ba7e001d42..9366cdf13f 100644 --- a/packages/liveui/liveevents.js +++ b/packages/liveui/liveevents.js @@ -1,44 +1,5 @@ Meteor.ui = Meteor.ui || {}; -// LiveEvents is an implementation of event delegation, a technique that -// listens to events on a subtree of the DOM by binding handlers on the -// root. -// -// _attachEvents installs handlers on a range of nodes, specifically the -// top-level nodes of a template in LiveUI, and detects events on -// descendents using bubbling. Events that bubble up are checked -// against the selectors in the event map to determine whether the -// user callback should be called. -// -// XXX We currently rely on jQuery for: -// - focusin/focusout support for Firefox -// - keeping track of handlers that have been bound -// - cross-browser event attaching (attachEvent/addEventListener) -// - event field and callback normalization (event.target, etc.) -// -// TODO: Fix event bubbling between multiple handlers. Have a story for -// the order of handler invocation and stick to it, and have -// event.stopPropagation() always do the right thing. -// For example, in a DOM of the form DIV > UL > LI, we might have -// an event selector on the DIV of the form "click ul, click li" or -// even "click *". In either case, every matched element should be -// visited in bottom-up order in a single traversal. To do this, -// we need to have only one event handler per event type per liverange. -// Then, what about events bound at different levels? Currently, -// handler firing order is determined first by liverange nesting -// level, and then by element nesting level. For example, if a -// liverange around the DIV selects the LI for an event, and a -// liverange around the UL selects the UL, then you'd think an -// event on the LI would bubble LI -> UL -> DIV. However, the handler -// on the UL will fire first. This might be something to document -// rather than fix -- i.e., handlers in event maps in inner liveranges -// will always fire before those in outer liveranges, regardless of -// the selected nodes. Most solutions requiring taking over the -// entire event flow, making live events play less well with the -// rest of the page or events bound by other libraries. For example, -// binding all handlers at the top level of the document, or completely -// faking event bubbling somehow. - (function() { // for IE 6-8 @@ -211,237 +172,7 @@ Meteor.ui = Meteor.ui || {}; }; - ////// WHAT WE SHOULD ACTUALLY DO - ////// - have one handler, have it walk the liveranges????? - ////// means a different abstraction boundary. - - var makeHandler = function(origType, selector, event_data, callback) { - return function(e) { - var event = (e || window.event); - var curNode = (event.currentTarget || this); - return handleEvent( - origType, selector, event_data, callback, curNode, event); - }; - }; - - var handleEvent = function(origType, selector, event_data, callback, - curNode, event) { - - event.type = origType; - event.target = (event.target || event.srcElement); - - if (selector) { - // use element's parentNode as a "context"; any elements - // referenced in the selector must be proper descendents - // of the context. - var contextNode = curNode.parentNode; - var results = $(contextNode).find(selector); - // target or ancestor must match selector - var selectorMatch = null; - for(var node = event.target; - node !== contextNode; - node = node.parentNode) { - if (_.contains(results, node)) { - // found the node that justifies handling - // this event - selectorMatch = node; - break; - } - if (origType === 'focus' || origType === 'blur') - break; // don't bubble - } - - if (! selectorMatch) - return; - } - - callback.call(event_data, event); - }; - - // Wire up events to DOM nodes. - // - // `start` and `end` are sibling nodes in order that define - // an inclusive range of DOM nodes. `events` is an event map, - // and `event_data` the object to bind to the callback (like the - // Meteor.ui.render options of the same names). - Meteor.ui._attachEvents = function (start, end, events, event_data) { - events = events || {}; - - var after = end.nextSibling; - for(var node = start; node && node !== after; node = node.nextSibling) { - - // map of event type to array of arrays of handlers, - // e.g. { click: [[handler1, handler2], [handler3]] } - // attached to node - var handler_data = node[tag]; - if (! handler_data) - handler_data = node[tag] = {}; - - // for handlers added this iteration, the array to extend - // for additional handlers of the same type, - // e.g. { click: [handler3] } - var handlers_by_type = {}; - - // iterate over `spec: callback` map - _.each(events, function(callback, spec) { - var clauses = spec.split(/,\s+/); - _.each(clauses, function (clause) { - var parts = clause.split(/\s+/); - if (parts.length === 0) - return; - - var eventType = parts.shift(); - var selector = parts.join(' '); - var rewrittenEventType = eventType; - // Rewrite focus and blur to non-bubbling focusin and focusout. - // We are relying on jquery to simulate focusin/focusout in Firefox, - // the only major browser that lacks support for them. - // When removing jquery dependency, use event capturing in Firefox, - // focusin/focusout in IE, and either in WebKit. - switch (eventType) { - case 'focus': - rewrittenEventType = 'focusin'; - break; - case 'blur': - rewrittenEventType = 'focusout'; - break; - case 'change': - if (wireIEChangeSubmitHack) - rewrittenEventType = 'cellchange'; - break; - case 'submit': - if (wireIEChangeSubmitHack) - rewrittenEventType = 'datasetcomplete'; - break; - } - - var t = rewrittenEventType; - - var handler_array = handlers_by_type[t]; - if (! handler_array) { - handler_array = []; - handler_data[t] = (handler_data[t] || []); - handler_data[t].push(handler_array); - handlers_by_type[t] = handler_array; - } - - var handler = makeHandler(eventType, selector, event_data, callback); - handler_array.push(handler); - - if (node.addEventListener) - node.addEventListener(eventType, handler, false); - else - node.attachEvent('on'+eventType, handler); - - }); - }); - } - }; - - // Prepare newly-created DOM nodes for event delegation. - // - // This is a notification to liveevents that gives it a chance - // to perform custom processing on nodes. `start` and `end` - // specify an inclusive range of siblings, and these nodes - // and their descendents are processed, inserting any hooks - // needed to make event delegation work. - Meteor.ui._prepareForEvents = function(node) { - - }; - - // Removes any events bound by Meteor.ui._attachEvent from - // `node`. Meteor.ui._resetEvents = function(node) { - + // no action needed, listeners are on document. }; - - // Make 'change' event bubble in IE 6-8, the only browser where it - // doesn't. We also fix the quirk that change events on checkboxes - // and radio buttons don't fire until blur, also on IE 6-8 and no - // other known browsers. - // - // Our solution is to bind an event handler to every element that - // might be the target of a change event. The event handler is - // generic, and simply refires a 'cellchange' event, an obscure - // IE event that does bubble and is unlikely to be used in an app. - // To fix checkboxes and radio buttons, use the 'propertychange' - // event instead of 'change'. - // - // We solve the 'submit' event problem similarly, using the IE - // 'datasetcomplete' event to bubble up a form submission. - // The tricky part is that the app must be able to call - // event.preventDefault() and have the form not submit. This - // is solved by blocking the original submit and calling - // submit() later, which never fires a 'submit' event itself. - // - // Relevant info: - // http://www.quirksmode.org/dom/events/change.html - var wireIEChangeSubmitHack = null; - if (document.attachEvent && - (! ('onchange' in document)) && - ('oncellchange' in document) && - ('ondatasetcomplete' in document)) { - // IE <= 8 - wireIEChangeSubmitHack = function(start, end) { - var wireNode = function(n) { - if (n.nodeName === 'INPUT') { - if (n.type === "checkbox" || n.type === "radio") { - n.detachEvent('onpropertychange', changeSubmitHandlerIE); - n.attachEvent('onpropertychange', changeSubmitHandlerIE); - } else { - n.detachEvent('onchange', changeSubmitHandlerIE); - n.attachEvent('onchange', changeSubmitHandlerIE); - } - } else if (n.nodeName === 'FORM') { - n.detachEvent('onsubmit', changeSubmitHandlerIE); - n.attachEvent('onsubmit', changeSubmitHandlerIE); - } - }; - - var after = end.nextSibling; - for(var n = start; n && n !== after; n = n.nextSibling) { - wireNode(n); - if (n.firstChild) { // element nodes only - _.each(n.getElementsByTagName('INPUT'), wireNode); - _.each(n.getElementsByTagName('FORM'), wireNode); - } - } - }; - // implement form submission after app has had a chance - // to preventDefault - document.attachEvent('ondatasetcomplete', function() { - var evt = window.event; - var target = evt && evt.srcElement; - if (target && target.nodeName === 'FORM' && - evt.returnValue !== false) - target.submit(); - }); - }; - - // this function must be a singleton (i.e. only one instance of it) - // so that detachEvent can find it. - var changeSubmitHandlerIE = function() { - var evt = window.event; - var target = evt && evt.srcElement; - if (! target) - return; - - var newEvent = document.createEventObject(); - - if (evt.type === 'propertychange' && evt.propertyName === 'checked' - || evt.type === 'change') { - // we appropriate 'oncellchange' as bubbling change - target.fireEvent('oncellchange', newEvent); - } - - if (evt.type === 'submit') { - // we appropriate 'ondatasetcomplete' as bubbling submit. - // call preventDefault now, let event bubble, and we - // will submit the form later if the app doesn't - // prevent it. - evt.returnValue = false; - target.fireEvent('ondatasetcomplete', newEvent); - } - }; - })(); \ No newline at end of file diff --git a/packages/liveui/liveevents_nonw3c.js b/packages/liveui/liveevents_nonw3c.js index e6c682571c..b9876421b8 100644 --- a/packages/liveui/liveevents_nonw3c.js +++ b/packages/liveui/liveevents_nonw3c.js @@ -61,17 +61,6 @@ Meteor.ui._loadNonW3CEvents = function() { }); }; - /*setTimeout(function() { - Meteor.startup(function() { - document.body.appendChild(document.createElement("DIV")); - document.body.lastChild.innerHTML = - '
'; - }); - }, 0); - var LOG = function(str) { - document.getElementById('mylog').innerHTML += '
'+str; - };*/ - var sendEvent = function(ontype, target) { var e = document.createEventObject(); e.synthetic = true;