forked from fanruan/fineui
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
5.8 KiB
145 lines
5.8 KiB
8 years ago
|
/**
|
||
|
* jQuery "splendid textchange" plugin
|
||
|
* http://benalpert.com/2013/06/18/a-near-perfect-oninput-shim-for-ie-8-and-9.html
|
||
|
*
|
||
|
* (c) 2013 Ben Alpert, released under the MIT license
|
||
|
*/
|
||
|
|
||
|
(function($) {
|
||
|
|
||
|
var testNode = document.createElement("input");
|
||
|
var isInputSupported = "oninput" in testNode &&
|
||
|
(!("documentMode" in document) || document.documentMode > 9);
|
||
|
|
||
|
var hasInputCapabilities = function(elem) {
|
||
|
// The HTML5 spec lists many more types than `text` and `password` on
|
||
|
// which the input event is triggered but none of them exist in IE 8 or
|
||
|
// 9, so we don't check them here.
|
||
|
// TODO: <textarea> should be supported too but IE seems to reset the
|
||
|
// selection when changing textarea contents during a selectionchange
|
||
|
// event so it's not listed here for now.
|
||
|
return elem.nodeName === "INPUT" &&
|
||
|
(elem.type === "text" || elem.type === "password");
|
||
|
};
|
||
|
|
||
|
var activeElement = null;
|
||
|
var activeElementValue = null;
|
||
|
var activeElementValueProp = null;
|
||
|
|
||
|
/**
|
||
|
* (For old IE.) Replacement getter/setter for the `value` property that
|
||
|
* gets set on the active element.
|
||
|
*/
|
||
|
var newValueProp = {
|
||
|
get: function() {
|
||
|
return activeElementValueProp.get.call(this);
|
||
|
},
|
||
|
set: function(val) {
|
||
|
activeElementValue = val;
|
||
|
activeElementValueProp.set.call(this, val);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* (For old IE.) Starts tracking propertychange events on the passed-in element
|
||
|
* and override the value property so that we can distinguish user events from
|
||
|
* value changes in JS.
|
||
|
*/
|
||
|
var startWatching = function(target) {
|
||
|
activeElement = target;
|
||
|
activeElementValue = target.value;
|
||
|
activeElementValueProp = Object.getOwnPropertyDescriptor(
|
||
|
target.constructor.prototype, "value");
|
||
|
|
||
|
Object.defineProperty(activeElement, "value", newValueProp);
|
||
|
activeElement.attachEvent("onpropertychange", handlePropertyChange);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* (For old IE.) Removes the event listeners from the currently-tracked
|
||
|
* element, if any exists.
|
||
|
*/
|
||
|
var stopWatching = function() {
|
||
|
if (!activeElement) return;
|
||
|
|
||
|
// delete restores the original property definition
|
||
|
delete activeElement.value;
|
||
|
activeElement.detachEvent("onpropertychange", handlePropertyChange);
|
||
|
|
||
|
activeElement = null;
|
||
|
activeElementValue = null;
|
||
|
activeElementValueProp = null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* (For old IE.) Handles a propertychange event, sending a textChange event if
|
||
|
* the value of the active element has changed.
|
||
|
*/
|
||
|
var handlePropertyChange = function(nativeEvent) {
|
||
|
if (nativeEvent.propertyName !== "value") return;
|
||
|
|
||
|
var value = nativeEvent.srcElement.value;
|
||
|
if (value === activeElementValue) return;
|
||
|
activeElementValue = value;
|
||
|
|
||
|
$(activeElement).trigger("textchange");
|
||
|
};
|
||
|
|
||
|
if (isInputSupported) {
|
||
|
$(document)
|
||
|
.on("input", function(e) {
|
||
|
// In modern browsers (i.e., not IE 8 or 9), the input event is
|
||
|
// exactly what we want so fall through here and trigger the
|
||
|
// event...
|
||
|
if (e.target.nodeName !== "TEXTAREA") {
|
||
|
// ...unless it's a textarea, in which case we don't fire an
|
||
|
// event (so that we have consistency with our old-IE shim).
|
||
|
$(e.target).trigger("textchange");
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
$(document)
|
||
|
.on("focusin", function(e) {
|
||
|
// In IE 8, we can capture almost all .value changes by adding a
|
||
|
// propertychange handler and looking for events with propertyName
|
||
|
// equal to 'value'.
|
||
|
// In IE 9, propertychange fires for most input events but is buggy
|
||
|
// and doesn't fire when text is deleted, but conveniently,
|
||
|
// selectionchange appears to fire in all of the remaining cases so
|
||
|
// we catch those and forward the event if the value has changed.
|
||
|
// In either case, we don't want to call the event handler if the
|
||
|
// value is changed from JS so we redefine a setter for `.value`
|
||
|
// that updates our activeElementValue variable, allowing us to
|
||
|
// ignore those changes.
|
||
|
if (hasInputCapabilities(e.target)) {
|
||
|
// stopWatching() should be a noop here but we call it just in
|
||
|
// case we missed a blur event somehow.
|
||
|
stopWatching();
|
||
|
startWatching(e.target);
|
||
|
}
|
||
|
})
|
||
|
|
||
|
.on("focusout", function() {
|
||
|
stopWatching();
|
||
|
})
|
||
|
|
||
|
.on("selectionchange keyup keydown", function() {
|
||
|
// On the selectionchange event, e.target is just document which
|
||
|
// isn't helpful for us so just check activeElement instead.
|
||
|
//
|
||
|
// 90% of the time, keydown and keyup aren't necessary. IE 8 fails
|
||
|
// to fire propertychange on the first input event after setting
|
||
|
// `value` from a script and fires only keydown, keypress, keyup.
|
||
|
// Catching keyup usually gets it and catching keydown lets us fire
|
||
|
// an event for the first keystroke if user does a key repeat
|
||
|
// (it'll be a little delayed: right before the second keystroke).
|
||
|
// Other input methods (e.g., paste) seem to fire selectionchange
|
||
|
// normally.
|
||
|
if (activeElement && activeElement.value !== activeElementValue) {
|
||
|
activeElementValue = activeElement.value;
|
||
|
$(activeElement).trigger("textchange");
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
})(jQuery);
|