liveevents/liveui refactor wip

This commit is contained in:
David Greenspan
2012-05-09 11:34:50 -07:00
parent a458293c9a
commit a1ec706982
4 changed files with 98 additions and 362 deletions

View File

@@ -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.
};
})();

View File

@@ -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();
});
};

View File

@@ -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

View File

@@ -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;
}
};
})();