700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > jQuery 插件开发——Menu(导航菜单)

jQuery 插件开发——Menu(导航菜单)

时间:2023-12-09 21:54:25

相关推荐

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 $(function () { 2// 说明:创建委托函数 3//context:函数上下文 4//params:参数【必须是数组形式】,可以为空 5Function.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};14var 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") {36con[name] = $.cloneObject(obj[name], deep);37 } else {38con[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: "", //面包削ID33 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:节点ID224 //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 { 2width: 200px;cursor: pointer;height: 33px;line-height: 33px; 3margin-top: 2px;background-image: url("../Images/MenuImg/bg.png"); 4 } 5 .menu_depth_1_icon { 6width: 11px;height: 11px;background-image: url("../Images/MenuImg/right-depth1.png"); 7margin-top: 10px;margin-right: 3px;float: left; 8 } 9 .menu_depth_1_icon_selected {10width: 11px;height: 11px;background-image: url("../Images/MenuImg/down-depth1.png");11margin-top: 10px;margin-right: 3px;float: left;12 }13 .menu_depth_other {14width: 200px;cursor: pointer;background-color: #FFFFFF;height: 33px;line-height: 33px;display: none;15border-bottom: 1px dashed #E0E0E0;16 }17 .menu_depth_other_icon {18width: 7px;height: 7px;background-image: url("../Images/MenuImg/right-depth2-1.png");19margin-top: 13px;margin-right: 3px;float: left;20 }21 .menu_depth_other_icon_selected {22width: 7px;height: 7px;background-image: url("../Images/MenuImg/right-depth2-2.png");23margin-top: 13px;margin-right: 3px;float: left;24 }25 .meun_title {26width: auto;height: 100%;float: left;27 }28 .meun_title_color {29color: #FF6600;30 }31 .meun_navArea {32width: 100%;height: auto;33 }34 35 /*面包削*/36 .meun_breadCut {37width: 100%;height: 30px;line-height: 30px;38 }39 .meun_breadCut_name {40width: auto;height: 30px;line-height: 30px;cursor: pointer;float: left;41 }42 .meun_breadCut_separator {43width: auto;height: 30px;line-height: 30px;margin: 0px 5px;float: left;44 }

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

第八部分:效果图

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。