views:

840

answers:

6

There are many ways the value of a <input type="text"> can change, including:

  • keypresses
  • copy/paste
  • modified with JavaScript
  • auto-completed by browser or a toolbar

I want my JavaScript function to be called (with the current input value) any time it changes. And I want it to be called right away, not just when the input loses focus.

I'm looking for the cleanest and most robust way to do this across all browsers (using jQuery preferably).

Example use case: On the Twitter Signup page, the username field's value gets shown in the url "http://twitter/username" below it.

+5  A: 

Unfortunately there is no event or set of events that matches your criteria. Keypresses and copy/paste can both be handled with the keyup event. Changes through JS are trickier. If you have control over the code that sets the textbox, your best bet is to modify it to either call your function directly or trigger a user event on the textbox:

// Compare the textbox's current and last value.  Report a change to the console.
function watchTextbox() {
  var txtInput = $('#txtInput');
  var lastValue = txtInput.data('lastValue');
  var currentValue = txtInput.val();
  if (lastValue != currentValue) {
    console.log('Value changed from ' + lastValue + ' to ' + currentValue);
    txtInput.data('lastValue', currentValue);
  }
}

// Record the initial value of the textbox.
$('#txtInput').data('lastValue', $('#txtInput').val());

// Bind to the keypress and user-defined set event.
$('#txtInput').bind('keypress set', null, watchTextbox);

// Example of JS code triggering the user event
$('#btnSetText').click(function (ev) {
  $('#txtInput').val('abc def').trigger('set');
});

If you don't have control over that code, you could use setInterval() to 'watch' the textbox for changes:

// Check the textbox every 100 milliseconds.  This seems to be pretty responsive.
setInterval(watchTextbox, 100);

This sort of active monitoring won't catch updates 'immediately', but it seems to be fast enough that there is no perceptible lag. As DrLouie pointed out in comments, this solution probably doesn't scale well if you need to watch lots of inputs. You can always adjust the 2nd parameter to setInterval() to check more or less frequently.

Annabelle
Just tested this, it actually works quite well. Good job!
idrumgood
FYI there are actually a slew of events that can be combined to match the OPs criteria in varying degrees across browsers (see links in first question comment). This answer does not use any of said events, and ironically will probably work just as well in all browsers, albeit with a slight lag ;)
Crescent Fresh
The OP wants to catch changes to the textbox via JavaScript (e.g. `myTextBox.value = 'foo';`. No JavaScript events handle that situation.
Annabelle
What if someone had 50 fields to keep tabs on?
drlouie - louierd
Yet, if you only have one field to monitor there's no reason not to go with this fix.
drlouie - louierd
Then I'd point them to idrumgood's solution. The OP only mentioned one field, and my impression was the JS code updating the textbox was out of his control.
Annabelle
Yes, I only have 1 field in my case, but I don't see why this wouldn't work reasonably well for a number of fields. It would probably be most efficient to just have 1 setTimeout that checks all inputs.
Dustin Boswell
@Douglas: *No JavaScript events handle that situation* - Correction: IE can handle it with `input.onpropertychange`, and FF2+, Opera 9.5, Safari 3, and Chrome 1 can handle it with `input.__defineSetter__('value', ...)` (which, although it's documented as not functional on DOM nodes, I can verify it works). The only divergence from IE's implementation is IE fires the handler not only on programmatic setting but also during key events.
Crescent Fresh
Well I do feel that `setInterval` is sort of a weapon of last resort. So I added an alternate solution you could use if you can modify the JS code that updates the textbox.
Annabelle
CF: interesting, hadn't thought of going the setter route!
Annabelle
Nice use of custom events! If teh OP can modified the source though, what is the advantage of using this approach?
Roatin Marth
Roatin: which approach are you referring to? Custom events versus calling the watch function directly? I like the custom event there because it keeps the setting code less aware of the details. If you meant the `setInterval` approach, there's no advantage and I wouldn't use it.
Annabelle
Can someone explain all the negatives of using setInterval in this case? The 100ms delay is perfectly acceptable in my case.
Dustin Boswell
Dustin: if you can modify the code that sets the textbox value, go with events. If not, setInterval should be fine for you. You just have to be careful with it because you're putting an extra constant load on the browser. Too much of that can degrade your site's performance especially in older hardware.
Annabelle
Douglas: how much extra load? On an "average" computer, would the setInterval() add 0.1% CPU or 10% or ...?
Dustin Boswell
On an average computer it'll be negligible.
Annabelle
@Douglas: of course I forgot `Object.watch` in Firefox, built for handling programmatic assignment.
Crescent Fresh
CF: Why haven't you written an answer yet? ;) Also, getters/setters for DOM properties don't seem to work in Chrome. If you define a setter, Chrome seems to expect a getter too. Also __lookupGetter__() and __lookupSetter__() both return null, so there's no way to actually get/set the real property.
Annabelle
@Douglas: no answer from me because the result would be too messy. Even I would downvote it. `setInterval` is the "cleanest". Re: `__defineSetter__`, you don't need `__lookupSetter__`/`__lookupGetter__` because there'd be nothing needed from them. Inside the setter implementation you'd just `setAttribute('value', value)`.
Crescent Fresh
I clearly need to read about getters/setters more. :) Sadly all my work must support IE6, so yeah... :P
Annabelle
+1  A: 

Well, best way is to cover those three bases you listed by yourself. A simple :onblur, :onkeyup, etc won't work for what you want, so just combine them.

KeyUp should cover the first two, and if Javascript is modifying the input box, well I sure hope it's your own javascript, so just add a callback in the function that modifies it.

idrumgood
+1 for the simple solution, although it's possible OP can't/doesn't want to modify the code making changes to the textbox.
Annabelle
Enumerating all the possible events doesn't seem robust to me. Does keyup work if the user holds delete down (i.e. a repeat-key)? What about auto-complete (or toolbars that auto-fill values)? Or are there are special input sources that don't fire keypresses? I'd be worried that there would be something like this that you would miss.
Dustin Boswell
A: 

you can see this example and choose which are the events that interest you:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"&gt;&lt;/script&gt; 
<title>evetns</title>
</head>
<body>
<form>
    <input class="controlevents" id="i1" type="text" /><br />
    <input class="controlevents" id="i2" type="text" /><br />
    <input class="controlevents" id="i3" type="text" /><br />
    <input class="controlevents" id="i4" type="text" /><br />
    <input class="controlevents" id="i5" type="text" /><br />
</form>
<div id="datatext"></div>
</body>
</html>
<script>
$(function(){

function testingevent(ev){
 if (ev.currentTarget.tagName=="INPUT")
  $("#datatext").append("<div>id : " + ev.currentTarget.id + ", tag: " + ev.currentTarget.tagName + ", type: "+ ev.type +"</div>");
} 

    var eventlist = ["resizeend","rowenter","dragleave","beforepaste","dragover","beforecopy","page","beforeactivate","beforeeditfocus","controlselect","blur",
                    "beforedeactivate","keydown","dragstart","scroll","propertychange","dragenter","rowsinserted","mouseup","contextmenu","beforeupdate",
                    "readystatechange","mouseenter","resize","copy","selectstart","move","dragend","rowexit","activate","focus","focusin","mouseover","cut",
        "mousemove","focusout","filterchange","drop","blclick","rowsdelete","keypress","losecapture","deactivate","datasetchanged","dataavailable",
        "afterupdate","mousewheel","keyup","movestart","mouseout","moveend","cellchange","layoutcomplete","help","errorupdate","mousedown","paste",
        "mouseleave","click","drag","resizestart","datasetcomplete","beforecut","change","error","abort","load","select"];

    var inputs = $(".controlevents");

    $.each(eventlist, function(i, el){
     inputs.bind(el, testingevent);
    });

});
</script>
andres descalzo
I think you forgot an event type :)
Dustin Boswell
Re `eventlist`: nope, nope, nope, nope, nope, nope, nope, nope, nope, nope, nope,nope, yes, nope, nope, yes, nope, nope, nope, nope, nope,nope, nope, nope, nope, nope, nope, nope, nope, nope, nope, nope, nope, maybe, nope, nope, nope, not sure, wtf?, nope, yes, nope, nope, nope, nope, nope, nope, sure, nope, nope, nope, nope, nope, nope, nope, nope, yes,nope, nope, nope, nope, nope, nope, definitely, nope, nope, nope, nope. But where's `oninput`?
Crescent Fresh
+3  A: 

Unfortunately, I think setInterval wins the prize:

<input type=text id=input_id />
<script>
setInterval(function() { ObserveInputValue($('#input_id').val()); }, 100);
</script>

It's the cleanest solution, at only 1 line of code. It's also the most robust, since you don't have to worry about all the different events/ways an input can get a value.

The downsides of using 'setInterval' don't seem to apply in this case:

  • The 100ms latency? For many applications, 100ms is fast enough.
  • Added load on the browser? In general, adding lots of heavy-weight setIntervals on your page is bad. But in this particular case, the added page load is undetectable.
  • It doesn't scale to many inputs? Most pages don't have more than a handful of inputs, which you can sniff all in the same setInterval.
Dustin Boswell
+2  A: 

This jQuery code catches immediate changes to any element, and should work across all browsers:

 $('.myElements').each(function() {
   // Save current value of element
   $(this).data('oldVal', $(this));

   // Look for changes in the value
   $(this).bind("propertychange keyup input paste", function(event){
      // If value has changed...
      if ($(this).data('oldVal') != $(this).val()) {
       // Updated stored value
       $(this).data('oldVal', $(this).val());

       // Do action
       ....
     }
   });
 });
phatmann
A: 

I may be late to the party here but can you not just use the .change() event that jQuery provides.

You should be able to do something like ...

$(#CONTROLID).change(function(){
    do your stuff here ...
});

You could always bind it to a list of controls with something like ...

var flds = $("input, textarea", window.document);

flds.live('change keyup', function() {
    do your code here ...
});

The live binder ensures that all elements that exist on the page now and in the future are handled.

The Great Gonzo