DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> XML學習教程 >> XML詳解 >> 利用ASP和XML制作菜單導航系統
利用ASP和XML制作菜單導航系統
編輯:XML詳解     

1.1 概述
   高效地利用Web頁面有限的空間並不容易,特別是要在頁面中安排大量的鏈接時尤為困難。如何才能組織好各種鏈接以便為其它重要內容留出空間?是一次性地展示所有鏈接還是分成多個頁面把它們深深地隱藏起來?顯然,這兩種方法都不理想。利用DHtml,我們可以在為用戶提供快速方便的訪問鏈接的同時,為其它內容保留足夠的頁面空間。
   本文介紹一個菜單系統的實現。這個菜單與Windows的“開始”菜單非常相似,用戶只需點擊一次鼠標即可訪問所有鏈接。菜單的內容由XML文檔定義,客戶端行為由DHtml實現。XML文檔的解析在服務器端完成,因此對用戶浏覽器是否支持XML並沒有要求。

  本文接下來的內容具體介紹其實現,包括XML文檔的結構以及如何分析這個XML文檔、配合客戶端Javascript一起構造出最終的菜單。
   1.2 靜態和動態
   頁面內容動態生成是指在用戶請求時才動態地(自動地)生成內容,靜態內容則是將自動生成的內容保存為文件,用戶請求的時候發送給他們的是這些靜態的文件。有讀者曾經來信指出這個問題,我們在《再論Web內容的發布——用XML與ASP分離內容與設計》中轉發了讀者來信並談了自己的看法。
   依賴於菜單的實際大小和菜單內容變化的頻繁程度,有些時候可能不想讓菜單動態生成。這時候可以將菜單代碼做成靜態的:運行本文介紹的腳本,將所生成的菜單Html代碼保存為包含文件再直接引用就可以了。如果可以采用這個方案的話,它將節省大量寶貴的服務器資源。
   本文以動態菜單為例具體介紹實現原理。不過為了方便讀者,在下載包中我們提供了兩個版本的服務器腳本,分別用於動態生成菜單和生成靜態的菜單HTML代碼。使用靜態版本時,先打開XMLCreateMenu_Entry.ASP頁面,在這裡輸入如下兩個信息:第一是所使用的XML文件,第二是包含文件的路徑,這個包含文件將保存Html格式的菜單代碼。輸入上述信息後即可自動運行服務器腳本生成菜單代碼。以後如果要改變菜單內容,只要修改XML文件,然後再重復上述過程就可以了。
   使用靜態菜單時,參考本文default.asp中的動態菜單使用方法,只需將default.asp中的< !--#include file="XMLCreateMenu_Dynamic.ASP"-- >改成包含靜態菜單文件即可。
   1.3 數據結構
   XML無疑是當前最為熱門的話題之一,人們普遍相信未來的Web就在於XML。不過本文使用XML來定義菜單內容並非是為了趕時髦,在這裡使用XML確實有其特別的好處。
   首先,既然使用了菜單系統,那麼它就不大可能只用在一、二個頁面上,往往是整個網站的所有頁面都要用到同一菜單。這就要求能夠快速地構造菜單,盡量地減少訪問菜單定義數據所消耗的時間。第二,菜單是一個層次結構,定義菜單內容的數據最好也具有層次結構。這不僅可以方便地體現出各個菜單項之間的從屬關系(從而有利於修改),而且在本文後面我們還可以看到,用遞歸函數來分析層次結構的數據是相當方便的。
XML可以很好地滿足這兩個要求,不僅數據可以快速地訪問(無需額外的網絡連接,無需登錄數據庫等),而且XML能夠非常自然地描述出數據的層次結構。此外,前面我們已經提到,XML文檔的解析在服務器端完成,因此並不要求用戶浏覽器支持XML。
   編輯XML文檔既可以使用普通的文本編輯器,也有許多專用的XML編輯器可供選擇,有關這方面的內容以及XML基礎概念的說明,請參見《XML簡明教程》,也請參見XML Tools。
   創建菜單系統的第一步是在XML文檔中定義菜單內容。在考慮了幾種可能的方法之後,我們決定采用最簡單的一種方法,使得不太熟悉XML的讀者也能理解和更改這個XML文件。每一個菜單項至少包含兩個數據,即菜單項的文本和鏈接;如果菜單項擁有多個子菜單,可以很方便地加入子節點。為保持整個系統的簡潔性,菜單最多不能超過三層。下面是本文XML文件的一個片斷。
  < menuItem >

   < hyperLink/ >

   < name >娛樂休閒< /name >

   < menuItem >

    < hyperLink >Link1.ASP< /hyperLink >


    < name >游戲< /name >

   < /menuItem >

   < menuItem >

    < hyperLink >link4.ASP< /hyperLink >

    < name >汽車/摩托車< /name >

   < /menuItem >

  < /menuItem >
   每一個菜單項由一個< menuItem >標記定義,它至少有兩個子節點:子節點< hyperLink >定義的是該菜單項的鏈接,而< name >包含該菜單項文本。
   可以看到,前面代碼中的第一個< hyperLink >沒有給出鏈接,這是因為這個菜單項用來彈出子菜單,它並不指向任何具體的Web頁面。子節點的定義方法和父節點定義方法相同,使用的都是< menuItem >標記。這種分層結構可以一直嵌套下去,但我們已經提到,嵌套層次不宜過多,菜單系統的唯一目的應該是方便用戶訪問,而不是使得訪問更加困難。 2.1 遍歷XML樹
在XML文檔中定義了菜單內容後,接下來的任務是遍歷和分析這些層次結構的數據、生成菜單的Html代碼。這主要通過一個遞歸函數walkTree實現。這部分代碼的效率非常重要,不能為了生成菜單而讓用戶等待太長的時間。
   在調用遞歸函數walkTree之前,我們首先要找出XML文檔的根。這個根可以利用以下代碼得到(完整的代碼請從本文後面下載):
 var XMLRoot = XMLDoc.documentElement;
   在得到文檔的根後,接下來程序創建一個數組來描述菜單項之間的層次關系。如下面的代碼所示,這個數組以JavaScript的eval函數動態命名。變量startMenu決定完整菜單名字的基礎部分,從本文後面我們還可以看到,客戶端代碼也要利用該菜單名字來激活菜單。
 var image    = "< img align=right vspace=2 height=10

          width=10 border=0 src=/School/UploadFiles_7810/201104/20110414130201849.gif>";

 var currentNode   = "";

 var newNode    = "";

 var currentMenu   = "";

 var startMenu   = "menu1";

 var level    = 1;

 var arrayHolderArray = new Array();

 var arrayNamesArray  = new Array();

 var tempString   = "";

 

 //數組的名字為DIV名字加"_Array"

 eval("var " + startMenu + "_Array = new Array()")       

 for (var i=0;i< XMLRoot.childNodes.length;i++) {

  currentNode = XMLRoot.childNodes.item(i);

  if (currentNode.hasChildNodes() == true && currentNode.childNodes.length >1) {

   currentMenu = startMenu + "_" + (i+1);
thisMenu = startMenu;

   if (currentNode.childNodes.length >2) {

     sMenuItem = escape("< SPAN id="" + thisMenu + "_span" + (i+1) +

       " class='cellOff' onMouSEOver="stateChange('" +

       currentMenu + "',this," + level +

       ")" onMouSEOut="stateChange('',this,'')" >" +

       image + currentNode.childNodes.item(1).text +

       "< /SPAN >< BR >

");

     eval(startMenu + "_Array[i] = sMenuItem")

     walkTree(currentNode)

   } else {

     sMenuItem = escape("< SPAN id="" + thisMenu + "_span" + (i+1) +

       "" class='cellOff' " +

       "onMouSEOver="stateChange('',this,'');hideDiv(" +

       level + ")" onMouSEOut="stateChange('',this,'')" " +

       "onClick="location.href='" +

       currentNode.childNodes.item(0).text + "'" >" +

       currentNode.childNodes.item(1).text +

       "< /SPAN >< BR >

");

     eval(startMenu + "_Array[i] = sMenuItem")    


   }

  }

 }
   創建初始的數組(名為menu1_Array)之後,程序檢查第一個< menuItem >標記是否含有子節點。要獲得子節點的數量非常簡單,只需訪問父節點的childNodes.length屬性即可得到。

檢查子節點數量的時候,判斷條件是childNotes.length屬性是否大於1,這是因為節點中的文本也是一個節點,length值包含它。如果以等於1作為判斷條件,即使唯一的子節點是文本節點而不是元素節點,if語句也將返回true(盡管我們還可以檢查子節點的類型,但不如當前方法簡潔)。
   分析節點的同時,所有必需的< DIV >屬性和客戶端事件響應代碼(文本)都保存到了變量sMenuItem,然而又把sMenuItem保存到數組,它在數組中的位置由當前在循環中的位置決定。如果當前節點包含元素類型的子節點,則調用walkTree()並將當前節點作為參數傳遞給它。
   2.2 遞歸函數walkTree
   所謂的遞歸函數,就是自己調用自己的函數。遞歸函數非常適合於處理層次結構的數據。在遍歷XML樹時,使用遞歸函數可以減少大量的代碼,一個簡單的遞歸結構可以處理數量龐大的子菜單。
   walkTree()函數以一個節點為參數,與前面所討論的過程類似,它將創建數組並檢查childNode.length屬性。下面是walkTree()的完整代碼:
  function walkTree(node) {

   level += 1;

   // 數組名字為DIV的名字加"_Array"

   eval("var " + currentMenu + "_Array = new Array()")      

   for (var j=2;j< node.childNodes.length;j++) {

    newNode = node.childNodes.item(j)

    if (newNode.hasChildNodes() == true && newNode.childNodes.length >2) {

    // 每一個節點擁有0=鏈接和1=文本節點

    // 因此如僅有這些子節點則不必再次調用函數

     currentMenu = currentMenu + "_" + (j-1);

     thisMenu = currentMenu.substring(0,currentMenu.length-2);

     sMenuItem = escape("< SPAN id="" + thisMenu + "_span" + (j-1) +

        "" class='cellOff' " + "onMouSEOver="stateChange('" + currentMenu +
"',this," + level + ")" onMouSEOut="stateChange('',this,'')" >" +

        image + newNode.childNodes.item(1).text +

        "< /SPAN >< BR >

");

     eval(thisMenu + "_Array[j-2] = sMenuItem");

     walkTree(newNode);

    } else {

      sMenuItem = escape("< SPAN id="" + currentMenu + "_span" +

         (j-1) + "" class='cellOff' " +

         "onMouSEOver="stateChange('',this,'');hideDiv(" +

         level + ")" onMouSEOut="stateChange('',this,'')" " +

         "onClick="location.href='" +

         newNode.childNodes.item(0).text + "'" >" +

         newNode.childNodes.item(1).text +

         "< /SPAN >< BR >

");

     eval(currentMenu + "_Array[j-2] = sMenuItem");

    }  

   }

   arrayNamesArray[arrayNamesArray.length] = currentMenu;

   tempString = (unescape(eval(currentMenu + "_Array.join('')")))

   arrayHolderArray[arrayHolderArray.length] = tempString

 

   currentMenu = currentMenu.substring(0,currentMenu.length-2); 

//結束函數返回前一菜單項

   level -= 1;

  }
   如果能夠找到子節點,則函數以當前節點為參數調用自己。每次函數調用自己都生成相應的數組和sMenuItem變量。當前菜單的名字通過變量currentMenu保存,並在thisMenu中保存一個新的名字,這使得程序可以方便地找出菜單項之間的層次關系,在後面的客戶端腳本我們可以看到這一點。
   請仔細閱讀walkTree()函數的結束部分。在前面代碼中創建的arrayNamesArray數組用來保存菜單的名字,接下來變量tempString獲取當前節點的所有數組信息並將它組織成為一個字符串,這個字符串被加入到另外一個名為arrayHolderArray的數組。數組arrayNamesArray的用途是保存菜單項的名字,把菜單項(及其下屬的子菜單項)放入< DIV >區時需要用到這些名字,所有< DIV >區將參考arrayNamesArray的內容命名。數組arrayHolderArray保存所有那些將寫入各個< DIV >區的信息,這些信息包括菜單項的名字、鏈接以及客戶端腳本的事件信息。

   這些數組之間的關系初看起來有點復雜,仔細觀察其實不難理解。walkTree函數利用這些數組在少量的代碼之內完成了大量的工作。
   准備好上述數組之後,接下來就可以把< DIV >區以及它們的內容寫入Web頁面。下面的代碼負責這部分工作(參見XMLcreateMenu_Dynamic.ASP)。
 //翻轉數組以便形成正確的< DIV >次序

 arrayHolderArray.reverse()

 arrayNamesArray.reverse()

 //遍歷數組,輸出< DIV >標記以及各個菜單項的代碼

 for (i=0;i< arrayNamesArray.length;i++) {

  Response.Write("< div id='" + arrayNamesArray[i] + "' class='clsMenu' >");

  Response.Write(arrayHolderArray[i]);

  Response.Write("< /div >

");

 }
   為何要將數組的次序翻轉一下呢?簡而言之,它將簡化我們以後的操作。翻轉之後,程序輸出的< DIV >區即以層次結構中的最前面子菜單到最後面子菜單為序;或者說,翻轉數組之後保證各個< DIV >區在Z-軸方向上有正確的次序。如果你還是不能一下子領會,那麼請看下圖,這是在注釋了兩個reverse方法調用後的菜單效果:
翻轉數組之後,接著就輸出< DIV >標記及其內容。如前所述,< DIV >區的名字由數組arrayNamesArray決定,< DIV >區的內容由數組arrayHolderArray決定。下面的代碼是前面代碼所輸出< DIV >區的一個片斷,其他< DIV >區也非常相似:
 < div id='menu1' class='clsMenu' >

   < span id="menu1_span1"

      class='cellOff'

      onMouSEOver="stateChange('',this,'');hideDiv(1)"

      onMouSEOut="stateChange('',this,'')"

      onClick="location.href='/default/default.ASP'" >主頁

   < /span >< br >

   < span id="menu1_span2"

      class='cellOff'

      onMouSEOver="stateChange('menu1_2',this,1)"

      onMouSEOut="stateChange('',this,'')" >

      < img align=right vspace=2 height=10

        width=10 border=0 src=/School/UploadFiles_7810/201104/20110414130201849.gif>娛樂休閒

    < /span >< br >

 < /div >

XMLMenuScript.JS文件包含了所有支持客戶端菜單操作的JavaScript代碼。在文件前面定義的變量可以用來定制菜單項在各種狀態(選中或不選中)下的顯示顏色,具體請參見XMLMenuScript.JS中的說明。應當說明的是,在當前實現中,客戶端腳本代碼要求運行在IE 4或以上版本。如果你希望菜單同時能夠支持Netscape浏覽器,這部分代碼要作相應的修改。
   當用戶單擊某個鏈接(如本文的“網站”)時菜單系統啟動。啟動菜單的Html代碼如下所示:
 < A id="start" onClick="startIt('menu1',this,0)" >

< B >< FONT color="#FFFFFF" >網站< /FONT >< /B >

   < IMG src="/School/UploadFiles_7810/201104/20110414130201265.gif" width="20" height="11" border=0 >

 < /A >
   onClick事件中指定的startIt()函數有三個參數:菜單名字,一個鏈接本身的引用,菜單層次。由於此時剛開始啟動菜單系統,因此指定層次為0。
   雖然一個頁面中可以有一個以上的菜單,但任何時候可見的菜單只有一個。startIt()函數檢查菜單是否已經激活。如果沒有激活,則調用下面的stateChange()函數。此外,startIt()還必須在菜單活動的時候隱藏Html < SELECT >元素和Java Applet(如果存在的話,參見startIt()的實現代碼)。
 function stateChange(menu,thisItem,level) {        

 //menu =待顯示的菜單,thisItem=當前菜單項的< SPAN >

 //level=菜單嵌套層次

  

  //鼠標所指向的菜單項改變,改變高亮度狀態

  if (currentSpanElement != thisItem.id && started != true) {

   //這行代碼僅在第一次進入時有用

   if (currentSpanElement == "") currentSpanElement = thisItem.id;

   eItemOld = eval("document.all('" + currentSpanElement + "')");

   eItemNew = eval("document.all('" + thisItem.id + "')");

   eParent = eItemNew.parentElement;

   //必須設置DIV背景色,否則默認透明


   eParent.style.background = offCellColor;      

    

   //取消以前鼠標停留菜單項的高亮度顏色
eItemOld.style.background = offCellColor;

   eItemOld.style.color = offTextColor;

   //突出顯示新選中的菜單項

   eItemNew.style.background = onCellColor;

   eItemNew.style.color = onTextColor;

   //跟蹤最後鼠標停留的位置

   currentSpanElement = thisItem.id;

  }

  

  if (menu != "") {

   eMenu = eval("document.all('" + menu + "')");

   eItem = eval("document.all('" + thisItem.id + "')");

   hideDiv(level);

   //跟蹤已經打開(顯示)的菜單

   menuArray[menuArray.length] = menu;

   var positionX = eItem.parentElement.offsetLeft +

          offsetMenuX + document.body.scrollLeft;

   var positionY =  eItem.parentElement.offsetTop +

          eItem.offsetTop + offsetMenuY + document.body.scrollTop;

   if (started) {

    positionX = clickX + startDistanceX + document.body.scrollLeft 

    positionY = clickY + startDistanceY + document.body.scrollTop

   }

   //如果屏幕寬度不足,將菜單顯示位置左移

   if ((positionX + eMenu.offsetWidth) >= document.body.clIEntWidth) {

    positionX -= (eMenu.offsetWidth * 1.3);
positionY += 15;

   }

   //如果菜單位置偏左,則右移

   if ((positionX + eMenu.offsetWidth) < = eMenu.offsetWidth) {

    positionX += (eMenu.offsetWidth * 1.3);

   }

   //如果菜單偏下,則上移菜單,使其底部和浏覽窗口底部對齊

   if ((positionY + eMenu.offsetHeight) >= document.body.clIEntHeight) {

    if (started != true) positionY = document.body.clIEntHeight - eMenu.offsetHeight;

   }

   eMenu.style.left = positionX;

   eMenu.style.top = positionY;

   //如果沒有在ASP腳本中翻轉數組,使用下面這行代碼

   //eMenu.style.zIndex = level;        

   eMenu.style.visibility='visible';

  }

  //菜單已經顯示

  started = false;

 }
   stateChange()函數有三個參數:菜單名字,當前菜單項的< SPAN >元素,以及層次。如果用戶選擇了一個不同的< SPAN >元素(菜單項),stateChange()改變該菜單項的背景和文本顏色,同時改變以前選中菜單項的狀態。此外,stateChange()還負責調用其他函數隱藏菜單以及在菜單位置不合適時,修改菜單的位置。
   菜單所用的樣式在menuStyle.CSS中,修改該文件可調整菜單的外觀。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved