Is there a way in jQuery to, given an element and a selector, select the last match of that selector before that element from an in-order traversal of the DOM tree?
For example, given the DOM tree:
<html>
<body>
<div class="a" id="div0" />
<div>
<div class="a" id="div1"/>
</div>
<div class="b" id="div2"/>
<div id="element"/>
<div class="a" id="div3"/>
<div class="b" id="div4"/>
</body>
</html>
Using the selector .a and element #element, you'd get #div1, and using the selector .b and the element #element, you'd get #div2.
The use case is writing a GreaseMonkey script to work across different versions of Firefox with some slightly mangled HTML. I've got an element I can find consistently in all versions, but another element I want to find is either a previous sibling of an ancestor of the element or a descendent of a previous sibling of an ancestor of the element (depending on the version).
All I can really rely on is that it is the last match of the selector that occurs before the element I have when doing an in-order traversal of the DOM tree.
This should do it, using your first example:
var $collection = $(".a, #element");
var eleIndex = $collection.index($("#element"));
var prevEl = $collection.eq(eleIndex - 1);
alert(prevEl.attr("id"));
http://jsfiddle.net/WdsGa/
Sortof. This will work for sibling elements just fine:
$("div.a:first").nextUntil("#element").last();
however, your nested div.a will not work with that snippet, I'm not sure of an efficient way to solve that. It selects the first element matched by div.a, then all siblings after it until it finds an element that matches #element, and then gets the last of those selected elements. But, again, it only works with siblings.
This should do it
var last = $(".a, #element").eq($(".a, #element").length-1);
Related
This piece of code appears in a js script I have been asked to modify - I'm not sure why it is written in this way, it doesn't make sense to me.
Can anyone help explain what it is doing, and if it can be simplified to be a little more meaningful?
var unformathtml = $(this).text();
if(unformathtml.trim().length>showChar) {
$(this).parent().parent().parent().parent().parent().find('.comment-footer').fadeOut();
}
Lets' pretend we have a DOM like this:
<parent-5>
<target-element>Content</target-element>
<parent-4>
<parent-3>
<parent-2>
<parent-1>
<focused-element>Some Text</focused-element>
</parent-1>
</parent-2>
</parent-3>
</parent-4>
</parent-5>
What this code is saying is "if the text inside of <focused-element> has more characters than showChar then fade out <target-element>.
A better way of doing this would be to give <parent-5> some kind of identifier, which could be an ID or a class, and target that instead of the repeated .parent() call.
Here's an example which showcases the idea:
$('#oldMethod').click(function() {
$(this)
.parent()
.parent()
.parent()
.parent()
.parent()
.find('.comment-footer')
.toggleClass('red');
});
$('#newMethod').click(function() {
$(this)
.closest('.comment-container')
.find('.comment-footer')
.toggleClass('red');
});
.red {
color: #F00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="comment-container">
<div>
<div>
<div>
<div>
<button id="oldMethod">Old</button>
<button id="newMethod">New</button>
</div>
</div>
</div>
</div>
<div class="comment-footer">Footer</div>
</div>
Wow, that really doesn't make much sense. It is doing this:
1) Getting the raw contents out of an element
2) Checking to see if it is longer than a certain length
3) If so, fading out another element on the page
The parents() thing is very error-prone. It is going up a very precise number of levels in the HTML tree and then descending downwards to find an element with a class of '.comment-footer'. As a result a slight rearrangement of either element in the DOM might result in the code no longer working, because it can't find the specified element.
What you want is to find the tag-to-hide more directly. Ideally, the element-to-hide and the element-that-decides-to-hide would be next to eachother in the DOM (i.e. the element being hidden would be a child or sibling of the element that decides whether or not to hide it). This makes it very easy for the one to find the other. If that isn't possible, your next best bet would be to simply assign an id to the element you are trying to hide and then select on that id directly:
var unformathtml = $(this).text();
if(unformathtml.trim().length>showChar) {
$('#to_hide').fadeOut();
}
As a quick aside, .text() is used (instead of .html()), because the former removes any HTML tags. This way you are measuring the amount of "actual" text inside $(this) to determine whether or not you want to hide said element. So that part is probably fine.
Let's say I have this piece of html:
<div>
<a class="target"/>
<div>
<a class="target"/>
<div>
<hr class="source"/>
</div>
</div>
</div>
I'd like to find the closest target from the source, meaning the one where I need to climb the fewest amount of parents. With a binding, I get the source element, that I'll note source. I want to find the second anchor, which is two levels deep, as it's closer to my source hr.
Here's what I have right now is, which works:
var target = source
.parentsUntil(".target").eq(0)
.find(".target")[0];
It seems rather uneffective though, because parentsUntil will test and return too many of the parents. I'd like it to stop on the first parent containing a .target element. Then I feel like calling find after makes jQuery look for target once more while it had already found it before with parentsUntil.
I can think of another solution that would involve iterating over source.parents() and calling find until I have a result but that would still search into branches that have already been explored.
Is there a function in jQuery or a custom algorithm I could leverage to get my result by exploring only the part of the tree that needs to be explored?
I'd suggest:
// starts at the element(s) with the class of
// 'source':
$('.source')
// finds the closest <div> element that contains
// an <a> element with the class-name of 'target':
.closest('div:has("a.target")')
// finds that contained <a> element with
// the class of 'target':
.find('a.target');
$('.source').closest('div:has("a.target")').find('a.target').addClass('found');
a::before {
content: attr(class);
}
.found {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<a class="target"></a>
<div>
<a class="target"></a>
<div>
<hr class="source" />
</div>
</div>
</div>
References:
addClass().
closest().
find().
:has() selector.
In your case you can use .parent() to select the parent div of .source element and use .prev() to get the previous element which is <a> in your case
$('.source').parent().prev()
or you can use
$('.source').parent().parent().find('a.target')
I have to retrieve hier2 object in jquery from following HTML code in ASPX page
<div>
<hier2:hierarchy >
<hier:x1></hier:x1>
<hier2:hierarchy >
</div>
I trier document.all.hier2 in IE 11 and its not working
what is the best approach to do this.
simply put:
document.getElementsByTagName("hier2:hierarchy");
returns a node list you can loop over containing all the hier2:hierarchy nodes.
More elaborate:
Those elements (or nodes) are part of the Document Object Model (DOM). In the past IE (and others) supported document.all. It's now deprecated. How to traverse the DOM. The DOM is a tree that contains a root and its children, grandchildren, etc.
basics:
document: This is the master object. The root of the page. All other elements are a descendant of this root.
document.documentElement: representing the HTML-element of the page.
Traversal:
document.getElementById: allows you to select a single element based upon its ID-attribute.
document.getElementsByTagName: allows you to select multiple nodes based upon the node name.
document.getElementsByClassName: allows you to select multiple nodes based upon the node's class.
document.querySelector: select a single node using css-selectors
document.querySelectorAll: same as the above, only for selecting multiple elements.
children or childNodes: a subtle difference, the first selects content nodes, while the latter selects all the nodes that are direct children (in the form of a node list) of an element.
parentNode or parentElement: select the parent element of the current element.
previousSibling or nextSibling: select the previous or next element.
Many more options here: https://developer.mozilla.org/en-US/docs/Web/API/Element
Some examples:
plain:
var elements = document.getElementsByTagName("hier2:hierarchy");
for (var i = 0; i < elements.length; i++)
{
document.querySelector("#display").innerHTML += elements[i].nodeName; //select the display div using css selector #display
}
<div>
<hier2:hierarchy>
<hier:x1></hier:x1>
</hier2:hierarchy>
</div>
<div id="display"></div>
In jQuery
$("hier2\\:hierarchy").each(function() {
$("#display").html(this.nodeName);
});
//$("hier2\\:hierarchy") is used to select the elements, mind the \\ to escape!
//each is used to traverse all the elements.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<hier2:hierarchy>
<hier:x1></hier:x1>
</hier2:hierarchy>
</div>
<div id="display"></div>
Special reminder here:
In using hier2:hierarchy will fail. You need to escape the : with \\: to
$("hier2\\:hierarchy")
I have read this question and its answer, and wish to take it a little bit further.
I want to use CasperJS.click(selector) function to click multiple links matching a selector. Please note that the links do not have a significant href tag.
Consider the following markup:
HTML:
<div>
<h1 class='myLink'>Cocacola</h1>
<div>
<div>
<h1 class='myLink'>Sprite</h1>
</div>
</div>
</div>
The answers I've mentioned on top here suggest deleting the links so we can click the remaining elements with casper.exists and so on. What if I don't want to manipulate the page?
For reasons beyond my conception, using:
document.querySelector("div .myLink:nth-of-type(1)");
catches the first h1, Cocacola. But:
document.querySelector("div .myLink:nth-of-type(2)");
returns null.
Fiddle here.
Any ideas? Many thanks!
CSS spec for :nth-of-type says that:
The :nth-of-type(an+b) pseudo-class notation represents an element that has an+b-1 siblings with the same expanded element name before it in the document tree, for any zero or positive integer value of n, and has a parent element.
That is, the elements will have to be siblings.
For example,
<div>
<h1 class='myLink'>Cocacola</h1>
<h1 class='myLink'>Miranda</h1>
<div>
<div>
<h1 class='myLink'>Sprite</h1>
</div>
</div>
</div>
The selector div .myLink:nth-of-type(2) will select Miranda. That is, given n siblings of type 'div .myLink', the selector will select the second element from them.
Here is the fiddle for the above example.
Hope this helps!
As mentioned, the reason :nth-of-type(1) works but :nth-of-type(2) doesn't is because there is only exactly one h1 of each type as a child of its parent div. The class selector .myLink is a separate condition entirely and does not affect how :nth-of-type() works.
The reason your first statement appears to return the first element, even though there are technically two elements matching :nth-of-type(1), is because querySelector() returns only the first match.
To obtain the first and second elements matching your selector, use querySelectorAll() instead of querySelector(), and an indexer instead of the :nth-of-type() pseudo-class:
var cocacola = document.querySelectorAll("div .myLink")[0];
var sprite = document.querySelectorAll("div .myLink")[1];
Is there a way I can find the grand parent of a div and apply style to it?
<div class="wrapBoxes">
<div class="filters"></div>
<div class="wrapContainer"> <-- Need to apply style to this -->
<div class="leftNav"></div>
<div id="container">
<div class="box"></div> <-- From here -->
</div>
</div>
</div>
Something with this logic?
$(".box").find(grandParent).applyWhateverCss to GrandParent
use parent twice:
$(".box").parent().parent().css('color', 'blue');
parent docs:
Description: Get the parent of each element in the current set of matched elements, optionally filtered by a selector.
You can use the closest as well.
$(".box").closest('.wrapContainer').css('color', 'blue');
closest docs:
Description: Get the first element that matches the selector, beginning at the current element and progressing up through the DOM tree.
If you are sure it will always be the grand parent, use parent().parent() otherwise use closest
as a performance u can also use .closest(selector[,context]) i.e
$('.box').closest('.wrapContainer','.wrapBoxes').css('color','yellow');
In this way u can limit the DOM traversal to the context of div.wrapBoxes only
$(".box").parent().parent().css('color','red'); should do it.
Alternatively if the class of "wrapContainer" is always there:
$(".box").parents('.wrapContainer').css('color','red');