UNPKG

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