123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- /**
-
- @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);
- });
|