0

I have a plain <input> element.

I would expect document.querySelectorAll('input[type="text"]') to retrieve (at least) that input, since the default input type is text.

But querySelectorAll does not return that input unless I explicitly set type="text".

I have two questions:

  1. Why does querySelectorAll ignore the input with an implicit type="text"?
  2. Is there a query to target an input whose implicit type is text that excludes all inputs whose types are not text?

Here is an example document illustrating the question:

var output = document.getElementById('output');

var textInputs = document.querySelectorAll('input[type="text"]');

output.innerText = 'input[type="text"] - ' + textInputs.length + ' elements';

var inputs = document.querySelectorAll('input');

output.innerText += '\ninput - ' + inputs.length + ' elements';

output.innerText += '\n\nFirst input type: ' + inputs[0].type;
#output {
  font-family: monospace;
}
<form>
  <input>
  <input type="text">
  <div id="output"></div>
</form>

And here is the JavaScript output in my browser (Chrome):

input[type="text"] - 1 elements
input - 2 elements

First input type: text
crenshaw-dev
  • 7,504
  • 3
  • 45
  • 81
  • 3
    Because it's an attribute equals selector. If the attribute doesn't exist, it doesn't equal `"text"`. – Kevin B May 24 '18 at 15:16
  • @Nope yeah. To be fair, the type _is_ `text` by default... Seems like this is just an eccentricity of `querySelectorAll` which I wasn't expecting. – crenshaw-dev May 24 '18 at 15:19
  • https://stackoverflow.com/questions/1533444/css-selector-to-match-an-element-without-attribute-x – epascarello May 24 '18 at 15:24
  • `document.querySelectorAll('input:not([type]), input[type=text]')` –  May 24 '18 at 15:25

3 Answers3

2

Attribute selectors don't take invalid or missing value defaults into account. They will only match when the attribute has explicitly been specified in the markup.

You can still account for the missing value default using input:not([type]), since an input element without a type attribute is guaranteed, by its missing value default, to be a text input (unless mutated by scripts).

var output = document.getElementById('output');

var textInputs = document.querySelectorAll('input:not([type]), input[type=text]');

output.innerText = 'input[type="text"] - ' + textInputs.length + ' elements';

var inputs = document.querySelectorAll('input');

output.innerText += '\ninput - ' + inputs.length + ' elements';

output.innerText += '\n\nFirst input type: ' + inputs[0].type;
#output { 
  font-family: monospace; 
}
<form>
    <input>
    <input type="text">
    <input type="number">
    <div id="output"></div>
</form>
crenshaw-dev
  • 7,504
  • 3
  • 45
  • 81
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Thanks for the clarification. Do you know of any documentation for this behavior (specifically, not `querySelectorAll` generically), and are you aware of an alternative query which will satisfy the requirement in question part 2? – crenshaw-dev May 24 '18 at 15:21
  • 1
    @mac9416: Unfortunately this isn't spelled out in either the HTML or Selectors specs. The closest I can find is [§2.4.3 of W3C HTML5](https://www.w3.org/TR/html5/infrastructure.html#keywords-and-enumerated-attributes) which describes the missing value default as what's used when the attribute is "missing" or "absent", and [the Selectors spec](https://www.w3.org/TR/selectors-3/#attribute-selectors) simply describing attribute selectors as matching based on the presence of attributes. You'll have to connect the dots from there. – BoltClock May 24 '18 at 15:30
  • @mac9416: Hmm, I didn't notice your suggested edit there. Sorry about that. – BoltClock May 24 '18 at 15:31
1

Why does querySelectorAll ignore the input with an implicit type="text"?

Since it is implicit, the attribute does not exist on the element, so the attribute selector doesn't match it.

Is there a query to target an input whose implicit type is text that excludes all inputs whose types are not text?

Match elements with no type attribute at all.

console.log(
  document.querySelectorAll("input[type=text], input:not([type])").length,
  "matching elements"
);
input[type=text],
input:not([type]) {
  background: blue;
}
<ol>
  <li><input></li>
  <li><input type="text"></li>
  <li><input type="number"></li>
</ol>
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

Here are some answers:

  1. As already mentioned by many, querySelectorAll needs the type to be explicit to match it.

  2. You may want to use the code in the snippet to "filter" the result of the querySelectorAll.

Third answer:
Using .setAttribute("type", "text") to explicitly add the attribute on the elements without it

// Add 'type="text"' on the ones which don't have it
document.querySelectorAll('input:not([type])').forEach(function(elm){
  elm.setAttribute("type", "text");
});

// Output
var inputs = document.querySelectorAll('input[type="text"]');
document.getElementById('output').innerText += 'input[type="text"] - ' + inputs.length + ' elements';
<form><!-- Added other input types -->
  <input>
  <input type="text">
  <input type="number">
  <input type="date">
  <div id="output"></div>
  <script>
  </script>
</form>

⋅ ⋅ ⋅

Second answer (others were faster):
Adding input:not([type]) in the querySelectorAll

var output = document.getElementById('output');
var inputs = document.querySelectorAll('input[type="text"], input:not([type])');

output.innerText += 'input[type="text"] - ' + inputs.length + ' elements';
<form><!-- Added other input types -->
  <input>
  <input type="text">
  <input type="number">
  <input type="date">
  <div id="output"></div>
  <script>
  </script>
</form>

⋅ ⋅ ⋅

First answer (not worth it):
Using a loop to check each element returned by the querySelectorAll

// Variables
var output = document.getElementById('output');
var inputs = document.querySelectorAll('input');
var textInputs = [],
  j = 0;

// Loop
for (i = 0; i < inputs.length; i++) {
  if (inputs[i].type == 'text') {
    textInputs[j] = inputs[i];
    j++
  }
}

// Output
output.innerText = 'input[type="text"] - ' + textInputs.length + ' elements';
<form><!-- Added other input types -->
  <input>
  <input type="text">
  <input type="number">
  <input type="date">
  <div id="output"></div>
  <script>
  </script>
</form>

Hope it helps.

Takit Isy
  • 9,688
  • 3
  • 23
  • 47