Solved

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

Posted on 2004-09-14
4
646 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
  • 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

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

Preface In the first article: A Better Website Login System (http://www.experts-exchange.com/A_2902.html) I introduced the EE Collaborative Login System and its intended purpose. In this article I will discuss some of the design consideratio…
Introduction Since I wrote the original article about Handling Date and Time in PHP and MySQL (http://www.experts-exchange.com/articles/201/Handling-Date-and-Time-in-PHP-and-MySQL.html) several years ago, it seemed like now was a good time to updat…
This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.
The viewer will the learn the benefit of plain text editors and code an HTML5 based template for use in further tutorials.

747 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

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now