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(导航菜单)的更多相关文章

  1. 我收集到的最好的jQuery和CSS3导航菜单

    jQuery和CSS3导航菜单在网页设计和开发的重要组成部分之一.利用jQuery+CSS3实现可以做出拥有各种动画效果的漂亮菜单.在这里,我们收集了一些最好的jQuery+CSS3实现的导航菜单. ...

  2. 20款jquery下拉导航菜单特效代码分享

    20款jquery下拉导航菜单特效代码分享 jquery仿京东商城左侧分类导航下拉菜单代码 jQuery企业网站下拉导航菜单代码 jQuery css3黑色的多级导航菜单下拉列表代码 jquery响应 ...

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

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

  4. BootstrapBlazor实战 Menu 导航菜单使用(1)

    实战BootstrapBlazorMenu 导航菜单的使用, 以及整合Freesql orm快速制作菜单项数据库后台维护页面 demo演示的是Sqlite驱动,FreeSql支持多种数据库,MySql ...

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

    接上篇: B08. BootstrapBlazor实战 Menu 导航菜单使用(1) 3.项目模板 节省时间,直接使用 Bootstrap Blazor App 模板快速搭建项目 传送门: https ...

  6. jQuery垂直二级导航菜单代码

    http://www.sucaihuo.com/js/395.html 分享一个简单的垂直二级菜单导航.   HTML <div id="my_menu" class=&qu ...

  7. jQuery+css3侧边栏导航菜单

    效果体验:http://hovertree.com/texiao/jquery/37/ 代码如下: <!doctype html> <html lang="zh" ...

  8. 两种思想实现基于jquery的延时导航菜单,可做延时触发器!

    1. 函数式 html如下: <div class="box"> <ul class="clear-fix"> <li class ...

  9. 使用Iview Menu 导航菜单(非 template/render 模式)

    1.首先直接参照官网Demo例子,将代码拷贝进项目中运行, 直接报错: Cannot read property 'mode' of undefined. 然后查看官网介绍,有一行注意文字,好吧. 2 ...

随机推荐

  1. 比ORA-24777: 我不使用不可移植数据库链接更郁闷的事情达成一致

    现场有一个同步误差,内容如下面:    java.sql.BatchUpdateException: ORA-24777: 不同意使用不可移植的数据库链路    at oracle.jdbc.driv ...

  2. Jquery 分页插件 Jquery Pagination

    Jquery 分页插件 Jquery Pagination 分页插件来说,我觉得适用就行,尽量简单然后能够根据不同的应用场景能够换肤.展现形式等. 对于初学者想写分页插件的同学,也可以看下源码,代码也 ...

  3. [jQuery1.9]Cannot read property ‘msie’ of undefined错误的解决方法

    原文:[jQuery1.9]Cannot read property 'msie' of undefined错误的解决方法 $.browser在jQuery1.9里被删除了,所以项目的js代码里用到$ ...

  4. Display Database Image using MS SQL Server 2008 Reporting Services

    原文 Display Database Image using MS SQL Server 2008 Reporting Services With the new release of MS SQL ...

  5. Ubuntu 15.10 x64 安装 Android SDK(转)

    操作系统:Ubuntu 15.10 x64 目标:安装 Android SDK 本文最后更新时间:2015-11-3 安装32位库文件 2013年9月的iPhone 5s是第一款64位手机,而Andr ...

  6. C++“窗体”程序设计启蒙

    [摘要]本文以C++菜菜鸟(仅仅须要学习了C++数据类型和控制结构就可以)为目标读者,用求解一元二次方程作为实例,展示窗体式程序的开发过程,获得初步体验.写作目的包含:(1)让学生通过模仿,开发出类似 ...

  7. eclipse+Java2WSDL+WSDL2Java 2012-12-06 12:32:43| 分类: j2ee |报道|字体大小 认购 一、eclipse如何使用低axis生成wsdl 可以使用

    eclipse+Java2WSDL+WSDL2Java 一.eclipse下怎样用axis生成wsdl 能够使用axis提供的Java2WSDL功能生成所要公布类的 WSDL,过程例如以下:  1.在 ...

  8. DirectX 9 UI三种设计学习笔记:文章4章Introducing DirectInput+文章5章Wrapping Direct3D

           本文从哈利_创.转载请注明出处.有问题欢迎联系本人!        邮箱:2024958085@qq.com 上一期的地址: DX 9 UI设计学习笔记之二 第4章 Introducin ...

  9. 第9章 组合模式(Composite Pattern)

    原文 第9章 组合模式(Composite Pattern) 概述: 组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理 ...

  10. C#如何设置session过期时间

    1.操作系统  步骤:开始——〉管理工具——〉Internet信息服务(IIS)管理器——〉网站——〉默认网站——〉  右键“属性”——〉主目录——〉配置——〉选项——〉启用会话状态——〉会话超时(在 ...