Javascript document.getElementById() with multiple elements - javascript

How does document.getElementById() handle it when there are multiple elements with the same ID? MDN simply says:
Returns a reference to the element by its ID.
In the "Notes" section of the documentation, where I would expect a mention of what should happen, all that was stated is:
If there is no element with the given id, this function returns null.
So I decided to find out myself, and I did a test in Chrome's dev console. Here's what I got:
> document.write('<div id="myid"></div>')
> document.getElementById("myid")
<div id="myid"></div>
> document.write('<span id="myid"></span>')
> document.getElementById("myid")
<div id="myid"></div>
It appears that it only returns the first value- could someone clarify that?

It will return the first element with the ID in the document because of the way the code runs.
Logically it is illegal for there to be two elements with the same id. So why look for another element with the same id as the one that is already found, there should only be one element with this ID. Most browsers scan the document from the top down, so the first element with a matching ID is returned.

I can't speak for all browsers, but I've been able to use the same ID multiple times only if they are a child of a uniquely ID'd item:
<div id="parent1">
<div id="item"></div>
</div>
<div id="parent2">
<div id="item"></div>
</div>
I could always access each individually if I included their parent name (using jQuery)
$("#parent1 #item") and $("#parent2 #item")

Different browsers may return different things because having duplicate IDs is not valid HTML in the first place!
This is undefined behavior.

MDN doesn't say what happens because that's illegal in the first place.
IDs must be unique.

Related

How can I direct Javascript to access the second element with a non-unique ID

I am trying to declare a paired list in my automation framework, and to do so I pass in two parameters of that list. The first parameter is the DOM id of the "Available" items list, while the second is the DOM id of the "Selected" items list.
var pairedList: newPairedList( "availableItemsListID" , "selectedItemsListID");
In the specific case I'm working on, both the availableItemsListID and the selectedItemsListID happen to have the same ID in the DOM.
The both ids are 'x-fieldset-bwrap', and I have tried the following to indicate the availableItemsListID is the first instance of the id, and the selectedItemsListID is the second instance of the id:
var pairedList: newPairedList( "/x-fieldset-bwrap/[0]" , "/x-fieldset-bwrap/[1]");
It seems to find the availableItemsList however when it attempts to get the selectedItemsList it fails. Does anyone have any suggestions on how to best handle the problem?
Thanks!
You can use document.querySelectorAll to select all elements that match a CSS selector.
document.querySelectorAll("#x-fieldset-bwrap") will match all elements with a id of x-fieldset-bwrap.
If you have the ability to change your system so that it does not generate elements with repeated IDs concurrently within the DOM, I would highly encourage you to do so.
If you can distinguish the first element from the second one somehow, then you can use that difference in your selection. See below:
function display($div) {
console.log($div.html());
}
display($('#unique'));
$('#unique').addClass('firstUnique');
display($('#unique:not(.firstUnique)'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="unique">One</div>
<div id="unique">Two</div>

Find the first id of any descendant using jQuery

For draggable divs in JS on my page, I want to store the last location in local storage so that when a user refreshes, the draggable elements on the page stay put.
My HTML is in general like this:
<div id="some_id" class="draggable">
<p>I am a draggable thing.</p>
</div>
I then use the id of the div as a key in local storage so that having multiple draggable objects on the page doesn't result in them all being given the same position on refresh.
However, templates like this are sometimes used inside a template which handles visibility, so sometimes they'll be like this:
<div class="visibility_container draggable">
<button class="close_button">Close</button>
<div id="some_id">
<p>I am a draggable thing.</p>
</div>
</div>
Note that the draggable class is added programmatically each time.
These templates may vary but will never have ids within them – they'd be pretty terrible templates if they did – so I only need to find the first descendant element which has an id and use the value of that id as my local storage key.
How can I find the nearest element with JS? I'm aware that jQuery has a .closest() method which finds the nearest ancestor – I need to go in the opposite direction. I'm also aware of jQuery's .find() which can find me all descendants matching a selector, but I'm unsure I can guarantee the order in which jQuery returns these children as the API docs were not clear on that point.
I'm also aware of jQuery's .find() which can find me all descendants matching a selector, but I'm unsure I can guarantee the order in which jQuery returns these children as the API docs were not clear on that point.
find lists elements in document order (aka "DOM order") (and you're right, I'm surprised not to see a clear statement of that in the docs). "Document order" is a well-defined DOM term, it means a depth-first search of the descendant elements. Or put it another way: The one whose text is first in the markup.
So for instance:
<div id="container">
<div>
<div>
<div id="one"></div>
</div>
<div id="two"></div>
</div>
...then
console.log($("#container").find("[id]").first().attr("id"));
...will log one, not two.
This document order thing is common across most of the jQuery API and DOM methods like querySelectorAll, getElementsByTagName, and such. I'm not having any luck finding a clear statement of it in the jQuery documentation, though, which seems like an oversight. The closest I've found so far is a bit documenting an exception to that (e.g., saying here that "The second and third...create a jQuery object using one or more DOM elements that were already selected in some other way...unlike most other multi-element jQuery operations, the elements are not sorted in DOM order." [my emphasis].) The multiple selector docs also point out that the results will be in document order (not the order of the selectors).

Complex JavaScript DOM query

Consider this markup:
<div data-something="something">
<div>
<div>
<div data-something="something">
<div data-something="something"></div>
</div>
</div>
</div>
</div>
<div data-something="something"></div>
I would like to query (not necessarily but preferably with a single query) all the elements that do not have any children with the "data-something" attribute. In other words, I would like to query all top level elements that do have this attribute. Please note that all this elements are not necessarily immediate descendants of the root element.
So the query should in this case produce two elements. The first one and the last one. Both two children of the first element should be omitted.
The best approach I could think of took about five seconds to process 300 elements which is completely inadequate and I would rather not even show it :)
Including Sizzle into Dojo worked.

JQuery "#id.class" Selector Mashup

I have some bullet points which I want to show more text below them on clicking them. They are both two separate Ps that are paired together by sharing a common id. So, what I am trying to do below is to find the element with (id_same_as_this.class), so that the element with the class "expand" as well as the id that matches the clicked on P is toggled. Does that make sense?
$(document).ready(function(){
$(".expandable").click(function(){
$(this.attr('id')+"."+"expand").toggle(800);
});
});
I only ask if the above code could be made to work because it would make the expandable bullet points in my web page significantly less code intensive than a lot of the examples I have read about.
$(this.attr('id')+"."+"expand").toggle(800);
Must be
$("#" + this.id +".expand").toggle(800);
You missed the # there. That said, you shouldn't ever have a common ID. By definition IDs are meant to be unique. If you have the same ID on multiple elements, while it may work now on the browsers you try, you have no guarantee it won't break in the next rev of jQuery (or Chrome, or Konqueror, or iOS Safari). There's also no reason to do it. You could just use classes or data-* attributes.
Yes this will work but you need a # before the ID
They are both two separate Ps that are paired together by sharing a common id.
IDs are unique. Two elements can't share a common ID, as that defeats the whole purpose of having a unique identifier. JavaScript assumes that you're using valid HTML, so document.getElementById() will return only the first element with a matching id. By using non-unique IDs, things will start breaking in unpredictable ways:
$('#foo').find('.bar') // Won't search past first #foo
$('#foo .bar') // Will search past first #foo in IE8+
Try restructuring your HTML to make this task easier. Maybe you could do something like this:
<ul id="bullets">
<li>
<h2>Title</div>
<div>Text</div>
</li>
</ul>
And then use a simple event handler:
$('#bullets h2').click(function() {
$(this).next().toggle(800);
});
You don't need id values for this at all (which is good, as from the comments on hungerpain's answer, you're using the same id value on more than one element, which is invalid).
Just do this:
$(document).ready(function(){
$(".expandable").click(function(){
$(this).find(".expand").toggle(800);
});
});
That will find the element with the class expand within the expandable that was clicked. No relying on unspecified behavior of selectors.
If you really need that data on the expandable, just put it in a data-* attribute. So instead of this invalid structure:
<!-- INVALID -->
<div id="foo27" class="expandable">
<div class="expand">...</div>
</div>
<div id="foo27" class="expandable">
<div class="expand">...</div>
</div>
Do this
<!-- VALID -->
<div data-id="foo27" class="expandable">
<div class="expand">...</div>
</div>
<div data-id="foo27" class="expandable">
<div class="expand">...</div>
</div>
Use the above code to do the expansion. If you need the value, use .attr("data-id") or .data("id") to get it.

access child DOM object from parent in dotted notation

Is there a way to select a child DOM object by treating it as data member of its parent DOM object? Imagine I have this code:
<div id=div1>
<div id=innerdiv1></div>
<div id=innerdiv2></div>
</div>
<div id=div2>
<div id=innerdiv1></div>
<div id=innerdiv2></div>
</div>
This example won't work in real life because both pairs of child divs have the same id's (innerdiv1, innerdiv2), but that's exactly what bothers me about the "id" thing.
Is there some way in javascript to access a child element as a data member, something like document.getElementById('div1.innerdiv1'), which would return a different object from document.getElementById('div2.innerdiv1').
I can't stand that each id has to be unique throughout the document. It becomes a major issue when you have a lot of code and you accidentally use the same id twice. It makes for really nasty bugs that are difficult to squash.
You can use document.querySelector in modern browsers. document.getElementById is pretty much obsolete.
document.querySelector('#div1 #innerdiv1')
You can use classes for the inner divs instead of ids, which do not need to be unique.
The document object has a getElementById method, but the returned elements do not have this method, and it couldn't take a string that isn't exactly the id of an element [if that's what you want try a library, like jQuery or Pumbaa80's suggestion of document.querySelector].
In some browsers you can try:
document.getElementById('div1').getElementsByTagName('div')[0]
As a side note, try dropping these two html documents into html5.validator.nu or http://validator.w3.org/#validate_by_input
<!DOCTYPE html><head><title>t</title></head><body>
<div id=div1><div id=d1>one</div><div id=d2>two</div>three</div>
<div id=div2><div id=d1>four</div><div id=d2>five</div>six</div>
</body>
Now you can totally avoid using the same id twice by just using classes instead.
<!DOCTYPE html><head><title>t</title></head><body>
<div id=div1><div class=d1>one</div><div class=d2>two</div>three</div>
<div id=div2><div class=d1>four</div><div class=d2>five</div>six</div>
</body>
For the first one, you could use document.querySelector('#div2 #d1') or $('#div2 #d1')
but I don't think the result would be guaranteed across all browsers due to the fact that this should be equivalent to writing document.querySelector('#d1') which you can see returns the first occurring id that matches, or $('#d1') which returns both id matching elements in an array.
And for the second one you could use document.querySelector('#div2 .d1') or $('#div2 .d1') or the other statements for very similar results, except that your html is valid this time. You don't even have to have css that defines d1 and d2 and if you used an attribute like class="d1 mySubHeading" and class="d2 mySubHeading" you could style both with mySubHeading and leave d1 and d2 there purely for selection via these methods.

Categories