14
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
  <script type="text/javascript">
    $( document ).ready( function(){
      $( "table > tr > td > input[id]" ).each( function( i, element ){ 
        alert( $( element ).attr( 'id' ) ) 
      });
    });
  </script>
</head>
<body>
  <form>
    <table>
      <tr><td>City:</td><td><input type="text" id="city" name="city" /></td></tr>
      <tr><td>state:</td><td><input type="text" id="state" name="state" /></td></tr>
    </table><br />
    <input type="submit" value="OK"/>
  </form>
</body>
</html>

When I write it this way, it doesn’t work because my browser automatically creates a <tbody> tag. So I have to write:

$( "table tr > td > input[id]" ).each( function( i, element ){ 
  alert( $( element ).attr( 'id' ) ) 
});

or:

$( "table > tbody > tr > td > input[id]" ).each( function( i, element ){ 
  alert( $( element ).attr( 'id' ) ) 
});

Can I rely on the implicit creation of the <tbody> tag, or should I not count on that?

Edit: added to explain my comment to Tim Down’s answer:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.js"></script>
  <script type="text/javascript">
    $( document ).ready( function() {
      var ids = [];
      var form = document.forms[0];
      var formEls = form.elements;
      var f_len = formEls.length;

      for ( var i = 0; i < f_len; ++i ) {
        ids.push( formEls[i].id );
      }

      var data = [ [ 'one', 'two', 'thre' ], [ 'four', 'five', 'six' ] ];
      var ids_len = ids.length;

      for ( i = 0; i < ids_len; i++ ){
        $( "#" + ids[i] ).autocomplete({
          source: data[i]
        });
      }
    });
  </script>
</head>
<body>
  <form>
    <table>
      <tr><td>A:</td><td><input type="text" id="a" name="a" /></td></tr>
      <tr><td>B:</td><td><input type="text" id="b" name="b" /></td></tr>
    </table><br />
    <input type="submit" value="OK"/>
  </form>
</body>
</html>

When I run this, the web console shows me a warning like this: Empty string to getElementById() is passed. One of the strings returned by form.elements is empty.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
sid_com
  • 24,137
  • 26
  • 96
  • 187
  • 3
    You'd save yourself a lot of headaches by simply adding an id to the form and then looping through the HTMLFormElement.elements collection. Please note that hideous queries like those in your post are very slow and problematic. –  Sep 10 '11 at 16:32
  • Closely related: http://stackoverflow.com/questions/938083/why-do-browsers-insert-tbody-element-into-table-elements . Answers there will also explain the spec. – Ciro Santilli OurBigBook.com Jul 20 '14 at 11:33

6 Answers6

7

It's not a question on relying on it being automatically created or not.

The question is if it's mandatory or not.

According to the HTML5 draft:

A tbody element's start tag may be omitted if the first thing inside the tbody element is a tr element, and if the element is not immediately preceded by a tbody thead, or tfoot element whose end tag has been omitted.

A tbody element’s end tag may be omitted if the tbody element is immediately followed by a tbody or tfoot element, or if there is no more content in the parent element.

So you can actually omit it if your code met the above conditions, otherwise it is needed.

As other people pointed out, even if it is needed, and the html parser won't find it because you didn't write it, it will be inserted into the DOM for you, as stated in the html5 specs.

This said, as a rule of thumb, never rely on anyone creating something automatically for you! (see below)

So even if the browser will create it for you, this doesn't mean newer browsers or new version of the same browser will follow the same way, and your code may become broken then.


Also, your JS could be optimized.

$( document ).ready( function(){
    $( "td > input[id]" ).each( function( i, element ){ 
        alert( element.id );
    });
});
  1. Always write semicolons at the end of statements. Don't rely on the JS engine write them for you!!! (see above).

  2. No need to call the jQuery function and create a jQuery object out of element just to call the attr() method to get the id. JavaScript already has the id() method to retrieve the id.

  3. If your actual markup is like the one you posted in your answer, you could write the jQuery selector like this: table input[id]. Or, if you have nested tables td > input[id] like gilly3 suggested.

Jose Faeti
  • 12,126
  • 5
  • 38
  • 52
  • Re point 3 - consider nested tables. Of course, in the posted example nested tables would be selected as well, but the actual code may have a stronger selector on the table. If nested tables are of no concern, you may want `td > input[id]` To make sure the input is not nested in a div or contained in a th. – gilly3 Sep 10 '11 at 15:25
  • @gilly3: exactly, that's why I didn't write the modification in the code I posted, but just hinted it, because it depends on specific cases. – Jose Faeti Sep 10 '11 at 15:27
  • Re your main answer - The crux of your argument is upon what is valid HTML, but jQuery acts on the DOM, not the markup. The documentation seems to indicate that if a `` is missing, one should be created implicitly. See Duri's answer. – gilly3 Sep 10 '11 at 15:28
  • @gilly3 not quite right. I'm not talking about what is valid html or not. I just reported what the current draft states. Also, relying on the browser doing something for you is always a bad choice. Did you know that the [html tag can be omitted](http://dev.w3.org/html5/markup/html.html#html-tags)? – Jose Faeti Sep 10 '11 at 15:35
  • 1
    The tbody tag can be omitted in the source (as with the html tag), but the parsing algorithm ensures that it is created in the DOM (see [duri's answer](http://stackoverflow.com/questions/7372243/can-i-rely-on-the-implicit-creation-of-the-tbody-tag/7372437#7372437)). So your answer is incorrect, since the jQuery selector acts on the DOM, not on the source. – mercator Sep 10 '11 at 18:28
  • 1
    Thanks, it's clearer now that you're really answering to *other*, but very relevant, questions: "Is the tbody tag required?" and "Is it a good idea to rely on its implicity creation?" – mercator Sep 10 '11 at 18:56
5

I disagree with @spike's answer, at least if we talk about ordinary HTML parsing (not using innerHTML). Every tr becomes a child of implicitly created tbody unless it's already a child of another thead, tbody or tfoot. jQuery.support.tbody is for tables created using innerHTML or probably another DOM methods or so. If you omit <tbody> in the markup, it's always inserted.

The tbody element is not optional, it just has optional both opening and closing tag. To doubt about implicit tbody creation is similar mistake as to doubt about implicit creation of html or body element.

To prove what I said, the HTML4 specification forbids any <tr> elements as a direct childs of <table>s:

<!ELEMENT TABLE - -
 (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)>

HTML5 specification says that <tr> can be, under certain circumstances, a child of <table>, however this applies only to DOM, not to markup parsing:

8.2.5.4.9 The "in table" insertion mode

...

A start tag whose tag name is one of: "td", "th", "tr"

Act as if a start tag token with the tag name "tbody" had been seen, then reprocess the current token.

duri
  • 14,991
  • 3
  • 44
  • 49
  • "tbody element is not optional" Well, it is. At least it is not required via the spec, as a table may have `tr` as childs, without a wrapping `tbody`. ([HTML4·](http://www.w3.org/TR/html4/struct/tables.html#h-11.2.1); [HTML5](http://www.w3.org/TR/html5/tabular-data.html#the-table-element)) – feeela Sep 10 '11 at 14:57
  • @feeela This only applies to DOM, not to HTML parsing. I've edited my answer and quoted relevant parts of specifications. OP's question is obviously related to markup parsing. – duri Sep 10 '11 at 15:08
  • This is an interesting point, and I'd love to see some documentation on it. Will the DOM always correspond to the HTML? Ie, could it mean that omitting an optional HTML element is just shortcut syntax for having the browser create the DOM element automatically? – gilly3 Sep 10 '11 at 15:16
  • Wow, you addressed my comment before I finished typing it. Nicely done! This implies that the correct answer is yes, you can rely on the browser creating a `` for you. – gilly3 Sep 10 '11 at 15:19
  • 1
    @gilly3 Yes, in case of `` you can. However, there's at least one case where the reliance on implicit creation of opening or closing tags is not safe: the `

    ` element has optional closing tag and HTML4 and HTML5 specs specify the list of elements that implicitly close `

    `; one of such elements is the `

    `. This means that `

    ` should mean `

    ` and the table should match the CSS selector `p + table`. However, at least in Firefox' quirks mode (not the standards mode) the behaviour is exactly the opposite and table matches `p > table` selector.
    – duri Sep 10 '11 at 15:29
  • "HTML5 specification says that `` can be... a child of ``, however this applies only to DOM, not to markup parsing:" Not quite. When you parse `text/html`, a tbody will always be added as per the parser requirement you quote. But the HTML5 content model also covers using an XML parser as with `application/xhtml+xml` There, the `` will become a child of `
    ` without any `` being inferred. The content model simply says that is valid.
    – Alohci Sep 10 '11 at 15:57
3

You can't rely on the browser automatically creating it. The HTML spec says that it should be optional, though I believe that Firefox and IE create it as you saw. You can use this property to find out how the browser behaves (true means it won't be added)

jQuery.support.tbody

Check out this example in a bunch of browsers: http://jsfiddle.net/CuBX9/1/

http://api.jquery.com/jQuery.support/

spike
  • 9,794
  • 9
  • 54
  • 85
3

To defend against the optional nature of the tbody tag (and by extension whatever the browser decides to do with its selector engine), you could write both selectors out:

$('table > tbody > tr > td, table > tr > td').find('input[type="text"]')

But that's honestly a bit of a hack. You'd be better off explicitly adding the <tbody> element and being done with it.

Alternatively, consider why you're even using the child combinator at all.

In your example, I don't know why you'd need to use such a complicated selector. You're not nesting tables (so no need to use child combinator), and you only have two text inputs. If all you want are the two text input fields, just use $('input[type="text"]').

Chris
  • 9,994
  • 3
  • 29
  • 31
  • This is really unnecessary and error prone. Please see the answer of tvanfosson. – feeela Sep 10 '11 at 14:58
  • 2
    I understand tvanfosson's answer, but that doesn't explain why you seem to think my answer is error-prone. – Chris Sep 10 '11 at 15:07
  • @feeela This method is less error-prone than tvanfossons' one. By using the descendant selector instead of a child selector, you also allow to match `` elements of tables inside the current table. – Rob W Feb 03 '12 at 13:43
1

You could just use the descendant selector instead of the parent selector, or some combination if it's important that the input be a child of a td. That way it wouldn't matter. Conversely, you could also just put the tbody elements in and use the full parent/child chain without worry.

$('table td > input[id]') 
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
1

If you're trying to get hold of all inputs within the <form> element that contains the table, this is the wrong approach, since there is a simple DOM approach that has existed since the dawn of JavaScript and works in all major scriptable browsers ever released. A form element has an elements property which is a collection of all the form controls within the form. All you need is to get hold of the form, which you can do in whichever way is most appropriate for you:

var form = document.forms[0];
var formEls = form.elements;
for (var i = 0, len = formEls.length; i < len; ++i) {
    alert(formEls[i].id);
}
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • Is it writing this way `for (var i = 0; i < formEls.length; ++i) {` slower? – sid_com Sep 11 '11 at 06:58
  • This way I get a void string and warnings if I for example process the `data` from a `data.push( formEls[i].id)` (instead alerting with `alert(formEls[i].id)`). – sid_com Sep 11 '11 at 07:32
  • @sid_com: Accessing the `elements` collection's `length` property only once and storing it in a variable is likely to be slightly faster than accessing it every iteration, but it will be a tiny gain in the context of a normal script. I don't understand what you're saying in your second comment though. Could you elaborate? – Tim Down Sep 11 '11 at 10:00
  • Added an example to my question. – sid_com Sep 11 '11 at 10:36
  • @sid_com: Possibly the problem is that the `elements` collection also contains the submit button, which has no ID. You could filter elements by type: `if (formEls[i].type == "text") {...}` – Tim Down Sep 11 '11 at 16:09