529 lines
16 KiB
JavaScript
529 lines
16 KiB
JavaScript
/**
|
||
|
||
@Name:dropdown 下拉菜单组件
|
||
@License:MIT
|
||
|
||
*/
|
||
|
||
layui.define(['jquery', 'laytpl', 'lay'], function(exports){
|
||
"use strict";
|
||
|
||
var $ = layui.$
|
||
,laytpl = layui.laytpl
|
||
,hint = layui.hint()
|
||
,device = layui.device()
|
||
,clickOrMousedown = (device.mobile ? 'click' : 'mousedown')
|
||
|
||
//模块名
|
||
,MOD_NAME = 'dropdown'
|
||
,MOD_INDEX = 'layui_'+ MOD_NAME +'_index' //模块索引名
|
||
|
||
//外部接口
|
||
,dropdown = {
|
||
config: {}
|
||
,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);
|
||
}
|
||
}
|
||
|
||
//操作当前实例
|
||
,thisModule = function(){
|
||
var that = this
|
||
,options = that.config
|
||
,id = options.id;
|
||
|
||
thisModule.that[id] = that; //记录当前实例对象
|
||
|
||
return {
|
||
config: options
|
||
//重置实例
|
||
,reload: function(options){
|
||
that.reload.call(that, options);
|
||
}
|
||
}
|
||
}
|
||
|
||
//字符常量
|
||
,STR_ELEM = 'layui-dropdown', STR_HIDE = 'layui-hide', STR_DISABLED = 'layui-disabled', STR_NONE = 'layui-none'
|
||
,STR_ITEM_UP = 'layui-menu-item-up', STR_ITEM_DOWN = 'layui-menu-item-down', STR_MENU_TITLE = 'layui-menu-body-title', STR_ITEM_GROUP = 'layui-menu-item-group', STR_ITEM_PARENT = 'layui-menu-item-parent', STR_ITEM_DIV = 'layui-menu-item-divider', STR_ITEM_CHECKED = 'layui-menu-item-checked', STR_ITEM_CHECKED2 = 'layui-menu-item-checked2', STR_MENU_PANEL = 'layui-menu-body-panel', STR_MENU_PANEL_L = 'layui-menu-body-panel-left'
|
||
|
||
,STR_GROUP_TITLE = '.'+ STR_ITEM_GROUP + '>.'+ STR_MENU_TITLE
|
||
|
||
//构造器
|
||
,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: 300 //延迟关闭的毫秒数,若 trigger 为 hover 时才生效
|
||
};
|
||
|
||
//重载实例
|
||
Class.prototype.reload = function(options){
|
||
var that = this;
|
||
that.config = $.extend({}, that.config, options);
|
||
that.init(true);
|
||
};
|
||
|
||
//初始化准备
|
||
Class.prototype.init = function(rerender){
|
||
var that = this
|
||
,options = that.config
|
||
,elem = options.elem = $(options.elem);
|
||
|
||
//若 elem 非唯一
|
||
if(elem.length > 1){
|
||
layui.each(elem, function(){
|
||
dropdown.render($.extend({}, options, {
|
||
elem: this
|
||
}));
|
||
});
|
||
return that;
|
||
}
|
||
|
||
//若重复执行 render,则视为 reload 处理
|
||
if(!rerender && elem[0] && elem.data(MOD_INDEX)){;
|
||
var newThat = thisModule.getThis(elem.data(MOD_INDEX));
|
||
if(!newThat) return;
|
||
|
||
return newThat.reload(options);
|
||
};
|
||
|
||
//初始化 id 参数
|
||
options.id = ('id' in options) ? options.id : that.index;
|
||
|
||
if(options.show) that.render(rerender); //初始即显示
|
||
that.events(); //事件
|
||
};
|
||
|
||
//渲染
|
||
Class.prototype.render = function(rerender){
|
||
var that = this
|
||
,options = that.config
|
||
,elemBody = $('body')
|
||
|
||
//默认菜单内容
|
||
,getDefaultView = function(){
|
||
var elemUl = $('<ul class="layui-menu layui-dropdown-menu"></ul>');
|
||
if(options.data.length > 0 ){
|
||
eachItemView(elemUl, options.data)
|
||
} else {
|
||
elemUl.html('<li class="layui-menu-item-none">no menu</li>');
|
||
}
|
||
return elemUl;
|
||
}
|
||
|
||
//遍历菜单项
|
||
,eachItemView = function(views, data){
|
||
//var views = [];
|
||
layui.each(data, function(index, item){
|
||
//是否存在子级
|
||
var isChild = item.child && item.child.length > 0
|
||
,isSpreadItem = ('isSpreadItem' in item) ? item.isSpreadItem : options.isSpreadItem
|
||
,title = item.templet
|
||
? laytpl(item.templet).render(item)
|
||
: (options.templet ? laytpl(options.templet).render(item) : item.title)
|
||
|
||
//初始类型
|
||
,type = function(){
|
||
if(isChild){
|
||
item.type = item.type || 'parent';
|
||
}
|
||
if(item.type){
|
||
return ({
|
||
group: 'group'
|
||
,parent: 'parent'
|
||
,'-': '-'
|
||
})[item.type] || 'parent';
|
||
}
|
||
return '';
|
||
}();
|
||
|
||
if(type !== '-' && (!item.title && !item.id && !isChild)) return;
|
||
|
||
//列表元素
|
||
var viewLi = $(['<li'+ function(){
|
||
var className = {
|
||
group: 'layui-menu-item-group'+ (
|
||
options.isAllowSpread ? (
|
||
isSpreadItem ? ' layui-menu-item-down' : ' layui-menu-item-up'
|
||
) : ''
|
||
)
|
||
,parent: STR_ITEM_PARENT
|
||
,'-': 'layui-menu-item-divider'
|
||
};
|
||
if(isChild || type){
|
||
return ' class="'+ className[type] +'"';
|
||
}
|
||
return '';
|
||
}() +'>'
|
||
|
||
//标题区
|
||
,function(){
|
||
//是否超文本
|
||
var viewText = ('href' in item) ? (
|
||
'<a href="'+ item.href +'" target="'+ (item.target || '_self') +'">'+ title +'</a>'
|
||
) : title;
|
||
|
||
//是否存在子级
|
||
if(isChild){
|
||
return '<div class="'+ STR_MENU_TITLE +'">'+ viewText + function(){
|
||
if(type === 'parent'){
|
||
return '<i class="layui-icon layui-icon-right"></i>';
|
||
} else if(type === 'group' && options.isAllowSpread){
|
||
return '<i class="layui-icon layui-icon-'+ (isSpreadItem ? 'up' : 'down') +'"></i>';
|
||
} else {
|
||
return '';
|
||
}
|
||
}() +'</div>'
|
||
|
||
}
|
||
return '<div class="'+ STR_MENU_TITLE +'">'+ viewText +'</div>';
|
||
}()
|
||
,'</li>'].join(''));
|
||
|
||
viewLi.data('item', item);
|
||
|
||
//子级区
|
||
if(isChild){
|
||
var elemPanel = $('<div class="layui-panel layui-menu-body-panel"></div>')
|
||
,elemUl = $('<ul></ul>');
|
||
|
||
if(type === 'parent'){
|
||
elemPanel.append(eachItemView(elemUl, item.child));
|
||
viewLi.append(elemPanel);
|
||
} else {
|
||
viewLi.append(eachItemView(elemUl, item.child));
|
||
}
|
||
}
|
||
|
||
views.append(viewLi);
|
||
});
|
||
return views;
|
||
}
|
||
|
||
//主模板
|
||
,TPL_MAIN = ['<div class="layui-dropdown layui-border-box layui-panel layui-anim layui-anim-downbit">'
|
||
,'</div>'].join('');
|
||
|
||
//如果是右键事件,则每次触发事件时,将允许重新渲染
|
||
if(options.trigger === 'contextmenu' || lay.isTopElem(options.elem[0])) rerender = true;
|
||
|
||
//判断是否已经打开了下拉菜单面板
|
||
if(!rerender && options.elem.data(MOD_INDEX +'_opened')) return;
|
||
|
||
//记录模板对象
|
||
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);
|
||
|
||
//坐标定位
|
||
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)
|
||
,data = othis.data('item') || {}
|
||
,isChild = data.child && data.child.length > 0;
|
||
|
||
if(!isChild && data.type !== '-'){
|
||
that.remove();
|
||
typeof options.click === 'function' && options.click(data, othis);
|
||
}
|
||
});
|
||
|
||
//触发菜单组展开收缩
|
||
that.elemView.find(STR_GROUP_TITLE).on('click', function(e){
|
||
var othis = $(this)
|
||
,elemGroup = othis.parent()
|
||
,data = elemGroup.data('item') || {}
|
||
|
||
if(data.type === 'group' && options.isAllowSpread){
|
||
thisModule.spread(elemGroup);
|
||
}
|
||
});
|
||
|
||
//如果是鼠标移入事件,则鼠标移出时自动关闭
|
||
if(options.trigger === 'mouseenter'){
|
||
that.elemView.on('mouseenter', function(){
|
||
clearTimeout(thisModule.timer);
|
||
}).on('mouseleave', function(){
|
||
that.delayRemove();
|
||
});
|
||
}
|
||
|
||
};
|
||
|
||
//位置定位
|
||
Class.prototype.position = function(obj){
|
||
var that = this
|
||
,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
|
||
,options = that.config
|
||
,elemPrev = thisModule.prevElem;
|
||
|
||
//若存在已打开的面板元素,则移除
|
||
if(elemPrev){
|
||
elemPrev.data('prevElem') && (
|
||
elemPrev.data('prevElem').data(MOD_INDEX +'_opened', false)
|
||
);
|
||
elemPrev.remove();
|
||
}
|
||
};
|
||
|
||
//延迟删除视图
|
||
Class.prototype.delayRemove = function(){
|
||
var that = this
|
||
,options = that.config;
|
||
clearTimeout(thisModule.timer);
|
||
|
||
thisModule.timer = setTimeout(function(){
|
||
that.remove();
|
||
}, options.delay);
|
||
};
|
||
|
||
//事件
|
||
Class.prototype.events = function(){
|
||
var that = this
|
||
,options = that.config;
|
||
|
||
//如果传入 hover,则解析为 mouseenter
|
||
if(options.trigger === 'hover') options.trigger = 'mouseenter';
|
||
|
||
//解除上一个事件
|
||
if(that.prevElem) that.prevElem.off(options.trigger, that.prevElemCallback);
|
||
|
||
//记录被绑定的元素及回调
|
||
that.prevElem = options.elem;
|
||
that.prevElemCallback = function(e){
|
||
clearTimeout(thisModule.timer);
|
||
that.e = e;
|
||
that.render();
|
||
e.preventDefault();
|
||
|
||
//组件打开完毕的时间
|
||
typeof options.ready === 'function' && options.ready(that.elemView, options.elem, that.e.target);
|
||
};
|
||
|
||
//触发元素事件
|
||
options.elem.on(options.trigger, that.prevElemCallback);
|
||
|
||
//如果是鼠标移入事件
|
||
if(options.trigger === 'mouseenter'){
|
||
//直行鼠标移出事件
|
||
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){
|
||
//菜单组展开和收缩
|
||
var elemIcon = othis.children('.'+ STR_MENU_TITLE).find('.layui-icon');
|
||
if(othis.hasClass(STR_ITEM_UP)){
|
||
othis.removeClass(STR_ITEM_UP).addClass(STR_ITEM_DOWN);
|
||
elemIcon.removeClass('layui-icon-down').addClass('layui-icon-up');
|
||
} else {
|
||
othis.removeClass(STR_ITEM_DOWN).addClass(STR_ITEM_UP);
|
||
elemIcon.removeClass('layui-icon-up').addClass('layui-icon-down')
|
||
}
|
||
};
|
||
|
||
//全局事件
|
||
;!function(){
|
||
var _WIN = $(window)
|
||
,_DOC = $(document);
|
||
|
||
//自适应定位
|
||
_WIN.on('resize', function(){
|
||
if(!dropdown.thisId) return;
|
||
var that = thisModule.getThis(dropdown.thisId);
|
||
if(!that) return;
|
||
|
||
if(!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] ||
|
||
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)
|
||
,parent = othis.parents('.layui-menu').eq(0)
|
||
,isChild = othis.hasClass(STR_ITEM_GROUP) || othis.hasClass(STR_ITEM_PARENT)
|
||
,filter = parent.attr('lay-filter') || parent.attr('id')
|
||
,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); //添加父级菜单选中样式
|
||
|
||
//触发事件
|
||
layui.event.call(this, MOD_NAME, 'click('+ filter +')', options);
|
||
}
|
||
});
|
||
|
||
//基础菜单的展开收缩事件
|
||
_DOC.on('click', (ELEM_LI + STR_GROUP_TITLE), function(e){
|
||
var othis = $(this)
|
||
,elemGroup = othis.parents('.'+ STR_ITEM_GROUP +':eq(0)')
|
||
,options = lay.options(elemGroup[0]);
|
||
|
||
if(('isAllowSpread' in options) ? options.isAllowSpread : true){
|
||
thisModule.spread(elemGroup);
|
||
};
|
||
});
|
||
|
||
//判断子级菜单是否超出屏幕
|
||
var ELEM_LI_PAR = '.layui-menu .'+ STR_ITEM_PARENT
|
||
_DOC.on('mouseenter', ELEM_LI_PAR, function(e){
|
||
var othis = $(this)
|
||
,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()));
|
||
};
|
||
}).on('mouseleave', ELEM_LI_PAR, function(e){
|
||
var othis = $(this)
|
||
,elemPanel = othis.children('.'+ STR_MENU_PANEL);
|
||
|
||
elemPanel.removeClass(STR_MENU_PANEL_L);
|
||
elemPanel.css('margin-top', 0);
|
||
});
|
||
|
||
}();
|
||
|
||
//重载实例
|
||
dropdown.reload = function(id, options){
|
||
var that = thisModule.getThis(id);
|
||
if(!that) return this;
|
||
|
||
that.reload(options);
|
||
return thisModule.call(that);
|
||
};
|
||
|
||
//核心入口
|
||
dropdown.render = function(options){
|
||
var inst = new Class(options);
|
||
return thisModule.call(inst);
|
||
};
|
||
|
||
exports(MOD_NAME, dropdown);
|
||
});
|