mirror of https://github.com/helloxz/onenav.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
528 lines
16 KiB
528 lines
16 KiB
/** |
|
|
|
@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); |
|
});
|
|
|