mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
pass tests in IE 6-10, FF/S/C (lots of cleanup needed)
This commit is contained in:
@@ -47,16 +47,29 @@ Meteor.ui = Meteor.ui || {};
|
||||
return;
|
||||
}
|
||||
|
||||
var SIMULATE_FOCUS_BLUR = 1;
|
||||
var SIMULATE_FOCUSIN_FOCUSOUT = 2;
|
||||
|
||||
// If we have focusin/focusout, use them to simulate focus/blur.
|
||||
// This has the nice effect of making focus/blur synchronous in IE 9+.
|
||||
// It doesn't work in Firefox, which lacks focusin/focusout completely
|
||||
// as of v11.0. This style of event support testing ('onfoo' in div)
|
||||
// doesn't work in Firefox 3.6, but does in IE and WebKit.
|
||||
var focusBlurMode = ('onfocusin' in document.createElement("DIV")) ?
|
||||
SIMULATE_FOCUS_BLUR : SIMULATE_FOCUSIN_FOCUSOUT;
|
||||
|
||||
var prefix = '_liveevents_';
|
||||
|
||||
var universalCapturer = function(event) {
|
||||
var type = event.type;
|
||||
var bubbles = event.bubbles;
|
||||
var target = event.target;
|
||||
console.log("captured ", type, " on ", target.nodeName);
|
||||
|
||||
target.addEventListener(type, universalHandler, false);
|
||||
|
||||
// according to the DOM event spec, ancestors for bubbling
|
||||
// purposes are determined at dispatch time (and ignore changes
|
||||
// to the DOM after that)
|
||||
var ancestors;
|
||||
if (bubbles) {
|
||||
ancestors = [];
|
||||
@@ -83,22 +96,58 @@ Meteor.ui = Meteor.ui || {};
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
|
||||
var universalHandler = function(event) {
|
||||
if (event.type === 'focus')
|
||||
console.log(event.target.nodeName);
|
||||
// fire synthetic focusin/focusout on blur/focus
|
||||
var doHandleHacks = function(event) {
|
||||
// fire synthetic focusin/focusout on blur/focus or vice versa
|
||||
if (event.currentTarget === event.target) {
|
||||
if (event.type === 'focus')
|
||||
sendUIEvent('focusin', event.target, true);
|
||||
else if (event.type === 'blur')
|
||||
sendUIEvent('focusout', event.target, true);
|
||||
if (focusBlurMode === SIMULATE_FOCUS_BLUR) {
|
||||
if (event.type === 'focusin')
|
||||
sendUIEvent('focus', event.target, false);
|
||||
else if (event.type === 'focusout')
|
||||
sendUIEvent('blur', event.target, false);
|
||||
} else { // SIMULATE_FOCUSIN_FOCUSOUT
|
||||
if (event.type === 'focus')
|
||||
sendUIEvent('focusin', event.target, true);
|
||||
else if (event.type === 'blur')
|
||||
sendUIEvent('focusout', event.target, true);
|
||||
}
|
||||
}
|
||||
// only respond to synthetic focusin/focusout
|
||||
if (event.type === 'focusin' || event.type === 'focusout') {
|
||||
if (! event.synthetic)
|
||||
return;
|
||||
// only respond to synthetic events of the types we are faking
|
||||
if (focusBlurMode === SIMULATE_FOCUS_BLUR) {
|
||||
if (event.type === 'focus' || event.type === 'blur') {
|
||||
if (! event.synthetic)
|
||||
return false;
|
||||
}
|
||||
} else { // SIMULATE_FOCUSIN_FOCUSOUT
|
||||
if (event.type === 'focusin' || event.type === 'focusout') {
|
||||
if (! event.synthetic)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
var doInstallHacks = 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) {
|
||||
if (eventType === 'focus')
|
||||
Meteor.ui._installLiveHandler(node, 'focusin');
|
||||
else if (eventType === 'blur')
|
||||
Meteor.ui._installLiveHandler(node, 'focusout');
|
||||
} else { // SIMULATE_FOCUSIN_FOCUSOUT
|
||||
if (eventType === 'focusin')
|
||||
Meteor.ui._installLiveHandler(node, 'focus');
|
||||
else if (eventType === 'focusout')
|
||||
Meteor.ui._installLiveHandler(node, 'blur');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var universalHandler = function(event) {
|
||||
if (doHandleHacks(event) === false)
|
||||
return;
|
||||
|
||||
var curNode = event.currentTarget;
|
||||
if (! curNode)
|
||||
return;
|
||||
@@ -151,12 +200,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
};
|
||||
|
||||
Meteor.ui._installLiveHandler = function(node, eventType) {
|
||||
// we completely fake focusin / focusout in the focus/blur handler
|
||||
// because Firefox doesn't support them natively.
|
||||
if (eventType === 'focusin')
|
||||
Meteor.ui._installLiveHandler(node, 'focus');
|
||||
else if (eventType === 'focusout')
|
||||
Meteor.ui._installLiveHandler(node, 'blur');
|
||||
doInstallHacks(node, eventType);
|
||||
|
||||
var propName = prefix + eventType;
|
||||
if (! document[propName]) {
|
||||
@@ -165,11 +209,6 @@ Meteor.ui = Meteor.ui || {};
|
||||
document.addEventListener(eventType, universalCapturer, true);
|
||||
}
|
||||
|
||||
// XXXX
|
||||
//_.each(document.getElementsByTagName("*"), function(elem) {
|
||||
//elem.addEventListener('focus', universalHandler, false);
|
||||
//elem.addEventListener('blur', universalHandler, false);
|
||||
//});
|
||||
};
|
||||
|
||||
////// WHAT WE SHOULD ACTUALLY DO
|
||||
|
||||
@@ -3,28 +3,38 @@
|
||||
|
||||
Meteor.ui._loadNonW3CEvents = function() {
|
||||
|
||||
var prefix = '_liveevents_';
|
||||
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');
|
||||
|
||||
// use object property value so it doesn't show up in innerHTML
|
||||
var TRUE = {};
|
||||
|
||||
var installOneHandler = function(node, eventType) {
|
||||
var propName = prefix + eventType;
|
||||
if (! node[propName]) {
|
||||
// only bind one event listener per type per node
|
||||
node[propName] = TRUE;
|
||||
node.attachEvent('on'+eventType, universalHandler);
|
||||
}
|
||||
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
|
||||
installOneHandler(node, eventType);
|
||||
installOne(node, prop);
|
||||
|
||||
var descendents = node.getElementsByTagName('*');
|
||||
|
||||
for(var i=0, N = descendents.length; i<N; i++)
|
||||
installOneHandler(descendents[i], eventType);
|
||||
installOne(descendents[i], prop);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,15 +61,64 @@ Meteor.ui._loadNonW3CEvents = function() {
|
||||
});
|
||||
};
|
||||
|
||||
/*setTimeout(function() {
|
||||
Meteor.startup(function() {
|
||||
document.body.appendChild(document.createElement("DIV"));
|
||||
document.body.lastChild.innerHTML =
|
||||
'<div style="position:absolute;top:0;right:0;width:300px;height:500px;font-size:10px;font-family:monospace;overflow:visible" id="mylog"></div>';
|
||||
});
|
||||
}, 0);
|
||||
var LOG = function(str) {
|
||||
document.getElementById('mylog').innerHTML += '<br>'+str;
|
||||
};*/
|
||||
|
||||
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;
|
||||
event.target = event.srcElement || document;
|
||||
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;
|
||||
@@ -127,16 +186,9 @@ Meteor.ui._loadNonW3CEvents = function() {
|
||||
};
|
||||
|
||||
var resetOne = function(node) {
|
||||
for(var k in node) {
|
||||
if (! node[k])
|
||||
continue;
|
||||
if (k.substring(0, prefix.length) !== prefix)
|
||||
continue;
|
||||
|
||||
var type = k.substring(prefix.length);
|
||||
|
||||
node.detachEvent('on'+type, universalHandler);
|
||||
}
|
||||
for(var k in node)
|
||||
if (node[k] === universalHandler)
|
||||
node[k] = null;
|
||||
};
|
||||
|
||||
Meteor.ui._resetEvents = function(node) {
|
||||
@@ -150,4 +202,14 @@ Meteor.ui._loadNonW3CEvents = function() {
|
||||
}
|
||||
};
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -1369,6 +1369,8 @@ testAsyncMulti("liveui - tricky events", [
|
||||
return render_func();
|
||||
}, { events: events, event_data: buf }));
|
||||
div.node().style.display = "block"; // make visible
|
||||
div.node().style.height = 0;
|
||||
div.node().style.overflow = 'hidden';
|
||||
|
||||
var getbuf = function() {
|
||||
var ret = buf.slice();
|
||||
@@ -1433,7 +1435,7 @@ testAsyncMulti("liveui - tricky events", [
|
||||
|
||||
var focusBuf = tester.focus();
|
||||
var blurBuf = tester.blur();
|
||||
tester.kill();
|
||||
tricky_events_kill_later(tester);
|
||||
|
||||
return [focusBuf, blurBuf];
|
||||
};
|
||||
@@ -1442,12 +1444,12 @@ testAsyncMulti("liveui - tricky events", [
|
||||
test.equal(focus_blur(textLevel1, 'focus input'),
|
||||
[['focus input'], []]);
|
||||
|
||||
|
||||
// focus on second-level input
|
||||
// issue #108
|
||||
test.equal(focus_blur(textLevel2,'focus input'),
|
||||
[['focus input'], []]);
|
||||
|
||||
|
||||
// focusin
|
||||
test.equal(focus_blur(textLevel1, 'focusin input'),
|
||||
[['focusin input'], []]);
|
||||
|
||||
@@ -14,7 +14,7 @@ Package.on_use(function (api) {
|
||||
api.use('jquery');
|
||||
|
||||
api.add_files(['liverange.js', 'liveui.js', 'innerhtml.js', 'smartpatch.js',
|
||||
'liveevents.js', 'liveevents_nonw3c.js'],
|
||||
'liveevents_nonw3c.js', 'liveevents.js'],
|
||||
'client');
|
||||
});
|
||||
|
||||
|
||||
@@ -14,8 +14,12 @@ var simulateEvent = function (node, event, args) {
|
||||
};
|
||||
|
||||
var focusElement = function(elem) {
|
||||
// This sequence is for benefit of IE 8 and 9;
|
||||
// test there before changing.
|
||||
window.focus();
|
||||
elem.focus();
|
||||
elem.focus(); // IE 8 seems to need a second call!
|
||||
elem.focus();
|
||||
|
||||
// focus() should set document.activeElement
|
||||
if (document.activeElement !== elem)
|
||||
throw new Error("focus() didn't set activeElement");
|
||||
|
||||
Reference in New Issue
Block a user