mirror of
https://gitee.com/layui/layui.git
synced 2025-04-05 17:38:02 +08:00
refactor: 重构 dropdown 打开与关闭逻辑 (#2349)
* refactor: 重构 dropdown 打开与关闭逻辑 * chore: 优化变量 * refactor: 保留采用 elem 的 jQuery Data 进行面板打开状态的判断 * fix: 优化延时移除面板时的实例不一致的问题
This commit is contained in:
parent
636551547b
commit
1670cbab8f
@ -46,6 +46,7 @@ layui.use(function(){
|
||||
// 绑定输入框
|
||||
dropdown.render({
|
||||
elem: '#ID-dropdown-demo-base-input',
|
||||
closeOnClick: false, // 不开启“打开与关闭的自动切换”,即点击输入框时始终为打开状态
|
||||
data: [{
|
||||
title: 'menu item 1',
|
||||
id: 101
|
||||
@ -118,4 +119,4 @@ layui.use(function(){
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
@ -65,20 +65,22 @@ layui.use(function(){
|
||||
|
||||
// 其他操作
|
||||
util.event('lay-on', {
|
||||
// 全局右键菜单
|
||||
// 改变触发右键菜单的目标元素
|
||||
contextmenu: function(othis){
|
||||
var ID = 'ID-dropdown-demo-contextmenu';
|
||||
if(!othis.data('open')){
|
||||
if (!othis.data('open')) {
|
||||
dropdown.reload(ID, {
|
||||
elem: document // 将事件直接绑定到 document
|
||||
elem: document // 设置全局元素右键
|
||||
});
|
||||
|
||||
layer.msg('已开启全局右键菜单,请尝试在页面任意处单击右键。')
|
||||
othis.html('取消全局右键菜单');
|
||||
othis.data('open', true);
|
||||
} else {
|
||||
dropdown.reload(ID, {
|
||||
elem: '#'+ ID // 重新绑定到指定元素上
|
||||
elem: '#'+ ID // 设置局部元素右键
|
||||
});
|
||||
|
||||
layer.msg('已取消全局右键菜单,恢复默认右键菜单')
|
||||
othis.html('开启全局右键菜单');
|
||||
othis.data('open', false);
|
||||
@ -86,4 +88,4 @@ layui.use(function(){
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
@ -316,11 +316,19 @@ layui.use(['table', 'dropdown'], function(){
|
||||
});
|
||||
}
|
||||
},
|
||||
id: 'dropdown-table-tool',
|
||||
align: 'right', // 右对齐弹出
|
||||
style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' // 设置额外样式
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// table 滚动时移除内部弹出的元素
|
||||
var tableInst = table.getOptions('test');
|
||||
tableInst.elem.next().find('.layui-table-main').on('scroll', function() {
|
||||
dropdown.close('dropdown-table-tool');
|
||||
});
|
||||
|
||||
|
||||
// 触发表格复选框选择
|
||||
table.on('checkbox(test)', function(obj){
|
||||
|
@ -35,9 +35,13 @@
|
||||
<button class="layui-btn" lay-on="close">close</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-bg-gray" style="margin-top: 30px; width: 100%; height: 300px; text-align: center;" id="demo20">
|
||||
<div class="layui-bg-gray" style="margin-top: 30px; width: 100%; height: 300px; text-align: center;" id="ID-dropdown-demo-contextmenu">
|
||||
<span class="layui-font-gray" style="position: relative; top:50%;">鼠标右键菜单</span>
|
||||
</div>
|
||||
<button class="layui-btn" style="margin-top: 15px;" lay-on="contextmenu">
|
||||
开启全局右键菜单
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -258,9 +262,9 @@ layui.use('dropdown', function () {
|
||||
}
|
||||
});
|
||||
|
||||
//右键
|
||||
// 右键
|
||||
dropdown.render({
|
||||
elem: document, //'#demo20' //也可绑定到 document,从而重置整个右键
|
||||
elem: '#ID-dropdown-demo-contextmenu', // 也可绑定到 document,从而重置整个右键
|
||||
trigger: 'contextmenu', //contextmenu
|
||||
isAllowSpread: false,
|
||||
//,style: 'width: 200px'
|
||||
@ -316,6 +320,28 @@ layui.use('dropdown', function () {
|
||||
}
|
||||
});
|
||||
|
||||
util.on({
|
||||
// 改变触发右键菜单的目标元素
|
||||
contextmenu: function(othis){
|
||||
var ID = 'ID-dropdown-demo-contextmenu';
|
||||
if (!othis.data('open')) {
|
||||
dropdown.reload(ID, {
|
||||
elem: document // 设置全局元素右键
|
||||
});
|
||||
|
||||
othis.html('取消全局右键菜单');
|
||||
othis.data('open', true);
|
||||
} else {
|
||||
dropdown.reload(ID, {
|
||||
elem: '#'+ ID // 设置局部元素右键
|
||||
});
|
||||
|
||||
othis.html('开启全局右键菜单');
|
||||
othis.data('open', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.render({
|
||||
elem: '#testopen',
|
||||
id: 'testopen',
|
||||
@ -330,8 +356,7 @@ layui.use('dropdown', function () {
|
||||
close: function(){
|
||||
dropdown.close('testopen')
|
||||
}
|
||||
},
|
||||
{trigger: 'mouseenter'});
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
|
@ -16,6 +16,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
// 模块名
|
||||
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';
|
||||
|
||||
// 外部接口
|
||||
@ -48,8 +49,6 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
var options = that.config;
|
||||
var id = options.id;
|
||||
|
||||
thisModule.that[id] = that; // 记录当前实例对象
|
||||
|
||||
return {
|
||||
config: options,
|
||||
// 重置实例
|
||||
@ -87,6 +86,8 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
|
||||
var STR_GROUP_TITLE = '.'+ STR_ITEM_GROUP + '>.'+ STR_MENU_TITLE;
|
||||
|
||||
var bodyElem = $('body');
|
||||
|
||||
// 构造器
|
||||
var Class = function(options){
|
||||
var that = this;
|
||||
@ -108,7 +109,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
delay: [200, 300], // 延时显示或隐藏的毫秒数,若为 number 类型,则表示显示和隐藏的延迟时间相同,trigger 为 hover 时才生效
|
||||
shade: 0, // 遮罩
|
||||
accordion: false, // 手风琴效果,仅菜单组生效。基础菜单需要在容器上追加 'lay-accordion' 属性。
|
||||
closeOnClick: false // 面板打开后,再次点击目标元素时是否关闭面板。行为取决于所使用的触发事件类型
|
||||
closeOnClick: true // 面板打开后,再次点击目标元素时是否关闭面板。行为取决于所使用的触发事件类型
|
||||
};
|
||||
|
||||
// 重载实例
|
||||
@ -138,10 +139,9 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
$.extend(options, lay.options(elem[0]));
|
||||
|
||||
// 若重复执行 render,则视为 reload 处理
|
||||
if(!rerender && elem[0] && elem.attr(MOD_ID)){
|
||||
if(!rerender && elem.attr(MOD_ID)){
|
||||
var newThat = thisModule.getThis(elem.attr(MOD_ID));
|
||||
if(!newThat) return;
|
||||
|
||||
return newThat.reload(options, type);
|
||||
}
|
||||
|
||||
@ -152,21 +152,29 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
elem.attr('id') || that.index
|
||||
);
|
||||
|
||||
elem.attr(MOD_ID, options.id);
|
||||
thisModule.that[options.id] = that; // 记录当前实例对象
|
||||
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(); // 事件
|
||||
// 若传入 hover,则解析为 mouseenter
|
||||
if (options.trigger === 'hover') {
|
||||
options.trigger = 'mouseenter';
|
||||
}
|
||||
|
||||
// 初始即显示或者面板弹出之后执行了刷新数据
|
||||
if(options.show || (type === 'reloadData' && that.mainElem && bodyElem.find(that.mainElem.get(0)).length)) that.render(type);
|
||||
|
||||
// 事件
|
||||
that.events();
|
||||
};
|
||||
|
||||
// 渲染
|
||||
Class.prototype.render = function(rerender, type){
|
||||
Class.prototype.render = function(type) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var customName = options.customName;
|
||||
var elemBody = $('body');
|
||||
|
||||
// 默认菜单内容
|
||||
var getDefaultView = function(){
|
||||
@ -276,42 +284,37 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
};
|
||||
|
||||
// 主模板
|
||||
var TPL_MAIN = ['<div class="layui-dropdown layui-border-box layui-panel layui-anim layui-anim-downbit" ' + MOD_ID + '="' + options.id + '">'
|
||||
,'</div>'].join('');
|
||||
|
||||
// 如果是右键事件,则每次触发事件时,将允许重新渲染
|
||||
if(options.trigger === 'contextmenu' || lay.isTopElem(options.elem[0])) rerender = true;
|
||||
|
||||
// 判断是否已经打开了下拉菜单面板
|
||||
if(!rerender && options.elem.data(MOD_INDEX +'_opened')) return;
|
||||
var TPL_MAIN = [
|
||||
'<div class="layui-dropdown layui-border-box layui-panel layui-anim layui-anim-downbit" ' + MOD_ID + '="' + options.id + '">',
|
||||
'</div>'
|
||||
].join('');
|
||||
|
||||
// 记录模板对象
|
||||
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());
|
||||
// 重载或插入面板内容
|
||||
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);
|
||||
|
||||
// 初始化某些属性
|
||||
if(options.className) that.elemView.addClass(options.className);
|
||||
if(options.style) that.elemView.attr('style', options.style);
|
||||
mainElem.addClass(options.className);
|
||||
mainElem.attr('style', options.style);
|
||||
|
||||
// 记录当前执行的实例索引
|
||||
dropdown.thisId = options.id;
|
||||
|
||||
// 插入视图
|
||||
that.remove(); // 移除非当前绑定元素的面板
|
||||
elemBody.append(that.elemView);
|
||||
options.elem.data(MOD_INDEX +'_opened', true);
|
||||
// 辞旧迎新
|
||||
that.remove(dropdown.thisId);
|
||||
bodyElem.append(mainElem);
|
||||
options.elem.data(MOD_INDEX_OPENED, true); // 面板已打开的标记
|
||||
|
||||
// 遮罩
|
||||
var shade = options.shade ? ('<div class="'+ STR_ELEM_SHADE +'" style="'+ ('z-index:'+ (that.elemView.css('z-index')-1) +'; background-color: ' + (options.shade[1] || '#000') + '; opacity: ' + (options.shade[0] || options.shade)) +'"></div>') : '';
|
||||
that.elemView.before(shade);
|
||||
var shade = options.shade ? ('<div class="'+ STR_ELEM_SHADE +'" style="'+ ('z-index:'+ (mainElem.css('z-index')-1) +'; background-color: ' + (options.shade[1] || '#000') + '; opacity: ' + (options.shade[0] || options.shade)) +'"></div>') : '';
|
||||
mainElem.before(shade);
|
||||
|
||||
// 如果是鼠标移入事件,则鼠标移出时自动关闭
|
||||
if(options.trigger === 'mouseenter'){
|
||||
that.elemView.on('mouseenter', function(){
|
||||
mainElem.on('mouseenter', function(){
|
||||
clearTimeout(thisModule.timer);
|
||||
}).on('mouseleave', function(){
|
||||
that.delayRemove();
|
||||
@ -319,18 +322,16 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
}
|
||||
}
|
||||
|
||||
// 坐标定位
|
||||
that.position();
|
||||
thisModule.prevElem = that.elemView; // 记录当前打开的元素,以便在下次关闭
|
||||
thisModule.prevElem.data('prevElem', options.elem); // 将当前绑定的元素,记录在打开元素的 data 对象中
|
||||
|
||||
that.position(); // 定位坐标
|
||||
dropdown.thisId = options.id; // 当前打开的面板 id
|
||||
|
||||
// 阻止全局事件
|
||||
that.elemView.find('.layui-menu').on(clickOrMousedown, function(e){
|
||||
mainElem.find('.layui-menu').on(clickOrMousedown, function(e){
|
||||
layui.stope(e);
|
||||
});
|
||||
|
||||
// 触发菜单列表事件
|
||||
that.elemView.find('.layui-menu li').on('click', function(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;
|
||||
@ -350,7 +351,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
});
|
||||
|
||||
// 触发菜单组展开收缩
|
||||
that.elemView.find(STR_GROUP_TITLE).on('click', function(e){
|
||||
mainElem.find(STR_GROUP_TITLE).on('click', function(e){
|
||||
var othis = $(this);
|
||||
var elemGroup = othis.parent();
|
||||
var data = elemGroup.data('item') || {};
|
||||
@ -361,10 +362,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
});
|
||||
|
||||
// 组件打开完毕的事件
|
||||
typeof options.ready === 'function' && options.ready(
|
||||
that.elemView,
|
||||
options.elem
|
||||
);
|
||||
typeof options.ready === 'function' && options.ready(mainElem, options.elem);
|
||||
};
|
||||
|
||||
// 位置定位
|
||||
@ -372,7 +370,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
|
||||
lay.position(options.elem[0], that.elemView[0], {
|
||||
lay.position(options.elem[0], that.mainElem[0], {
|
||||
position: options.position,
|
||||
e: that.e,
|
||||
clickType: options.trigger === 'contextmenu' ? 'right' : null,
|
||||
@ -380,25 +378,23 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
});
|
||||
};
|
||||
|
||||
// 删除视图
|
||||
Class.prototype.remove = function(){
|
||||
var that = this;
|
||||
// 移除面板
|
||||
Class.prototype.remove = function(id) {
|
||||
id = id || this.config.id;
|
||||
var that = thisModule.getThis(id); // 根据 id 查找对应的实例
|
||||
if (!that) return;
|
||||
|
||||
var options = that.config;
|
||||
var prevContentElem = thisModule.prevElem;
|
||||
|
||||
var mainElem = thisModule.findMainElem(id);
|
||||
|
||||
// 若存在已打开的面板元素,则移除
|
||||
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);
|
||||
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);
|
||||
}
|
||||
lay('.' + STR_ELEM_SHADE).remove();
|
||||
};
|
||||
|
||||
Class.prototype.normalizedDelay = function(){
|
||||
@ -412,7 +408,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟删除视图
|
||||
// 延迟移除面板
|
||||
Class.prototype.delayRemove = function(){
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
@ -427,40 +423,41 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
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){
|
||||
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){
|
||||
thisModule.timer = setTimeout(function(){
|
||||
that.render();
|
||||
}, that.normalizedDelay().show)
|
||||
}else{
|
||||
if(options.closeOnClick && options.elem.data(MOD_INDEX +'_opened') && options.trigger === 'click'){
|
||||
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{
|
||||
} else {
|
||||
that.render();
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// 触发元素事件
|
||||
options.elem.on(options.trigger, that.prevElemCallback);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// 如果是鼠标移入事件
|
||||
if (isMouseEnter) {
|
||||
@ -475,10 +472,16 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
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.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 + '"]');
|
||||
};
|
||||
|
||||
// 设置菜单组展开和收缩状态
|
||||
@ -523,7 +526,7 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
var that = thisModule.getThis(dropdown.thisId);
|
||||
if(!that) return;
|
||||
|
||||
if((that.elemView && !that.elemView[0]) || !$('.'+ STR_ELEM)[0]){
|
||||
if((that.mainElem && !that.mainElem[0]) || !$('.'+ STR_ELEM)[0]){
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -551,10 +554,10 @@ layui.define(['jquery', 'laytpl', 'lay', 'util'], function(exports){
|
||||
// 若触发的是绑定的元素,或者属于绑定元素的子元素,则不关闭
|
||||
// 满足条件:当前绑定的元素是 body document,或者是鼠标右键事件时,忽略绑定元素
|
||||
var isTriggerTarget = !(isTopElem || isCtxMenu) && (options.elem[0] === e.target || options.elem.find(e.target)[0]);
|
||||
var isPanelTarget = that.elemView && (e.target === that.elemView[0] || that.elemView.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')){
|
||||
if(e.type === 'touchstart' && options.elem.data(MOD_INDEX_OPENED)){
|
||||
$(e.target).hasClass(STR_ELEM_SHADE) && e.preventDefault();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user