Solved

Recursion and Javascript: Generating DOM-Compliant UL/LI based menus

Posted on 2004-09-14
4
666 Views
Last Modified: 2013-11-19
Hi all,

I'm putting together a DOM-compliant navigation solution for our intranet here, and have the following array of items:

/* Begin JavaScript Array Code */
var arrMenuItems = new Array();
var arrMenuItems[0] = new Array();
var arrMenuItems[0][0] = "Menu|1"; // This is the menu title //
var arrMenuItems[0][1] = "Menu Item|2";
var arrMenuItems[0][2] = new Array();
var arrMenuItems[0][2][0] = "Sub-Menu|3"; // This would be displayed under Menu Item //
var arrMenuItems[0][2][1] = "Sub-Menu Item|4"; // This would be displayed in a new menu //
var arrMenuItems[0][3] = "Another Menu Item|5";
var arrMenuItems[1] = new Array();
var arrMenuItems[1][0] = "2nd Menu|6";
var arrMenuItems[1][1] = "2nd Menu Item|7":
/* End JavaScript Array Code */

The above code is automatically generated by a server-side script which traverses a database of pages and returns them in this format, ready to be turned into a menu by some javascript. The object structure generated and placed back into the page would look something like this if rendered in HTML. Obviously the output will be generated directly in the DOM, so won't output as HTML:

<!-- Start of HTML Generated -->
<UL>
    <LI>Menu
        <UL>
            <LI><A HREF="GetPage.aspx?PageID=2">Menu Item</A></LI>
            <LI>Sub-Menu
                <UL>
                    <LI><A HREF="GetPage.aspx?PageID=4">Sub-Menu Item</A></LI>
                </UL>
            </LI>
            <LI><A HREF="GetPage.aspx?PageID=5">Another Menu Item</A></LI>
        </UL>
    </LI>
    <LI>2nd Menu
        <UL>
            <LI><A HREF="GetPage.aspx?PageID=7">2nd Menu Item</A></LI>
        </UL>
    </LI>
</UL>
<!-- End of HTML Generated -->

The javascript I have at the moment is based on three functions which call each other and populate a UL object created using document.createElement("UL"). It works fine, but only goes to two levels. I don't want to create a third-level function just to extend it to three levels, but every attempt I've had at recursive functions just doesn't work.

My code is shown below:

/* Begin JavaScript Traversion/Generation Code */
function fnCreateMenuItems() {

      this.newUL = document.createElement("UL");
      this.iCMI = 0

      // Traverse the arrMenuItems array and add each menu to the menu node
      for (this.iCMI = 0; this.iCMI < arrMenuItems.length; this.iCMI ++) {
            this.newUL.appendChild(fnTraverseArray(arrMenuItems[this.iCMI]));
      }
      
      document.getElementById("Menu").innerHTML = "";
      document.getElementById("Menu").appendChild(this.newUL);
}
function fnTraverseArray(objArray) {

      // Grab the array
      this.curArray = objArray;
      
      // Create the drop-down menu title (or Tab)
      this.newLItxt = document.createTextNode(this.curArray[0].split("|")[0]);
      this.newLI = document.createElement("LI");
      this.newLI.appendChild(this.newLItxt);

      // Check for more items in the array
      if (this.curArray.length > 1) {
            // Create a sub list for the menu items
            this.newLIUL = document.createElement("UL");

            // Loop through each menu item and add it to the UL
            for (this.iTA = 1; this.iTA < this.curArray.length; this.iTA ++) {
                  if(this.curArray[this.iTA].constructor == Array) {
                        this.newLIULLI = fnTraverseChildArray(this.curArray[this.iTA]);
                        this.newLIUL.appendChild(this.newLIULLI);
                  } else {
                        // Create the objects we need
                        this.newPageID = this.curArray[this.iTA].split("|")[1];
                        this.newLIULLIAtxt = document.createTextNode(this.curArray[this.iTA].split("|")[0]);                  
                        this.newLIULLIA = document.createElement("A");
                        this.newLIULLI = document.createElement("LI");
      
                        // Set any properties that need setting
                        this.newLIULLIA.href = "desktop.aspx?pageID=" + this.newPageID;
      
                        // Add the nodes to each other
                        this.newLIULLIA.appendChild(this.newLIULLIAtxt);
                        this.newLIULLI.appendChild(this.newLIULLIA);
                        
                        // And add the new LI to the UL
                        this.newLIUL.appendChild(this.newLIULLI);
                  }
            }

            // Add the sub list
            this.newLI.appendChild(this.newLIUL);
      }
      
      // Return it
      return this.newLI;
}
function fnTraverseChildArray(objArray) {

      // Grab the array
      this.chcurArray = objArray;
      
      // Create the drop-down menu title (or Tab)
      this.chnewLItxt = document.createTextNode(this.chcurArray[0].split("|")[0]);
      this.chnewLI = document.createElement("LI");
      this.chnewLI.className = "SubMenu";
      this.chnewLI.appendChild(this.chnewLItxt);

      // Check for more items in the array
      if (this.chcurArray.length > 1) {
            // Create a sub list for the menu items
            this.chnewLIUL = document.createElement("UL");
            
            debug("   " + this.chcurArray[0].split("|")[0]);

            // Loop through each menu item and add it to the UL
            for (this.chiTA = 1; this.chiTA < this.chcurArray.length; this.chiTA ++) {
                  // Create the objects we need
                  debug("   " + this.chcurArray[0].split("|")[0]);

                  this.chnewPageID = this.chcurArray[this.chiTA].split("|")[1];
                  this.chnewLIULLIAtxt = document.createTextNode(this.chcurArray[this.chiTA].split("|")[0]);                  
                  this.chnewLIULLIA = document.createElement("A");
                  this.chnewLIULLI = document.createElement("LI");

                  // Set any properties that need setting
                  this.chnewLIULLIA.href = "desktop.aspx?pageID=" + this.chnewPageID;

                  // Add the nodes to each other
                  this.chnewLIULLIA.appendChild(this.chnewLIULLIAtxt);
                  this.chnewLIULLI.appendChild(this.chnewLIULLIA);
                  
                  // And add the new LI to the UL
                  this.chnewLIUL.appendChild(this.chnewLIULLI);
            }

            // Add the sub list
            this.chnewLI.appendChild(this.chnewLIUL);
      }
      
      // Return it
      return this.chnewLI;
}
/* End JavaScript Traversion/Generation Code */

Reducing this into one calling function and one recursive traversal function is the aim of this question. 500 points to the person who can help me out here!

Thanks!
0
Comment
Question by:BigTone
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
  • 2
4 Comments
 
LVL 9

Expert Comment

by:cwolves
ID: 12054726
what's wrong with generating HTML?

<div id="container"></div>
<script language="javascript">
var arrMenuItems = new Array();
arrMenuItems[0] = new Array();
arrMenuItems[0][0] = "Menu|1"; // This is the menu title //
arrMenuItems[0][1] = "Menu Item|2";
arrMenuItems[0][2] = new Array();
arrMenuItems[0][2][0] = "Sub-Menu|3"; // This would be displayed under Menu Item //
arrMenuItems[0][2][1] = "Sub-Menu Item|4"; // This would be displayed in a new menu //
arrMenuItems[0][3] = "Another Menu Item|5";
arrMenuItems[1] = new Array();
arrMenuItems[1][0] = "2nd Menu|6";
arrMenuItems[1][1] = "2nd Menu Item|7";

function drawMenu(n){
      out='<ul>'
      for(var i=0; i<n.length; i++){
            if(isArray(n[i])){
                  out+=drawMenu(n[i]);
            } else {
                  tElem=n[i].split('|');
                  out+='<li><a href="GetPage.aspx?PageID='+tElem[1]+'">'+tElem[0]+'</a></li>';
            }
      }
      out+='</ul>';
      return out;
}
function isArray(obj) {
  return (obj.constructor.toString().indexOf("Array") != -1);
}
document.getElementById('container').innerHTML=drawMenu(arrMenuItems);
</script>

if you really don't want to, it's the same concept.  just change the lines that say "out+=" to appenchild, etc.
0
 

Author Comment

by:BigTone
ID: 12055152
Generating HTML is fine if the target output is HTML - however, the reason for generating DOM-compliant stuff here is that it has to work for XHTML and across all XHTML/DOM compatible browsers. The innerHTML property (for most DOM-compliant browsers) is readonly under an XHTML doctype - which is what our intranet is using.

As well as that, your code doesn't strictly work as I mentioned above (close, but no cigar! :o)). The 2nd menu and menu items don't cascade correctly and aren't shown in the right order. I have managed to put something together, but it just won't recurse and I can't for the life of me figure this out. The code that I thought I'd gotten to be recursive is shown at the bottom of the post.

I'm still v.happy to give the full 500 points if anyone can make my function below to recurse properly!

<!-- ------ BEGIN CODE ----- -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Traverse Test</title>
<script>

      /* Begin Menu Data */
      arrMenuItems = new Array();
      arrMenuItems[0] = new Array();
      arrMenuItems[0][0] = "Sales|2";
      arrMenuItems[0][1] = new Array();
      arrMenuItems[0][1][0] = "Stock|52";
      arrMenuItems[0][1][1] = "Stock Lists|6";
      arrMenuItems[0][1][2] = "Images Missing|7";
      arrMenuItems[0][2] = new Array();
      arrMenuItems[0][2][0] = "Advanced Search Leads|9";
      arrMenuItems[0][2][1] = "Available Leads|28";
      arrMenuItems[0][2][2] = "My Leads|29";
      arrMenuItems[0][3] = new Array();
      arrMenuItems[0][3][0] = "Marketing|62";
      arrMenuItems[0][3][1] = "Stock List Export|63";
      arrMenuItems[1] = new Array();
      arrMenuItems[1][0] = "Aftersales|38";
      arrMenuItems[1][1] = new Array();
      arrMenuItems[1][1][0] = "Aftersales Leagues|39";
      arrMenuItems[1][1][1] = "Tyre League|40";
      arrMenuItems[1][1][2] = "Oil Topup League|41";
      arrMenuItems[2] = new Array();
      arrMenuItems[2][0] = "Recruitment|56";
      arrMenuItems[2][1] = new Array();
      arrMenuItems[2][1][0] = "Vacancies|57";
      arrMenuItems[2][1][1] = "Vacancy Definitions|58";
      arrMenuItems[2][1][2] = "Current Vacancies|59";
      arrMenuItems[2][1][3] = "Vacancy Report|60";
      arrMenuItems[3] = new Array();
      arrMenuItems[3][0] = "Admin|42";
      arrMenuItems[3][1] = new Array();
      arrMenuItems[3][1][0] = "Purchase Manager|43";
      arrMenuItems[3][1][1] = "New Order|65";
      arrMenuItems[3][1][2] = "Order History|66";
      arrMenuItems[3][1][3] = new Array();
      arrMenuItems[3][1][3][0] = "Administration|67";
      arrMenuItems[3][1][3][1] = "Products|69";
      arrMenuItems[4] = new Array();
      arrMenuItems[4][0] = "Management|5";
      arrMenuItems[4][1] = "Web Leads Followup|14";
      /* End Menu Data */

      var createMenu = function() {
            var parentElement = document.getElementById("ParentDiv");

            var menuUL = document.createElement("UL");
            
            traverseArray(arrMenuItems, menuUL, 0);
            
            parentElement.appendChild(menuUL);
      }

      var traverseArray = function(arrObj, ulObj, iStart) {
            for (this.i = iStart; this.i < arrObj.length; this.i ++) {
                  var newLI = document.createElement("LI");
                  var newTxt;
                  var newUL;
                  var newA;
                  if (arrObj[this.i].constructor == Array) {
                        newTxt = document.createTextNode(arrObj[this.i][0].split("|")[0])
                        newLI.appendChild(newTxt);
                        newUL = document.createElement("UL");
                        traverseArray(arrObj[this.i], newUL, 1);
                        newLI.appendChild(newUL);
                  } else {
                        newA = document.createElement("A");
                        newA.href = "desktop.aspx?pageID=" + arrObj[this.i].split("|")[1];
                        newTxt = document.createTextNode(arrObj[this.i].split("|")[0]);
                        newA.appendChild(newTxt);
                        newLI.appendChild(newA);
                  }
                  ulObj.appendChild(newLI);
            }
      }
      
      window.onload = createMenu;
      
</script>
</head>

<body>
      <div id="ParentDiv">
      
      </div>
</body>
</html>
<!-- ------ END CODE ------ -->
0
 
LVL 9

Accepted Solution

by:
cwolves earned 500 total points
ID: 12055349
replace "this.i" with "i" and change your loop to start with "var i" :-)

for (var i = iStart; i < arrObj.length; i ++) {
0
 

Author Comment

by:BigTone
ID: 12055712
Sold, to the man with 500 new points.

Thanks cwolves!
0

Featured Post

Ready to get started with anonymous questions?

It's easy! Check out this step-by-step guide for asking an anonymous question on Experts Exchange.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Shoutout to Emily Plummer (http://www.experts-exchange.com/members/eplummer26.html) for giving me this article! She did most of it, I just finished it up and posted it for her :)    Introduction In a previous article (http://www.experts-exchang…
This article discusses how to implement server side field validation and display customized error messages to the client.
The viewer will learn how to create and use a small PHP class to apply a watermark to an image. This video shows the viewer the setup for the PHP watermark as well as important coding language. Continue to Part 2 to learn the core code used in creat…
The viewer will receive an overview of the basics of CSS showing inline styles. In the head tags set up your style tags: (CODE) Reference the nav tag and set your properties.: (CODE) Set the reference for the UL element and styles for it to ensu…
Suggested Courses

630 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question