@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
150 lines (133 loc) • 5.03 kB
JavaScript
/**
* 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(inputChangeEventName) {
var isIE8 = typeof document.dispatchEvent === 'undefined',
testNode = document.createElement("input"),
isInputEventSupported = "oninput" in testNode && (!("documentMode" in document) || document.documentMode > 9),
inputChangeEvent = function(){
var event;
if (isIE8) {
event = document.createEventObject();
event.type = 'change';
event.bubbles = true;
event.cancelable = false;
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(inputChangeEventName, true, false, {})
} return event;
},
origValueProp = null,
newValueProp = {
/**
* (For IE8<.) Replacement getter/setter for the `value` property that
* gets set on the active element.
*/
get: function() {
return origValueProp.get.call(this);
},
set: function(val) {
currentActive.value = val;
origValueProp.set.call(this, val);
}
},
currentActive = {'el':null, 'value':null};
function isInputField(elem) {
var elTagName = elem.nodeName.toUpperCase();
return elTagName === 'TEXTAREA' || elTagName === 'INPUT';
};
/**
* (For IE8<.) 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.
*/
function startWatching(target) {
currentActive = {'el':target, 'value':target.value};
origValueProp = Object.getOwnPropertyDescriptor(target.constructor.prototype, "value");
Object.defineProperty(currentActive.el, "value", newValueProp);
currentActive.el.attachEvent("onpropertychange", handlePropertyChange);
};
/**
* (For IE8<.) Removes the event listeners from the currently-tracked
* element, if any exists.
*/
function stopWatching() {
if (!currentActive.el) return;
// delete restores the original property definition
delete currentActive.el.value;
currentActive.el.detachEvent("onpropertychange", handlePropertyChange);
currentActive = {'el':null, 'value':null};
origValueProp = null;
};
function triggerInputEvent(el) {
if (isIE8) {
el.fireEvent('on'+inputChangeEventName, inputChangeEvent());
} else {
el.dispatchEvent(inputChangeEvent());
}
};
/**
* (For IE8<.) Handles a propertychange event, sending a textChange event if
* the value of the active element has changed.
*/
function handlePropertyChange(nativeEvent) {
if (nativeEvent.propertyName !== "value") return;
var value = nativeEvent.srcElement.value;
if (value === currentActive.value) return;
currentActive.value = value;
triggerInputEvent(currentActive.el);
};
function handleValueChanges() {
// On the selectionchange event, e.target is just document which
// isn't helpful for us so just check currentActive.el 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 (currentActive.el && currentActive.el.value !== currentActive.value) {
currentActive.value = currentActive.el.value;
triggerInputEvent(currentActive.el);
}
};
if (isInputEventSupported) {
if (inputChangeEventName !== 'input') {
document.addEventListener('input', function(e){
triggerInputEvent(e.target);
}, false);
}
} else {
document.attachEvent("onfocusin", 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 currentActive.value variable, allowing us to
// ignore those changes.
if (isInputField(e.srcElement)) {
// stopWatching() should be a noop here but we call it just in
// case we missed a blur event somehow.
stopWatching();
startWatching(e.srcElement);
}
});
document.attachEvent("onfocusout", function() {
stopWatching();
});
document.attachEvent("onselectionchange", handleValueChanges);
document.attachEvent("onkeyup", handleValueChanges);
document.attachEvent("onkeydown", handleValueChanges);
}
})('input');