Zairja
Zairja

Reputation: 1439

Traverse a nested list with Javascript/jQuery and store in array

I've seen several questions about converting an array/object into a nested list, but I've found only one relevant question to my issue. I've tried a few ways of accessing children of an element, but it only breaks my code further.

I have a nested, unordered list.

<div id="sortableSitemap">
    <ul class="sortable ui-sortable">
        <li id="world_news_now"><div>World</div>
            <ul>
                <li id="the_news"><div>The News</div></li>
            </ul>
        </li>
        <li id="sports_news_cool"><div>Sports</div></li>
    </ul>
</div>

At the moment, it contains 3 items and appears like so:

-World
    --The News
-Sports

There can be any number of nodes with varying depths. I'm trying to store the list into an array with some additional information.

Each node gets a numeric, sequential ID (first node is 1, second is 2) based on the order it appears, regardless of depth (i.e. World = 1, The News = 2, Sports = 3). I also want to store the ID of a node's parent (root is 0). So, the parent IDs would be: World = 0, The News = 1, Sports = 0.

The code below seems to work except when the list is like the one above. In that case, it assigns The News = 3 and its parent = 2 (Sports). For some reason, iterating through the items (children) ends up getting to the <ul> last, even if it's directly after an open <li>.

All but one jQuery solution I found ignores depth, and even then, I need the actual parent node's ID (which I currently keep in a stack based on whether I've gone down a level).

Why is this happening, and how can I modify my code to go through the list recursively?

var count = 0;
var pages = [];
var parentStack = [];

            function createNewLevel(items) {
                var length = items.length;
                for (var i = 0; i < length; i++) {

                    if (items[i].tagName == 'UL') {

                        parentStack.push(count);
                        createNewLevel($(items[i]).children().get());
                        parentStack.pop();

                    } else {
                        ++count;
                        pages.push({
                            pId: parentStack[parentStack.length - 1],
                            urlStr: $(items[i]).attr('id'), myId: count
                        });
                    }
                }

            }

            createNewLevel($('#sortableSitemap ul').get());

            console.log(pages);

Update: Here's a jsFiddle to show how the code does not work ("The News" should have "World" as its parent node).

Upvotes: 1

Views: 3286

Answers (1)

RANONO
RANONO

Reputation: 36

I've modified your original code. This should work for all combinations of nested lists. Instead of using children().get(), I used the native JS child methods. It will traverse everything in the list, but ignore elements unless they are <li> or <ul>. Good luck.

            var count = 0;
            var pages = [];
            var parentStack = [];

            var result = {};

            parentStack.push(0);

            function createNewLevel(obj) {
                var obj = obj || document.getElementById('sortableSitemap');

                if (obj.tagName == 'LI') {
                    ++count;
                    pages.push({
                        pId: parentStack[parentStack.length - 1],
                        urlStr: obj.id, myId: count
                    });
                }

                if (obj.hasChildNodes()) {
                    var child = obj.firstChild;
                    while (child) {
                        if (child.nodeType === 1) {

                            if (child.tagName == 'UL') {
                                parentStack.push(count);
                            }

                            createNewLevel(child);

                            if (child.tagName == 'UL') {
                                parentStack.pop();
                            }
                        }
                        child = child.nextSibling;
                    }
                }
            }

            createNewLevel();

Upvotes: 1

Related Questions