/** * dropdown * 下拉菜单组件 */ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){ "use strict"; var $ = layui.$; var laytpl = layui.laytpl; var util = layui.util; var hint = layui.hint(); var device = layui.device(); var clickOrMousedown = (device.mobile ? 'touchstart' : 'mousedown'); // 模块名 var MOD_NAME = 'dropdown'; var MOD_INDEX = 'layui_'+ MOD_NAME +'_index'; // 模块索引名 var MOD_ID = 'lay-' + MOD_NAME + '-id'; // 外部接口 var dropdown = { config: { customName: { // 自定义 data 字段名 id: 'id', title: 'title', children: 'child' } }, index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0, // 设置全局项 set: function(options){ var that = this; that.config = $.extend({}, that.config, options); return that; }, // 事件 on: function(events, callback){ return layui.onevent.call(this, MOD_NAME, events, callback); } }; // 操作当前实例 var thisModule = function(){ var that = this; var options = that.config; var id = options.id; thisModule.that[id] = that; // 记录当前实例对象 return { config: options, // 重置实例 reload: function(options){ that.reload.call(that, options); }, reloadData: function(options){ dropdown.reloadData(id, options); }, close: function () { that.remove() }, open: function () { that.render() } } }; // 字符常量 var STR_ELEM = 'layui-dropdown'; var STR_HIDE = 'layui-hide'; var STR_DISABLED = 'layui-disabled'; var STR_NONE = 'layui-none'; var STR_ITEM_UP = 'layui-menu-item-up'; var STR_ITEM_DOWN = 'layui-menu-item-down'; var STR_MENU_TITLE = 'layui-menu-body-title'; var STR_ITEM_GROUP = 'layui-menu-item-group'; var STR_ITEM_PARENT = 'layui-menu-item-parent'; var STR_ITEM_DIV = 'layui-menu-item-divider'; var STR_ITEM_CHECKED = 'layui-menu-item-checked'; var STR_ITEM_CHECKED2 = 'layui-menu-item-checked2'; var STR_MENU_PANEL = 'layui-menu-body-panel'; var STR_MENU_PANEL_L = 'layui-menu-body-panel-left'; var STR_ELEM_SHADE = 'layui-dropdown-shade'; var STR_GROUP_TITLE = '.'+ STR_ITEM_GROUP + '>.'+ STR_MENU_TITLE; // 构造器 var Class = function(options){ var that = this; that.index = ++dropdown.index; that.config = $.extend({}, that.config, dropdown.config, options); that.init(); }; // 默认配置 Class.prototype.config = { trigger: 'click', // 事件类型 content: '', // 自定义菜单内容 className: '', // 自定义样式类名 style: '', // 设置面板 style 属性 show: false, // 是否初始即显示菜单面板 isAllowSpread: true, // 是否允许菜单组展开收缩 isSpreadItem: true, // 是否初始展开子菜单 data: [], // 菜单数据结构 delay: [200, 300], // 延时显示或隐藏的毫秒数,若为 number 类型,则表示显示和隐藏的延迟时间相同,trigger 为 hover 时才生效 shade: 0, // 遮罩 accordion: false // 手风琴效果,仅菜单组生效。基础菜单需要在容器上追加 'lay-accordion' 属性。 }; // 重载实例 Class.prototype.reload = function(options, type){ var that = this; that.config = $.extend({}, that.config, options); that.init(true, type); }; // 初始化准备 Class.prototype.init = function(rerender, type){ var that = this; var options = that.config; // 若 elem 非唯一 var elem = $(options.elem); if(elem.length > 1){ layui.each(elem, function(){ dropdown.render($.extend({}, options, { elem: this })); }); return that; } // 合并 lay-options 属性上的配置信息 $.extend(options, lay.options(elem[0])); // 若重复执行 render,则视为 reload 处理 if(!rerender && elem[0] && elem.attr(MOD_ID)){ var newThat = thisModule.getThis(elem.attr(MOD_ID)); if(!newThat) return; return newThat.reload(options, type); } options.elem = $(options.elem); // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引 options.id = 'id' in options ? options.id : ( elem.attr('id') || that.index ); elem.attr(MOD_ID, options.id); // 初始化自定义字段名 options.customName = $.extend({}, dropdown.config.customName, options.customName); if(options.show || (type === 'reloadData' && that.elemView && $('body').find(that.elemView.get(0)).length)) that.render(rerender, type); //初始即显示或者面板弹出之后执行了刷新数据 that.events(); // 事件 }; // 渲染 Class.prototype.render = function(rerender, type){ var that = this; var options = that.config; var customName = options.customName; var elemBody = $('body'); // 默认菜单内容 var getDefaultView = function(){ var elemUl = $(''); if(options.data.length > 0 ){ eachItemView(elemUl, options.data) } else { elemUl.html('
  • 暂无数据
  • '); } return elemUl; }; // 遍历菜单项 var eachItemView = function(views, data){ // var views = []; layui.each(data, function(index, item){ // 是否存在子级 var isChild = item[customName.children] && item[customName.children].length > 0; var isSpreadItem = ('isSpreadItem' in item) ? item.isSpreadItem : options.isSpreadItem var title = function(title){ var templet = item.templet || options.templet; if(templet){ title = typeof templet === 'function' ? templet(item) : laytpl(templet).render(item); } return title; }(util.escape(item[customName.title])); // 初始类型 var type = function(){ if(isChild){ item.type = item.type || 'parent'; } if(item.type){ return ({ group: 'group' ,parent: 'parent' ,'-': '-' })[item.type] || 'parent'; } return ''; }(); if(type !== '-' && (!item[customName.title] && !item[customName.id] && !isChild)) return; //列表元素 var viewLi = $(['' //标题区 ,function(){ //是否超文本 var viewText = ('href' in item) ? ( ''+ title +'' ) : title; //是否存在子级 if(isChild){ return '
    '+ viewText + function(){ if(type === 'parent'){ return ''; } else if(type === 'group' && options.isAllowSpread){ return ''; } else { return ''; } }() +'
    ' } return '
    '+ viewText +'
    '; }() ,''].join('')); viewLi.data('item', item); //子级区 if(isChild){ var elemPanel = $('
    '); var elemUl = $(''); if(type === 'parent'){ elemPanel.append(eachItemView(elemUl, item[customName.children])); viewLi.append(elemPanel); } else { viewLi.append(eachItemView(elemUl, item[customName.children])); } } views.append(viewLi); }); return views; }; // 主模板 var TPL_MAIN = ['
    ' ,'
    '].join(''); // 如果是右键事件,则每次触发事件时,将允许重新渲染 if(options.trigger === 'contextmenu' || lay.isTopElem(options.elem[0])) rerender = true; // 判断是否已经打开了下拉菜单面板 if(!rerender && options.elem.data(MOD_INDEX +'_opened')) return; // 记录模板对象 that.elemView = $('.' + STR_ELEM + '[' + MOD_ID + '="' + options.id + '"]'); if (type === 'reloadData' && that.elemView.length) { that.elemView.html(options.content || getDefaultView()); } else { that.elemView = $(TPL_MAIN); that.elemView.append(options.content || getDefaultView()); // 初始化某些属性 if(options.className) that.elemView.addClass(options.className); if(options.style) that.elemView.attr('style', options.style); // 记录当前执行的实例索引 dropdown.thisId = options.id; // 插入视图 that.remove(); // 移除非当前绑定元素的面板 elemBody.append(that.elemView); options.elem.data(MOD_INDEX +'_opened', true); // 遮罩 var shade = options.shade ? ('
    ') : ''; that.elemView.before(shade); // 如果是鼠标移入事件,则鼠标移出时自动关闭 if(options.trigger === 'mouseenter'){ that.elemView.on('mouseenter', function(){ clearTimeout(thisModule.timer); }).on('mouseleave', function(){ that.delayRemove(); }); } } // 坐标定位 that.position(); thisModule.prevElem = that.elemView; // 记录当前打开的元素,以便在下次关闭 thisModule.prevElem.data('prevElem', options.elem); // 将当前绑定的元素,记录在打开元素的 data 对象中 // 阻止全局事件 that.elemView.find('.layui-menu').on(clickOrMousedown, function(e){ layui.stope(e); }); // 触发菜单列表事件 that.elemView.find('.layui-menu li').on('click', function(e){ var othis = $(this); var data = othis.data('item') || {}; var isChild = data[customName.children] && data[customName.children].length > 0; var isClickAllScope = options.clickScope === 'all'; // 是否所有父子菜单均触发点击事件 if(data.disabled) return; // 菜单项禁用状态 // 普通菜单项点击后的回调及关闭面板 if((!isChild || isClickAllScope) && data.type !== '-'){ var ret = typeof options.click === 'function' ? options.click(data, othis) : null; ret === false || (isChild || that.remove()); layui.stope(e); } }); // 触发菜单组展开收缩 that.elemView.find(STR_GROUP_TITLE).on('click', function(e){ var othis = $(this); var elemGroup = othis.parent(); var data = elemGroup.data('item') || {}; if(data.type === 'group' && options.isAllowSpread){ thisModule.spread(elemGroup, options.accordion); } }); // 组件打开完毕的事件 typeof options.ready === 'function' && options.ready( that.elemView, options.elem ); }; // 位置定位 Class.prototype.position = function(obj){ var that = this; var options = that.config; lay.position(options.elem[0], that.elemView[0], { position: options.position, e: that.e, clickType: options.trigger === 'contextmenu' ? 'right' : null, align: options.align || null }); }; // 删除视图 Class.prototype.remove = function(){ var that = this; var options = that.config; var prevContentElem = thisModule.prevElem; // 若存在已打开的面板元素,则移除 if(prevContentElem){ var prevId = prevContentElem.attr(MOD_ID); var prevTriggerElem = prevContentElem.data('prevElem'); var prevInstance = thisModule.getThis(prevId); var prevOnClose = prevInstance.config.close; prevTriggerElem && prevTriggerElem.data(MOD_INDEX +'_opened', false); prevContentElem.remove(); delete thisModule.prevElem; typeof prevOnClose === 'function' && prevOnClose.call(prevInstance.config, prevTriggerElem); } lay('.' + STR_ELEM_SHADE).remove(); }; Class.prototype.normalizedDelay = function(){ var that = this; var options = that.config; var delay = [].concat(options.delay); return { show: delay[0], hide: delay[1] !== undefined ? delay[1] : delay[0] } } // 延迟删除视图 Class.prototype.delayRemove = function(){ var that = this; var options = that.config; clearTimeout(thisModule.timer); thisModule.timer = setTimeout(function(){ that.remove(); }, that.normalizedDelay().hide); }; // 事件 Class.prototype.events = function(){ var that = this; var options = that.config; // 若传入 hover,则解析为 mouseenter if(options.trigger === 'hover') options.trigger = 'mouseenter'; // 解除上一个事件 if(that.prevElem) that.prevElem.off(options.trigger, that.prevElemCallback); // 是否鼠标移入时触发 var isMouseEnter = options.trigger === 'mouseenter'; // 记录被绑定的元素及回调 that.prevElem = options.elem; that.prevElemCallback = function(e){ clearTimeout(thisModule.timer); that.e = e; // 若为鼠标移入事件,则延迟触发 isMouseEnter ? ( thisModule.timer = setTimeout(function(){ that.render(); }, that.normalizedDelay().show) ) : that.render(); e.preventDefault(); }; // 触发元素事件 options.elem.on(options.trigger, that.prevElemCallback); // 如果是鼠标移入事件 if (isMouseEnter) { // 执行鼠标移出事件 options.elem.on('mouseleave', function(){ that.delayRemove(); }); } }; // 记录所有实例 thisModule.that = {}; // 记录所有实例对象 // 获取当前实例对象 thisModule.getThis = function(id){ var that = thisModule.that[id]; if(!that) hint.error(id ? (MOD_NAME +' instance with ID \''+ id +'\' not found') : 'ID argument required'); return that; }; // 设置菜单组展开和收缩状态 thisModule.spread = function(othis, isAccordion){ var contentElem = othis.children('ul'); var needSpread = othis.hasClass(STR_ITEM_UP); var ANIM_MS = 200; // 动画执行完成后的操作 var complete = function() { $(this).css({'display': ''}); // 剔除临时 style,以适配外部样式的状态重置; }; // 动画是否正在执行 if (contentElem.is(':animated')) return; // 展开 if (needSpread) { othis.removeClass(STR_ITEM_UP).addClass(STR_ITEM_DOWN); contentElem.hide().stop().slideDown(ANIM_MS, complete); } else { // 收缩 contentElem.stop().slideUp(ANIM_MS, complete); othis.removeClass(STR_ITEM_DOWN).addClass(STR_ITEM_UP); } // 手风琴 if (needSpread && isAccordion) { var groupSibs = othis.siblings('.' + STR_ITEM_DOWN); groupSibs.children('ul').stop().slideUp(ANIM_MS, complete); groupSibs.removeClass(STR_ITEM_DOWN).addClass(STR_ITEM_UP); } }; // 全局事件 (function(){ var _WIN = $(window); var _DOC = $(document); // 自适应定位 _WIN.on('resize', function(){ if(!dropdown.thisId) return; var that = thisModule.getThis(dropdown.thisId); if(!that) return; if((that.elemView && !that.elemView[0]) || !$('.'+ STR_ELEM)[0]){ return false; } var options = that.config; if(options.trigger === 'contextmenu'){ that.remove(); } else { that.position(); } }); // 点击任意处关闭 _DOC.on(clickOrMousedown, function(e){ if(!dropdown.thisId) return; var that = thisModule.getThis(dropdown.thisId) if(!that) return; var options = that.config; // 若触发的是绑定的元素,或者属于绑定元素的子元素,则不关闭 // 满足条件:当前绑定的元素不是 body document,或者不是鼠标右键事件 if(!(lay.isTopElem(options.elem[0]) || options.trigger === 'contextmenu')){ if( e.target === options.elem[0] || options.elem.find(e.target)[0] || (that.elemView && e.target === that.elemView[0]) || (that.elemView && that.elemView.find(e.target)[0]) ) return; } that.remove(); }); // 基础菜单的静态元素事件 var ELEM_LI = '.layui-menu:not(.layui-dropdown-menu) li'; _DOC.on('click', ELEM_LI, function(e){ var othis = $(this); var parent = othis.parents('.layui-menu').eq(0); var isChild = othis.hasClass(STR_ITEM_GROUP) || othis.hasClass(STR_ITEM_PARENT); var filter = parent.attr('lay-filter') || parent.attr('id'); var options = lay.options(this); // 非触发元素 if(othis.hasClass(STR_ITEM_DIV)) return; // 非菜单组 if(!isChild){ // 选中 parent.find('.'+ STR_ITEM_CHECKED).removeClass(STR_ITEM_CHECKED); // 清除选中样式 parent.find('.'+ STR_ITEM_CHECKED2).removeClass(STR_ITEM_CHECKED2); // 清除父级菜单选中样式 othis.addClass(STR_ITEM_CHECKED); //添加选中样式 othis.parents('.'+ STR_ITEM_PARENT).addClass(STR_ITEM_CHECKED2); // 添加父级菜单选中样式 options.title = options.title || $.trim(othis.children('.'+ STR_MENU_TITLE).text()); // 触发事件 layui.event.call(this, MOD_NAME, 'click('+ filter +')', options); } }); // 基础菜单的展开收缩事件 _DOC.on('click', (ELEM_LI + STR_GROUP_TITLE), function(e){ var othis = $(this); var elemGroup = othis.parents('.'+ STR_ITEM_GROUP +':eq(0)'); var options = lay.options(elemGroup[0]); var isAccordion = typeof othis.parents('.layui-menu').eq(0).attr('lay-accordion') === 'string'; if(('isAllowSpread' in options) ? options.isAllowSpread : true){ thisModule.spread(elemGroup, isAccordion); } }); // 判断子级菜单是否超出屏幕 var ELEM_LI_PAR = '.layui-menu .'+ STR_ITEM_PARENT _DOC.on('mouseenter', ELEM_LI_PAR, function(e){ var othis = $(this); var elemPanel = othis.find('.'+ STR_MENU_PANEL); if(!elemPanel[0]) return; var rect = elemPanel[0].getBoundingClientRect(); // 是否超出右侧屏幕 if(rect.right > _WIN.width()){ elemPanel.addClass(STR_MENU_PANEL_L); // 不允许超出左侧屏幕 rect = elemPanel[0].getBoundingClientRect(); if(rect.left < 0){ elemPanel.removeClass(STR_MENU_PANEL_L); } } // 是否超出底部屏幕 if(rect.bottom > _WIN.height()){ elemPanel.eq(0).css('margin-top', -(rect.bottom - _WIN.height() + 5)); } }).on('mouseleave', ELEM_LI_PAR, function(e){ var othis = $(this) var elemPanel = othis.children('.'+ STR_MENU_PANEL); elemPanel.removeClass(STR_MENU_PANEL_L); elemPanel.css('margin-top', 0); }); })(); // 关闭面板 dropdown.close = function(id){ var that = thisModule.getThis(id); if(!that) return this; that.remove(); return thisModule.call(that); }; // 打开面板 dropdown.open = function(id){ var that = thisModule.getThis(id); if(!that) return this; that.render(); return thisModule.call(that); } // 重载实例 dropdown.reload = function(id, options, type){ var that = thisModule.getThis(id); if(!that) return this; that.reload(options, type); return thisModule.call(that); }; // 仅重载数据 dropdown.reloadData = function(){ var args = $.extend([], arguments); args[2] = 'reloadData'; // 重载时,与数据相关的参数 var dataParams = new RegExp('^('+ [ 'data', 'templet', 'content' ].join('|') + ')$'); // 过滤与数据无关的参数 layui.each(args[1], function (key, value) { if(!dataParams.test(key)){ delete args[1][key]; } }); return dropdown.reload.apply(null, args); }; // 核心入口 dropdown.render = function(options){ var inst = new Class(options); return thisModule.call(inst); }; exports(MOD_NAME, dropdown); });