pass tests in IE 6-10, FF/S/C (lots of cleanup needed)

This commit is contained in:
David Greenspan
2012-05-07 19:11:23 -07:00
parent a288e9a26c
commit feed0fce0f
5 changed files with 159 additions and 52 deletions

View File

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

View File

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

View File

@@ -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'], []]);

View File

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

View File

@@ -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");