tags:

views:

89

answers:

4
+2  Q: 

find nesting depth

So, take this html for example:

<ul>
    <li>
        <a href="#">Test</a>

        <ul>
            <li><a href="#">Test</a></li>
            <li><a href="#">Test</a></li>
            <li><a href="#">Test</a></li>
            <li><a href="#">Test</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Test</a>

        <ul>
            <li><a href="#">Test</a></li>
            <li>
                <a href="#">Test</a>

                <ul>
                    <li><a href="#">Test</a></li>
                    <li><a href="#">Test</a></li>
                    <li><a href="#">Test</a></li>
                    <li><a href="#">Test</a></li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

I want to find out the maximum depth of this structure, in this example it would be 3, as the structure goes 3 levels down. But the structure can have an arbitrary depth.

Either a javascript/jquery solution or a PHP based solution using the DOM extension would be fine. I really cant think of any nice way of doing this. Maybe there is an xpath expression that does exactly what I want?

EDIT: To clarify: Depth in this case is the maximum nesting of ul elements, in this case 3.

A: 

The "nice" way would be to count the parents of a specific element. Lets say, there was an anchor with the id == foobar, you could do

$('#foobar').parents.length - 1

that would return the number of parent nodes, which also would be the depth.

And the "ugly" way would be a recursive function which crawls through every element in your markup until there are no more child nodes.

jAndy
Unfortunately, there are no ids available. Furthermore this would count ALL parent nodes of #foobar, not just the parent uls of the node?
Max
@Max: You could pass `parents()` a selector string. So if you're only interested in `uls` it would be `$('#foobar').parents('ul').length - 1`
jAndy
+3  A: 

If you didn't want to have to specify the lowest selector, this will give you the maximum number of nest levels:

var n = 0

$('ul').each(function(i){ 
    if (($(this).parents('ul').length + 1) > n) { n = $(this).parents('ul').length + 1; }
});

Edit: the variable n contains the number of nest levels, so for the example above, 3.

Andy Casey
That seems to do the trick, thanks!
Max
+1  A: 

This recursive function would work, if you wanted to specify the root. I've used "body" in the following example:

function Recurse($item, depth) {
    $item.each(function() {
        depth.count++;
        if (depth.count > depth.max) {
            depth.max = depth.count;
        }
        Recurse($(this).children(), depth);
    });
    depth.count--;
    return depth.max;
}


$(document).ready(function() {
    alert(Recurse($("body"), { count: 0, max:0 }));
}

Also here at this fiddle: http://jsfiddle.net/RqHzf/

James Wiseman
Awesome function btw, for another project I could translate this easily to PHP - counting the levels of an array structure!
Max
Thanks. Felt good writing it!
James Wiseman
+1  A: 

Even though you already accepted an answer, I'm going to throw this one out there since I think it will be more efficient.

Try it out: http://jsfiddle.net/3haSq/

Math.max.apply(null, $('ul:not(:has(ul))').map(function() {
    return $(this).parents('ul').length;
}).get()) + 1;
  • $('ul:not(:has(ul))') only concerns itself with <ul> elements that do not have a nested <ul>
  • .map().get() gives you an array of the quantities of their parent <ul> elements
  • The native Math.max() finds the maximum
  • Add +1 at the end to represent the <ul> that was originally selected.

EDIT:

The reason we're doing Math.max.apply() instead of Math.max() is pretty simple.

Normally Math.max() requires several int arguments, like this:

Math.max(12, 23, 34);  // results in `34`

This is fine as long as you know exactly how many arguments there will be beforehand. Not the situation here.

Normally the .apply() method is used to call a function but change the value this for that function call. The first parameter for .apply() is the new value of this. Here we're passing null because we really don't care what the value is.

The important part of .apply() that we care about is the second parameter that takes an Array of arguments that would normally be passed to the function. So taking the example above, we would do this:

Math.max.apply(null, [12,23,34]);  // Converts the Array to a list of arguments

So as you can see, this allows Math.max() to accept one Array of ints instead of several comma separated ints.

(There is a similar method called .call() which takes parameters in the same manner as the original function. As in:)

Math.max.call(null, 12,23,34);   // This version takes several arguments
                                 //     instead of one Array.
patrick dw
Pretty nice stuff, thanks!
Max
@Max - You're welcome, and thanks. :o) Let me know if it needs further explanation.
patrick dw
Well, one question: what exactly does Math.max.apply() do? Why doesn´t it work e. g. with just Math.max()?
Max
@Max - Good question. I'll update my answer in a minute to give an explanation.
patrick dw
@Max - Updated my answer. So because we've reduced the number of `<ul>` elements, and because we're using a native `Math` method instead of comparison operators with variable updates, you'll get better performance.
patrick dw
Thanks for the explanation! Pretty neat trick, really. I´d give you another upvote if I could, thank you very much!
Max
@Max - Not a problem. Glad I could help. :o)
patrick dw