Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a nested UL menu based on the URL path structure of menu items

I have an array of menu items, each containing Name and URL like this:

var menuItems = [  
    {  
        name : "Store",  
        url : "/store"  
    },  
    {  
        name : "Travel",  
        url : "/store/travel"  
    },  
    {  
        name : "Gardening",  
        url : "/store/gardening"  
    },  
    {  
        name : "Healthy Eating",  
        url : "/store/healthy-eating"  
    },  
    {  
        name : "Cook Books",  
        url : "/store/healthy-eating/cook-books"  
    },  
    {  
        name : "Single Meal Gifts",  
        url : "/store/healthy-eating/single-meal-gifts"  
    },  
    {  
        name : "Outdoor Recreation",  
        url : "/store/outdoor-recreation"  
    },  
    {  
        name : "Hiking",  
        url : "/store/outdoor-recreation/hiking"  
    },  
    {  
        name : "Snowshoeing",  
        url : "/store/outdoor-recreation/hiking/snowshoeing"  
    },  
    {  
        name : "Skiing",  
        url : "/store/outdoor-recreation/skiing"  
    },  
    {  
        name : "Physical Fitness",  
        url : "/store/physical-fitness"  
    },  
    {  
        name : "Provident Living",  
        url : "/store/provident-living"  
    }  
]  

I've been trying with no success to render this as an unordered list with a nested UL structure that follows the URL path structure like so:

<ul>  
    <li><a href="/store">Store</a>  
        <ul>  
        <li><a href="/store/travel">Travel</a></li>  
        <li><a href="/store/gardening">Gardening</a></li>  
        <li><a href="/store/healthy-eating">Healthy Eating</a>  
            <ul>  
            <li><a href="/store/healthy-eating/cook-books">Cook Books</a></li>  
            <li><a href="/store/healthy-eating/single-meal-gifts">Single Meal Gifts</a></li>
            </ul>  
        </li>
        <li><a href="/store/outdoor-recreation">Outdoor Recreation</a>  
            <ul>  
            <li><a href="/store/outdoor-recreation/hiking">Hiking</a>  
                <ul>  
                <li><a href="/store/outdoor-recreation/hiking/snowshoeing">Snowshoeing</a></li>
                </ul>  
            </li>  
            <li><a href="/store/outdoor-recreation/skiing">Skiing</a></li>  
            </ul>  
        </li>
        <li><a href="/store/physical-fitness">Physical Fitness</a></li>  
        <li><a href="/store/provident-living">Provident Living</a></li>  
        </ul>  
    </li>  
</ul>  

All of the examples I've seen begin with a data structure that reflects the parent-child relationship (e.g. xml or JSON), but I'm having a very difficult time pulling this out of the URL and using it to render the new structure.

If anyone could please steer me in the right direction for how to do this using jQuery, I'd really appreciate it. I realize I probably need to use some recursive functions or maybe jQuery templates, but these things are still a bit new to me.
Thanks

like image 271
Jake Avatar asked Aug 20 '11 18:08

Jake


3 Answers

I think the best solution is firstly to convert your data structure to a tree one, with parent/children relations. Render this structure will then be easier, as the UL itself has a tree structure.

You can convert menuItems using these couple of functions

// Add an item node in the tree, at the right position
function addToTree( node, treeNodes ) {

    // Check if the item node should inserted in a subnode
    for ( var i=0; i<treeNodes.length; i++ ) {
        var treeNode = treeNodes[i];

        // "/store/travel".indexOf( '/store/' )
        if ( node.url.indexOf( treeNode.url + '/' ) == 0 ) {
            addToTree( node, treeNode.children );

            // Item node was added, we can quit
            return;
        }
    }

    // Item node was not added to a subnode, so it's a sibling of these treeNodes
    treeNodes.push({
        name: node.name,
        url: node.url,
        children: []
    });
}

//Create the item tree starting from menuItems
function createTree( nodes ) {
    var tree = [];

    for ( var i=0; i<nodes.length; i++ ) {
        var node = nodes[i];
        addToTree( node, tree );
    }

    return tree;
}

var menuItemsTree = createTree( menuItems );
console.log( menuItemsTree );

The resulting menuItemsTree will be an object like this

[
  {
    "name":"Store",
    "url":"/store",
    "children":[
      {
        "name":"Travel",
        "url":"/store/travel",
        "children":[

        ]
      },
      {
        "name":"Gardening",
        "url":"/store/gardening",
        "children":[

        ]
      },
      {
        "name":"Healthy Eating",
        "url":"/store/healthy-eating",
        "children":[
          {
            "name":"Cook Books",
            "url":"/store/healthy-eating/cook-books",
            "children":[

            ]
          },
          {
            "name":"Single Meal Gifts",
            "url":"/store/healthy-eating/single-meal-gifts",
            "children":[

            ]
          }
        ]
      },
      {
        "name":"Outdoor Recreation",
        "url":"/store/outdoor-recreation",
        "children":[
          {
            "name":"Hiking",
            "url":"/store/outdoor-recreation/hiking",
            "children":[
              {
                "name":"Snowshoeing",
                "url":"/store/outdoor-recreation/hiking/snowshoeing",
                "children":[

                ]
              }
            ]
          },
          {
            "name":"Skiing",
            "url":"/store/outdoor-recreation/skiing",
            "children":[

            ]
          }
        ]
      },
      {
        "name":"Physical Fitness",
        "url":"/store/physical-fitness",
        "children":[

        ]
      },
      {
        "name":"Provident Living",
        "url":"/store/provident-living",
        "children":[

        ]
      }
    ]
  }
]

You mentioned you already have html renderer for trees, right? If you need further help let us know!

like image 152
Davide Avatar answered Oct 05 '22 01:10

Davide


12 simple lines of code:

var rootList = $("<ul>").appendTo("body");
var elements = {};
$.each(menuItems, function() {
    var parent = elements[this.url.substr(0, this.url.lastIndexOf("/"))];
    var list = parent ? parent.next("ul") : rootList;
    if (!list.length) {
        list = $("<ul>").insertAfter(parent);
    }
    var item = $("<li>").appendTo(list);
    $("<a>").attr("href", this.url).text(this.name).appendTo(item);
    elements[this.url] = item;
});

http://jsfiddle.net/gilly3/CJKgp/

like image 35
gilly3 Avatar answered Oct 05 '22 02:10

gilly3


Although I like the script of gilly3 the script produces list with different element nesting of <li> and <ul> than was originally asked. So instead of


   <li><a href="/store">Store</a>
     <ul>
        <li><a href="/store/travel">Travel</a></li>
        ...
     </ul>
   </li>
It produces

   <li><a href="/store">Store</a>
   </li>
   <ul>
      <li><a href="/store/travel">Travel</a></li>
      ...
   </ul>
This may cause incompatibilities for utilities or frameworks working with such generated menu and producing interactive menu with animation (e.g. superfish.js). So I updated the 12 lines script
var rootList = $("<ul>").appendTo("body");
var elements = {};
$.each(menuItems, function() {
    var parent = elements[this.url.substr(0, this.url.lastIndexOf("/"))];
    var list = parent ? parent.children("ul") : rootList;
    if (!list.length) {
        list = $("<ul>").appendTo(parent);
    }
    var item = $("<li>").appendTo(list);
    $("<a>").attr("href", this.url).text(this.name).appendTo(item);
    elements[this.url] = item;
});

http://jsfiddle.net/tomaton/NaU4E/

like image 43
Tomas Kulhanek Avatar answered Oct 05 '22 03:10

Tomas Kulhanek