mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
liveevents/liveui refactor wip
This commit is contained in:
@@ -2,6 +2,57 @@ Meteor.ui = Meteor.ui || {};
|
||||
|
||||
(function() {
|
||||
|
||||
var returnFalse = function() { return false; };
|
||||
var returnTrue = function() { return true; };
|
||||
|
||||
// inspired by jQuery fix()
|
||||
Meteor.ui._fixEvent = function(event) {
|
||||
var originalStopPropagation = event.stopPropagation;
|
||||
var originalPreventDefault = event.preventDefault;
|
||||
event.isPropagationStopped = returnFalse;
|
||||
event.isDefaultPrevented = returnFalse;
|
||||
event.stopPropagation = function() {
|
||||
event.isPropagationStopped = returnTrue;
|
||||
if (originalStopPropagation)
|
||||
originalStopPropagation.call(event);
|
||||
else
|
||||
event.cancelBubble = true; // IE
|
||||
};
|
||||
event.preventDefault = function() {
|
||||
event.isDefaultPrevented = returnTrue;
|
||||
if (originalPreventDefault)
|
||||
originalPreventDefault.call(event);
|
||||
else
|
||||
event.returnValue = false; // IE
|
||||
};
|
||||
|
||||
var type = event.type;
|
||||
|
||||
if (event.metaKey === undefined)
|
||||
event.metaKey = event.ctrlKey;
|
||||
if (/^key/.test(type)) {
|
||||
// KEY EVENTS
|
||||
// Add `which`
|
||||
if (event.which === null)
|
||||
event.which = event.charCode !== null ? event.charCode : event.keyCode;
|
||||
} else if (/^(?:mouse|contextmenu)|click/.test(type)) {
|
||||
// MOUSE EVENTS
|
||||
// Add relatedTarget, if necessary
|
||||
if (! event.relatedTarget && event.fromElement)
|
||||
event.relatedTarget = (event.fromElement === event.target ?
|
||||
event.toElement : event.fromElement);
|
||||
// Add which for click: 1 === left; 2 === middle; 3 === right
|
||||
if (! event.which && event.button !== undefined ) {
|
||||
var button = event.button;
|
||||
event.which = (button & 1 ? 1 :
|
||||
(button & 2 ? 3 :
|
||||
(button & 4 ? 2 : 0 )));
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
// for IE 6-8
|
||||
if (! document.addEventListener) {
|
||||
Meteor.ui._loadNoW3CEvents();
|
||||
@@ -57,7 +108,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
|
||||
var doHandleHacks = function(event) {
|
||||
var universalHandler = function(event) {
|
||||
// fire synthetic focusin/focusout on blur/focus or vice versa
|
||||
if (event.currentTarget === event.target) {
|
||||
if (focusBlurMode === SIMULATE_FOCUS_BLUR) {
|
||||
@@ -76,19 +127,19 @@ Meteor.ui = Meteor.ui || {};
|
||||
if (focusBlurMode === SIMULATE_FOCUS_BLUR) {
|
||||
if (event.type === 'focus' || event.type === 'blur') {
|
||||
if (! event.synthetic)
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
} else { // SIMULATE_FOCUSIN_FOCUSOUT
|
||||
if (event.type === 'focusin' || event.type === 'focusout') {
|
||||
if (! event.synthetic)
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
Meteor.ui._dispatchEvent(event);
|
||||
};
|
||||
|
||||
var doInstallHacks = function(node, eventType) {
|
||||
Meteor.ui._installLiveHandler = function(node, eventType) {
|
||||
// install handlers for the events used to fake events of this type,
|
||||
// in addition to handlers for the real type
|
||||
if (focusBlurMode === SIMULATE_FOCUS_BLUR) {
|
||||
@@ -102,69 +153,6 @@ Meteor.ui = Meteor.ui || {};
|
||||
else if (eventType === 'focusout')
|
||||
Meteor.ui._installLiveHandler(node, 'blur');
|
||||
}
|
||||
};
|
||||
|
||||
var returnFalse = function() { return false; };
|
||||
var returnTrue = function() { return true; };
|
||||
|
||||
var universalHandler = function(event) {
|
||||
if (doHandleHacks(event) === false)
|
||||
return;
|
||||
|
||||
var curNode = event.currentTarget;
|
||||
if (! curNode)
|
||||
return;
|
||||
|
||||
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, curNode);
|
||||
if (! innerRange)
|
||||
return;
|
||||
|
||||
var originalStopPropagation = event.stopPropagation;
|
||||
var originalPreventDefault = event.preventDefault;
|
||||
event.isPropagationStopped = returnFalse;
|
||||
event.isDefaultPrevented = returnFalse;
|
||||
event.stopPropagation = function() {
|
||||
event.isPropagationStopped = returnTrue;
|
||||
originalStopPropagation.call(event);
|
||||
};
|
||||
event.preventDefault = function() {
|
||||
event.isDefaultPrevented = returnTrue;
|
||||
originalPreventDefault.call(event);
|
||||
};
|
||||
|
||||
var type = event.type;
|
||||
|
||||
for(var range = innerRange; range; range = range.findParent(true)) {
|
||||
if (! range.event_handlers)
|
||||
continue;
|
||||
|
||||
_.each(range.event_handlers, function(h) {
|
||||
if (h.type !== type)
|
||||
return;
|
||||
|
||||
var selector = h.selector;
|
||||
if (selector) {
|
||||
var contextNode = range.containerNode();
|
||||
var results = $(contextNode).find(selector);
|
||||
if (! _.contains(results, curNode))
|
||||
return;
|
||||
}
|
||||
|
||||
var returnValue = h.callback.call(range.event_data, event);
|
||||
if (returnValue === false) {
|
||||
// extension due to jQuery
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
if (event.isPropagationStopped())
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.ui._installLiveHandler = function(node, eventType) {
|
||||
doInstallHacks(node, eventType);
|
||||
|
||||
var propName = prefix + eventType;
|
||||
if (! document[propName]) {
|
||||
@@ -175,7 +163,4 @@ Meteor.ui = Meteor.ui || {};
|
||||
|
||||
};
|
||||
|
||||
Meteor.ui._resetEvents = function(node) {
|
||||
// no action needed, listeners are on document.
|
||||
};
|
||||
})();
|
||||
@@ -1,204 +0,0 @@
|
||||
|
||||
// For IE 6-8.
|
||||
|
||||
Meteor.ui._loadNoW3CEvents = function() {
|
||||
|
||||
var installOne = function(node, prop) {
|
||||
// install handlers for faking focus/blur if necessary
|
||||
if (prop === 'onfocus')
|
||||
installOne(node, 'onfocusin');
|
||||
else if (prop === 'onblur')
|
||||
installOne(node, 'onfocusout');
|
||||
// install handlers for faking bubbling change/submit
|
||||
else if (prop === 'onchange') {
|
||||
installOne(node, 'oncellchange');
|
||||
if (node.nodeName === 'INPUT' &&
|
||||
(node.type === 'checkbox' || node.type === 'radio')) {
|
||||
installOne(node, 'onpropertychange');
|
||||
return;
|
||||
}
|
||||
} else if (prop === 'onsubmit')
|
||||
installOne(node, 'ondatasetcomplete');
|
||||
|
||||
node[prop] = universalHandler;
|
||||
};
|
||||
|
||||
Meteor.ui._installLiveHandler = function(node, eventType) {
|
||||
// use old-school event binding, so that we can
|
||||
// access the currentTarget as `this` in the handler.
|
||||
var prop = 'on'+eventType;
|
||||
|
||||
if (node.nodeType === 1) { // ELEMENT
|
||||
installOne(node, prop);
|
||||
|
||||
var descendents = node.getElementsByTagName('*');
|
||||
|
||||
for(var i=0, N = descendents.length; i<N; i++)
|
||||
installOne(descendents[i], prop);
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.ui._attachSecondaryEvents = function(innerRange) {
|
||||
var types = {};
|
||||
for(var range = innerRange; range; range = range.findParent()) {
|
||||
if (range === innerRange)
|
||||
continue;
|
||||
|
||||
if (! range.event_handlers)
|
||||
continue;
|
||||
|
||||
_.each(range.event_handlers, function(h) {
|
||||
types[h.type] = true;
|
||||
});
|
||||
}
|
||||
|
||||
_.each(types, function(z, t) {
|
||||
for(var n = innerRange.firstNode(),
|
||||
after = innerRange.lastNode().nextSibling;
|
||||
n && n !== after;
|
||||
n = n.nextSibling)
|
||||
Meteor.ui._installLiveHandler(n, t);
|
||||
});
|
||||
};
|
||||
|
||||
var sendEvent = function(ontype, target) {
|
||||
var e = document.createEventObject();
|
||||
e.synthetic = true;
|
||||
target.fireEvent(ontype, e);
|
||||
return e.returnValue;
|
||||
};
|
||||
|
||||
var universalHandler = function() {
|
||||
var event = window.event;
|
||||
var type = event.type;
|
||||
var target = event.srcElement || document;
|
||||
event.target = target;
|
||||
if (this.nodeType !== 1)
|
||||
return; // sanity check that we have a real target (always an element)
|
||||
event.currentTarget = this;
|
||||
var curNode = this;
|
||||
|
||||
// simulate focus/blur so that they are synchronous
|
||||
if (curNode === target && ! event.synthetic) {
|
||||
if (type === 'focusin')
|
||||
sendEvent('onfocus', curNode);
|
||||
else if (type === 'focusout')
|
||||
sendEvent('onblur', curNode);
|
||||
else if (type === 'change')
|
||||
sendEvent('oncellchange', curNode);
|
||||
else if (type === 'propertychange') {
|
||||
if (event.propertyName === 'checked')
|
||||
sendEvent('oncellchange', curNode);
|
||||
} else if (type === 'submit') {
|
||||
sendEvent('ondatasetcomplete', curNode);
|
||||
}
|
||||
}
|
||||
// ignore non-simulated events of types we simulate
|
||||
if ((type === 'focus' || event.type === 'blur' || event.type === 'change' ||
|
||||
event.type === 'submit') && ! event.synthetic) {
|
||||
if (event.type === 'submit')
|
||||
event.returnValue = false; // block all native submits, we will submit
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'cellchange' && event.synthetic) {
|
||||
type = event.type = 'change';
|
||||
}
|
||||
if (type === 'datasetcomplete' && event.synthetic) {
|
||||
type = event.type = 'submit';
|
||||
}
|
||||
|
||||
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, curNode);
|
||||
if (! innerRange)
|
||||
return;
|
||||
|
||||
var isPropagationStopped = false;
|
||||
event.stopPropagation = function() {
|
||||
isPropagationStopped = true;
|
||||
event.cancelBubble = true;
|
||||
};
|
||||
event.preventDefault = function() {
|
||||
event.returnValue = false;
|
||||
};
|
||||
|
||||
// inspired by jQuery fix():
|
||||
if (event.metaKey === undefined)
|
||||
event.metaKey = event.ctrlKey;
|
||||
if (/^key/.test(type)) {
|
||||
// KEY EVENTS
|
||||
// Add `which`
|
||||
if (event.which === null)
|
||||
event.which = event.charCode !== null ? event.charCode : event.keyCode;
|
||||
} else if (/^(?:mouse|contextmenu)|click/.test(type)) {
|
||||
// MOUSE EVENTS
|
||||
// Add relatedTarget, if necessary
|
||||
if (! event.relatedTarget && event.fromElement)
|
||||
event.relatedTarget = (event.fromElement === event.target ?
|
||||
event.toElement : event.fromElement);
|
||||
// Add which for click: 1 === left; 2 === middle; 3 === right
|
||||
if (! event.which && event.button !== undefined ) {
|
||||
var button = event.button;
|
||||
event.which = (button & 1 ? 1 :
|
||||
(button & 2 ? 3 :
|
||||
(button & 4 ? 2 : 0 )));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(var range = innerRange; range; range = range.findParent(true)) {
|
||||
if (! range.event_handlers)
|
||||
continue;
|
||||
|
||||
_.each(range.event_handlers, function(h) {
|
||||
if (h.type !== type)
|
||||
return;
|
||||
|
||||
var selector = h.selector;
|
||||
if (selector) {
|
||||
var contextNode = range.containerNode();
|
||||
var results = $(contextNode).find(selector);
|
||||
if (! _.contains(results, curNode))
|
||||
return;
|
||||
}
|
||||
|
||||
var returnValue = h.callback.call(range.event_data, event);
|
||||
if (returnValue === false) {
|
||||
// extension due to jQuery
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
if (isPropagationStopped)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var resetOne = function(node) {
|
||||
for(var k in node)
|
||||
if (node[k] === universalHandler)
|
||||
node[k] = null;
|
||||
};
|
||||
|
||||
Meteor.ui._resetEvents = function(node) {
|
||||
if (node.nodeType === 1) { // ELEMENT
|
||||
resetOne(node);
|
||||
|
||||
var descendents = node.getElementsByTagName('*');
|
||||
|
||||
for(var i=0, N = descendents.length; i<N; i++)
|
||||
resetOne(descendents[i]);
|
||||
}
|
||||
};
|
||||
|
||||
// submit forms that aren't preventDefaulted
|
||||
document.attachEvent('ondatasetcomplete', function() {
|
||||
var evt = window.event;
|
||||
var target = evt && evt.srcElement;
|
||||
if (evt.synthetic && target &&
|
||||
target.nodeName === 'FORM' &&
|
||||
evt.returnValue !== false)
|
||||
target.submit();
|
||||
});
|
||||
|
||||
};
|
||||
@@ -61,9 +61,6 @@ Meteor.ui._loadNoW3CEvents = function() {
|
||||
});
|
||||
};
|
||||
|
||||
var returnFalse = function() { return false; };
|
||||
var returnTrue = function() { return true; };
|
||||
|
||||
var sendEvent = function(ontype, target) {
|
||||
var e = document.createEventObject();
|
||||
e.synthetic = true;
|
||||
@@ -111,89 +108,7 @@ Meteor.ui._loadNoW3CEvents = function() {
|
||||
type = event.type = 'submit';
|
||||
}
|
||||
|
||||
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, curNode);
|
||||
if (! innerRange)
|
||||
return;
|
||||
|
||||
event.isPropagationStopped = returnFalse;
|
||||
event.isDefaultPrevented = returnFalse;
|
||||
event.stopPropagation = function() {
|
||||
event.isPropagationStopped = returnTrue;
|
||||
event.cancelBubble = true;
|
||||
};
|
||||
event.preventDefault = function() {
|
||||
event.isDefaultPrevented = returnTrue;
|
||||
event.returnValue = false;
|
||||
};
|
||||
|
||||
// inspired by jQuery fix():
|
||||
if (event.metaKey === undefined)
|
||||
event.metaKey = event.ctrlKey;
|
||||
if (/^key/.test(type)) {
|
||||
// KEY EVENTS
|
||||
// Add `which`
|
||||
if (event.which === null)
|
||||
event.which = event.charCode !== null ? event.charCode : event.keyCode;
|
||||
} else if (/^(?:mouse|contextmenu)|click/.test(type)) {
|
||||
// MOUSE EVENTS
|
||||
// Add relatedTarget, if necessary
|
||||
if (! event.relatedTarget && event.fromElement)
|
||||
event.relatedTarget = (event.fromElement === event.target ?
|
||||
event.toElement : event.fromElement);
|
||||
// Add which for click: 1 === left; 2 === middle; 3 === right
|
||||
if (! event.which && event.button !== undefined ) {
|
||||
var button = event.button;
|
||||
event.which = (button & 1 ? 1 :
|
||||
(button & 2 ? 3 :
|
||||
(button & 4 ? 2 : 0 )));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for(var range = innerRange; range; range = range.findParent(true)) {
|
||||
if (! range.event_handlers)
|
||||
continue;
|
||||
|
||||
_.each(range.event_handlers, function(h) {
|
||||
if (h.type !== type)
|
||||
return;
|
||||
|
||||
var selector = h.selector;
|
||||
if (selector) {
|
||||
var contextNode = range.containerNode();
|
||||
var results = $(contextNode).find(selector);
|
||||
if (! _.contains(results, curNode))
|
||||
return;
|
||||
}
|
||||
|
||||
var returnValue = h.callback.call(range.event_data, event);
|
||||
if (returnValue === false) {
|
||||
// extension due to jQuery
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
if (event.isPropagationStopped())
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var resetOne = function(node) {
|
||||
for(var k in node)
|
||||
if (node[k] === universalHandler)
|
||||
node[k] = null;
|
||||
};
|
||||
|
||||
Meteor.ui._resetEvents = function(node) {
|
||||
if (node.nodeType === 1) { // ELEMENT
|
||||
resetOne(node);
|
||||
|
||||
var descendents = node.getElementsByTagName('*');
|
||||
|
||||
for(var i=0, N = descendents.length; i<N; i++)
|
||||
resetOne(descendents[i]);
|
||||
}
|
||||
Meteor.ui._dispatchEvent(event);
|
||||
};
|
||||
|
||||
// submit forms that aren't preventDefaulted
|
||||
|
||||
@@ -370,7 +370,6 @@ Meteor.ui = Meteor.ui || {};
|
||||
}
|
||||
|
||||
var copyFunc = function(t, s) {
|
||||
Meteor.ui._resetEvents(t);
|
||||
Meteor.ui._LiveRange.transplant_tag(Meteor.ui._tag, t, s);
|
||||
};
|
||||
|
||||
@@ -588,4 +587,45 @@ Meteor.ui = Meteor.ui || {};
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.ui._dispatchEvent = function(event) {
|
||||
var curNode = event.currentTarget;
|
||||
if (! curNode)
|
||||
return;
|
||||
|
||||
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, curNode);
|
||||
if (! innerRange)
|
||||
return;
|
||||
|
||||
Meteor.ui._fixEvent(event);
|
||||
|
||||
var type = event.type;
|
||||
|
||||
for(var range = innerRange; range; range = range.findParent(true)) {
|
||||
if (! range.event_handlers)
|
||||
continue;
|
||||
|
||||
_.each(range.event_handlers, function(h) {
|
||||
if (h.type !== type)
|
||||
return;
|
||||
|
||||
var selector = h.selector;
|
||||
if (selector) {
|
||||
var contextNode = range.containerNode();
|
||||
var results = $(contextNode).find(selector);
|
||||
if (! _.contains(results, curNode))
|
||||
return;
|
||||
}
|
||||
|
||||
var returnValue = h.callback.call(range.event_data, event);
|
||||
if (returnValue === false) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
if (event.isPropagationStopped())
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user