惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
小众软件
小众软件
云风的 BLOG
云风的 BLOG
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
量子位
S
Secure Thoughts
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
G
Google Developers Blog
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Martin Fowler
Martin Fowler
The GitHub Blog
The GitHub Blog
Google DeepMind News
Google DeepMind News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
S
Security Affairs
Help Net Security
Help Net Security
博客园 - 三生石上(FineUI控件)
AWS News Blog
AWS News Blog
博客园 - 叶小钗
Recent Commits to openclaw:main
Recent Commits to openclaw:main
V2EX - 技术
V2EX - 技术
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
H
Heimdal Security Blog
W
WeLiveSecurity
C
Check Point Blog

Sophie Alpert

There are no lossless transformations of natural-language text Deconstructing “services” I don’t want AI agents controlling my laptop Materialized views are obviously useful TODOs aren’t for doing Everyone is wrong about that Slack flowchart Hire me to empower and upskill your eng team How React Changed the Web Forever: A Documentary Fast and maintainable patterns for fetching from a database React Conf: “Building a Custom React Renderer” Why review code? Metrics by proxy Yak shaving and fixing Voice React Conf: “React Today and Tomorrow” Why we host conference talk dry runs React Podcast: Inside React Type errors with inference need stacks Observable programming React 16: an API-compatible rewrite Hi, I’m trans. Initializing on the main thread using dispatch_once Using React to speed up the Khan Academy question editor What I did at Khan Academy, 2012 edition Preventing XSS attacks when embedding JSON in HTML Rolling back to an old revision in Mercurial (like git reset)
A near-perfect oninput shim for IE 8 and 9
Sophie Alpert · 2013-06-18 · via Sophie Alpert

I recently began using React, which, like many libraries, has an events system designed so event properties can be normalized between browsers and custom events can be created. I contributed a synthetic event called onChange which is essentially a cross-browser shim for the input event (which is natively supported in modern browsers but not in older versions of IE).

In this post I’ll explain how the input event works and how I’m simulating it in IE 8 and 9. I’ll also point you to a jQuery plugin I made so you can take advantage of my textchange event regardless of whether you’re using React yet or not.

Understanding the input event

First off, what is the input event? If you have an <input> element and want to receive events whenever the value changes, the most obvious thing to do is to listen to the change event. Unfortunately, change fires only after the text field is defocused, rather than on each keystroke. The next obvious choice is the keyup event, which is triggered whenever a key is released. Unfortunately, keyup doesn’t catch input that doesn’t involve the keyboard (e.g., pasting from the clipboard using the mouse) and only fires once if a key is held down, rather than once per inserted character.

Both keydown and keypress do fire repeatedly when a key is held down, but both fire immediately before the value changes, so to read the new value you have to defer the handler to the next event loop using setTimeout(fn, 0) or similar, which slows down your app. Of course, like keyup, neither keydown nor keypress fires for non-keyboard input events, and all three can fire in cases where the value doesn’t change at all (such as when pressing the arrow keys).

The input event was created to solve all of these problems. It fires immediately whenever the value of a text box changes, whether due to the keyboard or another input device. (Technically, the spec says that browsers can delay the event if they so choose, but all modern browsers seem to fire it immediately.) It’s supported in most common browsers, with the notable exceptions of IE 8 (which doesn’t support it at all) and IE 9 (which has a buggy implementation).

Simulating the input event in IE 8

Let’s see how to simulate the input event in IE 8. Conveniently, IE supports a proprietary event called propertychange which fires whenever any property of an element changes, including value. Thus, you can get 80% of the way there by doing:

el.attachEvent("onpropertychange", function(e) {
    if (e.propertyName === "value") {
        // Fire textchange event on el
    }
});

However, we don’t want to fire the textchange event if the value has been changed from JavaScript code because such a notification is inconsistent with how the input event works (and almost always unhelpful). In addition, the propertychange event doesn’t bubble like most events do, so you have to bind to each input whose value you want to track (rather than binding on document and relying on event delegation).

IE 8 allows you to use Object.defineProperty on DOM elements, so to ignore value changes caused by JavaScript, we can override the property setter for value to intercept the change and silence our event. To avoid the overhead of listening to propertychange, we can listen to the focus and blur events and only listen to changes on the currently focused element. Now our shim looks something like this:

var activeElement = null;
var activeElementValue = null;

// On focus, start watching the element
document.addEventListener("focusin", function(e) {
    var target = e.srcElement;
    if (target.nodeName !== "INPUT") return;

    // Store a reference to the focused element and its current value
    activeElement = target;
    activeElementValue = target.value;

    // Listen to the propertychange event
    activeElement.attachEvent("onpropertychange", handlePropertyChange);

    // Override .value to track changes from JavaScript
    var valueProp = Object.getOwnPropertyDescriptor(
            HTMLInputElement.prototype, 'value');
    Object.defineProperty(activeElement, {
        get: function() { return valueProp.get.call(this); },
        set: function(val) {
            activeElementValue = val;
            valueProp.set.call(this, val);
        }
    });
});

// And on blur, stop watching
document.addEventListener("focusout", function(e) {
    if (!activeElement) return;

    // Stop listening to propertychange and restore the original .value prop
    activeElement.detachEvent("onpropertychange", handlePropertyChange);
    delete activeElement.value;

    activeElement = null;
    activeElementValue = null;
});

function handlePropertyChange(e) {
    if (e.propertyName === "value" &&
            activeElementValue !== activeElement.value) {
        activeElementValue = activeElement.value;
        // Fire textchange event on activeElement
    }
};

Now we’re about 90% of the way there. Our textchange event is fired when typing normally, deleting text (with backspace, forward-delete, or the context menu), cutting, pasting, and dragging to reorder characters.

Turns out that IE 8 has a bug where it fails to fire propertychange on the very first keystroke following a value change from JavaScript code — it fires only keydown, keypress, and keyup. To fix this, we can bind to the keyup event and trigger textchange if the value’s changed. Of course, there’s one final case: if the user holds down a key immediately after some JavaScript code changes value, then keyup won’t fire until the key is released. Since we want an event for every character, we can also bind to keydown in order to catch the second keydown (which is fired just before propertychange for the second character).

Phew! Now we have an event that works 99% of the time in IE 8.

Simulating the input event in IE 9

Surprisingly (or maybe unsurprisingly), IE 9 has some problems of its own.

IE 9 claims to support the input event, but it fails to fire the input event upon deleting text. Worse yet, text deletions don’t trigger propertychange either.

Fortunately, the IE developers were merciful and decided to fire the selectionchange event on every deletion, presumably because the cursor is moving and that counts as the selection changing. If we bind to selectionchange as well as propertychange, keyup, and keydown, we can catch all input events.

(IE 9 seems to have a bug where if you set the value of a <textarea> during a selectionchange event, the cursor gets moved to the start of the text box, and it refuses to move the cursor using createTextRange. I haven’t figured out how to get around this problem — please let me know if you find a way.)

Putting it all together

It’s amazing how many cross-browser quirks there are to deal with, but if we combine all of these techniques together, we can finally create an event that works consistently across all browsers. If anyone knows how to improve any of the solutions laid out in this post, please let me know.

If you’re using React, there’s no plugin needed; simply use onChange={...} and you’re done. If you’re not using React yet, I’ve created a jQuery plugin, jquery.splendid.textchange.js, that abstracts away cross-browser differences and dispatches textchange events via jQuery that behave almost identically in IE 8, IE9, and modern browsers.