mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
event capturing
This commit is contained in:
@@ -9,7 +9,7 @@ if (Meteor.isClient) {
|
||||
if (! Package.jquery)
|
||||
throw new Error("Meteor UI jQuery adapter: jQuery not found.");
|
||||
|
||||
var $ = Package.jquery.jQuery;
|
||||
var jQuery = Package.jquery.jQuery;
|
||||
|
||||
var DomBackend = {
|
||||
// Must use jQuery semantics for `context`, not
|
||||
@@ -22,7 +22,7 @@ if (Meteor.isClient) {
|
||||
// jQuery fragments are built specially in
|
||||
// IE<9 so that they can safely hold HTML5
|
||||
// elements.
|
||||
return $.buildFragment(nodeArray, document);
|
||||
return jQuery.buildFragment(nodeArray, document);
|
||||
},
|
||||
parseHTML: function (html) {
|
||||
// Return an array of nodes.
|
||||
@@ -30,10 +30,50 @@ if (Meteor.isClient) {
|
||||
// jQuery does fancy stuff like creating an appropriate
|
||||
// container element and setting innerHTML on it, as well
|
||||
// as working around various IE quirks.
|
||||
return $.parseHTML(html);
|
||||
return jQuery.parseHTML(html);
|
||||
},
|
||||
// `selector` is non-null. `type` is one type (but
|
||||
// may be in backend-specific form, e.g. have namespaces).
|
||||
delegateEvents: function (elem, type, selector, handler) {
|
||||
$(elem).on(type, selector, handler);
|
||||
},
|
||||
undelegateEvents: function (elem, type, handler) {
|
||||
$(elem).off(type, handler);
|
||||
},
|
||||
bindEventCapturer: function (elem, type, handler) {
|
||||
var wrapper = function (event) {
|
||||
event = jQuery.event.fix(event);
|
||||
event.currentTarget = event.target;
|
||||
// XXX maybe could fire more jQuery-specific stuff
|
||||
// here, like special event hooks? At the end of the
|
||||
// day, though, jQuery just can't bind capturing
|
||||
// handlers, and if we're not putting the handler
|
||||
// in jQuery's queue, we can't call high-level
|
||||
// internal funcs like `dispatch`.
|
||||
handler.call(elem, event);
|
||||
};
|
||||
handler._meteorui_wrapper = wrapper;
|
||||
|
||||
type = this.parseEventType(type);
|
||||
// add *capturing* event listener
|
||||
elem.addEventListener(type, wrapper, true);
|
||||
},
|
||||
unbindEventCapturer: function (elem, type, handler) {
|
||||
type = this.parseEventType(type);
|
||||
elem.removeEventListener(type, handler._meteorui_wrapper);
|
||||
},
|
||||
parseEventType: function (type) {
|
||||
// strip off namespaces
|
||||
var dotLoc = type.indexOf('.');
|
||||
if (dotLoc >= 0)
|
||||
return type.slice(0, dotLoc);
|
||||
return type;
|
||||
},
|
||||
|
||||
// XXX EVERYTHING BELOW THIS POINT IS A WORK IN PROGRESS XXX
|
||||
|
||||
watchElement: function (elem) {
|
||||
$(elem).on('meteor_ui_domrange_gc', $.noop);
|
||||
jQuery(elem).on('meteor_ui_domrange_gc', jQuery.noop);
|
||||
},
|
||||
// Called when an element is removed from the DOM using the
|
||||
// back-end library directly, either by removing it directly
|
||||
@@ -44,7 +84,7 @@ if (Meteor.isClient) {
|
||||
};
|
||||
|
||||
// See http://bugs.jquery.com/ticket/12213#comment:23
|
||||
$.event.special.meteor_ui_domrange_gc = {
|
||||
jQuery.event.special.meteor_ui_domrange_gc = {
|
||||
teardown: function() {
|
||||
DomBackend.onRemoveElement(this);
|
||||
}
|
||||
|
||||
@@ -583,6 +583,8 @@ DomRange.prototype.elements = function (intoArray) {
|
||||
// In this case, Sortable wants to call `refresh`
|
||||
// on the div, not the each, so it would use this function.
|
||||
DomRange.refresh = function (element) {
|
||||
// note: this "top-level ranges" code could be its
|
||||
// own API call.
|
||||
var topLevelRanges = [];
|
||||
for (var n = element.firstChild;
|
||||
n; n = n.nextSibling) {
|
||||
@@ -772,6 +774,23 @@ DomRange.prototype.$ = function (selector) {
|
||||
|
||||
///// EVENTS
|
||||
|
||||
// List of events to always delegate, never capture.
|
||||
// Since jQuery fakes bubbling for certain events in
|
||||
// certain browsers (like `submit`), we don't want to
|
||||
// get in its way.
|
||||
//
|
||||
// We could list all known bubbling
|
||||
// events here to avoid creating speculative capturers
|
||||
// for them, but it would only be an optimization.
|
||||
var eventsToDelegate = {
|
||||
blur: 1, change: 1, click: 1, focus: 1, focusin: 1,
|
||||
focusout: 1, reset: 1, submit: 1
|
||||
};
|
||||
|
||||
var EVENT_MODE_TBD = 0;
|
||||
var EVENT_MODE_BUBBLING = 1;
|
||||
var EVENT_MODE_CAPTURING = 2;
|
||||
|
||||
// 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) {
|
||||
@@ -811,15 +830,20 @@ DomRange.prototype.on = function (events, selector, handler) {
|
||||
type: type,
|
||||
selector: selector,
|
||||
$ui: this.component,
|
||||
handler: handler
|
||||
handler: handler,
|
||||
mode: EVENT_MODE_TBD
|
||||
};
|
||||
// It's important that lowLevelHandler be a different
|
||||
info.handlers.push(handlerRecord);
|
||||
|
||||
// It's important that delegatedHandler be a different
|
||||
// instance for each handlerRecord, because its identity
|
||||
// is used to remove it. Capture handlerRecord in a
|
||||
// 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) {
|
||||
handlerRecord.delegatedHandler = (function (h) {
|
||||
return function (evt) {
|
||||
if ((! selector) && evt.currentTarget !== evt.target)
|
||||
// no selector means only fire on target
|
||||
@@ -830,10 +854,45 @@ DomRange.prototype.on = function (events, selector, handler) {
|
||||
};
|
||||
})(handlerRecord);
|
||||
|
||||
info.handlers.push(handlerRecord);
|
||||
var tryCapturing = (! eventsToDelegate.hasOwnProperty(
|
||||
DomBackend.parseEventType(type)));
|
||||
|
||||
$(parentNode).on(type, selector || '*',
|
||||
handlerRecord.lowLevelHandler);
|
||||
if (tryCapturing) {
|
||||
handlerRecord.capturingHandler = (function (h) {
|
||||
return function (evt) {
|
||||
if (h.mode === EVENT_MODE_TBD) {
|
||||
// must be first time we're called.
|
||||
if (evt.bubbles) {
|
||||
// this type of event bubbles, so don't
|
||||
// get called again.
|
||||
h.mode = EVENT_MODE_BUBBLING;
|
||||
DomBackend.unbindEventCapturer(
|
||||
h.elem, h.type, h.capturingHandler);
|
||||
return;
|
||||
} else {
|
||||
// this type of event doesn't bubble,
|
||||
// so unbind the delegation, preventing
|
||||
// it from ever firing.
|
||||
h.mode = EVENT_MODE_CAPTURING;
|
||||
DomBackend.undelegateEvents(
|
||||
h.elem. hType, h.delegatedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
h.delegatedHandler(evt);
|
||||
};
|
||||
})(handlerRecord);
|
||||
|
||||
DomBackend.bindEventCapturer(
|
||||
parentNode, type,
|
||||
handlerRecord.capturingHandler);
|
||||
} else {
|
||||
handlerRecord.mode = EVENT_MODE_BUBBLING;
|
||||
}
|
||||
|
||||
DomBackend.delegateEvents(parentNode, type,
|
||||
selector || '*',
|
||||
handlerRecord.delegatedHandler);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user