After posting my first technical blog post, Effectively detecting user input in JavaScript, I felt like I needed to expand on a few things that I didn’t cover. For instance, the fact that input is a bubbling event and propertychange is not, or that detecting support for input is made extra difficult by Firefox. Instead of expanding that article, I decided I would write a snippet to try and normalize the event a little better. I chose jQuery as a base for the code, mostly because I wanted to learn more about writing custom events for jQuery, but partly because I knew it would make a lot of things easier. The result is the input event plug-in below.
I wasn’t really sure where to start, but Googling turned up Ben Alman’s excellent jQuery special events post which tells you more or less everything you need to know. After that, I was able to create a basic plug-in that would provide an interface for jQuery.fn.bind("input"). The plug-in decides between attaching to input, propertychange or a host of fall back events that may or may not be supported in the browser. In short, it tries to get as much of the functionality of input as possible, but there’ll always be at least some functionality, for instance, plain keyboard input detection.
Note that the plug-in does have the following limitations in browsers that don’t support input:
- There’s no “catch-all-changes” fallback. I had considered running a timer, when an element is focused or moused over, that constantly checks for value changes. However, this would trigger when the value of the element is changed programmatically, which may not be desired.
- Because IE’s
propertychangetriggers when the value of an element is changed programmatically, that browser suffers from the problem described by the limitation above this one. It is possible that an infinite loop could be caused by always changing the value of the element in the event handler. A workaround, if you need to do this, might be to temporarily unbind whilst you change the value, rebinding after changing. - It currently only works with input and textarea elements. I may be able to get some support in for contentEditable elements in the future, but they don’t fire a
propertychangeevent in IE when typing or pasting, which makes them awkward for capturing input. Firefox, Opera and IE9 don’t support the event on contentEditable elements anyway. It will not work withEvent delegation works as of version 1.1live()ordelegate(). This is due to how thepropertychangeevent doesn’t bubble.It doesn’t work around the bugs Opera has with the input event. I’m not sure which versions are affected and it would be a pain to figure it all out.The plugin should now provide the best level of compatibility possible in all versions of Opera.
Changelog
- v1.1: Oct 24, 2011 — Fixed many browser implementation issues, introduced
live/delegatecompatibility, changed event special name totxtinput. see full post. - v1.0: Oct 18, 2010 — 1st version
The following fiddle (very basically) demonstrates the event:
[iframe http://jsfiddle.net/AndyE/sdkBs/embedded/result 99% 300px]
And here’s the full code.
/*
jQuery `input` special event v1.1
http://whattheheadsaid.com/projects/input-special-event
(c) 2010-2011 Andy Earnshaw
MIT license
www.opensource.org/licenses/mit-license.php
*/
(function($, udf) {
var ns = ".inputEvent ",
// A bunch of data strings that we use regularly
dataBnd = "bound.inputEvent",
dataVal = "value.inputEvent",
dataDlg = "delegated.inputEvent",
// Set up our list of events
bindTo = [
"input", "textInput", "propertychange", "paste", "cut", "keydown", "drop",
""].join(ns),
// Events required for delegate, mostly for IE support
dlgtTo = [ "focusin", "mouseover", "dragstart", "" ].join(ns),
// Elements supporting text input, not including contentEditable
supported = {TEXTAREA:udf, INPUT:udf},
// Events that fire before input value is updated
delay = { paste:udf, cut:udf, keydown:udf, drop:udf, textInput:udf };
$.event.special.txtinput = {
setup: function(data, namespaces, handler) {
var triggerTimer,
bndCount,
changeTimer,
// Get references to the element
elem = this,
$elem = $(this),
triggered = false;
if (elem.tagName in supported) {
bndCount = $.data(elem, dataBnd) || 0;
if (!bndCount)
$elem.bind(bindTo, handler);
$.data(elem, dataBnd, ++bndCount);
$.data(elem, dataVal, elem.value);
} else {
$elem.bind(dlgtTo, function (e) {
var target = e.target;
if (target.tagName in supported && !$.data(elem, dataDlg)) {
bndCount = $.data(target, dataBnd) || 0;
if (!bndCount)
target.bind(bindTo, handler);
// make sure we increase the count only once for each bound ancestor
$.data(elem, dataDlg, true);
$.data(target, dataBnd, ++bndCount);
$.data(target, dataVal, target.value);
}
});
}
function handler (e) {
var elem = e.target;
// Clear previous timers because we only need to know about 1 change
window.clearTimeout(timer), timer = null;
// Return if we've already triggered the event
if (triggered)
return;
// paste, cut, keydown and drop all fire before the value is updated
if (e.type in delay && !timer) {
// ...so we need to delay them until after the event has fired
timer = window.setTimeout(function () {
if (elem.value !== $.data(elem, dataVal)) {
$(elem).trigger("txtinput");
$.data(elem, dataVal, elem.value);
}
}, 0);
}
else if (e.type == "propertychange") {
if (e.originalEvent.propertyName == "value") {
$(elem).trigger("txtinput");
$.data(elem, dataVal, elem.value);
triggered = true;
window.setTimeout(function () {
triggered = false;
}, 0);
}
}
else {
$(elem).trigger("txtinput");
$.data(elem, dataVal, elem.value);
triggered = true;
window.setTimeout(function () {
triggered = false;
}, 0);
}
}
},
teardown: function () {
var elem = $(this);
elem.unbind(dlgtTo);
elem.find("input, textarea").andSelf().each(function () {
bndCount = $.data(this, dataBnd, ($.data(this, dataBnd) || 1)-1);
if (!bndCount)
elem.unbind(bindTo);
});
}
};
// Setup our jQuery shorthand method
$.fn.input = function (handler) {
return handler ? this.bind("txtinput", handler) : this.trigger("txtinput");
}
})(jQuery);
Thanks to: Nick Craver, Tim Down, Ben Alman and Daniel Friesen. Please post any issues you come across in the comments.
uJee
January 12th, 2011 at 12:29 pm
I would like to know how to use this,
and I need to support the onpropertychange event in firefox and was not able to do that.
please provide your suggestion on this.
Andy
January 14th, 2011 at 1:39 pm
uJee,
Look at the attached jsFiddle example just above the source code above – if you click the `+` button to the right of the blue bar it will take you directly to the example’s source code. Remember that you also need to include jQuery 1.4.2 or later.
As for onpropertychange in Firefox, that’s not going to happen, I’m afraid. onpropertychange works as a substitute for oninput because it fires when an input element’s value changes.
gmoulin
February 15th, 2011 at 9:45 am
Exactly what i was looking for.
.
Congratulation on the good work
Wynand
March 17th, 2011 at 1:59 pm
For an unknown reason, line 99 causes some arbitrary functionality of a project I’m working on to break in Firefox. Remarking only that line seems to solve the issue without any apparent side effects.
Andy
March 28th, 2011 at 12:48 pm
@Wynand,
Which version of Firefox have you tested it in? That code is specifically geared towards Firefox, which doesn’t initialize DOM event attributes as properties properly.
Christo
March 17th, 2011 at 2:28 pm
Wow! Good work!
Darkthread
April 26th, 2011 at 10:24 am
Great Job!
It’s really useful for scenarios with IME issue, thank you.
Lucas
May 24th, 2011 at 9:00 am
Nice script!
).
But I’ve found a little bug in IE v8 ( yes, our loved IE
If you press two or more keys at the same time, IE doesn’t call the handler again with this change. It only calls the handler with the first letter/key changed. So until the next keyevent the input doesn’t realize the change.
Thanks!
Using the oninput event with onkeydown as its fallback
June 5th, 2011 at 4:41 am
Using the oninput event with onkeydown as its fallback…
HTML5 standardizes the oninput event handler, which should be used to detect user input in JavaScript. Sure, you could use onkeydown or onkeyup instead, but those were never really designed for this particular use case, and it shows. [ ]…
Mathias Bynens
June 5th, 2011 at 5:10 am
Nice work Andy!
Instead of
window.event.propertyNameyou could usee.originalEvent.propertyNameon line 35.Tim Down
June 9th, 2011 at 1:58 pm
Unfortunately the input event isn’t supported on Safari prior to version 5 for textareas. However, it supports a textinput event instead. More feature detection required…
Tim Down
June 9th, 2011 at 2:16 pm
Actually, scratch that, I made the mistake of believing something I’d read. Doing some research now.
Tim Down
June 10th, 2011 at 9:07 am
Actually I was right. The input event is not supported on textareas on Safari prior to version 5, and you can use the textInput (with that capitalization) DOM 3 event instead from Safari 3.1.
Andy
June 15th, 2011 at 1:46 pm
Tim,
Thanks for notifying me of this. I’ve been half-working on a solution to this and almost all other cross-browser inconsistencies since I saw you posted the same issue on my jQuery 1.7 ticket.
The solution I’m working with at the moment ditches feature detection entirely, in favour of graceful degradation. It appears to work well and even allows for delegation/live binding in older IE versions – I’m just having a problem dealing with multiple bindings and unbindings. I’m hoping I get the time to complete this soon, as it’s apparent more now than ever that something is needed to work around all these implementation differences.
Lucas
September 5th, 2011 at 12:39 pm
And how is this solution going?
I’ve seen this http://jsfiddle.net/AndyE/sdkBs/ Is this your last version?
In the other hand, do you know the new DOM3 event called “textInput” (although they’re discussing if the ‘i’ should or not be capitalized)?
It works in last WebKit browsers (Chrome).
Andy
September 5th, 2011 at 10:41 pm
I’m working on tackling a few different cross browser issues at the moment, I reached the limit of my jQuery custom event knowledge in the last revision and was planning on seeking further help (perhaps from Ben Alman). The problem is that the event has several bugs in different browsers and, for this reason, I believe that jQuery needs to add support for this event internally to deal with these issues. I tried to raise this with the jQuery team but it was (unfortunately) shot down. I’ve been rather busy with work since, but I intend on picking it back up shortly and trying to get the teardown working.
The revision you posted, in it’s current state, works reasonably well across browsers assuming you don’t need to unbind the event. One of the fallback events it binds to is
textInput, however support for this event is rather poor. Keep your eye on this page for updates.Lucas
September 12th, 2011 at 8:12 am
Thanks for the reply.
I saw the jQuery ticket you mention, and its Resolution to wontfix
If you can find it useful, I also found a few references and some tryes:
http://www.useragentman.com/blog/2011/05/12/fixing-oninput-in-ie9-using-html5widgets/
http://blog.danielfriesen.name/2010/02/16/html5-browser-maze-oninput-support/
http://blog.danielfriesen.name/2010/02/16/html5-browser-maze-oninput-support/#comment-112959404
I’ll be using the Robin Winslow solution which works ok in my case.
Anyway I’ll be keeping the eye to your head
P.D.: Really original reference to Futurama head’s
The head-avatar is made by you?
Andy
September 17th, 2011 at 12:58 pm
Nope, it was made about 10 years ago by someone I barely knew on the Internet
世纪之光
November 1st, 2011 at 6:24 am
It can not catch the ‘Backspace’ key?
Andy
November 1st, 2011 at 8:55 am
IE9 has a bug with oninput and the backspace key. The latest update to the plugin works around that bug.
世纪之光
November 1st, 2011 at 11:12 am
Great,thank you!
I am going to introduce it on my blog
Lasse
November 9th, 2011 at 2:29 pm
I had to alter the plugin this way to make it work. Unaltered script seems to work for everyone else. Am I missing something?
// Clear previous timers because we only need to know // about 1 change
// EDIT: If there are any previous timers defined
if (typeof timer != ‘undefined’) {
window.clearTimeout(timer), timer = null;
} else {
var timer = false
}
Example how I use it elsewhere (in CoffeeScript):
$(‘#textarea_id’).input((event) ->
console.log(‘test’)
)
fxcn
November 30th, 2011 at 8:21 am
demo not work, i debug with chrome,found problem:
a.js:76 Uncaught ReferenceError: timer is not defined
Ruben
December 12th, 2011 at 4:49 pm
Same happening here. I put the code in an jquery.input.js file, included it in my html and then called .input(function(event){ … });
Ruben
December 19th, 2011 at 12:42 pm
I fixed that adding a
timer,
in line 31.