故事背景:由于最近太忙了,已经很久没有写jquery插件开发系列了。但是凭着自己对这方面的爱好,我还是抽了一些时间来过一下插件瘾的。今天的主题是导航菜单,这个我相信不管做B/S还是做C/S都非常熟悉一个功能模块。其实大家有没有发现,我们开发插件的目的是为了重用,既然是需要重用的肯定也是开发中常用的,所以说白了,我们开发插件的需求来自开发中常用的功能。只要你想,你仔细分析,相信绝大部分常用功能都可以分装出来做插件的。额。。。有种秀智商的赶脚啊,呵呵,不好意思,想到哪里就说道哪里了。相信大家还是能清楚啥时需要开发插件的。本篇文章其实需求来源是来源于我现在做的一个项目,但是后期我又做了优化,和原有需求不同。当然,我改的这个版本的样式就没有那么炫了。但是代码肯定优化了。

  还是我一直提到的,你开发插件,你肯定要清楚该插件是做啥的,啥时用。也就是需求分析要做好。相信有人会说又装13了,其实这不是装,因为menu是大家所熟知的,但是我也相信就算大家熟知的事情你也不一定就了解它的所有功能。开发的插件是根据业务来的,不同的业务需求对导航菜单的要求也不同。不管是样式还是功能。例如面包削,这个就是菜单的“附赠”品,很多网站需要有,但是也有很多网站不需要。所以,请大家也不要装,除非你真的是大牛,可以目空一切。但是一般大牛都好像很谦虚的,很深奥的样子,至少我看到的都不错,^_^ 

  对了,其实有一句好我很想说的就是,如果你喜欢或者有意向开发jquery插件的,请你熟悉一下div+css页面布局,如果你这方面不熟悉,其实是苦恼的。相信开发过的人都知道。很多人会说我们公司有前端专门做样式的,但是我想说的是,多学点没什么坏处。这样方便你开发,能提高自己写的代码质量。

  好了,感觉一扯就像吃了炫迈似的,根本停不下来,其实也就是说开发需要扯。。。^_^。。。我是想看文章的人也很累,让大家轻松一点。

  故事主题:jquery插件开发——Menu,导航菜单开发。

  正常的menu功能:1、实现菜单的切换  2、实现切换内容的加载  3、控制菜单的收缩  4、控制样式变更

  附加功能:面包削导航

  本次开发用了大量的递归思想,其实好的递归可以为你节省很多很多代码,但是说实话,复杂的递归在错误排查上还是很繁琐的。所以我们要量力而行,当然还是希望大家能熟练运用递归,毕竟你将来是要成为牛X的猿,所以你就必须会各种算法。

  当然本次和上次开发的插件想必又添加了委托思想事件句柄。当然这个我也得感谢我的一个同事,是在他的提醒下,我添加的,这样写的确实现了元素和事件间的解耦。当然这个也是模仿面向对象思想中的开发了。

  其实当你真正去多次开发插件时候,你就会发现,其实开发插件就分三步走。

  第一步:定义插件和参数  var menu = function () {this.defaultParams = {};};

  第二步:定义插件属性、方法    menu.prototype = {constructor: menu,init:function (params){}};

  第三步:对外分装  $.menu = new menu();  

  其实就是这三步,然后写好每一步实现就好了。很简单吧。^_^我感觉这三步就像一个系统的架构一样,大的方向定下来,下面就是向框架中填充东西,实现功能即可。当然,开发中你要把公共部分先剥离出来,下面具体讲解开发的代码。分为以下几个部分。

  第一部分:这部分是公共部分,比上一次写的多了delegate,这个下面注册事件的时候会用到,理解就像面向对象语言中理解一样。如果对委托不是很清楚的可以百度看看,相信这种思想已经为大部分人所知了。

  代码如下:

 1 $(function () {
 2     // 说明:创建委托函数
 3     //      context:函数上下文
 4     //      params:参数【必须是数组形式】,可以为空
 5     Function.prototype.delegate = function (context, params) {
 6         var func = this;
 7         return function () {
 8             if (params == null) {
 9                 return func.apply(context);
10             }
11             return func.apply(context, params);
12         };
13     };
14     var menuCommon = {
15         coverObject: function (obj1, obj2) {
16             var o = this.cloneObject(obj1, false);
17             var name;
18             for (name in obj2) {
19                 if (obj2.hasOwnProperty(name)) {
20                     o[name] = obj2[name];
21                 }
22             }
23             return o;
24         },
25         cloneObject: function (obj, deep) {
26             if (obj === null) {
27                 return null;
28             }
29             var con = new obj.constructor();
30             var name;
31             for (name in obj) {
32                 if (!deep) {
33                     con[name] = obj[name];
34                 } else {
35                     if (typeof (obj[name]) == "object") {
36                         con[name] = $.cloneObject(obj[name], deep);
37                     } else {
38                         con[name] = obj[name];
39                     }
40                 }
41             }
42             return con;
43         },
44         // 说明:实现委托
45         delegate: function (func, context, params) {
46             if ($.isFunction(func)) {
47                 return func.delegate(context, params);
48             } else {
49                 return $.noop;
50             }
51         },
52         getParam: function (param) {
53             if (typeof (param) == "undefined") {
54                 return "";
55             } else {
56                 return param;
57             }
58         }
59     };
60 });

   第二部分:定义导航默认参数,其中的data参数的格式我已经给出。

 1 var menu = function () {
 2         //参数定义
 3         this.defaultParams = {
 4             id: "",     //导航容器ID
 5             data: "",   //数据  包含title、depth、recordId、parentId、children
 6             //格式:[
 7             //       {title: "第一级——1",
 8             //        depth:1,
 9             //        recordId:1,
10             //        parentId:0,
11             //        children:[
12             //                  {title: "第二级",
13             //                   depth:2,
14             //                   recordId:3,
15             //                   parentId:1,
16             //                   children:[]
17             //                  }]
18             //       },
19             //       {title: "第一级——2",
20             //        depth:1,
21             //        recordId:2,
22             //        parentId:0,
23             //        children:[
24             //                  {title: "第二级",
25             //                   depth:2,
26             //                   recordId:4,
27             //                   parentId:2,
28             //                   children:[]
29             //                  }]
30             //       }]
31             boolBreadCut: true,    //是否要面包削
32             breadCutId: "",         //面包削ID
33             navClickCallback: $.noop     //导航点击回调事件
34         };
35         this.options = {};
36  };

  第三部分:定义属性、方法,并代码实现。这部分很重要,封装了各种方法,包括我说的事件句柄、递归等思想都在这里体现。代码中有注释。其中createMenu、getNodeById  getBreadCutNameList这个三个方法是用递归实现的。

  1 menu.prototype = {
  2         constructor: menu,
  3         init: function (params) {
  4             this.options = $.coverObject(this.defaultParams, params);
  5             this._init();
  6         },
  7         _init: function () {
  8             this._initMenu();
  9         },
 10         _initMenu: function () {
 11             if (this.options.data == null) {
 12                 return;
 13             }
 14             if (this.options.data.length < 1) {
 15                 return;
 16             }
 17             var htmlStr = this.createMenu(this.options.data, this.options.id, "");
 18             $("#" + this.options.id).html(htmlStr);
 19             //注册导航事件
 20             this._registeNavClick();
 21         },
 22
 23         //生成菜单的Html元素
 24         createMenu: function (data, id, htmlStr) {
 25             $.each(data, function (i, item) {
 26                 var depth = item.depth;
 27                 var recordId = item.recordId;
 28                 var parentId = item.parentId;
 29                 var marginLeft = parseInt(item.depth) * 20;
 30
 31                 htmlStr += "<div class='zwsMenu' depth='" + depth + "'>";
 32
 33                 if (depth === 1) {
 34                     htmlStr += "<div id='" + recordId + "' isShow='true' depth='" + depth + "' parentId='" + parentId + "' class='menu_depth_1' >";
 35                     htmlStr += "    <div class='menu_depth_1_icon' attrIcon='firstLevel' style ='margin-left:" + marginLeft + "px;'></div>";//第一级小图标
 36                     htmlStr += "    <div class ='meun_title' >" + item.title + "</div>";//标题
 37                     htmlStr += "</div>";
 38                 } else {
 39                     htmlStr += "<div id='" + recordId + "' isShow='true' depth='" + depth + "' parentId='" + parentId + "' class='menu_depth_other' >";
 40                     htmlStr += "    <div class='menu_depth_other_icon' attrIcon='otherLevel' style ='margin-left:" + marginLeft + "px;'></div>";//其他级小图标
 41                     htmlStr += "    <div class ='meun_title' >" + item.title + "</div>";//标题
 42                     htmlStr += "</div>";
 43                 }
 44
 45                 if (item.children != null && item.children.length > 0) {
 46                     htmlStr += "<div class='meun_navArea' depth='" + item.children[0].depth + "' parentId='" + recordId + "' isShow='false' navArea=''>";
 47
 48                     htmlStr = menu.prototype.createMenu(item.children, id, htmlStr);
 49
 50                     htmlStr += "</div>";
 51                 }
 52
 53                 htmlStr += "</div>";
 54             });
 55
 56             return htmlStr;
 57         },
 58
 59         /*****************(注册事件 begin)*****************/
 60
 61         //说明:
 62         //      注册导航事件
 63         _registeNavClick: function () {
 64             var options = this.options;
 65
 66             $("div[depth][isShow='true']").each(function (i, item) {
 67                 var itemClick = $.delegate(menu.prototype._handleNavClick, this, [{ item: item }]);//样式改变
 68                 var itemClickCallBack = $.delegate(options.navClickCallback, this);//回调事件
 69                 var itemShowBreadCut = $.delegate(menu.prototype.createBreadCut, this, [{ item: item, options: options }]);//面包削
 70
 71                 $(item).click(itemClick);
 72                 $(item).click(itemClickCallBack);
 73                 $(item).click(itemShowBreadCut);
 74
 75             });
 76         },
 77
 78         //说明:
 79         //      注册面包削事件
 80         _registeBreadCutClick: function () {
 81             $(".meun_breadCut_name").each(function (i, item) {
 82                 var breadCutId = $(this).attr("id");
 83                 var itemClick = $.delegate(menu.prototype._handleBreadCutClick, this, [{ breadCutId: breadCutId }]);//样式改变
 84                 $(item).click(itemClick);
 85             });
 86         },
 87
 88         /*****************(注册事件 end)*****************/
 89
 90         /*****************(事件句柄 begin)*****************/
 91
 92         //说明:
 93         //      导航事件句柄
 94         //      params:导航每行元素
 95         _handleNavClick: function (params) {
 96             var id = params.item.id;
 97
 98             var isShow = $("div[navArea][parentId='" + id + "']").attr("isShow");
 99             var depth = parseInt($("#" + id).attr("depth"));
100             var parentId = parseInt($("#" + id).attr("parentId"));
101
102             //当前深度级的导航
103             var currDepthLevel = $("div[parentId='" + parentId + "'][depth='" + depth + "']");
104
105             //获取下级导航区域
106             var navHide = currDepthLevel.next("div[depth='" + (depth + 1) + "'][navArea]");
107             navHide.attr("isShow", "false").css("display", "none");
108
109             //将所有导航都置成 未选中状态
110             var navHideIconOtherLevel = $("div[attrIcon='otherLevel']");
111             var meunTitle = $("div.meun_title");
112             navHideIconOtherLevel.removeClass("menu_depth_other_icon_selected").addClass("menu_depth_other_icon");
113             meunTitle.removeClass("meun_title_color");
114
115             //当点击第一级导航时候
116             if (depth === 1) {
117                 var navHideIconFirstLevel = $("div[attrIcon='firstLevel']");
118                 navHideIconFirstLevel.removeClass("menu_depth_1_icon_selected").addClass("menu_depth_1_icon");
119             }
120
121             //获取第一级导航和其他级导航中图标
122             var iconFirst = $("#" + id).find("div[attrIcon='firstLevel']");
123             var iconOther = $("#" + id).find("div[attrIcon='otherLevel']");
124             var currTitle = $("#" + id).find("div.meun_title");
125
126             //控制当前点击的导航的下级导航是否显示
127             var navCurr = $("div[parentId='" + id + "']");
128             if (isShow == "true") {
129                 navCurr.attr("isShow", "false").css("display", "none");
130
131                 currTitle.removeClass("meun_title_color");
132                 iconFirst.removeClass("menu_depth_1_icon_selected").addClass("menu_depth_1_icon");
133                 iconOther.removeClass("menu_depth_other_icon_selected").addClass("menu_depth_other_icon");
134             }
135             else {
136                 navCurr.attr("isShow", "true").css("display", "block");
137
138                 currTitle.addClass("meun_title_color");
139                 iconFirst.removeClass("menu_depth_1_icon").addClass("menu_depth_1_icon_selected");
140                 iconOther.removeClass("menu_depth_other_icon").addClass("menu_depth_other_icon_selected");
141             }
142         },
143
144         //说明:
145         //      面包削事件句柄
146         _handleBreadCutClick: function (params) {
147             var breadCutId = params.breadCutId;
148             var navId = breadCutId.substr(9, breadCutId.length - 9);
149             $("#" + navId).click();
150         },
151
152         /*****************(事件句柄 end)*****************/
153
154         //说明:
155         //     验证面包削
156         validateBreadCut: function (options) {
157             if (!options.boolBreadCut) {
158                 return false;
159             }
160             var breadCutObj = $("#" + options.breadCutId);  //面包削区域
161             if (options.breadCutId == "" || breadCutObj.length < 1) {
162                 return false;
163             }
164             return true;
165         },
166
167         //说明:
168         //      创建面包削
169         createBreadCut: function (params) {
170             var item = params.item;
171             var itemId = item.id;
172             var options = params.options;
173             var optionData = options.data;
174
175             if (!menu.prototype.validateBreadCut(options)) {
176                 return;
177             }
178
179             var depth = parseInt($("#" + itemId).attr("depth"));
180             var separator = "<div class='meun_breadCut_separator'> &gt; </div>";//分隔符
181             var breadCutHtml = "";
182             breadCutHtml += "<div class='meun_breadCut'>";
183
184             var itemNode = menu.prototype.getNodeById(itemId, optionData);
185
186             var breadCutNodeList = menu.prototype.getBreadCutNodeList(itemNode, optionData, []);
187
188             for (var i = 1; i <= depth; i++) {
189                 breadCutHtml += "<div id='breadCut_" + breadCutNodeList[depth - i].recordId + "' class='meun_breadCut_name'>";
190                 breadCutHtml += breadCutNodeList[depth - i].title;
191                 breadCutHtml += "</div>";
192                 if (i != depth) {
193                     breadCutHtml += separator;
194                 }
195             }
196             breadCutHtml += "</div>";
197
198             $("#" + options.breadCutId).html(breadCutHtml);
199
200             //注册事件
201             menu.prototype._registeBreadCutClick();
202
203         },
204
205         //说明:
206         //      获取面包削列表
207         //      item:当前点击的导航
208         //      optionsData:数据源
209         //      breadCutNameList:返回列表
210         getBreadCutNodeList: function (item, optionData, breadCutNameList) {
211             if (item != null && item.parentId >= 0) {
212                 var node = menu.prototype.getNodeById(item.recordId, optionData);
213                 breadCutNameList.push(node);//获得列表
214
215                 item = menu.prototype.getNodeById(item.parentId, optionData);
216                 menu.prototype.getBreadCutNodeList(item, optionData, breadCutNameList);
217             }
218             return breadCutNameList;
219         },
220
221         //说明:
222         //      根据ID获取节点
223         //      id:节点ID
224         //      optionsData:数据源
225         getNodeById: function (id, optionsData) {
226             if (id < 1) {
227                 return null;
228             }
229             $.each(optionsData, function (i, v) {
230                 if (v.recordId == id) {
231                     nodeTS = v;
232                     return false;
233                 }
234                 if (v.children.length > 0) {
235                     menu.prototype.getNodeById(id, v.children);
236                 }
237             });
238             return typeof (nodeTS) !== "undefined" ? nodeTS : null;
239         }
240     };

menu.prototype 代码

  第四部分:这部分很简单,就是对外封装,一句话而已。

1 $.menu = new menu();

  第五部分:这部分当然是调用啦^_^,具体的参数说明在定义默认参数的时候都用注释,这里就不再累述。

1 $.menu.init({
2       id: "leftMenu",
3       data: data,//注意格式
4       navClickCallback: function () {
5       },
6       boolBreadCut: true,
7       breadCutId: "mbx"
8 });

 第六部分:样式表,本次样式比较简单、少,所以可以贴出来。

 1 .menu_depth_1 {
 2     width: 200px;cursor: pointer;height: 33px;line-height: 33px;
 3     margin-top: 2px;background-image: url("../Images/MenuImg/bg.png");
 4 }
 5 .menu_depth_1_icon {
 6     width: 11px;height: 11px;background-image: url("../Images/MenuImg/right-depth1.png");
 7     margin-top: 10px;margin-right: 3px;float: left;
 8 }
 9 .menu_depth_1_icon_selected {
10     width: 11px;height: 11px;background-image: url("../Images/MenuImg/down-depth1.png");
11     margin-top: 10px;margin-right: 3px;float: left;
12 }
13 .menu_depth_other {
14     width: 200px;cursor: pointer;background-color: #FFFFFF;height: 33px;line-height: 33px;display: none;
15     border-bottom: 1px dashed #E0E0E0;
16 }
17 .menu_depth_other_icon {
18     width: 7px;height: 7px;background-image: url("../Images/MenuImg/right-depth2-1.png");
19     margin-top: 13px;margin-right: 3px;float: left;
20 }
21 .menu_depth_other_icon_selected {
22     width: 7px;height: 7px;background-image: url("../Images/MenuImg/right-depth2-2.png");
23     margin-top: 13px;margin-right: 3px;float: left;
24 }
25 .meun_title {
26     width: auto;height: 100%;float: left;
27 }
28 .meun_title_color {
29     color: #FF6600;
30 }
31 .meun_navArea {
32     width: 100%;height: auto;
33 }
34
35 /*面包削*/
36 .meun_breadCut {
37     width: 100%;height: 30px;line-height: 30px;
38 }
39 .meun_breadCut_name {
40     width: auto;height: 30px;line-height: 30px;cursor: pointer;float: left;
41 }
42 .meun_breadCut_separator {
43     width: auto;height: 30px;line-height: 30px;margin: 0px 5px;float: left;
44 }

   第七部分:当然是最后测试了啊,测试很重要,要相信好的代码是测出来的。哈哈。。。

  第八部分:效果图

  本来是没有添加这部分内容的,原因是当时我没有好的图片做效果(本人随熟练布局,但是不会ps。。。),今天我请我公司的UI设计师给我简单的画了几张图,在此也表示很感谢我的那位同事。下面是我截的效果图,效果图上也有些说明。可能不是很好看,但是功能杠杠的,哈哈^_^

  总结:其实本次开发比较急,按照我上两篇文章,其实我应该在添加一个主题部分的,当然这里就是为什么说我要大家学习div+css的原因了,如果你会布局,你可以做出各种你喜欢的主题风格。这次我偷懒了,没有加上,后期我会补上。文章比较长,很多知识点我也没有写详细。如果有需要源码的或者想共同探讨的同仁,随时联系我,QQ:296319075 ,注明园友就好,同时也希望大家也能提出宝贵意见,不吝赐教。秉承共同探讨、共同进步!如有转载,请注明出处,谢谢!^_^

  

  

转载于:https://www.cnblogs.com/mzws/p/menu.html

jQuery 插件开发——Menu(导航菜单)相关推荐

  1. 基于jQuery垂直多级导航菜单代码

    基于jQuery垂直多级导航菜单代码是一款黑色风格的jQuery竖直导航菜单特效下载.效果图如下: 在线预览    源码下载 实现的代码. html代码: <ul class="ce& ...

  2. jQuery天猫商品分类导航菜单

    jQuery天猫商品分类导航菜单 一.HTML模块相关源码 HTML文件:index.html <!DOCTYPE html> <html><head><me ...

  3. BootstrapBlazor实战 Menu 导航菜单使用(2)

    接上篇: B08. BootstrapBlazor实战 Menu 导航菜单使用(1) 实战BootstrapBlazorMenu 导航菜单的使用, 以及整合Freesql orm快速制作菜单项数据库后 ...

  4. ant vue 树形菜单横向显示_ant design vue menu 导航菜单

    ant design vue menu 导航菜单 ant design vue menu 导航菜单是一个网站或者系统的重要功能,通过导航可以对网站或者系统的功能进行分门别类. 水平导航菜单 例子 首页 ...

  5. React Ant Design Menu导航菜单跳转

    昨天小编在学习React使用Ant Design Menu导航菜单时,发现Ant Design4.2版本之后的 Menu 导航菜单更新了写法.新写法不再需要自行拼接 JSX,直接采用数组写法直接用 i ...

  6. jq ajax写二级导航,jQuery实现二级导航菜单的示例

    实用JQ实现导航二级菜单效果,导航菜单在网站中非常常见,有的网站可能会出现三级菜单及多级菜单模式,下面我们来简单的实现一个二级菜单的效果. 部分效果截图: 整体代码: 导航菜单案例 *{ paddin ...

  7. vue、Menu 导航菜单、Menu属性事件、vue Menu 全部导航菜单、vue Menu 全部属性事件

    vue.Menu 导航菜单.Menu属性事件.vue Menu 全部导航菜单.vue Menu 全部属性事件 设计规则 何时使用 代码演示 顶部导航 内嵌菜单 缩起内嵌菜单 只展开当前父级菜单 垂直菜 ...

  8. 从Antd 源码到自我实现之 Menu 导航菜单

    Antd Menu 简述 Menu 为页面和功能提供导航的菜单列表. 导航菜单是一个网站的灵魂,用户依赖导航在各个页面中进行跳转.一般分为顶部导航和侧边导航,顶部导航提供全局性的类目和功能,侧边导航提 ...

  9. JQuery实现旅游导航菜单应用方便

    实现后的效果图是: HTML代码部分: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...

最新文章

  1. Python 自动化办公之 Excel 对比工具
  2. 变频电源要怎么测定额定容量
  3. .Net中删除数据前进行外键冲突检测
  4. oracle如何处理死锁,Oracle死锁处理实例
  5. 使用haystack实现django全文检索搜索引擎功能
  6. android 推流地址可以多人用,Android直播实现(一)Android端推流、播放
  7. html js关闭浏览器,js关闭页面(兼容浏览器)
  8. 已知背景和物体的均值方差,求最佳分割阈值
  9. 向日葵如何远程桌面Linux,最快的远程桌面向日葵软件
  10. css集合——好看的按钮样式+阴影+渐变
  11. R语言survival包的survfit函数拟合生存曲线数据、survminer包的ggsurvplot函数可视化生存曲线、conf.int参数为曲线添加置信区间、pval参数添加分组生存曲线p值
  12. 复数乘法的交换律、结合律及乘法 对加法的分配律证明过程
  13. pca降维算法java_PCA降维算法
  14. html 运行css实现3D正方体旋转制作进阶,调整图大小及远近(二)
  15. Qt实现word文档转html
  16. Ubuntu下软件安装
  17. 登录onedrive显示无法连接服务器,Win10专业版系统下OneDrive无法登录提示连接到OneDrive时出现问题怎么办...
  18. 交换机上STP端口角色选举规则
  19. 【Python自动化Excel】pandas处理Excel的拆分、合并
  20. 经典大学课程:石油大学教学视频

热门文章

  1. CSDN图片去水印干货
  2. 大一c语言餐馆点菜对话,餐馆点餐英语情景对话练习
  3. 大潜能:如何借助他人的力量获得成功
  4. 阿里电话面试总结(人生第一次面试!!)
  5. 计算机教学质量提升,计算机科学系开展系列活动提升教育教学质量
  6. 十年学会程序设计(十年磨一剑)
  7. python开发工具PyCharm最新版本新增功能介绍
  8. Java常用算法——迭代 递归篇
  9. 小白兔是世界上笑话最多的禽兽
  10. 说明此动作能充分运动到下半身有效锻炼臀部及腹