JQUERY 插件开发——MENU(导航菜单)
JQUERY 插件开发——MENU(导航菜单)
故事背景:由于最近太忙了,已经很久没有写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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
$(function () { // 说明:创建委托函数 // context:函数上下文 // params:参数【必须是数组形式】,可以为空 Function.prototype.delegate = function (context, params) { var func = this; return function () { if (params == null) { return func.apply(context); } return func.apply(context, params); }; }; var menuCommon = { coverObject: function (obj1, obj2) { var o = this.cloneObject(obj1, false); var name; for (name in obj2) { if (obj2.hasOwnProperty(name)) { o[name] = obj2[name]; } } return o; }, cloneObject: function (obj, deep) { if (obj === null) { return null; } var con = new obj.constructor(); var name; for (name in obj) { if (!deep) { con[name] = obj[name]; } else { if (typeof (obj[name]) == "object") { con[name] = $.cloneObject(obj[name], deep); } else { con[name] = obj[name]; } } } return con; }, // 说明:实现委托 delegate: function (func, context, params) { if ($.isFunction(func)) { return func.delegate(context, params); } else { return $.noop; } }, getParam: function (param) { if (typeof (param) == "undefined") { return ""; } else { return param; } } };}); |
第二部分:定义导航默认参数,其中的data参数的格式我已经给出。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
var menu = function () { //参数定义 this.defaultParams = { id: "", //导航容器ID data: "", //数据 包含title、depth、recordId、parentId、children //格式:[ // {title: "第一级——1", // depth:1, // recordId:1, // parentId:0, // children:[ // {title: "第二级", // depth:2, // recordId:3, // parentId:1, // children:[] // }] // }, // {title: "第一级——2", // depth:1, // recordId:2, // parentId:0, // children:[ // {title: "第二级", // depth:2, // recordId:4, // parentId:2, // children:[] // }] // }] boolBreadCut: true, //是否要面包削 breadCutId: "", //面包削ID navClickCallback: $.noop //导航点击回调事件 }; this.options = {};}; |
第三部分:定义属性、方法,并代码实现。这部分很重要,封装了各种方法,包括我说的事件句柄、递归等思想都在这里体现。代码中有注释。其中createMenu、getNodeById getBreadCutNameList这个三个方法是用递归实现的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
menu.prototype = { constructor: menu, init: function (params) { this.options = menuCommon.coverObject(this.defaultParams, params); this._init(); }, _init: function () { if (this.options.data == null) { return; } if (this.options.data.length < 1) { return; } var data = this.options.data; var id = this.options.id; var htmlStr = this.createMenu(data, id, ""); $("#" + id).html(htmlStr); this._registeNavClick(); }, //生成菜单的Html元素 createMenu: function (data, id, htmlStr) { $.each(data, function (i, item) { var depth = item.depth; var recordId = item.recordId; var parentId = item.parentId; var marginLeft = parseInt(item.depth) * 20; htmlStr += "<div class='zws' depth='" + depth + "'>"; htmlStr += " <div id='" + recordId + "' isShow='true' depth='" + depth + "' parentId='" + parentId + "' "; if (depth === 1) { htmlStr += "class='menu_depth_1' >"; } else { htmlStr += "class='menu_depth_other' >"; } htmlStr += "<div style ='width: 16px;height: 16px;margin-top: 11px;margin-left:" + marginLeft; htmlStr += "px;float: left;'></div>";//小图标 htmlStr += "<div class ='meun_title'>" + item.title + "</div>";//标题 htmlStr += "</div>"; if (item.children != null && item.children.length > 0) { htmlStr += "<div class='meun_navArea' depth='" + item.children[0].depth + "' parentId='" + recordId + "'"; htmlStr +=" isShow='false' navArea=''>"; //递归实现 htmlStr = menu.prototype.createMenu(item.children, id, htmlStr); htmlStr += "</div>"; } htmlStr += "</div>"; }); return htmlStr; }, //注册事件 _registeNavClick: function () { var options = this.options; $("div[depth][isShow='true']").each(function (i, item) { var itemClick = menuCommon.delegate(menu.prototype._handleNavClick, this, [{ item: item }]);//样式改变 var itemClickCallBack = menuCommon.delegate(options.navClickCallback, this);//回调事件 var itemShowBreadCut = menuCommon.delegate(menu.prototype.createBreadCut, this, [{ item: item, options: options }]);//面包削 $(item).click(itemClick); $(item).click(itemClickCallBack); $(item).click(itemShowBreadCut); }); }, //事件句柄 _handleNavClick: function (params) { var id = params.item.id; var isShow = $("div[navArea][parentId='" + id + "']").attr("isShow"); var depth = parseInt($("#" + id).attr("depth")); var parentId = parseInt($("#" + id).attr("parentId")); var navHide = $("div[parentId='" + parentId + "'][depth='" + depth + "']~div[depth='" + (depth + 1) + "'][navArea]"); navHide.attr("isShow", "false"); navHide.css("display", "none"); var navCurr = $("div[parentId='" + id + "']"); if (isShow == "true") { navCurr.attr("isShow", "false"); navCurr.css("display", "none"); } else { navCurr.attr("isShow", "true"); navCurr.css("display", "block"); } }, //说明: // 验证面包削 validateBreadCut: function (options) { if (!options.boolBreadCut) { return false; } var breadCutObj = $("#" + options.breadCutId); //面包削区域 if (options.breadCutId == "" || breadCutObj.length < 1) { return false; } return true; }, //说明: // 创建面包削 createBreadCut: function (params) { var item = params.item; var itemId = item.id; var options = params.options; var optionData = options.data; if (!menu.prototype.validateBreadCut(options)) { return; } var depth = params.item.depth; var separator = "<div class='meun_breadCut_separator'> > </div>";//分隔符 var breadCutHtml = ""; breadCutHtml += "<div class='meun_breadCut'>"; var itemNode = menu.prototype.getNodeById(itemId, optionData); var breadCutNameList = menu.prototype.getBreadCutNameList(itemNode, optionData, []); for (var i = 1; i <= depth; i++) { breadCutHtml += "<div class='meun_breadCut_name'>"; breadCutHtml += breadCutNameList[depth - i]; breadCutHtml += "</div>"; if (i != depth) { breadCutHtml += separator; } } breadCutHtml += "</div>"; $("#" + options.breadCutId).html(breadCutHtml); }, //说明: // 获取面包削名称列表 // item:当前点击的导航 // optionsData:数据源 // breadCutNameList:返回列表 getBreadCutNameList: function (item, optionData, breadCutNameList) { if (item != null && item.parentId >= 0) { var title = menu.prototype.getNodeById(item.recordId, optionData).title; breadCutNameList.push(title);//获得列表 item = menu.prototype.getNodeById(item.parentId, optionData); //递归实现 menu.prototype.getBreadCutNameList(item, optionData, breadCutNameList); } return breadCutNameList; }, //说明: // 根据ID获取节点 // id:节点ID // optionsData:数据源 getNodeById: function (id, optionsData) { if (id < 1) { return null; } $.each(optionsData, function (i, v) { if (v.recordId == id) { nodeTS = v; return false; } if (v.children.length > 0) { //递归实现 menu.prototype.getNodeById(id, v.children, nodeTS); } }); return nodeTS; } }; |
第四部分:这部分很简单,就是对外封装,一句话而已。
|
1
|
$.menu = new menu(); |
第五部分:这部分当然是调用啦^_^,具体的参数说明在定义默认参数的时候都用注释,这里就不再累述。
|
1
2
3
4
5
6
7
8
9
|
$.menu.init({ id: "leftMenu", data: data, navClickCallback:function() { //这里是点击导航的回调事件,一般用于加载页面 }, boolBreadCut: true, breadCutId: "mbx"}); |
第六部分:样式表,本次样式比较简单、少,所以可以贴出来。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
.menu_depth_1{ width: 200px;cursor:pointer; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; line-height: 1.8em !important; margin: 0px !important; outline: rgb(0, 0, 0) !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; min-height: inherit !important; color: gray !important; background: none !important;">#FFD3D2;height: 38px;line-height: 38px;margin-top:2px;}.menu_depth_other{ width: 200px;cursor:pointer; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; border-bottom-left-radius: 0px !important; border: 0px !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; line-height: 1.8em !important; margin: 0px !important; outline: rgb(0, 0, 0) !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important; box-sizing: content-box !important; font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; min-height: inherit !important; color: gray !important; background: none !important;">#FFD3D2;height: 38px;line-height: 38px;margin-top:2px;display:none}.meun_title{ width: auto;height: 100%;float: left;}.meun_navArea{ width:100%;height:auto;}/*面包削*/.meun_breadCut { width:100%;height:30px;line-height:30px;}.meun_breadCut_name{ width:auto;height:30px;line-height:30px;float:left}.meun_breadCut_separator { width:auto;height:30px;line-height:30px;margin: 0px 5px;float:left} |
第七部分:当然是最后测试了啊,测试很重要,要相信好的代码是测出来的。哈哈。。。
总结:其实本次开发比较急,按照我上两篇文章,其实我应该在添加一个主题部分的,当然这里就是为什么说我要大家学习div+css的原因了,如果你会布局,你可以做出各种你喜欢的主题风格。这次我偷懒了,没有加上,后期我会补上。文章比较长,很多知识点我也没有写详细。如果有需要源码的或者想共同探讨的同仁,随时联系我,QQ:296319075 ,注明园友就好,同时也希望大家也能提出宝贵意见,不吝赐教。秉承共同探讨、共同进步!如有转载,请注明出处,谢谢!^_^
JQUERY 插件开发——MENU(导航菜单)的更多相关文章
- 我收集到的最好的jQuery和CSS3导航菜单
jQuery和CSS3导航菜单在网页设计和开发的重要组成部分之一.利用jQuery+CSS3实现可以做出拥有各种动画效果的漂亮菜单.在这里,我们收集了一些最好的jQuery+CSS3实现的导航菜单. ...
- 20款jquery下拉导航菜单特效代码分享
20款jquery下拉导航菜单特效代码分享 jquery仿京东商城左侧分类导航下拉菜单代码 jQuery企业网站下拉导航菜单代码 jQuery css3黑色的多级导航菜单下拉列表代码 jquery响应 ...
- 基于jQuery垂直多级导航菜单代码
基于jQuery垂直多级导航菜单代码是一款黑色风格的jQuery竖直导航菜单特效下载.效果图如下: 在线预览 源码下载 实现的代码. html代码: <ul class="ce&q ...
- BootstrapBlazor实战 Menu 导航菜单使用(1)
实战BootstrapBlazorMenu 导航菜单的使用, 以及整合Freesql orm快速制作菜单项数据库后台维护页面 demo演示的是Sqlite驱动,FreeSql支持多种数据库,MySql ...
- B08. BootstrapBlazor实战 Menu 导航菜单使用(2)
接上篇: B08. BootstrapBlazor实战 Menu 导航菜单使用(1) 3.项目模板 节省时间,直接使用 Bootstrap Blazor App 模板快速搭建项目 传送门: https ...
- jQuery垂直二级导航菜单代码
http://www.sucaihuo.com/js/395.html 分享一个简单的垂直二级菜单导航. HTML <div id="my_menu" class=&qu ...
- jQuery+css3侧边栏导航菜单
效果体验:http://hovertree.com/texiao/jquery/37/ 代码如下: <!doctype html> <html lang="zh" ...
- 两种思想实现基于jquery的延时导航菜单,可做延时触发器!
1. 函数式 html如下: <div class="box"> <ul class="clear-fix"> <li class ...
- 使用Iview Menu 导航菜单(非 template/render 模式)
1.首先直接参照官网Demo例子,将代码拷贝进项目中运行, 直接报错: Cannot read property 'mode' of undefined. 然后查看官网介绍,有一行注意文字,好吧. 2 ...
随机推荐
- Ubuntu文件的复制、移动和删除命令
先说说cp复制命令 该命令的功能是将给出的文件或文件夹复制到还有一文件或文件夹中,同MSDOS下的copy命令一样,功能十分强大. 语法: cp [选项] 源文件或文件夹 目标文件或文件夹 说明:该命 ...
- 连载:面向对象的葵花宝典:思考、技巧与实践(39) - 设计原则 vs 设计模式
它的设计原则,和设计模式,是否该用它? ============================================================================= 在& ...
- log4j与commons-logging,slf4j的关系(转)
前面有一篇日志中简单的介绍了 log4j,同时也介绍了它与commons-logging的关系,但是突然冒出来一个slf4j,并且slf4j有取代commons-logging的趋势,所以,我们可以推 ...
- 添加AD验证(域身份验证)到现有网站
每个网站几乎都会有用户登录的模块,登录就会涉及到身份验证的过程.通常的做法是在页面上有个登录的Form,然后根据用户名和密码到数据库中去进行验证. 而验证后如何在网站的各个页面维持这种认证过的状态,有 ...
- 中国人被“清朝GDP世界第一”忽悠了!
中国人被"清朝GDP世界第一"忽悠了!"鸦片战争前的清朝GDP世界第一",这一说法在中国流传非常广.追根溯源,最早提出这一观点的似乎是英国学者麦迪森,他的一项猜 ...
- iframe参数
iframe参数: <iframe src="test.jsp" width="100″ height="50″ frameborder="no ...
- Nyoj 吝啬的国度(图论&&双DFS)
描述在一个吝啬的国度里有N个城市,这N个城市间只有N-1条路把这个N个城市连接起来.现在,Tom在第S号城市,他有张该国地图,他想知道如果自己要去参观第T号城市,必须经过的前一个城市是几号城市(假设你 ...
- 35,000FT大气压力的问题
原来的问题:压力在喷气客机的飞行FL350(35,000脚)大约210mmHg.这是个大气压的数量? 在每平方英寸磅? 多少帕斯卡尔? http://wenwen.sogou.com/z/q33797 ...
- Yii Framework2.0开发教程(5)数据库mysql性能
继续<Yii Framework2.0开发教程(3)数据库mysql入门> 首先给予一定的尊重yii2数据库支持引进 Yii 基于 PHP's PDO一个成熟的数据库访问层的建立.它提供了 ...
- js 正则学习小记之左最长规则
原文:js 正则学习小记之左最长规则 昨天我在判断正则引擎用到的方法是用 /nfa|nfa not/ 去匹配 "nfa not",得到的结果是 'nfa'.其实我们的本意是想得到整 ...