zoukankan      html  css  js  c++  java
  • 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:参数【必须是数组形式】,可以为空
     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 ,注明园友就好,同时也希望大家也能提出宝贵意见,不吝赐教。秉承共同探讨、共同进步!如有转载,请注明出处,谢谢!^_^

       

      

  • 相关阅读:
    女孩提出分手的N种理由
    Attribute应用,简化ANF自定义控件初始化过程
    关于Web的动态页面与静态页面分开的想法.
    .Net面试题
    算法题,不用递归,构造树型
    花两个小时,做了个分页控件
    事件应用,为系统提供扩展功能
    绘制半透明的图片
    Tile Studio简介(转载)
    Thinking in Java 摘录笔记
  • 原文地址:https://www.cnblogs.com/mzws/p/menu.html
Copyright © 2011-2022 走看看