基于jQuery的下拉菜单插件,诸位上眼!!!
前言
很久没有写博客了,话说真的工作后才发现很多需要学的,有很多不足。
加之最近工作没有什么沉淀,现在团队又面临解散,反正闲着也是闲着,就自己写了个插件,反正水平就这样,当时自我总结吧!
应用背景
在我们工作中,经常会遇到这种需求:
① 鼠标点击某个文本框时出现下拉菜单
② 常用的操作鼠标划上出现下拉菜单
③ 按钮类应用
我们会用到这种功能往往原因是因为地方小了,按钮多了,这往往说明产品设计一般出问题了。。。 但是,我辈屁民豪不关注产品(没资格插手),所以需要完成以上功能;
其实总的来说,这些功能还是非常实用的。
于是,为了应对以上场景,我工作中先是做了一个,然后又遇到了,然后又遇到了,所以最后就写了这么一个东西。
集中展示
几个功能放到一起了,前端代码如下:
View Code
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <style type="text/css"> 6 body{ font: 12px/1.231 tahoma,arial,宋体; } 7 .drop_list_items , .drop_list_items ul { display: none; position:absolute; background-color: #FFFFFF; border: 1px solid #D2D2D2; padding: 2px; margin: 0; } 8 .drop_list_items li { margin: 0; padding: 4px; list-style: none; cursor: pointer; } 9 .drop_list_items li:hover { background-color: #3399FF; } 10 .drop_list_items li.parent_drop_list { padding: 4px; list-style: none; } 11 .drop_list_items li.cur_active { background-color: #3399FF; } 12 .z800 { z-index: 800; } 13 </style> 14 <script type="text/javascript" src="http://www.cnblogs.com/jquery-1.7.1.min.js"></script> 15 <script src="DropList.js" type="text/javascript"></script> 16 <script type="text/javascript"> 17 18 //方案一 19 $(document).ready(function () { 20 new DropList({ 21 id: 'click_btn_drop', 22 dropItems: [ 23 ['短信选定用户', 'select'], 24 ['短信全部用户', 'all'], 25 ['短信未发送用户用户', 'all_else'] 26 ], 27 func: function (e, scope, listEl) { 28 var el = $(this); 29 alert(el.html()); 30 scope.closeList(); 31 var s = ''; 32 } 33 }); 34 35 new DropList({ 36 id: 'div1', 37 open: '1', 38 close: '1', 39 dropItems: [ 40 ['昵称'], 41 ['姓名'], 42 ['性别'], 43 ['联系方式'] 44 ], 45 func: function (e, scope, listEl, toggleEl) { 46 var el = $(this); 47 scope.closeList(); 48 toggleEl.val(el.html()); 49 } 50 }); 51 52 new DropList({ 53 id: 'click_text_drop', 54 dropItems: [ 55 ['昵称'], 56 ['姓名'], 57 ['性别'], 58 ['联系方式'] 59 ], 60 func: function (e, scope, listEl, toggleEl) { 61 var el = $(this); 62 scope.closeList(); 63 toggleEl.val(el.html()); 64 } 65 }); 66 67 }); 68 </script> 69 </head> 70 <body> 71 72 73 <div id="click_btn_drop" style=" width:140px;" > 74 点击按钮出现下拉菜单 75 </div> 76 <br /> 77 <br /> 78 79 80 <input id="click_text_drop" type="text" /> 81 <br /> 82 <br /> 83 <div id="div1" style=" width:140px;" > 84 鼠标滑动 85 </div> 86 87 </body> 88 </html>
js代码:
View Code
var DropList = function (opts) { if (!opts.id) { alert('请指定触发展开事件的元素id'); return false; } //触发展开元素id this.toggleId = opts.id; this.toggleEl = opts.id ? $('#' + opts.id) : $('body'); this.key = opts.id ? opts.id + '_list' : new Date().getTime(); this.open = opts.open || 'click'; //展开菜单方式 mousein this.close = opts.close || 'click'; //关闭菜单方式 mouseleave //this.initShow = false; //判断是否初始化出现菜单绑定事件 /*下拉菜单数据,可能出现多级菜单数据格式: [['v', 'k', []], ['v', {}, []], ['v', 'k', [['v', 'k', []], ['v', 'k', []]] ] */ this.dropItems = opts.dropItems || null; this.loadData = opts.loadData; //用于异步加载下拉菜单数据//具有层级关系 this.listEl = null; this.func = opts.func || null; //点击时的事件处理 //同步方式加载 if (this.dropItems) { this.initDropItems(); this.eventBind(); } else { } }; DropList.prototype.closeList = {}; DropList.prototype.dropItemLoad = function (data, el) { for (var i in data) { var item = data[i]; var tmp = $('<li></li>'); el.append(tmp); //标签已装载 if (item[0]) { tmp.html(item[0]); } if (item[1] || typeof item[1] == 'number') { if (typeof item[1] == 'string' || typeof item[1] == 'number') { tmp.attr('id', item[1]); } else { for (_k in item[1]) { tmp.attr(_k, item[1][_k]); } } } if (item[2] && item[2]['length']) {//此处需要递归 var child = $('<ul ></ul>') tmp.append(child); tmp.addClass('parent_drop_list'); this.dropItemLoad(item[2], child); } } }; //['v', 'k', []] DropList.prototype.initDropItems = function () { var scope = this; var dropItems = scope.dropItems; var listEl = $('<ul class="drop_list_items" id="' + scope.key + '"></ul>'); $('body').append(listEl); scope.dropItemLoad(dropItems, listEl); scope.listEl = listEl; }; DropList.prototype.closeList = function () { var listEl = this.listEl; listEl.find('li').removeClass('cur_active'); listEl.find('ul').hide(); listEl.hide(); }; DropList.prototype.eventBind = function () { var scope = this; var listEl = scope.listEl; var toggleEl = scope.toggleEl; var open = scope.open; var close = scope.close; var func = scope.func; var obj_len = function (o) { var len = 0; for (var k in o) { len++; } return len; }; var func_cls = function () { if (close == 'click') { $(document).click(function (e) { var el = $(e.target); var is_el = false; //判断父元素是否为 while (el.attr('id') != scope.key) { if (el.is("ul") || el.is('li')) { is_el = true; el = el.parent(); } else { break; } } if (el.attr('id') == scope.toggleId) { is_el = true; } if (!is_el) { scope.closeList(); if (scope.closeList[scope.toggleId]) delete scope.closeList[scope.toggleId]; if (obj_len(scope.closeList) == 0) $(document).unbind('click'); var s = ''; } }); } else { listEl.mouseleave(function (e) { scope.closeList(); if (scope.closeList[scope.toggleId]) delete scope.closeList[scope.toggleId]; listEl.unbind('mouseleave'); }); } }; //确认弹出层位置 var func_init_pos = function (el) { var offset = el.offset(); var h = el.height(); var p_top = el.css('padding-top'); var p_bottom = el.css('padding-bottom'); listEl.css('min-width', (parseInt(el.css('width')) + parseInt(el.css('padding-left')) + parseInt(el.css('padding-right')) - 6) + 'px') listEl.css('left', parseInt(offset.left) + 'px'); listEl.css('top', (parseInt(offset.top) + parseInt(h) + parseInt(p_top) + parseInt(p_bottom)) + 'px'); }; if (open == 'click') { toggleEl.unbind('click').click(function (e) { var el = $(this); var drop_list_items = $('.drop_list_items'); func_init_pos(el); drop_list_items.removeClass('z800'); listEl.addClass('z800'); listEl.show(); func_cls(); scope.closeList[scope.toggleId] = 1; //e.stopPropagation(); //阻止冒泡 }); } else { toggleEl.unbind('mouseenter').mouseenter(function (e) { var el = $(this); var drop_list_items = $('.drop_list_items'); func_init_pos(el); drop_list_items.removeClass('z800'); listEl.addClass('z800'); listEl.show(); func_cls(); //e.stopPropagation(); //阻止冒泡 }); } listEl.delegate('li', 'mouseenter', function (e) { var el = $(this); listEl.find('li').removeClass('cur_active'); listEl.find('ul').hide(); el.addClass('cur_active'); el.children().show(); el = el.parent(); while (el.attr('id') != scope.key) { if (el.is("li")) { el.addClass('cur_active'); } if (el.is('ul')) { el.show(); } el = el.parent(); } e.stopPropagation(); }); if (func && typeof func == 'function') { listEl.delegate('li', 'click', function (e) { func.call(this, e, scope, listEl, toggleEl); e.stopPropagation(); }); } }; function initNewDrop(opts) { new DropList(opts); }
难点&后续
做的过程中还是遇到了几个问题的,比如:
① 菜单展开后如何关闭
② 多级菜单如何处理
不完善的级联效果
③ 事件如何回调
最后做出了这个比较简陋的东东。。。。
但是做完后也发现了一些问题:
① 像这种菜单在最左最下出现时没有解决;
② 然后菜单项过多时候也没有出现像select的滚动条;
③ 由于个人水平,整个代码的质量亦有问题;
④ 开始也考虑了异步数据加载的问题,但是着实有点难便放弃了,功能代码有一点,有兴趣的同学可以看看:
View Code
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <style type="text/css"> 6 * 7 { 8 margin:0; 9 } 10 body 11 { 12 font: 12px/1.231 tahoma,arial,宋体; 13 } 14 div 15 { 16 width:160px; 17 margin:5px; 18 } 19 20 .drop_btn ul 21 { 22 position:absolute; 23 background-color: #FFFFFF; 24 border: 1px solid #D2D2D2; 25 padding: 2px; 26 line-height: 18px; 27 display: none; 28 top: 25px; 29 z-index: 500; 30 31 } 32 33 .drop_btn li 34 { 35 list-style: none; 36 } 37 .drop_btn_toggle 38 { 39 background: url("http://shz.qq.com/statics/images/button.png") repeat-x scroll 0 0 #E5E5E5; 40 border: 1px solid #999999; 41 box-shadow: 0 1px 0 #E5E5E5; 42 border-radius: 3px; 43 cursor: pointer; 44 height: 28px; 45 line-height: 28px; 46 *height: 18px; 47 *line-height: 18px; 48 padding: 3px 6px; 49 vertical-align: middle; 50 zoom:1; 51 } 52 .drop_btn_open .drop_btn_toggle 53 { 54 background: url("http://shz.qq.com/statics/images/button_selected.png") repeat-x scroll 0 0 #B4B4B4; 55 border-color: #CCCCCC #B1B1B1 #AFAFAF #BEBEBE; 56 color: #515151; 57 } 58 .drop_btn_toggle .icon 59 { 60 border-left: 4px dashed transparent; 61 border-right: 4px dashed transparent; 62 border-top: 4px solid; 63 display: inline-block; 64 width: 0; 65 height: 0; 66 margin: 11px 0 0 4px; 67 *margin: 6px 0 0 4px; 68 overflow: hidden; 69 vertical-align: top; 70 } 71 72 div.drop_btn_open ul 73 { 74 display: block; 75 } 76 .drop_btn li 77 { 78 padding: 2px; 79 cursor: pointer; 80 } 81 .drop_btn li:hover 82 { 83 background-color: #3399FF; 84 } 85 </style> 86 <script type="text/javascript" src="http://www.cnblogs.com/jquery-1.7.1.min.js"></script> 87 <script src="DropList.js" type="text/javascript"></script> 88 <script type="text/javascript"> 89 90 //方案一 91 $(document).ready(function () { 92 var click = new DropList({ 93 id: 'click_btn_drop', 94 toggleText: '给用户发送短信', 95 openType: 'move', 96 drop_items: [ 97 { id: 'select', text: '短信选定用户' }, 98 { id: 'all', text: '短信全部用户' }, 99 { id: 'all_else', text: '短信未发送用户用户' } 100 ], 101 func: function (e, container) { 102 var el = $(this); 103 alert(el.html()) 104 var s = ''; 105 } 106 }); 107 108 var move = new DropList({ 109 id: 'move_btn_drop', 110 toggleText: '给用户发送短信', 111 112 loadData: function (callBack) { 113 var scope = this; 114 $.get('Handler.ashx', function (data) { 115 if (data && typeof data == 'string') { 116 data = eval('(' + data + ')'); 117 } 118 data = data.data; 119 var param = []; 120 param.push({ text: '报名人数:' + data.reg_num }); 121 var type = data.notice; 122 if (type == 0) { 123 msg = '不发送短信'; 124 } else if (type == 1) { 125 msg = '自动短信'; 126 } else if (type == 3) { 127 msg = '手动短信'; 128 } 129 param.push({ text: '短信类型:' + msg }); 130 param.push({ text: '<a href="#">自动短信条数:' + data.sms_auto_count + '</a>'}); 131 param.push({ text: '<a href="http://www.baidu.com/" target="_blank">手动短信条数:' + data.sms_manual_count }); 132 133 scope.drop_items = param; 134 callBack(); 135 136 var s = ''; 137 138 }); 139 } 140 }); 141 142 var text = new DropList({ 143 id: 'click_text_drop', 144 toggleType: 'text', 145 drop_items: [ 146 { text: '昵称' }, 147 { text: '姓名' }, 148 { text: '性别' }, 149 { text: '联系方式' } 150 ], 151 func: function (e, container, toggleEl) { 152 var el = $(this); 153 toggleEl.val(el.html()); 154 } 155 }); 156 157 }); 158 </script> 159 </head> 160 <body> 161 162 点击按钮出现下拉菜单 163 <div id="click_btn_drop" class="drop_btn"></div> 164 <br /> 165 <br /> 166 167 滑动按钮出现下拉菜单 168 <div id="move_btn_drop" class="drop_btn"></div> 169 <br /> 170 <br /> 171 172 点击文本出现下拉菜单 173 <div id="click_text_drop" class="drop_btn"></div> 174 <br /> 175 <br /> 176 177 <!--<div class="drop_btn"> 178 <a class="drop_btn_toggle"><span>给用户发短信<i class="icon"></i></span></a> 179 <ul class="drop_items"> 180 <li>短信选中用户</li> 181 <li>短信全部用户</li> 182 <li>短信未发送用户</li> 183 </ul> 184 </div>--> 185 186 </body> 187 </html> 188 189 /// <reference path="http://www.cnblogs.com/jquery-1.7.1.min.js" /> 190 191 192 var DropList = function (opts) { 193 this.id = opts.id || ''; 194 //组件容器 195 this.container = $('#' + this.id); 196 //确定点击/滑动元素为按钮或者文本框(button/text) 197 this.toggleType = opts.toggleType || 'button'; 198 this.toggleText = opts.toggleText || '请点击我'; 199 //展开方式(点击/滑动) 200 this.openType = opts.openType || 'click'; 201 this.drop_items = opts.drop_items || []; 202 this.loadData = opts.loadData; 203 this.func = opts.func; 204 205 if (this.drop_items && this.drop_items[0]) { 206 this.init(); 207 this.eventBind(); 208 } else { 209 if (this.loadData && typeof this.loadData == 'function') { 210 this.asyncLoad(); 211 } 212 } 213 }; 214 215 DropList.prototype.initBtn = function () { 216 var scope = this; 217 var container = scope.container; 218 var openType = scope.openType; 219 var toggleType = scope.toggleType; 220 var toggleText = scope.toggleText; 221 var toggleEl = ''; 222 if (toggleType == 'button') { 223 toggleEl = '<a class="drop_btn_toggle"><span>' + toggleText + '<i class="icon"></i></span></a>'; 224 } else { 225 toggleEl = '<input class="drop_text_toggle" type="text" />'; 226 } 227 //获得点击元素用以添加事件 228 scope.toggleEl = $(toggleEl); 229 container.append(scope.toggleEl); 230 }; 231 232 DropList.prototype.initDropItems = function () { 233 var scope = this; 234 var container = scope.container; 235 //组装下拉元素 236 var drop_items = scope.drop_items; 237 var item_container = $('<ul class="drop_items"></ul>'); 238 container.append(item_container); 239 for (var i in drop_items) { 240 var item = drop_items[i]; 241 var tmp = $('<li></li>'); 242 if (item.id) { 243 tmp.attr('id', item.id); 244 } 245 if (item.text) { 246 tmp.html(item.text); 247 } 248 item_container.append(tmp); 249 } 250 //活动下拉菜单 251 scope.item_container = item_container; 252 }; 253 DropList.prototype.init = function () { 254 //组装触发元素 255 var scope = this; 256 scope.initBtn(); 257 scope.initDropItems(); 258 }; 259 260 DropList.prototype.eventBind = function () { 261 var scope = this; 262 var container = scope.container; //父容器 263 var toggleType = scope.toggleType; //触发方式 264 var toggleEl = scope.toggleEl; //点击元素 265 var item_container = scope.item_container; //下拉菜单 266 var openType = scope.openType; 267 var func = scope.func; 268 269 if (openType == 'click') { 270 toggleEl.click(function (e) { 271 container.addClass('drop_btn_open'); 272 273 var el = $(this); 274 var offset = el.offset(); 275 var s = el.height(); 276 s = el.css('height'); 277 var p_top = el.css('padding-top'); 278 var p_bottom = el.css('padding-bottom'); 279 item_container.css('min-width', el.css('width')) 280 item_container.css('left', parseInt(offset.left) + 'px'); 281 item_container.css('top', (parseInt(offset.top) + parseInt(s) + parseInt(p_top) + parseInt(p_bottom)) + 'px'); 282 283 284 //菜单出现后便阻止冒泡 285 e.stopPropagation(); 286 $(document).unbind('click').click(function (ee) { 287 if (container.hasClass('drop_btn_open')) { 288 $('.drop_btn').removeClass('drop_btn_open'); 289 } 290 $(document).unbind('click'); 291 }); 292 }); 293 } else { 294 toggleEl.mousemove(function () { 295 container.addClass('drop_btn_open'); 296 297 var el = $(this); 298 var offset = el.offset(); 299 var s = el.height(); 300 s = el.css('height'); 301 var p_top = el.css('padding-top'); 302 var p_bottom = el.css('padding-bottom'); 303 item_container.css('min-width', el.css('width')) 304 item_container.css('left', parseInt(offset.left) + 'px'); 305 item_container.css('top', (parseInt(offset.top) + parseInt(s) + parseInt(p_top) + parseInt(p_bottom)) + 'px'); 306 307 $(document).unbind('click').click(function (ee) { 308 if (container.hasClass('drop_btn_open')) { 309 $('.drop_btn').removeClass('drop_btn_open'); 310 } 311 $(document).unbind('click'); 312 }); 313 }); 314 } 315 316 // $("table").delegate("td", "hover", function () { 317 // $(this).toggleClass("hover"); 318 // }); 319 //处理选项处理事件 320 if (func && typeof func == 'function') { 321 item_container.delegate('li', 'click', function (e) { 322 func.call(this, e, container, toggleEl); 323 }); 324 } 325 }; 326 327 DropList.prototype.asyncLoad = function () { 328 var scope = this; 329 scope.initBtn(); 330 scope.asyncBtnEventBind(); 331 332 // scope.initDropItems(); 333 }; 334 335 DropList.prototype.asyncBtnEventBind = function () { 336 var scope = this; 337 var container = scope.container; //父容器 338 var toggleType = scope.toggleType; //触发方式 339 var toggleEl = scope.toggleEl; //点击元素 340 var loadData = scope.loadData; 341 var openType = scope.openType; 342 343 //处理点击事件 344 if (openType == 'click') { 345 toggleEl.click(function (e) { 346 container.addClass('drop_btn_open'); 347 348 var el = $(this); 349 350 351 //若是没有下拉菜单便添加数据 352 if (!scope.item_container) { 353 //加载异步数据 354 if (loadData && typeof loadData == 'function') { 355 loadData.call(scope, function () { 356 scope.initDropItems(); 357 scope.asyncDropEventBind(el); 358 }); 359 } 360 } 361 362 //菜单出现后便阻止冒泡 363 e.stopPropagation(); 364 $(document).unbind('click').click(function (ee) { 365 if (container.hasClass('drop_btn_open')) { 366 $('.drop_btn').removeClass('drop_btn_open'); 367 } 368 $(document).unbind('click'); 369 }); 370 }); 371 } else { 372 toggleEl.mousemove(function () { 373 if (container.hasClass('drop_btn_open')) { 374 $('.drop_btn').removeClass('drop_btn_open'); 375 } 376 $(document).unbind('click'); 377 }); 378 } 379 }; 380 381 DropList.prototype.asyncDropEventBind = function (el) { 382 var scope = this; 383 var item_container = scope.item_container; //下拉菜单 384 385 var offset = el.offset(); 386 var s = el.height(); 387 s = el.css('height'); 388 var p_top = el.css('padding-top'); 389 var p_bottom = el.css('padding-bottom'); 390 item_container.css('min-width', el.css('width')) 391 item_container.css('left', parseInt(offset.left) + 'px'); 392 item_container.css('top', (parseInt(offset.top) + parseInt(s) + parseInt(p_top) + parseInt(p_bottom)) + 'px'); 393 394 var func = scope.func; 395 396 // $("table").delegate("td", "hover", function () { 397 // $(this).toggleClass("hover"); 398 // }); 399 //处理选项处理事件 400 if (func && typeof func == 'function') { 401 item_container.delegate('li', 'click', function (e) { 402 func.call(this, e, container, toggleEl); 403 }); 404 } 405 };
所以先贴出来和各位看看,后续小生再行优化,希望能把这个功能做好!