<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src=".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=".6.3/jquery.min.js"></script>
<script src=".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.
<!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.
- 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. – user1385191 Commented Sep 10, 2011 at 16:32
- Closely related: stackoverflow.com/questions/938083/… . Answers there will also explain the spec. – Ciro Santilli OurBigBook.com Commented Jul 20, 2014 at 11:33
6 Answers
Reset to default 7It'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 );
});
});
Always write semicolons at the end of statements. Don't rely on the JS engine write them for you!!! (see above).
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 theid()
method to retrieve the id.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 tablestd > input[id]
like gilly3 suggested.
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.
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/
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"]')
.
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]')
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);
}