Direct descendants only with jQuery's find() - javascript

Is it possible to select only direct descendants of an element using jQuery's find() or children() functions?
I have several ul elements, each with other ul elements inside them, and some root li elements too. I store a specific parent ul in a variable (as a jQuery object) and then look for any of the root li elements within using: my_root_ul.find('li');.
However, this method also finds any li that belongs to the ul inside the ul, if that makes sense.
My question is, how can I select only direct descendants of type li within the my_root_ul object using find(). Ordinarily, we could use something like $('ul > li') to return only direct li elements, but it must be possible to filter down the returned elements?
Here is an example to demonstrate what I mean:
<ul>
<li>I want this
<ul>
<li>I don't want this</li>
<li>I don't want this</li>
<li>I don't want this</li>
</ul>
</li>
<li>I want this</li>
<li>I want this</li>
</ul>

Like this:
my_root_ul.find('> li');
.children() also selects only the immediate children, so you can use that also:
my_root_ul.children('li');

Related

Select LI children from a not none-displayed UL

The title sounds strange but what I want to achieve is simple.
In a tree of uls I want to get all li children from any ul that have not the - inline - style display: none. So I found this post and I mixed with the negation function :not(). The result was:
'ul:not([style*="display: none"]) .k-item'
Where .k-item is a common class for all my li elements. That selector worked in this simple fiddle. The problem is that it doesn't works in my application. I have a screenshot of some console commands that will illustrate my scenario:
As you can see on second command, it returns some li elements that lies under an ul which haves display: none among other attributes in its inline style. Example of those unexpected li with attribute data-uid with values starting with 099d, bbca and 14d2.
I don't know what I'm doing wrong or if exists a better selector for that purpose.
I would suggest using jQuery's :visible rather than looking for something in the style string and string matching in the style string could be problematic.
$("ul:visible .k-item")
First of all get all the li and check whether its parent (ul) is visible.
jsfiddle
$('li', '#layers').each(function(){
if($(this).parent().is(":visible")){
alert($(this).text())
}
});
OR
a neat version
jsfiddle
$(".k-item:visible").each(function(){
alert($(this).text())
});
Try using
$('ul:not([style*="display: none"]) li.k-item').each(function() { alert($(this).html()) });
HTML
<ul style="display: none">
<li class="k-item">1</li>
<li class="k-item">2</li>
<li class="k-item">3</li>
</ul>
<ul>
<li class="k-item">4</li>
<li class="k-item">5</li>
<li class="k-item">6</li>
</ul>
Fiddle: http://jsfiddle.net/3M2ZM/

jQuery selector and cloning in a smart fashion

I am having trouble finding the way to solve this issue. I have this ul-menu output by Wordpress:
<ul class="menu">
<li>
Page 1
</li>
<li>
Page 2
</li>
</ul>
But I want the end result to be like this - cloning and appending the anchor and put a clone below:
<ul class="menu">
<li>
Page 1
Page 1
</li>
<li>
Page 2
Page 2
</li>
</ul>
I have used jQuery - but I am not having any luck at all for 2 hours of trial and error. This is as close as I can get. But it is wrong.
/*jQuery*/
$('.menu li a:first-child').eq(0).clone().insertAfter('.menu li a:first-child');
Fiddle here: http://jsfiddle.net/67jXz/1/
You're not supposed to .eq(0); that will limit it to the first a element that's matched, so that will be cloned and inserted after every subsequent a, resulting in copies of "Page 1".
Instead, you need to perform the cloning and inserting for each individual element by iterating with .each(), like so:
$('.menu li a:first-child').each(function() {
$(this).clone().insertAfter(this);
});
Note that the .insertAfter(this) part refers to inserting the cloned element after the original element that was matched by the .menu li a:first-child selector; the same this in $(this) that references the matched element.
Updated fiddle
Try this code:
$(function(){
$('.menu li a:first-child').each(function(k,v){
$(v).clone().insertAfter(v);
});
});
jsfiddle

Add different classes to list item parents using jquery

I have a list with different levels of depth:
<ul>
<li>Item 1</li>
<li>Item 2
<ul class="sub-menu">
<li>Sub item 1</li>
<li>Sub item 2</li>
<li>Sub item 3
<ul class="sub-menu">
<li>Subsub item 1</li>
<li>Subsub item 2</li>
</ul>
</li>
<li>Sub item 4</li>
</ul>
</li>
<li>Item 3</li>
</ul>
I am using the following jquery script to add a class to the parents:
$("ul li ul").parent().addClass("menuparent");
Is there a way to add this class only to the top level parent li's and a different class for all other (deeper) parent li's?
Here is one way to do it.
$("ul li ul").parent().addClass("otherclass");
$("ul li >ul").parent().removeClass("otherclass").addClass("menuparent");
​
http://jsfiddle.net/MdBa5/
You could run a closest check to see if there are any parents that are li.
if ($element.closest("li").length === 0) {
$element.addClass("topLvl");
} else {
$element.addClass("innerLvl");
}
Try:
$("ul:first>li").addClass("menuparent")
.find('li>ul').parent().addClass('otherparent');
http://jsfiddle.net/3UcdM/
Here's a recursive solution:
function markNestedLists(par, level){
par.addClass("level-" + level);
par.children("li").children("ul").each(function(){
markNestedLists($(this), level + 1);
});
}
markNestedLists($("ul").first(), 1);
http://jsfiddle.net/h9xvY/1/
If you know the ID of the parent of the topmost UL you could use it like so:
markNestedLists($("#myParent > ul"), 1);
Depending on what you want the names of the classes to be, I'd use this:
$(function(){
$('ul li').addClass(function(){
return 'depth-' + $(this).parents('ul').length;
});
});​
You can see a working example here: http://jsfiddle.net/russelluresti/3peCS/
If you want special class names, and not number additions, you'd have to run a switch statement. But the concept is the same. Use a function inside addClass to determine the depth (by using parents()) and return the appropriate value.
Ideally, there's some absolute reference like an ID or other searchable attribute that is unique to the top-most ul, but you can work around that. Either way, the important thing is the child selector: > instead of the implicit descendant selector. It will specify that you only want to find ul's that are exactly so many levels below that top-level element.
When, in your base case, you use:
$("ul li ul").parent()
you get all ul's that are any descendant (children, grandchildren, great-grandchildren...) of any li's, that are descendant of any ul's. Instead, you'd use:
$("ul#topmost > li > ul").parent()
which gets you only a ul that is the child of an li that is a child of the specific ul at the top of the tree.
If you don't have an id or other explicit selector for the top of the tree, the top-level ul must itself be a child of either a div or body or some other block-level element. So, you can clearly and distinctly get the hierarchy you want by just adding that parent of the top-level ul:
$("body > ul > li > ul").parent()
Also: I forgot that you also wanted to be able to select the other parent li's that aren't captured by the selector above. You can do that using the :not selector, or JQuery's .not() method, like so:
$("li>ul:not(body > ul > li > ul)").parent()
To combine the two lines into one, you'd first add the deeperParent class to all such li's, then filter for the top-level parent, and assign menuParent only there:
$("li>ul").parent().addClass('deeperParent').filter('body > ul > li').
removeClass('deeperParent').addClass('menuParent')

Child selector using `querySelectorAll` on a DOM collection

Let's presume you got a list with nested child lists.
<ul>
<li></li>
<li>
<ul>
<li></li>
<li></li>
</ul>
</li>
<li></li>
</ul>
And use document.querySelectorAll() to make a selection:
var ul = document.querySelectorAll("ul");
How can i use the ul collection to get the direct child elements?
ul.querySelectorAll("> li");
// Gives 'Error: An invalid or illegal string was specified'
Let's presume ul is cached somehow (otherwise i could have done ul > li directly).
In jQuery this works:
$("ul").find("> li");
But it doesn't in native querySelectorAll. Any solutions?
The correct way to write a selector that is "rooted" to the current element is to use :scope.
ul.querySelectorAll(":scope > li");
See my answer here for an explanation and a robust, cross-browser solution: https://stackoverflow.com/a/21126966/1170723
Because the ul returned is a NodeList, it doesn't implicitly loop over its contents like a jQuery collection. You'd need to use ul[0].querySelectorAll() or better still select the ul with querySelector().
Besides that, querySelectorAll() won't take a > and work from its current context. However, you can get it to work using lazd's answer (though check the browser compatibility), or any of these workarounds (which should have no browser issues)...
[].filter.call(ul.querySelectorAll("li"), function(element){
return element.parentNode == ul;
});
jsFiddle.
This will select all li elements that are descendants of your ul, and then remove the ones which are not direct descendants.
Alternatively, you could get all childNodes and then filter them...
[].filter.call(ul.childNodes, function(node) {
return node.nodeType == 1 && node.tagName.toLowerCase() == 'li';
});
jsFiddle.
You need to iterate over the NodeList returned by document.querySelectorAll() and then call element.querySelectorAll() for each element in that list.

Jquery recursive function combine nested li into one

I am trying to convert a nested li into one single li using recursive method using jquery
html as follows
<ul>
<li>item-1
<ul>
<li>item-1.1</li>
</ul>
</li>
<li>item-2
<ul>
<li>item-2.1</li>
<li>item-2.2
<ul>
<li>item-2.2.1</li>
<li>item-2.2.2</li>
</ul>
</li>
</ul>
</li>
<li>item-3
<ul>
<li>item-3.1</li>
</ul>
</li>
<li>item-4</li>
<li>item-5</li>
</ul>
Final single li as below
<ul>
<li>item-1</li>
<li>item-2</li>
<li>item-3</li>
<li>item-4</li>
<li>item-5</li>
<li>item-1.1</li>
<li>item-2.1</li>
<li>item-2.2</li>
<li>item-3.1</li>
<li>item-2.2.1</li>
<li>item-2.2.2</li>
</ul>
basically loop through each level then append to the end of the list.
Any ideas how I can achieve this? so it can handle any level of the list item.
Here is a recursive approach that will give the output you're looking for:
function recurseFetchListItems($ul)
{
var $li = $ul.remove().children("li").remove();
if ($li.length) {
$li = $li.add(recurseFetchListItems($li.children("ul")));
}
return $li;
}
It uses add() to accumulate the different levels of list items, while removing each level from the document. It also uses children() instead of find() in order to process a single depth level per call.
From there, you only have to start from the first <ul> element, add the cumulated set of list items back to the document, and wrap them in a new <ul> element:
$(document).ready(function() {
recurseFetchListItems($("ul:first")).appendTo("body").wrapAll("<ul>");
});
You can see the results in this fiddle.
Original (misguided) answer follows:
You don't really need a recursive function to do that, because whole DOM element trees can be matched with a single selector. For instance, $("li") matches all the list items, whatever their depth is.
So, to achieve what you want, we only need to match all the <li> elements, remove their parent <ul> elements from the document, then wrap the list items into a new <ul> element using wrapAll() and add that element back:
$(document).ready(function() {
$("li").parent().remove().end().appendTo("body").wrapAll("<ul>");
});
You can see the results in this fiddle.

Categories