3

Some background: I have a site with a table displaying a list of entities. Using jQuery I make it possible to click on a row in the table which will load some extra data about the selected entity using AJAX. A new row is inserted into the table below the clicked row. This row contains more detailed information about the entity and is not really a row, just in HTML terms. It contains a whole bunch of data. When you click on another row, the details row currently shown will be hidden and the details row for the newly clicked entity is shown (loaded using AJAX if not loaded yet). No problems here.

On the details row there is also a select box. After loading the details row using Ajax, I want to bind the onchange event of the select. I do the following jQuery selection:

$("tr.entity_details[data-entity-id=" + entityId + "] select#SelectedDropDownValue")

The problem: This also works fine, except in some very particular case (which I havn't been able to completely define yet). Sometimes it doesn't select the select element. I use VS2010 to debug and entered these two items into my watch window:

$("tr.entity_details[data-entity-id=" + entityId + "] select#SelectedDropDownValue").length
$("tr.entity_details[data-entity-id=" + entityId + "] select[id=SelectedDropDownValue]").length

In the cases where the first one doesn't select anything, the second one does. This is really weird to me, because I thought that those two selectors are equivalent.

The situation when I could produce this is: I have two entities in my table.

  • I click on the bottom one first: no problem; then I click on the top one: no problem.
  • I click on the top one first: no problem; then I click on the bottom one: PROBLEM.

I have no idea what could cause this. I'm thinking maybe something with me inserting new rows and hiding them in javascript, causing the selection to go a bit off. But that would be a bug I suppose. Or maybe it has something to do with that the details row contains another table and the select is in side that inner table.

EDIT:

To add some more information. I tested this under IE9 and Chrome. I am developing an ASP.NET MVC 3 application using jQuery 1.5.1. The details row is generated by a partial view. In this partial view I use the Html Helper DropDownListFor. This helper generates the select element based on a C# property you pass to the function. It uses the property's name to generate a name and id for the element. This causes all the select elements in the page to have the same id. As some people already mentioned, that would probably be the cause.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Matthijs Wessels
  • 6,530
  • 8
  • 60
  • 103

3 Answers3

4

I work on a site that is, well, not compliant. We've got many HTML elements that have the same ID. I've noticed that $('#someid') grabs only the first element it finds with that ID while $('[id="someid"]') grabs all elements with that ID.

Xyan Ewing
  • 902
  • 1
  • 8
  • 11
  • 3
    @Mully, you should only have **ONE** element per ID, the ID is meant to be unique. – Naftali May 05 '11 at 15:31
  • Hmm, I think the select elements in all the "details" rows all get the same id. I'll add some more background info about what I use etc. to the question. – Matthijs Wessels May 05 '11 at 15:37
  • 1
    @Neal I think that's what he meant by "not compliant" haha – Nic May 05 '11 at 15:44
  • I would think that because I add `tr.entity_details[data-entity-id=" + entityId + "]"` to the first part of the selector that within that part there is only one element with that ID. But I guess I should just select on something else. Maybe add a class or select on the name attribute (also generated by ASP.NET MVC). – Matthijs Wessels May 05 '11 at 15:51
  • 3
    The reason why `#someid` isn't the same as `[id="someid"]` is because one's an ID selector that gets fed to `document.getElementById()` while the other's an attribute selector that gets fed to `querySelectorAll` (or Sizzle if it's not supported). – BoltClock May 05 '11 at 16:21
  • @BoltClock, so adding a prefix (like I did) to a selection by id (with `#`) is pointless then? – Matthijs Wessels May 05 '11 at 16:26
  • @Matthijs Wessels: In most cases it is. – BoltClock May 05 '11 at 16:28
  • @Neal, yeah, I know it's supposed to be unique. I'm working with inherited code. :/ – Xyan Ewing May 05 '11 at 18:08
  • I just found this question again, and decided I'd post my own answer to expand on the topic at hand. (cc @Matthijs Wessels) – BoltClock Jan 22 '13 at 07:19
3

When encountering a selector string containing only an ID selector, like $('#my-id'), jQuery feeds that ID directly to document.getElementById() to grab the first element with that ID, without any additional steps. This is a well-known and well-documented fact.

If there's anything else in the selector at all, jQuery skips the document.getElementById() call entirely. If the browser supports document.querySelectorAll() and the selector is a supported CSS selector, then jQuery will use that API to query the DOM without ever using its own engine Sizzle. Otherwise, it defers to Sizzle.

Now, as far as CSS selectors are concerned, an ID selector should always match element so long as it is identified by that ID, regardless of whether HTML says that it's invalid to have more than one such element in the same document. See this answer for an explanation. I am not entirely sure if Sizzle behaves the same way, but it is very likely that a native implementation would behave as such. Of course, since the markup would have to be invalid for it to make a difference, this is not something you should rely on.

An attribute selector like $('[id=my-id]'), on the other hand, does not carry the same semantics as an ID selector. It does not mean "the (or any) element that is identified by my-id"; rather, it simply selects any element with an attribute that happens to be called id, with the value my-id. So in jQuery and CSS, and regardless of document semantics, it always matches any element so long as it has that attribute and value.

That said, since you're working with HTML and HTML says not to identify multiple elements with the same ID, then as mentioned in the comments undefined behavior can occur when you do. In such a circumstance you may be able to get away with using an attribute selector which guarantees as many matches as possible, but of course I wouldn't recommend going down that route.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
0

IDs are often abused. If you are injecting new DOM nodes with JavaScript, there's probably a moment when you have the node inside a variable. You can simply attach the event handler to the variable; there is no need to assign an ID, inject the node into the document and then search for it by ID.

Álvaro González
  • 142,137
  • 41
  • 261
  • 360
  • Just updated the question with some more background. I use ASP.NET MVC to generate the html server-side. The ajax call receives the html and just inserts it after the clicked row. – Matthijs Wessels May 05 '11 at 15:46