1

I have some xml from a webservice that I would like to parse and create a nested unordered list so that I can style using CSS to make it look like a treeview. The xml is like below:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfNavMenuItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ HR EMP SELF SERVICE</PRIMARY_FOLDER>
        <MENU_DISPLAY_NAME>Accommodation Request</MENU_DISPLAY_NAME>
    </NavMenuItem>
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ HR EMP SELF SERVICE</PRIMARY_FOLDER>
        <MENU_DISPLAY_NAME>Additional Personal Information</MENU_DISPLAY_NAME>
    </NavMenuItem>
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ HR EMP SELF SERVICE</PRIMARY_FOLDER>
        <MENU_DISPLAY_NAME>All Actions Awaiting Your Attention</MENU_DISPLAY_NAME>
    </NavMenuItem>
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ CORP HR TIME SELF SERVICE</PRIMARY_FOLDER>
        <SECONDARY_FOLDER>Time</SECONDARY_FOLDER>
        <MENU_DISPLAY_NAME>Create Timecard</MENU_DISPLAY_NAME>
    </NavMenuItem>
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ CORP HR TIME SELF SERVICE</PRIMARY_FOLDER>
        <SECONDARY_FOLDER>Time</SECONDARY_FOLDER>
        <MENU_DISPLAY_NAME>Recent Timecards</MENU_DISPLAY_NAME>
    </NavMenuItem>
    <NavMenuItem>
        <DOMAIN_USERNAME>SomeUser</DOMAIN_USERNAME>
        <PRIMARY_FOLDER>XYZ CORP HR TIME SELF SERVICE</PRIMARY_FOLDER>
        <SECONDARY_FOLDER>Time</SECONDARY_FOLDER>
        <MENU_DISPLAY_NAME>Timecard Search</MENU_DISPLAY_NAME>
    </NavMenuItem>
</ArrayOfNavMenuItem>

and my poor man's attempt at jQuery is so far. I am not sure how I can capture primary folder, secondary folder structure before I iterate over each nav item and add them to <li></li> inside of the appropriate folders

My fiddle is here..

//AJAX CALL
$(response).find('NavMenuItem').each(function (index) {
var test = ($(this).find('SECONDARY_FOLDER').length > 0) ? '<ul><li><label for="subfolder' + index +'">' + $(this).find("SECONDARY_FOLDER").text() + '</label><input type="checkbox" id="subfolder' + index + '">' : '';            
$('.SearchResults').append('<ul class="tree"><li><label for="folder' + index + '">' + $(this).find("PRIMARY_FOLDER").text() + '</label>'
  + '<input type="checkbox" checked id="folder' + index + '" />'
  + test
  + '<ul><li class="file"><a href="#">' + $(this).find("MENU_DISPLAY_NAME").text() + '</a></li></ul>'       
  + '</li></ul>');             
});

But my desired HTML output is something like this:

    <ul class="tree">
        <li>
          <label for="folder1">XYZ CORP HR TIME SELF SERVICE </label> 
          <input type="checkbox" checked id="folder1" />   
            <ul>
                <li><label for="subfolder1">Time</label> <input type="checkbox" id="subfolder1" />

                        <ul>
                            <li class="file"><a href="#" target="_tab">Create Timecard</a></li>
                            <li class="file"><a href="#" target="_tab">Recent Timecards</a></li>
                            <li class="file"><a href="#" target="_tab">Timecard Search</a></li>                                   
                        </ul>
                </li>
            </ul>        
        </li>

        <li>
            <label for="folder2">XYZ HR EMP SELF SERVICE</label> <input type="checkbox"  id="folder2" />                
            <ul>
                <li class="file"><a href="#" target="_tab">Accommodation Request</a></li>
                <li class="file"><a href="#" target="_tab">Additional Personal Information</a></li>
                <li class="file"><a href="#" target="_tab">All Actions Awaiting Your Attention</a></li>           
            </ul>
        </li>
    </ul>
2
  • My problem here is that I was originally building this solution by transforming xml using XSLT so I had a way to template and return call whenever there is a match, hence effectively creating groupings based on matches. I am having difficulty emulating that template match kind of behavior in jQuery. But now I can't use XSLT. It has to be all jQuery/JavaScript/CSS solution on the client side Commented Mar 15, 2016 at 15:49
  • I will write the code for it, but have you checked this out: stackoverflow.com/questions/6578154/… ? Commented Mar 15, 2016 at 16:09

2 Answers 2

1

Check this out:

var tree=$('<ul></ul>').addClass('tree'); // create an unordered list with the class 'tree'
$('NavMenuItem',response).each(function() { //for each item in NavMenuItem
    var primary=$('PRIMARY_FOLDER',this).text(); //get the text of the PRIMARY_FOLDER element
    var secondary=$('SECONDARY_FOLDER',this).text();
    var displayName=$('MENU_DISPLAY_NAME',this).text();
    if (!primary) return; // this shouldn't happen, but still, if no primary folder, ignore it
    //search for a direct listitem that has a direct child a label
    // that has the primary text as content (get the label, then return its parent)
    var li=$('>li>label:contains("'+primary+'"):first',tree).parent();
    if (!li.length) { // if not found, create it
        var index=$('>li',tree).length+1; // the number of list items in the tree plus 1
        var li=$('<li></li>') // create a list item
            .append($('<label for="folder'+index+'"></label>').text(primary)) //that contains a label
            .append('<input type="checkbox" checked id="folder'+index+'" />') // and an input
            .append('<ul></ul>'); // and an unordered list
        tree.append(li); //add it to the tree
    }
    if (secondary) { //if we have a secondary folder
        var ul = $('>ul',li); // find the unordered list in it
        // same as with primary, only here
        var li2=$('>li>label:contains("'+secondary+'"):first',ul).parent();
        if (!li2.length) {
            var index=$('>li',ul).length+1;
            var li2=$('<li></li>')
                .append($('<label for="subfolder'+index+'"></label>').text(secondary))
                .append('<input type="checkbox" checked id="subfolder'+index+'" />')
                .append('<ul></ul>')
            ul.append(li2);
        }
        li=li2; //set the current listitem to be the secondary
    }
    var ul = $('>ul',li); //get the unordered list child of the current listitem
    var index=$('>li',ul).length+1; //compute file index (if you need it)
    //add the file
    ul.append($('<li></li>').addClass('file').append($('<a href="#" target="_tab"></a>').text(displayName)));
});
// add the tree to the element with class 'SearchResults'
$('.SearchResults').append(tree);

I did it as jQuery as possible, although my opinion is that you should create a javascript structure that has everything grouped, then transform it to jQuery DOM manipulation.

Sign up to request clarification or add additional context in comments.

2 Comments

Wow! This is exactly what I needed. This is advanced stuff for me, so currently studying your code. Thanks very much!
I updated the code with comments. I again stress that this is not perfect or performant code, it is just for learning purposes. The best code would repeat the same algorithm in a data structure, then dumbly just create the tree afterwards. Also some weird names of folders might mess the :contains selector. This code has the advantage that you could add items to an existing tree in the page just by running the function on a NavItem
1

It seems your problem is that you want to add am unordered list to the element with the class SearchResults, and then add a list item to the list for each element of the XML, instead you are adding an unordered list for each element. First step is to fix that:

var tree=$('<ul class="tree"></ul>').appendTo('.SearchResults');
$(response).find('NavMenuItem').each(function (index) {
    var test = ($(this).find('SECONDARY_FOLDER').length > 0) 
        ? '<ul><li><label for="subfolder' + index + '">' + $(this).find("SECONDARY_FOLDER").text() + '</label><input type="checkbox" id="subfolder' + index + '">' 
        : '';
    tree.append('<li><label for="folder' + index + '">' + $(this).find("PRIMARY_FOLDER").text() + '</label>'
         + '<input type="checkbox" checked id="folder' + index + '" />'
         + test
         + '<ul><li class="file"><a href="#">' + $(this).find("MENU_DISPLAY_NAME").text() + '</a></li></ul>'
         + '</li>');
});

Then some observations:

  • $(this).find('SECONDARY_FOLDER') is the same as $('SECONDARY_FOLDER',this)
  • if (X>0) is the same as if (X)
  • JQuery has a nice way of chaining methods, so you can append items as you are adding them like $('<li></li').append($('<input />').css({display:'none'}).val(someValue)), etc. which might make your code more readable and thus maintainable.

Hope this helps.

1 Comment

I am not just adding each element in the XML to the SearchResults div, I am also needing to group the elements based on the primary folder values, and secondary folder values. if you look at the desired html output, you will see what I am talking about..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.