mirror of https://github.com/layui/layui
feat(form-select): 新增 `lay-append-to` 属性 (#1926)
* feat(form-select): 新增 `lay-append-to` 属性 docs(form-select): 更新文档 fix: 修复定向渲染 BUG fix: 修复点击元素外部关闭面板时的一些问题 refactor: 重命名部分变量 feat: lay.onClickOutside 新增 capture 选项 feat: lay.onClickOutside 新增 detectIframe 属性 chore: 修改部分变量命名 fix: 窗口大小改变时,面板位置异常 revert: 1fe122b fix: 修复一些边缘情况 refactor: 将 lay-append-to-body 重命名为 lay-append-to fix: 重新渲染时,应该移除旧的面板元素 * update code * docs(form): 新增 `lay-append-position` 属性的文档介绍 * docs: 优化 select 在 table 多样化编辑中的示例 * docs(form-select): 增加 `lay-append-to` 的相关示例 * feat(form-select): 新增 `lay-append-position` 属性,用于设置 `select` 面板开启 `lay-append-to` 属性后的定位方式 * chore(form-select): 剔除多余代码 * docs(form-select): 优化 select 在 layer 中使用的示例 当鼠标滑动 layer 内部滚动条时移除下拉框,以规避错位 --------- Co-authored-by: 贤心 <3277200+sentsim@users.noreply.github.com>pull/2018/head
parent
d61226ae93
commit
2e5ad41aef
|
@ -118,6 +118,8 @@ form 还可以借助*栅格*实现更灵活的响应式布局。
|
|||
| lay-skin | [#详见](checkbox.html#default) | 设置 UI 风格。 `<input type="checkbox">`,`<input type="radio">` 元素 **私有属性** |
|
||||
| lay-search | 默认不区分大小写;<br>设置`cs`区分大小写 | 给 `select` 组件开启搜索功能。`<select>` 元素 **私有属性** |
|
||||
| lay-creatable <sup>2.9.7+</sup> | 无需值 | 是否允许创建新条目,需要配合 `lay-search` 使用。`<select>` 元素 **私有属性** |
|
||||
| lay-append-to <sup>2.9.12+</sup> <sup>实验性</sup> | `body` | 是否将 select 面板追加到 body 元素中。`<select>` 元素 **私有属性** |
|
||||
| lay-append-position <sup>2.9.12+</sup> <sup>实验性</sup> | `absolute` 绝对定位 (默认)<br>`fixed` 固定定位 | 用于设置 select 面板开启 `lay-append-to` 属性后的定位方式。`<select>` 元素 **私有属性** |
|
||||
| lay-submit | 无需值 | 设置元素(一般为`<button>` 标签)触发 `submit` 提交事件 |
|
||||
| lay-ignore | 无需值 | 设置表单元素忽略渲染,即让元素保留系统原始 UI 风格 |
|
||||
|
||||
|
|
|
@ -179,6 +179,62 @@ toc: true
|
|||
</textarea>
|
||||
</pre>
|
||||
|
||||
<h2 id="lay-append" lay-toc="{hot: true}">独立选择框 <sup>2.9.12+</sup></h2>
|
||||
|
||||
在 `<select>` 元素中设置 `lay-append-to="body"` 属性,可将 select 面板插入到 `<body>` 根节点下,以便让选择框从 form 结构中剥离,成为更灵活的独立选择框。借助该特性,可完美解决 select 在 table, layer 等组件中使用的若干问题。
|
||||
|
||||
### 1. 在 table 中使用 select
|
||||
|
||||
参考 table 示例: [实现多样化编辑](/docs/2/table/#demo-editmodes)
|
||||
|
||||
### 2. 在 layer 中使用 select
|
||||
|
||||
<pre class="layui-code" lay-options="{preview: true, layout: ['preview', 'code'], tools: ['full'], done: function(obj){
|
||||
obj.render();
|
||||
}}">
|
||||
<textarea>
|
||||
<button class="layui-btn" lay-on="layer-select">弹出 layer+select</button>
|
||||
|
||||
<!-- import layui -->
|
||||
<script>
|
||||
layui.use(function(){
|
||||
var form = layui.form;
|
||||
var table = layui.table;
|
||||
var layer = layui.layer;
|
||||
var util = layui.util;
|
||||
|
||||
// 事件
|
||||
util.on({
|
||||
// 在 layer 中使用 select
|
||||
"layer-select": function() {
|
||||
layer.open({
|
||||
type: 1, // page 层类型
|
||||
area: ['500px', '300px'],
|
||||
title: 'layer+select',
|
||||
shadeClose: true, // 点击遮罩区域,关闭弹层
|
||||
maxmin: true, // 允许全屏最小化
|
||||
// 注: 这里特别对 select 设置了 lay-append-position 属性,以便与 layer 的定位方式保持一致
|
||||
content: '<form class="layui-form layui-padding-3" lay-filter="test"><select lay-append-to="body" lay-append-position="fixed"><option value="">请选择</option<option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="AAA">选项 A</option><option value="BBB">选项 B</option><option value="CCC">选项 C</option></select></form>',
|
||||
success: function (layero) {
|
||||
// 定向渲染 select
|
||||
form.render(layero.find('.layui-form select'));
|
||||
|
||||
// 鼠标滑动 layer 内部滚动条时移除下拉框,以规避错位
|
||||
// 若 layer 内部不存在滚动条,以下代码可删除
|
||||
var selectElem = layero.find('.layui-form-select');
|
||||
layero.find('.layui-layer-content').on('scroll', function() {
|
||||
selectElem.removeClass('layui-form-selected');
|
||||
layui.$('.layui-select-panel-wrap').remove();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</textarea>
|
||||
</pre>
|
||||
|
||||
|
||||
<h2 id="on" lay-toc="{hot: true}">选择框事件</h2>
|
||||
|
||||
|
|
|
@ -1,28 +1,19 @@
|
|||
<table class="layui-hide" id="ID-table-demo-editmodes"></table>
|
||||
{{!<!-- 原生 select 模板(推荐) -->
|
||||
<script type="text/html" id="TPL-select-primary">
|
||||
{{!<!-- select -->
|
||||
<!--注: 自 2.9.12 版本开始,select 可与 table 实现友好兼容。若使用旧版本,则推荐采用原生 select(即添加 lay-ignore 属性)-->
|
||||
<script type="text/html" id="TPL-select-demo">
|
||||
{{# var cityList = d.cityList || ["北京","上海","广州","城市-1"]; }}
|
||||
<select name="city" class="layui-border select-demo-primary" lay-ignore>
|
||||
<option value="">原生 select</option>
|
||||
{{# layui.each(cityList, function(i, v){ }}
|
||||
<option value="{{= v }}" {{= v === d.city ? 'selected' : '' }}>{{= v }}</option>
|
||||
{{# }); }}
|
||||
</select>
|
||||
</script>
|
||||
<!-- layui select 在 table 中使用(不推荐。因为当 select 出现在 table 底部时,可能会撑起多余高度) -->
|
||||
<script type="text/html" id="TPL-select-city">
|
||||
{{# var cityList = d.cityList || ["北京","上海","广州","城市-1"]; }}
|
||||
<select name="city" lay-filter="select-demo">
|
||||
<select name="city" lay-filter="select-demo" lay-append-to="body">
|
||||
<option value="">select 方式</option>
|
||||
{{# layui.each(cityList, function(i, v){ }}
|
||||
<option value="{{= v }}" {{= v === d.city ? 'selected' : '' }}>{{= v }}</option>
|
||||
{{# }); }}
|
||||
</select>
|
||||
</script>
|
||||
<!-- 推荐 -->
|
||||
<!-- dropdpwn -->
|
||||
<script type="text/html" id="TPL-dropdpwn-demo">
|
||||
<button class="layui-btn layui-btn-primary dropdpwn-demo">
|
||||
<span>{{= d.sex || '保密' }}</span>
|
||||
<span>{{= d.sex || '无' }}</span>
|
||||
<i class="layui-icon layui-icon-down layui-font-12"></i>
|
||||
</button>
|
||||
</script>
|
||||
|
@ -45,6 +36,7 @@ layui.use(function(){
|
|||
var dropdown = layui.dropdown;
|
||||
var laydate = layui.laydate;
|
||||
var colorpicker = layui.colorpicker;
|
||||
var util = layui.util;
|
||||
|
||||
// 渲染
|
||||
table.render({
|
||||
|
@ -59,49 +51,40 @@ layui.use(function(){
|
|||
].join(''),
|
||||
cols: [[ // 表头
|
||||
{field: 'id', title: 'ID', width:80, align: 'center', fixed: 'left'},
|
||||
{field: 'city', title: '原生 select', width:135, unresize: true, templet: '#TPL-select-primary'},
|
||||
//{field: 'city', title: 'layui select', width:150, templet: '#TPL-select-city'},
|
||||
{field: 'sex', title: 'dropdown', width:115, unresize: true, align: 'center', templet: '#TPL-dropdpwn-demo'},
|
||||
{field: 'date', title: 'laydate', width:150, templet: '#TPL-laydate-demo'},
|
||||
{field: 'color', title: 'color', width:80, unresize: true, align: 'center', templet: '#TPL-colorpicker-demo'},
|
||||
{field: 'city', title: 'select', minWidth: 150, templet: '#TPL-select-demo'},
|
||||
{field: 'sex', title: 'dropdown', width: 130, unresize: true, align: 'center', templet: '#TPL-dropdpwn-demo'},
|
||||
{field: 'date', title: 'laydate', minWidth: 150, templet: '#TPL-laydate-demo'},
|
||||
{field: 'color', title: 'color', width: 80, unresize: true, align: 'center', templet: '#TPL-colorpicker-demo'},
|
||||
{field: 'sign', title: '文本', edit: 'textarea'}
|
||||
]],
|
||||
done: function(res, curr, count){
|
||||
var options = this;
|
||||
|
||||
// 获取当前行数据
|
||||
// 获取当前行数据 - 自定义方法
|
||||
table.getRowData = function(tableId, elem){
|
||||
var index = $(elem).closest('tr').data('index');
|
||||
return table.cache[tableId][index] || {};
|
||||
};
|
||||
|
||||
// 原生 select 事件
|
||||
var tableViewElem = this.elem.next();
|
||||
|
||||
// 解除 tbSelect 命名空间下的所有 change 事件处理程序
|
||||
tableViewElem.off("change.tbSelect");
|
||||
// 将 '.select-demo-primary' 元素的 change 事件委托给 tableViewElem, 事件命名空间为 tbSelect
|
||||
tableViewElem.on("change.tbSelect", ".select-demo-primary", function () {
|
||||
var value = this.value; // 获取选中项 value
|
||||
var data = table.getRowData(options.id, this); // 获取当前行数据(如 id 等字段,以作为数据修改的索引)
|
||||
|
||||
// 更新数据中对应的字段
|
||||
data.city = value;
|
||||
|
||||
// 显示 - 仅用于演示
|
||||
layer.msg('选中值: '+ value +'<br>当前行数据:'+ JSON.stringify(data));
|
||||
});
|
||||
// 展示数据 - 仅用于演示
|
||||
var showData = function(data) {
|
||||
return layer.msg('当前行最新数据:<br>'+ util.escape(JSON.stringify(data)), {
|
||||
offset: '16px',
|
||||
anim: 'slideDown'
|
||||
});
|
||||
};
|
||||
|
||||
// layui form select 事件
|
||||
form.on('select(select-demo)', function(obj){
|
||||
console.log(obj); // 获取选中项数据
|
||||
|
||||
var value = obj.value; // 获取选中项 value
|
||||
// 获取当前行数据(如 id 等字段,以作为数据修改的索引)
|
||||
var data = table.getRowData(options.id, obj.elem);
|
||||
|
||||
// 更新数据中对应的字段
|
||||
data.city = value;
|
||||
console.log(data);
|
||||
|
||||
// 显示当前行最新数据 - 仅用于示例展示
|
||||
showData(data);
|
||||
});
|
||||
|
||||
// dropdown 方式的下拉选择
|
||||
|
@ -127,8 +110,8 @@ layui.use(function(){
|
|||
// 更新数据中对应的字段
|
||||
data.sex = obj.title;
|
||||
|
||||
// 显示 - 仅用于演示
|
||||
layer.msg('选中值: '+ obj.title +'<br>当前行数据:'+ JSON.stringify(data));
|
||||
// 显示当前行最新数据 - 仅用于示例展示
|
||||
showData(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -141,8 +124,8 @@ layui.use(function(){
|
|||
// 更新数据中对应的字段
|
||||
data.date = value;
|
||||
|
||||
// 显示 - 仅用于演示
|
||||
layer.msg('选中值: '+ value +'<br>当前行数据:'+ JSON.stringify(data));
|
||||
// 显示当前行最新数据 - 仅用于示例展示
|
||||
showData(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -155,8 +138,8 @@ layui.use(function(){
|
|||
// 更新数据中对应的字段
|
||||
data.color = value;
|
||||
|
||||
// 显示 - 仅用于演示
|
||||
layer.msg('选中值: '+ value +'<br>当前行数据:'+ JSON.stringify(data));
|
||||
// 显示当前行最新数据 - 仅用于示例展示
|
||||
showData(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -174,8 +157,8 @@ layui.use(function(){
|
|||
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
|
||||
// …
|
||||
|
||||
// 显示 - 仅用于演示
|
||||
layer.msg('编辑值: '+ value +'<br>当前行数据:'+ JSON.stringify(data));
|
||||
// 显示当前行最新数据 - 仅用于示例展示
|
||||
showData(data);
|
||||
});
|
||||
|
||||
// 更多编辑方式……
|
||||
|
|
|
@ -860,6 +860,8 @@ hr.layui-border-black{border-width: 0 0 1px;}
|
|||
:root .layui-form-selected .layui-edge{margin-top: -9px\0/IE9;}
|
||||
.layui-form-selectup dl{top: auto; bottom: 42px;}
|
||||
.layui-select-none{margin: 5px 0; text-align: center; color: #999;}
|
||||
.layui-select-panel-wrap {position: absolute; z-index: 99999999;}
|
||||
.layui-select-panel-wrap dl{position: relative; display: block; top:0;}
|
||||
|
||||
.layui-select-disabled .layui-disabled{border-color: #eee !important;}
|
||||
.layui-select-disabled .layui-edge{border-top-color: #d2d2d2}
|
||||
|
|
|
@ -382,38 +382,29 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
var TITLE = 'layui-select-title';
|
||||
var NONE = 'layui-select-none';
|
||||
var CREATE_OPTION = 'layui-select-create-option';
|
||||
var initValue = '';
|
||||
var thatInput;
|
||||
var PANEL_WRAP = 'layui-select-panel-wrap'
|
||||
var PANEL_ELEM_DATA = 'layui-select-panel-elem-data';
|
||||
var selects = elem || elemForm.find('select');
|
||||
|
||||
// 隐藏 select
|
||||
var hide = function(e, clear){
|
||||
if(!$(e.target).parent().hasClass(TITLE) || clear){
|
||||
var elem = $('.' + CLASS);
|
||||
elem.removeClass(CLASS+'ed ' + CLASS+'up');
|
||||
if(elem.hasClass('layui-select-creatable')){
|
||||
elem.children('dl').children('.' + CREATE_OPTION).remove();
|
||||
}
|
||||
thatInput && initValue && thatInput.val(initValue);
|
||||
}
|
||||
thatInput = null;
|
||||
};
|
||||
|
||||
// 各种事件
|
||||
var events = function(reElem, disabled, isSearch, isCreatable){
|
||||
var events = function(reElem, titleElem, disabled, isSearch, isCreatable, isAppendTo){
|
||||
var select = $(this);
|
||||
var title = reElem.find('.' + TITLE);
|
||||
var title = titleElem;
|
||||
var input = title.find('input');
|
||||
var dl = reElem.find('dl');
|
||||
var dds = dl.children('dd');
|
||||
var dts = dl.children('dt'); // select 分组dt元素
|
||||
var index = this.selectedIndex; // 当前选中的索引
|
||||
var nearElem; // select 组件当前选中的附近元素,用于辅助快捷键功能
|
||||
var initValue = '';
|
||||
var removeClickOutsideEvent;
|
||||
|
||||
if(disabled) return;
|
||||
|
||||
// 搜索项
|
||||
var laySearch = select.attr('lay-search');
|
||||
// 目前只支持 body
|
||||
var appendTarget = select.attr('lay-append-to') || 'body';
|
||||
var appendPosition = select.attr('lay-append-position');
|
||||
|
||||
// #1449
|
||||
// IE10 和 11 中,带有占位符的 input 元素获得/失去焦点时,会触发 input 事件
|
||||
|
@ -422,15 +413,29 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
|
||||
// 展开下拉
|
||||
var showDown = function(){
|
||||
if(isAppendTo){
|
||||
// 如果追加面板元素后出现滚动条,触发元素宽度可能会有变化,所以先追加面板元素
|
||||
reElem.appendTo(appendTarget).css({width: title.width() + 'px'});
|
||||
|
||||
var updatePosition = function(){
|
||||
lay.position(title[0], reElem[0], {
|
||||
position: appendPosition,
|
||||
allowBottomOut: true,
|
||||
offset: [0, 5]
|
||||
});
|
||||
}
|
||||
|
||||
updatePosition();
|
||||
$(window).on('resize.lay_select_resize', updatePosition);
|
||||
}
|
||||
var top = reElem.offset().top + reElem.outerHeight() + 5 - $win.scrollTop();
|
||||
var dlHeight = dl.outerHeight();
|
||||
var dds = dl.children('dd');
|
||||
|
||||
index = select[0].selectedIndex; // 获取最新的 selectedIndex
|
||||
reElem.addClass(CLASS+'ed');
|
||||
title.parent().addClass(CLASS+'ed');
|
||||
dds.removeClass(HIDE);
|
||||
dts.removeClass(HIDE);
|
||||
nearElem = null;
|
||||
|
||||
// 初始选中样式
|
||||
dds.removeClass(THIS);
|
||||
|
@ -444,22 +449,35 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
followScroll();
|
||||
|
||||
if(needPlaceholderPatch){
|
||||
dl.off('mousedown.select.ieph').on('mousedown.select.ieph', function(){
|
||||
dl.off('mousedown.lay_select_ieph').on('mousedown.lay_select_ieph', function(){
|
||||
input[0].__ieph = true;
|
||||
setTimeout(function(){
|
||||
input[0].__ieph = false;
|
||||
}, 60)
|
||||
});
|
||||
}
|
||||
|
||||
removeClickOutsideEvent = lay.onClickOutside(
|
||||
isAppendTo ? reElem[0] : dl[0],
|
||||
function(){
|
||||
hideDown();
|
||||
initValue && input.val(initValue);
|
||||
},
|
||||
{ignore: title}
|
||||
);
|
||||
};
|
||||
|
||||
// 隐藏下拉
|
||||
var hideDown = function(choose){
|
||||
reElem.removeClass(CLASS+'ed ' + CLASS+'up');
|
||||
title.parent().removeClass(CLASS+'ed ' + CLASS+'up');
|
||||
input.blur();
|
||||
nearElem = null;
|
||||
isCreatable && dl.children('.' + CREATE_OPTION).remove();
|
||||
|
||||
removeClickOutsideEvent && removeClickOutsideEvent();
|
||||
if(isAppendTo){
|
||||
reElem.detach();
|
||||
$(window).off('resize.lay_select_resize');
|
||||
}
|
||||
|
||||
if(choose) return;
|
||||
|
||||
notOption(input.val(), function(none){
|
||||
|
@ -503,10 +521,9 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
|
||||
// 点击标题区域
|
||||
title.on('click', function(e){
|
||||
reElem.hasClass(CLASS+'ed') ? (
|
||||
title.parent().hasClass(CLASS+'ed') ? (
|
||||
hideDown()
|
||||
) : (
|
||||
hide(e, true),
|
||||
showDown()
|
||||
);
|
||||
dl.find('.'+NONE).remove();
|
||||
|
@ -668,7 +685,6 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
input.on('input propertychange', layui.debounce(search, 50)).on('blur', function(e){
|
||||
var selectedIndex = select[0].selectedIndex;
|
||||
|
||||
thatInput = input; // 当前的 select 中的 input 元素
|
||||
initValue = $(select[0].options[selectedIndex]).text(); // 重新获得初始选中值
|
||||
|
||||
// 如果是第一项,且文本值等于 placeholder,则清空初始值
|
||||
|
@ -721,40 +737,55 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
reElem.find('dl>dt').on('click', function(e){
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).off('click', hide).on('click', hide); // 点击其它元素关闭 select
|
||||
|
||||
if(isAppendTo){
|
||||
titleElem.on('_lay-select-destroy', function(){
|
||||
reElem.remove();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 仅 appendTo 使用,移除触发元素时,自动移除面板元素
|
||||
$.event.special['_lay-select-destroy'] = {
|
||||
remove: function( handleObj ) {
|
||||
handleObj.handler();
|
||||
}
|
||||
};
|
||||
|
||||
// 初始渲染 select 组件选项
|
||||
selects.each(function(index, select){
|
||||
var othis = $(this)
|
||||
,hasRender = othis.next('.'+CLASS)
|
||||
,disabled = this.disabled
|
||||
,value = select.value
|
||||
,selected = $(select.options[select.selectedIndex]) // 获取当前选中项
|
||||
,optionsFirst = select.options[0];
|
||||
var othis = $(this);
|
||||
var hasRender = othis.next('.'+CLASS);
|
||||
var disabled = this.disabled;
|
||||
var value = select.value;
|
||||
var selected = $(select.options[select.selectedIndex]); // 获取当前选中项
|
||||
var optionsFirst = select.options[0];
|
||||
|
||||
if(typeof othis.attr('lay-ignore') === 'string') return othis.show();
|
||||
|
||||
var isSearch = typeof othis.attr('lay-search') === 'string'
|
||||
,isCreatable = typeof othis.attr('lay-creatable') === 'string' && isSearch
|
||||
,placeholder = optionsFirst ? (
|
||||
optionsFirst.value ? TIPS : (optionsFirst.innerHTML || TIPS)
|
||||
) : TIPS;
|
||||
var isCreatable = typeof othis.attr('lay-creatable') === 'string' && isSearch
|
||||
var isAppendTo = typeof othis.attr('lay-append-to') === 'string'
|
||||
var placeholder = optionsFirst
|
||||
? (optionsFirst.value ? TIPS : (optionsFirst.innerHTML || TIPS))
|
||||
: TIPS;
|
||||
|
||||
// 替代元素
|
||||
var reElem = $(['<div class="'+ (isSearch ? '' : 'layui-unselect ') + CLASS
|
||||
,(disabled ? ' layui-select-disabled' : '')
|
||||
,(isCreatable ? ' layui-select-creatable' : '') + '">'
|
||||
,'<div class="'+ TITLE +'">'
|
||||
,(disabled ? ' layui-select-disabled' : '') + '"></div>'].join(''));
|
||||
|
||||
var triggerElem = $([
|
||||
'<div class="'+ TITLE +'">'
|
||||
,('<input type="text" placeholder="'+ util.escape($.trim(placeholder)) +'" '
|
||||
+('value="'+ util.escape($.trim(value ? selected.html() : '')) +'"') // 默认值
|
||||
+((!disabled && isSearch) ? '' : ' readonly') // 是否开启搜索
|
||||
+' class="layui-input'
|
||||
+(isSearch ? '' : ' layui-unselect')
|
||||
+ (disabled ? (' ' + DISABLED) : '') +'">') // 禁用状态
|
||||
,'<i class="layui-edge"></i></div>'
|
||||
,'<dl class="layui-anim layui-anim-upbit'+ (othis.find('optgroup')[0] ? ' layui-select-group' : '') +'">'
|
||||
,'<i class="layui-edge"></i>'
|
||||
,'</div>'].join(''));
|
||||
|
||||
var contentElem = $(['<dl class="layui-anim layui-anim-upbit'+ (othis.find('optgroup')[0] ? ' layui-select-group' : '') +'">'
|
||||
,function(options){
|
||||
var arr = [];
|
||||
layui.each(options, function(index, item){
|
||||
|
@ -771,11 +802,27 @@ layui.define(['lay', 'layer', 'util'], function(exports){
|
|||
arr.length === 0 && arr.push('<dd lay-value="" class="'+ DISABLED +'">没有选项</dd>');
|
||||
return arr.join('');
|
||||
}(othis.find('*')) +'</dl>'
|
||||
,'</div>'].join(''));
|
||||
].join(''));
|
||||
|
||||
hasRender[0] && hasRender.remove(); // 如果已经渲染,则Rerender
|
||||
othis.after(reElem);
|
||||
events.call(this, reElem, disabled, isSearch, isCreatable);
|
||||
// 如果已经渲染,则Rerender
|
||||
if(hasRender[0]){
|
||||
if(isAppendTo){
|
||||
var panelWrapElem = hasRender.data(PANEL_ELEM_DATA);
|
||||
panelWrapElem && panelWrapElem.remove();
|
||||
}
|
||||
hasRender.remove();
|
||||
}
|
||||
if(isAppendTo){
|
||||
reElem.append(triggerElem);
|
||||
othis.after(reElem);
|
||||
var contentWrapElem = $('<div class="'+ CLASS + ' ' + PANEL_WRAP +'"></div>').append(contentElem);
|
||||
reElem.data(PANEL_ELEM_DATA, contentWrapElem); // 将面板元素对象记录在触发元素 data 中,重新渲染时需要清理旧面板元素
|
||||
events.call(this, contentWrapElem, triggerElem, disabled, isSearch, isCreatable, isAppendTo);
|
||||
}else{
|
||||
reElem.append(triggerElem).append(contentElem);
|
||||
othis.after(reElem);
|
||||
events.call(this, reElem, triggerElem, disabled, isSearch, isCreatable, isAppendTo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -270,6 +270,7 @@
|
|||
* @param {string | number} [opts.margin=5] - 边距
|
||||
* @param {Event} [opts.e] - 事件对象,仅右键生效
|
||||
* @param {boolean} [opts.SYSTEM_RELOAD] - 是否重载,用于出现滚动条时重新计算位置
|
||||
* @param {[offsetX:number, offsetY:number]} [opts.offset] - 相对于触发元素的额外偏移量[x,y]
|
||||
* @example
|
||||
* ```js
|
||||
* <button id="targetEl">dropdown</button>
|
||||
|
@ -370,10 +371,12 @@
|
|||
// 定位类型
|
||||
var position = opts.position;
|
||||
if(position) elem.style.position = position;
|
||||
var offsetX = opts.offset ? opts.offset[0] : 0;
|
||||
var offsetY = opts.offset ? opts.offset[1] : 0;
|
||||
|
||||
// 设置坐标
|
||||
elem.style.left = left + (position === 'fixed' ? 0 : scrollArea(1)) + 'px';
|
||||
elem.style.top = top + (position === 'fixed' ? 0 : scrollArea()) + 'px';
|
||||
elem.style.left = left + (position === 'fixed' ? 0 : scrollArea(1)) + offsetX + 'px';
|
||||
elem.style.top = top + (position === 'fixed' ? 0 : scrollArea()) + offsetY + 'px';
|
||||
|
||||
// 防止页面无滚动条时,又因为弹出面板而出现滚动条导致的坐标计算偏差
|
||||
if(!lay.hasScrollbar()){
|
||||
|
@ -676,6 +679,99 @@
|
|||
}
|
||||
}();
|
||||
|
||||
/**
|
||||
* 监听指定元素外部的点击
|
||||
* @param {HTMLElement} target - 被监听的元素
|
||||
* @param {(e: Event) => void} handler - 事件触发时执行的函数
|
||||
* @param {object} [options] - 选项
|
||||
* @param {string} [options.event="pointerdown"] - 监听的事件类型
|
||||
* @param {HTMLElement | Window} [options.scope=document] - 监听范围
|
||||
* @param {Array<HTMLElement | string>} [options.ignore] - 忽略监听的元素或选择器字符串
|
||||
* @param {boolean} [options.capture=true] - 对内部事件侦听器使用捕获阶段
|
||||
* @returns {() => void} - 返回一个停止事件监听的函数
|
||||
*/
|
||||
lay.onClickOutside = function(target, handler, options){
|
||||
options = options || {};
|
||||
var eventType = options.event || ('onpointerdown' in window ? 'pointerdown' : 'mousedown');
|
||||
var scopeTarget = options.scope || document;
|
||||
var ignore = options.ignore || [];
|
||||
var useCapture = 'capture' in options ? options.capture : true;
|
||||
|
||||
var listener = function(event){
|
||||
var el = target;
|
||||
var eventTarget = event.target || event.srcElement;
|
||||
var eventPath = getEventPath(event);
|
||||
|
||||
if (!el || el === eventTarget || eventPath.indexOf(el) !== -1){
|
||||
return;
|
||||
}
|
||||
if(shouldIgnore(event, eventPath)){
|
||||
return;
|
||||
}
|
||||
|
||||
handler(event);
|
||||
};
|
||||
|
||||
function shouldIgnore(event, eventPath){
|
||||
var eventTarget = event.target || event.srcElement;
|
||||
for(var i = 0; i < ignore.length; i++){
|
||||
var target = ignore[i];
|
||||
if(typeof target === 'string'){
|
||||
var targetElements = document.querySelectorAll(target);
|
||||
for(var j = 0; j < targetElements.length; j++){
|
||||
var targetEl = targetElements[i];
|
||||
if(targetEl === eventTarget || eventPath.indexOf(targetEl) !== -1){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(target && (target === eventTarget || eventPath.indexOf(target) !== -1)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getEventPath(event){
|
||||
var path = (event.composedPath && event.composedPath()) || event.path;
|
||||
var eventTarget = event.target || event.srcElement;
|
||||
|
||||
if (path !== null && path !== undefined){
|
||||
return path;
|
||||
}
|
||||
|
||||
function getParents(node, memo){
|
||||
memo = memo || [];
|
||||
var parentNode = node.parentNode;
|
||||
|
||||
return parentNode
|
||||
? getParents(parentNode, memo.concat([parentNode]))
|
||||
: memo;
|
||||
}
|
||||
|
||||
return [eventTarget].concat(getParents(eventTarget));
|
||||
}
|
||||
|
||||
function bindEventListener(elem, eventName, handler, opts){
|
||||
elem.addEventListener
|
||||
? elem.addEventListener(eventName, handler, opts)
|
||||
: elem.attachEvent('on' + eventName, handler);
|
||||
|
||||
return function(){
|
||||
elem.removeEventListener
|
||||
? elem.removeEventListener(eventName, handler, opts)
|
||||
: elem.detachEvent('on' + eventName, handler);
|
||||
}
|
||||
}
|
||||
|
||||
return bindEventListener(
|
||||
scopeTarget,
|
||||
eventType,
|
||||
listener,
|
||||
lay.passiveSupported ? { passive: true, capture: useCapture } : useCapture
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* lay 元素操作
|
||||
|
|
Loading…
Reference in New Issue