Find the first id of any descendant using jQuery - javascript

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).

Related

Do HTML elements have 'hidden indexes' for the DOM?

For example, document.getElementsByClassName("whatever") returns a list of elements, and each element has an index (so the element x is the [3] in that list, for example).Do HTML elements save that index inside the element, somehow? Or they're 'unaware' of their position?
Example of the usage I'd do with that property:
You click an element with class "People", using event.target when onclick. So you want to know which position it has, in the 'People' list. Let's say it's event.target.classNameIndex. So once you know the index, you can do things in JavaScript.
Obviously the simple alternative I can think of this is simply picking event.target and searching it inside the getElementsByClassName list. Or simply giving IDs to all elements. But avoiding that would be nice.
Hope you understand my question. :)
No
The elements are generated either dynamically or statically and are independent from everything done with them after being displayed. There are pure javascript ways of obtaining the index of an element in a array-like structure but they will most likely depend on the use of a element.onClick function and pairing them with other elements via some sort of selector.
No, for lots of reasons.
First of all, you are doing a query on the internal DOM structure, and the DOM tree itself might change immediately after your query. Elements can be added, moved or removed.
Furthermore, two very different queries might have overlapping results. E.g. query 1 might return:
[ <div id="a">, <div id="b"> ]
While query 2 could return:
[ <div id="b">, <div id="c"> ]
(for simplicity I am representing the results as arrays)
In the above, how would the element <div id="b"> know its unique and unchanging "index", given the truly infinite amount of possible queries, not the mention the possibly variable DOM again?

how to reference a child element on a cloned element

I am running some tests on a DOM element,
the result of the tests is one of the element descendants.
for example:
<div id="myelement" class="some-class">
<div class="some-child-class"></div>
<div class="some-other-child-class">
<div class="grandchild-class"></div>
<div class="another-grandchild"></div>*
</div>
</div>
let's assume that:
test(document.getElementById('myelement'));
will return the Element marked with asterisk
Now my problem is:
The test procedure is heavy and resource consuming.
I don't want to run it when i don't have to.
And sometimes, I clone an element that has already been tested (meaning - i KNOW the result of the test), but since I am getting an object reference as a result I can't use it to access the relevant child on the cloned element.
Is there any efficient way of somehow "save" the relative path from a parent Element to a specific descendant DOM element and then "apply" it on another element?
So you could assign unique ids to each element and cache the test results in an Object at the Elements id. However, i dont know if this is useful. An example implementation:
var test=function(el){
return this[el.id] || (this[el.id]=advancedtesting(el));
}.bind({});
So you could do:
test(document.getElementById('myelement'));//caches
test(document.getElementById('myelement'));//returns the cached
You could use jQuery for that.
The selectors they use can take the form of 'nth-child' or 'nth-of-type' (see documentation here).
What is does is that targets child element from the position they have relative from where you start from.
In your case if you want to start from your first element and go down to the starred one you can do:
$('#myelement').find('div:nth-child(2) > div:nth-child(2)')
What this does is that it takes #myelement as a base from which you will begin the search, and after that it goes down to the second child element that is a div, and again into this div's second child element.
You could reuse that selector with a different base.

jQuery - parent() vs closest()

Say I have the following markup:
<div class="parent">
<div class="child">
<div class="grand-child">
And I want to get .parent starting from .grand-child. I can do either of the following two:
$('.grand-child').parent().parent();
$('.grand-child').closest('.parent');
Overall, closest() feels like a nicer approach since:
Only one function
It is irrelevant to other divs between the .grand-child and .parent
Specifically, due to advantage of #2 above, if I were to change my markup to
<div class="parent">
<div class="child">
<div class="nanny">
<div class="grand-child">
Then I need to add an extra parent() but closest() still functions fine.
So is there a reason why you would ever choose parent() or closest()?
you should use $('.grand-child').closest('.parent'); because .parent().parent() is strongly based on your html structure if in future you add another div inside one of these then you will get wrong element using parent().parent()
Suppose you have html like:
<div class="parent">
<div class="child">
<div class="grand-child-container">
<div class="grand-child">
now what will happen if you use .parent().parent() it will give you wrong element, so recommended way is to use closest() as it is much better.
According to docs:
parent() gets the parent of each element in the current set of matched elements, optionally filtered by a selector.
closest() For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
Differences:
if you scroll little down you can see differences between these two by official jquery site.
There are also some more good answers available on differences between these two:
parent vs closest
Difference between jQuery parent(), parents() and closest() functions
Performance:
Also performance wise closest() is better than parent(), you can check Benchmark between closest() and parent()
If you look at the code, .parent() is basically just a wrapper for the DOM call .parentNode. It's very quick and efficient, since it's essentially a single call to a browser built-in.
https://github.com/jquery/jquery/blob/master/src/traversing.js#L133
.closest(selector) is indeed much safer, because it guarantees you don't loop up past, or don't loop too briefly and stop before, you arrive at the intended parent - regardless of the actual shape of the DOM. But it's obviously also much more expensive. Look at the code:
https://github.com/jquery/jquery/blob/master/src/traversing.js#L63
That it's a loop at all is inherently pricier. It's also doing a lot more checks each iteration. In a very deep DOM, if you're for example using .closest to ask "Is this tag a child of X?" it will loop up the entire DOM tree to the body tag with no other bound. If you're 1000 tags deep that's 1000 pointless loops to find it. The same can occur if you're simply wrong about where the tag you're calling this is placed.
So, .parent() is highly efficient for a DOM where you're very certain about the structure. .closest(selector) is for less-trusted DOMs, although somewhat dangerous if you have no idea what the DOM looks like.

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