2

Posting this here for discussion before filing a bug with JQuery.

Consider this fiddle:

http://jsfiddle.net/tWPS6/2/

I set the value attribute of one input in HTML, the other using Javascript. I then ask JQuery to select empty inputs (inputs with value == ""). It returns a length of 1 (and the one input in it is #input2).

It's as though this empty attribute selector is selecting based on the original value of the input tags, in the original HTML, rather than the current value, even though any other means of checking the value attribute (like .value or .val()) will show me it's not in fact empty.

This appears to be a bug in JQuery. Correct?

Chris Moschini
  • 36,764
  • 19
  • 160
  • 190

2 Answers2

4

First and foremost:

This is not a bug in jQuery.

There is a significant difference between an element attribute:

var attr = element.getAttribute('foo');

and an element property:

var prop = element.foo;

This answer lays it out very nicely — the short version is that attribute vs. property is messy, and browsers haven't always gotten it right, but you almost always want to use the property, not the attribute when working with the DOM.

Forget about attributes. You nearly always want a property, not an attribute. Where both a property and an attribute with the same name exists, the property always represents the current state while the attribute (in most browsers) represents the initial state. This affects properties such as the value property of <input> elements.


If you now understand the attribute cluster!@#$, you should hopefully see where this is going.

The selector 'input[value=""]' is an attribute selector. That means it's not a property selector. In both rounds in your example, $('input[value=""]') matches only <input id="input2" value=""/>, since that element (and only that element) is the one that:

  • Has a value attribute, and
  • That value attribute is equal to the empty string.

.val() sets the <input> element's value property, not the value attribute. The attribute selector matches, as the name implies, the element's attribute, so of course it's not going to select anything different if the attribute hasn't changed.

Additionally, jQuery does all sorts of fancy stuff under the hood which:

  • Provides consistent behavior across browsers, and
  • Provides behavior consistent with the documentation, and
  • Especially with .attr() (which has quite the checkered past) can be really ugly.

Oh, and one last link: John Resig on jQuery 1.6 and .attr(). Read it.

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • I have in fact read .attr() in the past. So are you suggesting that all code setting the value of inputs in my code should be setting it with .attr() instead of .value and .val()? That's a serious rewrite - and probably a bit harmful to code size. – Chris Moschini Jul 29 '11 at 03:18
  • No, all the code setting the value should stick to `.val()`. You cannot _select_ by value using an _attribute_ selector, so that's what you'd change. – Matt Ball Jul 29 '11 at 03:20
  • Yes, just trying to find the cheapest codepath (at this point simply because it seems there should be one). It seems there ought to be a $(selector).has(function(tag) { return ; } in JQuery to handle these sorts of common cases. Thanks for the answer! – Chris Moschini Jul 29 '11 at 03:28
  • That _does_ exist. It's called [`.filter()`](http://api.jquery.com/filter). I was just hunting for a previous answer of mine... ***edit*** ah, [here it is](http://stackoverflow.com/questions/6413450/jquery-selector-on-dynamic-input-value/6413542#6413542) – Matt Ball Jul 29 '11 at 03:36
  • I see. Why call one grep and the other filter, when they do the same thing? Also, I'm proposing something slightly different - .has() would return true immediately as soon as it finds even one match for the function (and return false if it reaches the end); both grep() and filter() run across the entire collection no matter what, and return an array. – Chris Moschini Jul 29 '11 at 03:51
  • 1
    `$.grep()` is similar to, though not exactly the same as, `.filter()`. `$.grep()` is poorly named, though, since it has nothing to do with regular expressions. – Matt Ball Jul 29 '11 at 03:57
  • If you're only looking for a single result from filter, you can use `.each()` instead, and `return false` as soon as you've found your match in order to break the iteration. It's syntactically clunkier, though. – Matt Ball Jul 29 '11 at 04:00
0

try giving this a shot:

var emptyInputsRound1 = $('input[value=""]');
$('#input2').attr("value",'hi2'); //notice change
var emptyInputsRound2 = $('input[value=""]');
$('#output').text(emptyInputsRound1.length + ', ' + emptyInputsRound2.length);

working example: http://jsfiddle.net/fSaQh/

Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • But then, when it's set like so: http://jsfiddle.net/fSaQh/1/ Back to the strange result. This really seems like a bug in JQuery. – Chris Moschini Jul 29 '11 at 00:51
  • It is pretty odd, it seems like .val() may just affect the jquery object wrapping your element and not the actual dom, while attr() changes the dom, I'm anxious to see what some jquery experts have to say about this – Kevin Bowersox Jul 29 '11 at 00:56