1 自定義菜單導航欄插件的必要性
看圖說話,下面是利用自定義的菜單導航欄插件simpleMenu創建的網站導航示例:
插件默認提供的是如上圖的導航欄樣式,即一二級菜單為橫向分布;三四級菜單為縱向分布。
使用插件時,可以修改默認參數,目前插件提供了設置菜單的分布方式:橫向或縱向;菜單的位置:依賴上一級菜單欄的定位:上下左右定位。
修改調用參數,將一二級菜單改為縱向排列;並將三級菜單的顯示位置改為二級菜單欄的右側(其他的和默認保持一致),修改后運行效果如下圖:
細心的觀察,會發現上面兩個菜單導航欄的風格基本一致,只是在局部有所調整。之所以這樣設計,是因為隨着網站的逐漸豐富,菜單導航欄的需求也會經常發生變化。通過參數化配置菜單,能夠在現有的基礎上,快速適應新的變化。當然這樣做也會對我們編寫插件提出更要的要求和更大的挑戰,況且始終無法做一個萬能的菜單出來!
2 使用jQuery UI的Widget庫自定義菜單導航欄插件
jQuery UI的功能很強大,主要分為3個部分:交互、微件(Widget)和效果庫。其中交互是一些與鼠標交互的內容如拖動、縮放、選擇和排序等;微件主要是一些節目的擴展包括對話框、日歷、進度條,當然也有類似的菜單導航;效果庫是用於提供豐富的動畫效果,讓動畫不在局限於animate方法。
雖然jQuery UI已經提供了類似導航的功能,但其功能和效果往往難以滿足復雜項目的需求。下面說說重點,我所希望實現的插件功能是:插件對外提供創建菜單的接口函數,輸入參數為包含菜單項信息的數據源,輸出為對應的多級菜單。利用jQuery UI的Widget庫,我們可以輕松自定義新的jQuery UI微件,滿足上述功能需求。
2.1 創建Widget插件
通過Widget Factory創建微件的方法是向$.widget()傳遞一個widget的名稱和原型對象。例如在菜單導航欄插件中,定義方式如下:
var menu = $.widget('myPlugin.simpleMenu', { version: "1.0.1", pathPrefix: $.myPlugin.config.pathPrefix, options: { /*default params*/ }, _create: function () { }, /*具體實現函數略*/ _destroy: function() { }, });
這里在myPlugin命名空間下定義了一個名為simpleMenu的Widget,即菜單導航欄插件。在給定的原型對象中還定義了插件的版本,路徑前綴,默認參數,創建函數,實現函數等。要注意的是,Widget插件的真正創建時從_create開始的,這個函數我們可以重寫覆蓋,至於內部是怎么調用的這個過程插件對我們透明。
2.2 需要實現的具體功能
之前已經說了,菜單導航欄插件的輸入數據為包含菜單項信息的數據源。考慮到通用性,推薦使用JSON作為數據源。JSON數據的組裝以及讀取都很簡單,難點在於如何將JSON數據轉換為任意層次的菜單,並且保證菜單每一級菜單項都能正確地綁定監聽事件(鼠標點擊或鼠標經過、離開等事件)。
生成的菜單導航和原始的JSON數據都是具有層次結構的,為了生成與JSON層次相同的菜單層次,最簡單有效的方法就是遞歸調用。然而多級菜單的遞歸調用並不是一次成型的,而是按需調用——用戶點擊或經過某個菜單項時才觸發一次遞歸調用。下面首先貼出createMenu的代碼,這段代碼主要實現的就是根據menuList(待加載的菜單項集合)和menuPath(上一級菜單項的路徑)來加載當前菜單項集合。例如第一次執行該函數時,menuList為原始的菜單項-JSON數組,menuPath為"item"-頂層初始值;遍歷menuList的每個元素(對應一級菜單的菜單項),首先生成帶初始樣式的html結構;然后設置當前級別(如一級)菜單的狀態,包括在文檔中的位置,綁定事件,以及在菜單項過多的時候及時提供滾動換頁功能;遞歸在綁定事件函數中產生,當用戶觸發綁定事件時,會調用createMenu函數,並傳遞兩個參數:觸發菜單項的下級菜單集合以及路徑信息。
createMenu私有函數的實現如下:
_createMenu: function(menuList, menuPath){ var pathArr = menuPath.split('-'), level = pathArr.length, menuClass = ".menu" + level, menuDiv, menuUl, content = "", menuSubflag, menuName, menuItem; if((menuDiv = this.element.find(menuClass)).length === 0){ return; } menuUl = menuDiv.children("ul"); /*循環生成level級菜單的html代碼並添加到菜單ul中*/ for(var i = 0, len = menuList.length; i < len; i++){ menuItem = menuList[i]; menuName = menuItem.menuName; menuSubflag = ""; /*判斷菜單項是否為葉子菜單(不含子菜單)*/ if(menuItem.subMenu && menuItem.subMenu.length > 0){ if(level > 1){ menuName += "<div class='sub'></div>"; } } else{ menuSubflag = " class = 'leaf'"; } content += "<li data-path='" + menuPath + "-" + (i + 1) + "'" + menuSubflag + ">" + menuName + "</li>"; } menuUl.html(content); menuDiv.show(); /*顯示當前的菜單欄(有些菜單欄默認設置為隱藏)*/ /*設置level級菜單欄的位置*/ this._setPosition(pathArr, menuDiv); /*綁定菜單鼠標點擊或經過事件*/ this._bindMenuEvents(menuList, menuUl, level); /*設置菜單的水平滾動*/ this._setMenuScroll(); }
綁定菜單項事件函數如下:
/*菜單事件函數,處理菜單的點擊或鼠標經過事件*/ _bindMenuEvents: function(menuList, menuUl, level){ /*記錄當前的this指針,以便在綁定函數內部使用*/ var that = this; /*給菜單項綁定鼠標事件-鼠標點擊或是鼠標經過事件*/ menuUl.children("li").bind(this.options.menuEvents[level - 1], (function(menuList){ return function(){ $(this).addClass('select').siblings().removeClass('select'); var subMenu = menuList[$(this).index()].subMenu; that._createMenu(subMenu, $(this).attr("data-path")); } })(menuList)); /*給葉子菜單項綁定鼠標點擊事件-執行跳轉(一級菜單不執行跳轉)*/ if(level <= 1){ return; } menuUl.children("li.leaf").click(function(menuList){ return function(){ var menuItem = menuList[$(this).index()]; that._executeMenu(menuItem); }; }(menuList)); }
上面的代碼實現了任意級菜單的動態生成。接下來需要做的只是在此基礎上豐富插件,增加靈活性。后續開發中,我增加了設置菜單排列方向的功能:比如設置一級菜單橫向或縱向排列;還增加了菜單定位功能:比如生成的二級菜單相對於觸發它的一級菜單項的位置靠右或正下方顯示。
在提供額外功能的同時,也面臨更多的問題:首先這些功能增加了使用者的學習成本,其次有些功能如菜單定位中存在一些並不合理的設置。這些都期待在以后不斷完善!
3 插件使用方法
插件的使用方法:首先需要提供一個格式正確的JSON菜單數據,目前自定義的插件在處理時主要依據JSON中的menuName、subMenu、actionList等key來取得實際的value值,所以提供的JSON數據必須類似下面的例子形式。然后使用插件名來調用插件時:注意在插件參數中除了提供菜單的基本信息外,還可以指定一個回調函數來處理鏈接的跳轉;通過回調函數實現了菜單生成插件本身與執行跳轉邏輯的解耦,方便使用者根據實際情況編寫回調函數來選擇跳轉的方式。
$(document).ready(function(){ var menuJson = [ {"menuName": "導航菜單欄目1", "menuCode": "", "subMenu": [ {"menuName": "二級菜單1", "menuCode": "", "subMenu": [ {"menuName": "三級菜單1", "menuCode": "", "subMenu": [ {"menuName": "四級菜單1", "menuCode": "", "actionList": "www.abchina.com", "subMenu": []} ] }, {"menuName": "三級菜單2", "menuCode": "", "subMenu": [ {"menuName": "四級菜單1", "menuCode": "", "subMenu": []} ] } ] }, {"menuName": "二級菜單2", "menuCode": "", "subMenu": []} ] }, {"menuName": "導航菜單欄目2", "menuCode": "", "subMenu": [ {"menuName": "二級菜單3", "menuCode": "", "subMenu": [ {"menuName": "三級菜單3", "menuCode": "", "subMenu": []} ] }, {"menuName": "二級菜單4", "menuCode": "", "subMenu": []} ] }, {"menuName": "導航菜單欄目3", "menuCode": "", "subMenu": [ {"menuName": "二級菜單5", "menuCode": "", "subMenu": []} ] } ]; $(".menu").simpleMenu({menuList: menuJson, executeMenu: function(actionList){ window.location.href = actionList; }}); });
本博文為個人原創作品,轉載時請注明出處。如有建議,請直接回帖交流,謝謝!