import layui from '../core.js'; import $ from 'jquery'; import { laytpl } from './laytpl.js'; import lay from './lay.js'; import { util } from './util.js'; /** * dropdown * 下拉菜单组件 */ 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_INDEX_OPENED = MOD_INDEX + '_opened'; 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; 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_DISABLED = 'layui-disabled'; 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' 属性。 closeOnClick: true // 面板打开后,再次点击目标元素时是否关闭面板。行为取决于所使用的触发事件类型 }; // 重载实例 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.attr(MOD_ID)){ var newThat = thisModule.getThis(elem.attr(MOD_ID)); if(!newThat) return; return newThat.reload(options, type); } options.elem = $(options.elem); options.target = $('body'); // 后续考虑开放 target 元素 // 初始化 id 属性 - 优先取 options > 元素 id > 自增索引 options.id = 'id' in options ? options.id : ( elem.attr('id') || that.index ); thisModule.that[options.id] = that; // 记录当前实例对象 elem.attr(MOD_ID, options.id); // 目标元素已渲染过的标记 // 初始化自定义字段名 options.customName = $.extend({}, dropdown.config.customName, options.customName); // 若传入 hover,则解析为 mouseenter if (options.trigger === 'hover') { options.trigger = 'mouseenter'; } // 初始即显示或者面板弹出之后执行了刷新数据 if(options.show || (type === 'reloadData' && that.mainElem && options.target.find(that.mainElem.get(0)).length)) that.render(type); // 事件 that.events(); }; // 渲染 Class.prototype.render = function(type) { var that = this; var options = that.config; var customName = options.customName; // 默认菜单内容 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(''); // 重载或插入面板内容 var content = options.content || getDefaultView(); var mainElemExisted = thisModule.findMainElem(options.id); if (type === 'reloadData' && mainElemExisted.length) { // 是否仅重载数据 var mainElem = that.mainElem = mainElemExisted; mainElemExisted.html(content); } else { // 常规渲染 var mainElem = that.mainElem = $(TPL_MAIN); mainElem.append(content); // 初始化某些属性 mainElem.addClass(options.className); mainElem.attr('style', options.style); // 辞旧迎新 that.remove(dropdown.thisId); options.target.append(mainElem); options.elem.data(MOD_INDEX_OPENED, true); // 面板已打开的标记 // 遮罩 var shade = options.shade ? ('
    ') : ''; mainElem.before(shade); // 如果是鼠标移入事件,则鼠标移出时自动关闭 if(options.trigger === 'mouseenter'){ mainElem.on('mouseenter', function(){ clearTimeout(thisModule.timer); }).on('mouseleave', function(){ that.delayRemove(); }); } } that.position(); // 定位坐标 dropdown.thisId = options.id; // 当前打开的面板 id // 阻止全局事件 mainElem.find('.layui-menu').on(clickOrMousedown, function(e){ layui.stope(e); }); // 触发菜单列表事件 mainElem.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, e) : null; ret === false || (isChild || that.remove()); layui.stope(e); } }); // 触发菜单组展开收缩 mainElem.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(mainElem, options.elem); }; // 位置定位 Class.prototype.position = function(obj){ var that = this; var options = that.config; lay.position(options.elem[0], that.mainElem[0], { position: options.position, e: that.e, clickType: options.trigger === 'contextmenu' ? 'right' : null, align: options.align || null }); }; // 移除面板 Class.prototype.remove = function(id) { id = id || this.config.id; var that = thisModule.getThis(id); // 根据 id 查找对应的实例 if (!that) return; var options = that.config; var mainElem = thisModule.findMainElem(id); // 若存在已打开的面板元素,则移除 if (mainElem[0]) { mainElem.prev('.' + STR_ELEM_SHADE).remove(); // 先移除遮罩 mainElem.remove(); options.elem.removeData(MOD_INDEX_OPENED); delete dropdown.thisId; typeof options.close === 'function' && options.close(options.elem); } }; 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; 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; // 是否鼠标移入时触发 var isMouseEnter = options.trigger === 'mouseenter'; var trigger = options.trigger + '.lay_dropdown_render'; // 始终先解除上一个触发元素的事件(如重载时改变 elem 的情况) if (that.thisEventElem) that.thisEventElem.off(trigger); that.thisEventElem = options.elem; // 触发元素事件 options.elem.off(trigger).on(trigger, function(e) { clearTimeout(thisModule.timer); that.e = e; // 主面板是否已打开 var opened = options.elem.data(MOD_INDEX_OPENED); // 若为鼠标移入事件,则延迟触发 if (isMouseEnter) { if (!opened) { thisModule.timer = setTimeout(function(){ that.render(); }, that.normalizedDelay().show); } } else { // 若为 click 事件,则根据主面板状态,自动切换打开与关闭 if (options.closeOnClick && opened && options.trigger === 'click') { that.remove(); } else { that.render(); } } e.preventDefault(); }); // 如果是鼠标移入事件 if (isMouseEnter) { // 执行鼠标移出事件 options.elem.on('mouseleave', function(){ that.delayRemove(); }); } }; // 记录所有实例 thisModule.that = {}; // 记录所有实例对象 // 获取当前实例对象 thisModule.getThis = function(id) { if (id === undefined) { throw new Error('ID argument required'); } return thisModule.that[id]; }; // 根据 id 从页面查找组件主面板元素 thisModule.findMainElem = function(id) { return $('.' + STR_ELEM + '[' + MOD_ID + '="' + id + '"]'); }; // 设置菜单组展开和收缩状态 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.mainElem && !that.mainElem[0]) || !$('.'+ STR_ELEM)[0]){ return false; } var options = that.config; if(options.trigger === 'contextmenu'){ that.remove(); } else { that.position(); } }); // 点击任意处关闭 lay(_DOC).on(clickOrMousedown, function(e){ if(!dropdown.thisId) return; var that = thisModule.getThis(dropdown.thisId); if(!that) return; var options = that.config; var isTopElem = lay.isTopElem(options.elem[0]); var isCtxMenu = options.trigger === 'contextmenu'; // 若触发的是绑定的元素,或者属于绑定元素的子元素,则不关闭 // 满足条件:当前绑定的元素是 body document,或者是鼠标右键事件时,忽略绑定元素 var isTriggerTarget = !(isTopElem || isCtxMenu) && (options.elem[0] === e.target || options.elem.find(e.target)[0]); var isPanelTarget = that.mainElem && (e.target === that.mainElem[0] || that.mainElem.find(e.target)[0]); if(isTriggerTarget || isPanelTarget) return; // 处理移动端点击穿透问题 if(e.type === 'touchstart' && options.elem.data(MOD_INDEX_OPENED)){ $(e.target).hasClass(STR_ELEM_SHADE) && e.preventDefault(); } // 点击 dropdown 外部时的回调 if(typeof options.onClickOutside === 'function'){ var shouldClose = options.onClickOutside(e); if(shouldClose === false) return; } that.remove(); }, lay.passiveSupported ? { passive: false} : false); // onClickOutside 检测 iframe _WIN.on('blur', function(e){ if(!dropdown.thisId) return; var that = thisModule.getThis(dropdown.thisId); if(!that) return; if(!that.config.elem.data(MOD_INDEX_OPENED)) return; setTimeout(function(){ if(document.activeElement && document.activeElement.tagName === 'IFRAME' && that.mainElem && that.mainElem[0] && that.mainElem[0].contains && !that.mainElem[0].contains(document.activeElement) ){ // 点击 dropdown 外部时的回调 if(typeof that.config.onClickOutside === 'function'){ var shouldClose = that.config.onClickOutside(e.originalEvent); if(shouldClose === false) return; } that.remove(); } }, 0); }); // 基础菜单的静态元素事件 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); }; export { dropdown as default, dropdown }; //# sourceMappingURL=dropdown.js.map