mirror of
https://gitee.com/layui/layui.git
synced 2025-06-28 13:34:27 +08:00
refactor: 重构 tabs 代码大量细节
This commit is contained in:
parent
c1aa3bc6c9
commit
82a497927c
@ -7,10 +7,11 @@
|
||||
<link rel="stylesheet" href="../src/css/layui.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layui-container layui-padding-3">
|
||||
<div class="layui-container layui-padding-3 layui-text">
|
||||
<h2>动态操作</h2>
|
||||
<div class="layui-tabs layui-hide-v" id="demoTabs1" lay-options="{closable: true, headerMode:'scroll'}">
|
||||
<ul class="layui-tabs-header">
|
||||
<li lay-id="aaa" lay-unclosed="true" class="layui-this">Tab1</li>
|
||||
<li lay-id="aaa" lay-closable="false">Tab1</li>
|
||||
<li lay-id="bbb">Tab2</li>
|
||||
<li lay-id="ccc">Tab3</li>
|
||||
<li lay-id="ddd">Tab4</li>
|
||||
@ -18,7 +19,7 @@
|
||||
<li lay-id="fff">Tab6</li>
|
||||
</ul>
|
||||
<div class="layui-tabs-body">
|
||||
<div class="layui-tabs-item layui-show">Tab Content-1</div>
|
||||
<div class="layui-tabs-item">Tab Content-1</div>
|
||||
<div class="layui-tabs-item">Tab Content-2</div>
|
||||
<div class="layui-tabs-item">Tab Content-3</div>
|
||||
<div class="layui-tabs-item">Tab Content-4</div>
|
||||
@ -35,14 +36,10 @@
|
||||
<button class="layui-btn" lay-on="add">添加 Tab</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-py">
|
||||
方法渲染:
|
||||
<hr class="layui-my">
|
||||
<div id="demoTabs2"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-py">卡片风格:</div>
|
||||
<h2>方法渲染</h2>
|
||||
<div id="demoTabs2"></div>
|
||||
|
||||
<h2>卡片风格</h2>
|
||||
<div class="layui-tabs layui-tabs-card" lay-options="{index: 1}">
|
||||
<ul class="layui-tabs-header">
|
||||
<li>标题1</li>
|
||||
@ -62,8 +59,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-py">卡片 + 边框:</div>
|
||||
|
||||
<h3>卡片 + 边框</h3>
|
||||
<div class="layui-tabs layui-tabs-card layui-panel layui-inline">
|
||||
<ul class="layui-tabs-header layui-bg-tint">
|
||||
<li class="layui-this">标题1</li>
|
||||
@ -94,8 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-py">HASH:</div>
|
||||
|
||||
<h2>标签 HASH 定位</h2>
|
||||
<div class="layui-tabs layui-hide-v" id="demoTabs-hash">
|
||||
<ul class="layui-tabs-header">
|
||||
<li lay-id="A1" class="layui-this"><a href="#A1">标题题题题题题1</a></li>
|
||||
@ -109,8 +104,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="layui-py">标签嵌套:</div>
|
||||
|
||||
<h2>标签嵌套</h2>
|
||||
<div class="layui-tabs layui-tabs-card" lay-options="{headerMode:'normal'}">
|
||||
<ul class="layui-tabs-header">
|
||||
<li class="layui-this">标题1</li>
|
||||
@ -150,10 +144,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-py" id="demoTabs3">
|
||||
给任意元素添加 Tab 功能:
|
||||
<hr class="layui-my">
|
||||
|
||||
<h2>给任意元素绑定 tabs 切换功能</h2>
|
||||
<div id="demoTabs3">
|
||||
<style>
|
||||
#demoTabsHeader .layui-btn.layui-this{border-color: #eee; color: #000; background: none;}
|
||||
#demoTabsBody .test-item{display: none;}
|
||||
@ -168,18 +160,19 @@
|
||||
<div class="test-item">内容 222</div>
|
||||
<div class="test-item">内容 333</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../src/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['tabs', 'form', 'util'], function(){
|
||||
layui.use(function() {
|
||||
var tabs = layui.tabs
|
||||
var util = layui.util;
|
||||
var layer = layui.layer;
|
||||
var dropdown = layui.dropdown;
|
||||
|
||||
// 事件
|
||||
// 自定义事件
|
||||
util.on({
|
||||
add: function(){
|
||||
var n = Math.random()*1000 | 0; // 演示标记
|
||||
@ -198,6 +191,69 @@
|
||||
}
|
||||
});
|
||||
|
||||
// tabs 关闭前的事件
|
||||
tabs.on('beforeClose(demoTabs1)', function(data) {
|
||||
console.log('beforeClose', data);
|
||||
|
||||
// 关闭确认提示
|
||||
layer.confirm(`确定关闭标签「${this.innerText}」吗?`, function(i) {
|
||||
tabs.close('demoTabs1', data.index, true); // 强制关闭对应的标签项
|
||||
layer.close(i); // 关闭确认框
|
||||
});
|
||||
return false; // 阻止标签默认关闭
|
||||
});
|
||||
|
||||
// tabs 关闭后的事件
|
||||
tabs.on('afterClose(demoTabs1)', function(data) {
|
||||
console.log('afterClose', data);
|
||||
});
|
||||
|
||||
// tabs 切换前的事件
|
||||
tabs.on('beforeChange(demoTabs1)', function(data) {
|
||||
console.log('beforeChange', data);
|
||||
|
||||
// 切换确认提示
|
||||
/*layer.confirm(`确定从「当前标签」切换到标签「${this.innerText}」吗?`, function(i) {
|
||||
tabs.change('demoTabs1', data.to.index, true); // 强制切换
|
||||
layer.close(i); // 关闭确认框
|
||||
});
|
||||
return false; // 阻止标签默认关闭*/
|
||||
});
|
||||
|
||||
// tabs 切换后的事件
|
||||
tabs.on('afterChange(demoTabs1)', function(data) {
|
||||
console.log('afterChange', data);
|
||||
});
|
||||
|
||||
// 为标签头添加上下文菜单
|
||||
dropdown.render({
|
||||
elem: '#demoTabs1 .layui-tabs-header>li',
|
||||
trigger: 'contextmenu',
|
||||
data: [{
|
||||
title: '关闭',
|
||||
type: 'this'
|
||||
}, {
|
||||
title: '关闭其他标签页',
|
||||
type: 'other'
|
||||
}, {
|
||||
title: '关闭右侧标签页',
|
||||
type: 'right'
|
||||
}, {
|
||||
type: '-'
|
||||
}, {
|
||||
title: '关闭所有标签页',
|
||||
type: 'all'
|
||||
}],
|
||||
click: function(data, othis, event) {
|
||||
var index = this.elem.index();
|
||||
if (data.type === 'this') {
|
||||
tabs.close('demoTabs1', index); // 关闭当前标签
|
||||
} else {
|
||||
tabs.closeMult('demoTabs1', data.type, index); // 批量关闭标签
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 方法渲染
|
||||
tabs.render({
|
||||
elem: '#demoTabs2',
|
||||
@ -225,10 +281,7 @@
|
||||
tabs.render({
|
||||
elem: '#demoTabs3',
|
||||
header: ['#demoTabsHeader', '>button'],
|
||||
body: ['#demoTabsBody', '>.test-item'],
|
||||
change: function(obj) {
|
||||
console.log(obj);
|
||||
}
|
||||
body: ['#demoTabsBody', '>.test-item']
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -1278,19 +1278,21 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh
|
||||
/* 下拉菜单 */
|
||||
.layui-dropdown{position: absolute; left: -999999px; top: -999999px; z-index: 77777777; margin: 5px 0; min-width: 100px;}
|
||||
.layui-dropdown:before{content:""; position: absolute; width: 100%; height: 6px; left: 0; top: -6px;}
|
||||
.layui-dropdown-shade{top: 0; left: 0; width: 100%; height: 100%; _height: expression(document.body.offsetHeight+"px"); position: fixed; _position: absolute; pointer-events: auto;}
|
||||
.layui-dropdown-shade{top: 0; left: 0; width: 100%; height: 100%; position: fixed; pointer-events: auto;}
|
||||
|
||||
|
||||
/* Tabs 标签页 */
|
||||
.layui-tabs{position: relative;}
|
||||
.layui-tabs *{box-sizing: border-box;}
|
||||
.layui-tabs.layui-hide-v{overflow: hidden;}
|
||||
.layui-tabs-header{position: relative; left: 0; height: 40px; white-space: nowrap; font-size: 0; transition: all .16s; -webkit-transition: all .16s;}
|
||||
.layui-tabs-header{position: relative; left: 0; height: 40px; padding: 0 !important; white-space: nowrap; font-size: 0; transition: all .16s; -webkit-transition: all .16s;}
|
||||
.layui-tabs-header:after,
|
||||
.layui-tabs-scroll:after{content: ""; position: absolute; left: 0; bottom: 0; z-index: 0; width: 100%; border-bottom: 1px solid #eee;}
|
||||
.layui-tabs-header li{position: relative; display: inline-block; vertical-align: middle; line-height: 40px; margin: 0 !important; padding: 0 16px; text-align: center; cursor: pointer; font-size: 14px; transition: all .16s; -webkit-transition: all .16s;}
|
||||
.layui-tabs-header li:first-child{margin-left: 0;}
|
||||
.layui-tabs-header li a{display: block; padding: 0 16px; margin: 0 -16px;}
|
||||
.layui-tabs-header li a{display: block; padding: 0 16px; margin: 0 -16px; color: inherit;}
|
||||
.layui-tabs-header li a:hover{text-decoration: none;}
|
||||
.layui-tabs-header li:hover,
|
||||
.layui-tabs-header .layui-this{color: #16baaa;}
|
||||
.layui-tabs-header .layui-this:after{content: ""; position: absolute; left:0; top: 0; z-index: 1; width: 100%; height: 100%; border-bottom: 3px solid #16baaa; box-sizing: border-box; pointer-events: none;}
|
||||
.layui-tabs-header .layui-badge,
|
||||
|
@ -15,8 +15,8 @@ layui.define('component', function(exports) {
|
||||
// 默认配置
|
||||
config: {
|
||||
elem: '.layui-tabs',
|
||||
trigger: 'click', // 触发事件
|
||||
headerMode: 'auto' // 标签头部的显示模式
|
||||
trigger: 'click', // 标签切换的触发事件
|
||||
headerMode: 'auto' // 标签头部的显示模式 auto | scroll | normal
|
||||
},
|
||||
|
||||
CONST: {
|
||||
@ -25,8 +25,7 @@ layui.define('component', function(exports) {
|
||||
CLOSE: 'layui-tabs-close',
|
||||
BODY: 'layui-tabs-body',
|
||||
ITEM: 'layui-tabs-item',
|
||||
CARD: 'layui-tabs-card',
|
||||
TRIGGER_NAME: 'LAY_TABS_ELEM_callback'
|
||||
CARD: 'layui-tabs-card'
|
||||
},
|
||||
|
||||
isRenderOnEvent: false,
|
||||
@ -37,70 +36,70 @@ layui.define('component', function(exports) {
|
||||
var options = that.config;
|
||||
|
||||
// 标签页元素项
|
||||
that.elemHeader = ['.'+ component.CONST.HEADER + ':eq(0)', '>li'];
|
||||
that.elemBody = ['.'+ component.CONST.BODY + ':eq(0)', '>.'+ component.CONST.ITEM];
|
||||
that.headerElem = ['.'+ component.CONST.HEADER + ':eq(0)', '>li'];
|
||||
that.bodyElem = ['.'+ component.CONST.BODY + ':eq(0)', '>.'+ component.CONST.ITEM];
|
||||
|
||||
// 获取头部和内容元素
|
||||
that.elemItem = function(){
|
||||
var thisElem = that.thisElem || options.elem;
|
||||
// 获取标签容器中的 header body 相关元素
|
||||
that.getContainer = function() {
|
||||
var elem = that.documentElem || options.elem;
|
||||
return {
|
||||
header: {
|
||||
elem: thisElem.find(that.elemHeader[0]),
|
||||
items: thisElem.find(that.elemHeader.join(''))
|
||||
elem: elem.find(that.headerElem[0]),
|
||||
items: elem.find(that.headerElem.join(''))
|
||||
},
|
||||
body: {
|
||||
elem: thisElem.find(that.elemBody[0]),
|
||||
items: thisElem.find(that.elemBody.join(''))
|
||||
elem: elem.find(that.bodyElem[0]),
|
||||
items: elem.find(that.bodyElem.join(''))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 如果传入 header 参数
|
||||
// 若 header 选项类型为数组
|
||||
if (layui.type(options.header) === 'array') {
|
||||
if (options.header.length === 0) return;
|
||||
|
||||
// 是否为元素绑定
|
||||
// 给任意元素绑定 tabs 切换功能
|
||||
if (typeof options.header[0] === 'string') {
|
||||
that.elemHeader = options.header.concat();
|
||||
that.thisElem = $('body');
|
||||
that.headerElem = options.header.concat();
|
||||
that.documentElem = $(document);
|
||||
} else { // 方法传值渲染
|
||||
that.elemView = $('<div class="layui-tabs"></div>');
|
||||
if (options.className) that.elemView.addClass(options.className);
|
||||
|
||||
var elemHeader = $('<ul class="layui-tabs-header"></ul>');
|
||||
var elemBody = $('<div class="layui-tabs-body"></div>');
|
||||
var headerElem = $('<ul class="layui-tabs-header"></ul>');
|
||||
var bodyElem = $('<div class="layui-tabs-body"></div>');
|
||||
|
||||
// 生成标签项
|
||||
layui.each(options.header, function(i, item){
|
||||
var elemHeaderItem = that.renderHeaderItem(item);
|
||||
elemHeader.append(elemHeaderItem);
|
||||
headerElem.append(elemHeaderItem);
|
||||
});
|
||||
layui.each(options.body, function(i, item){
|
||||
var elemBodyItem = that.renderBodyItem(item);
|
||||
elemBody.append(elemBodyItem);
|
||||
bodyElem.append(elemBodyItem);
|
||||
});
|
||||
|
||||
that.elemView.append(elemHeader).append(elemBody);
|
||||
that.elemView.append(headerElem).append(bodyElem);
|
||||
options.elem.html(that.elemView);
|
||||
}
|
||||
} else {
|
||||
that.renderClose(); // 初始化标签关闭结构
|
||||
}
|
||||
|
||||
// 如果传入 body 参数
|
||||
// 若 body 选项类型为数组
|
||||
if (layui.type(options.body) === 'array') {
|
||||
if (typeof options.body[0] === 'string') {
|
||||
that.thisElem = $('body');
|
||||
that.elemBody = options.body.concat();
|
||||
that.documentElem = $(document);
|
||||
that.bodyElem = options.body.concat();
|
||||
}
|
||||
}
|
||||
|
||||
// 初始选中项
|
||||
var data = that.data();
|
||||
if ('index' in options && data.index != options.index) {
|
||||
that.change(that.findHeaderItem(options.index));
|
||||
} else if (data.index === -1) {
|
||||
that.change(that.findHeaderItem(0));
|
||||
that.change(that.findHeaderItem(options.index), true);
|
||||
} else if (data.index === -1) { // 初始选中项为空时,默认选中第一个
|
||||
that.change(that.findHeaderItem(0), true);
|
||||
}
|
||||
|
||||
// 初始化滚动结构
|
||||
@ -112,41 +111,45 @@ layui.define('component', function(exports) {
|
||||
}
|
||||
|
||||
// 回调
|
||||
typeof options.ready === 'function' && options.ready(data);
|
||||
layui.event.call(options.elem[0], component.CONST.MOD_NAME, 'ready('+ options.id +')', data); // 事件
|
||||
typeof options.afterRender === 'function' && options.afterRender(data);
|
||||
|
||||
// 渲染成功后的事件
|
||||
layui.event.call(
|
||||
options.elem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'afterRender('+ options.id +')',
|
||||
data
|
||||
);
|
||||
},
|
||||
|
||||
// 事件
|
||||
events: function() {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var TRIGGER_NAME = component.CONST.TRIGGER_NAME;
|
||||
var thisElem = that.thisElem ? elemItem.header.elem : options.elem;
|
||||
var container = that.getContainer();
|
||||
var MOD_NAME = component.CONST.MOD_NAME;
|
||||
var TRIGGER_NAMESPACE = '.lay_'+ MOD_NAME + '_trigger';
|
||||
var delegatedElement = that.documentElem ? container.header.elem : options.elem;
|
||||
|
||||
// 移除重复事件
|
||||
if (thisElem.data(TRIGGER_NAME)) {
|
||||
thisElem.off(options.trigger, thisElem.data(TRIGGER_NAME));
|
||||
}
|
||||
|
||||
// 点击标签头部
|
||||
var elemHeaderItem = that.thisElem ? that.elemHeader[1] : that.elemHeader.join('');
|
||||
thisElem.data(TRIGGER_NAME, function(){
|
||||
// 标签头部事件
|
||||
var trigger = options.trigger + TRIGGER_NAMESPACE;
|
||||
var elemHeaderItem = that.documentElem ? that.headerElem[1] : that.headerElem.join('');
|
||||
delegatedElement.off(trigger).on(trigger, elemHeaderItem, function() {
|
||||
that.change($(this));
|
||||
}).on(options.trigger, elemHeaderItem, thisElem.data(TRIGGER_NAME));
|
||||
});
|
||||
|
||||
// resize 事件
|
||||
// 窗口 resize 事件
|
||||
if (!inner.onresize) {
|
||||
var timer;
|
||||
$(window).on('resize', function(){
|
||||
$(window).on('resize', function() {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function(){
|
||||
layui.each(component.cache.id, function(key){
|
||||
layui.each(component.cache.id, function(key) {
|
||||
var that = component.getThis(key);
|
||||
if(!that) return;
|
||||
that.roll('init');
|
||||
});
|
||||
},50);
|
||||
}, 50);
|
||||
});
|
||||
inner.onresize = true;
|
||||
}
|
||||
@ -164,116 +167,160 @@ layui.define('component', function(exports) {
|
||||
|
||||
/**
|
||||
* 增加标签
|
||||
* @param {*} obj
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.title - 标签标题
|
||||
* @param {string} opts.content - 标签内容
|
||||
* @param {string} opts.id - 标签的 lay-id 属性值
|
||||
* @param {string} [opts.index] - 活动标签索引,默认取当前选中标签的索引
|
||||
* @param {('append'|'prepend'|'after'|'before')} [opts.mode='append'] - 标签插入方式
|
||||
* @param {string} [opts.closable] - 标签是否可关闭。初始值取决于 options.closable
|
||||
* @param {string} [opts.headerItem] - 自定义标签头部元素
|
||||
* @param {string} [opts.bodyItem] - 自定义标签内容元素
|
||||
* @param {Function} [opts.done] - 标签添加成功后执行的回调函数
|
||||
*/
|
||||
Class.prototype.add = function(obj){
|
||||
Class.prototype.add = function(opts) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var newHeaderItem = that.renderHeaderItem(obj);
|
||||
var newBodyItem = that.renderBodyItem(obj);
|
||||
var container = that.getContainer();
|
||||
var newHeaderItem = that.renderHeaderItem(opts);
|
||||
var newBodyItem = that.renderBodyItem(opts);
|
||||
|
||||
// 插入方式
|
||||
if (obj.mode === 'curr') { // 在当前标签后插入
|
||||
if (/(before|after)/.test(opts.mode)) { // 在活动标签前后插入
|
||||
var data = that.data();
|
||||
data.thisHeader.after(newHeaderItem);
|
||||
data.thisBody.after(newBodyItem);
|
||||
} else {
|
||||
var hasOwnIndex = opts.hasOwnProperty('index');
|
||||
var headerItem = hasOwnIndex ? that.findHeaderItem(opts.index) : data.thisHeaderItem;
|
||||
var bodyItem = hasOwnIndex ? that.findBodyItem(opts.index) : data.thisHeaderItem;
|
||||
headerItem[opts.mode](newHeaderItem);
|
||||
bodyItem[opts.mode](newBodyItem);
|
||||
} else { // 在标签最前后插入
|
||||
var mode = ({
|
||||
prepend: 'prepend'
|
||||
,append: 'append'
|
||||
})[obj.mode || 'append'] || 'append';
|
||||
elemItem.header.elem[mode](newHeaderItem);
|
||||
elemItem.body.elem[mode](newBodyItem);
|
||||
prepend: 'prepend', // 插入标签到最前
|
||||
append: 'append' // 插入标签到最后
|
||||
})[opts.mode || 'append'] || 'append';
|
||||
container.header.elem[mode](newHeaderItem);
|
||||
container.body.elem[mode](newBodyItem);
|
||||
}
|
||||
|
||||
// 将插入项切换为当前标签
|
||||
that.change(newHeaderItem);
|
||||
that.change(newHeaderItem, true);
|
||||
|
||||
// 回调
|
||||
var params = that.data();
|
||||
typeof obj.done === 'function' && obj.done(params);
|
||||
typeof options.add === 'function' && options.add(params);
|
||||
layui.event.call(newHeaderItem[0], component.CONST.MOD_NAME, 'add('+ options.id +')', params); // 事件
|
||||
typeof opts.done === 'function' && opts.done(params);
|
||||
};
|
||||
|
||||
// 关闭指定标签
|
||||
Class.prototype.close = function(thisHeader) {
|
||||
if(!thisHeader || !thisHeader[0]) return;
|
||||
/**
|
||||
* 关闭指定标签
|
||||
* @param {Object} thisHeaderItem - 当前标签头部项元素
|
||||
* @param {boolean} force - 是否强制删除
|
||||
*/
|
||||
Class.prototype.close = function(thisHeaderItem, force) {
|
||||
if(!thisHeaderItem || !thisHeaderItem[0]) return;
|
||||
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var index = thisHeader.index();
|
||||
var index = thisHeaderItem.index();
|
||||
|
||||
if (!thisHeader[0]) return;
|
||||
if (!thisHeaderItem[0]) return;
|
||||
|
||||
// 不可关闭项
|
||||
if (thisHeader.attr('lay-unclosed')) return;
|
||||
// 标签是否不可关闭
|
||||
if (thisHeaderItem.attr('lay-closable') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果关闭的是当前标签,则更换当前标签下标
|
||||
if (thisHeader.hasClass(component.CONST.CLASS_THIS)) {
|
||||
if (thisHeader.next()[0]) {
|
||||
that.change(thisHeader.next());
|
||||
} else if(thisHeader.prev()[0]) {
|
||||
that.change(thisHeader.prev());
|
||||
// 当前标签相关数据
|
||||
var params = that.data();
|
||||
|
||||
// 标签关闭前的事件。若非强制关闭,可则根据事件的返回结果决定是否关闭
|
||||
if (!force) {
|
||||
var closable = layui.event.call(
|
||||
thisHeaderItem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'beforeClose('+ options.id +')',
|
||||
$.extend(params, {
|
||||
index: thisHeaderItem.index()
|
||||
})
|
||||
);
|
||||
|
||||
// 是否阻止关闭
|
||||
if (closable === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果关闭的是当前标签,则更换当前标签索引
|
||||
if (thisHeaderItem.hasClass(component.CONST.CLASS_THIS)) {
|
||||
if (thisHeaderItem.next()[0]) {
|
||||
that.change(thisHeaderItem.next(), true);
|
||||
} else if(thisHeaderItem.prev()[0]) {
|
||||
that.change(thisHeaderItem.prev(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除元素
|
||||
thisHeader.remove();
|
||||
thisHeaderItem.remove();
|
||||
that.findBodyItem(index).remove();
|
||||
|
||||
that.roll('auto', index);
|
||||
|
||||
// 回调
|
||||
// 获取当前标签相关数据
|
||||
var params = that.data();
|
||||
typeof options.close === 'function' && options.close(params);
|
||||
layui.event.call(params.thisHeader[0], component.CONST.MOD_NAME, 'close('+ options.id +')', params); // 事件
|
||||
|
||||
// 标签关闭后的事件
|
||||
layui.event.call(
|
||||
params.thisHeaderItem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'afterClose('+ options.id +')',
|
||||
params
|
||||
);
|
||||
};
|
||||
|
||||
// 批量关闭标签
|
||||
Class.prototype.closeMore = function(type, index) {
|
||||
/**
|
||||
* 批量关闭标签
|
||||
* @see tabs.close
|
||||
*/
|
||||
Class.prototype.closeMult = function(mode, index) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var container = that.getContainer();
|
||||
var data = that.data();
|
||||
var headers = elemItem.header.items;
|
||||
var bodys = elemItem.body.items;
|
||||
var FILTER = ':not([lay-unclosed])';
|
||||
var headers = container.header.items;
|
||||
var bodys = container.body.items;
|
||||
var DISABLED_CLOSE_SELECTOR = '[lay-closable="false"]'; // 不可关闭标签选择器
|
||||
var FILTER = ':not('+ DISABLED_CLOSE_SELECTOR +')'; // 不可关闭标签过滤器
|
||||
|
||||
index = index === undefined ? data.index : index;
|
||||
|
||||
// 遍历
|
||||
headers.each(function(i){
|
||||
// 将标签头 lay-closable 属性值同步到 body 项
|
||||
headers.each(function(i) {
|
||||
var othis = $(this);
|
||||
var unclosed = othis.attr('lay-unclosed');
|
||||
|
||||
// 标注不可关闭项
|
||||
if (unclosed) {
|
||||
bodys.eq(i).attr('lay-unclosed', unclosed);
|
||||
var closableAttr = othis.attr('lay-closable');
|
||||
if (closableAttr) {
|
||||
bodys.eq(i).attr('lay-closable', closableAttr);
|
||||
}
|
||||
});
|
||||
|
||||
// 移交当前标签项
|
||||
if (!data.thisHeader.attr('lay-unclosed')) {
|
||||
if(type === 'all' || !type){
|
||||
var nextHeader = headers.filter(':gt('+ data.index +')[lay-unclosed]').eq(0);
|
||||
var prevHeader = $(headers.filter(':lt('+ data.index +')[lay-unclosed]').get().reverse()).eq(0);
|
||||
// 若当前选中标签也允许关闭,则尝试寻找不可关闭的标签并将其选中
|
||||
if (data.thisHeaderItem.attr('lay-closable') !== 'false') {
|
||||
if(mode === 'all' || !mode){
|
||||
var nextHeader = headers.filter(':gt('+ data.index +')'+ DISABLED_CLOSE_SELECTOR).eq(0);
|
||||
var prevHeader = $(headers.filter(':lt('+ data.index +')'+ DISABLED_CLOSE_SELECTOR).get().reverse()).eq(0);
|
||||
if (nextHeader[0]) {
|
||||
that.change(nextHeader);
|
||||
that.change(nextHeader, true);
|
||||
} else if(prevHeader[0]) {
|
||||
that.change(prevHeader);
|
||||
that.change(prevHeader, true);
|
||||
}
|
||||
} else if(index !== data.index) {
|
||||
that.change(that.findHeaderItem(index));
|
||||
} else if(index !== data.index) { // 自动切换到活动标签(功能可取消)
|
||||
that.change(that.findHeaderItem(index), true);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行批量关闭标签
|
||||
if (type === 'other') { // 关闭其他标签
|
||||
if (mode === 'other') { // 关闭其他标签
|
||||
headers.eq(index).siblings(FILTER).remove();
|
||||
bodys.eq(index).siblings(FILTER).remove();
|
||||
} else if(type === 'right') { // 关闭右侧标签
|
||||
} else if(mode === 'right') { // 关闭右侧标签
|
||||
headers.filter(':gt('+ index +')'+ FILTER).remove();
|
||||
bodys.filter(':gt('+ index +')'+ FILTER).remove();
|
||||
} else { // 关闭所有标签
|
||||
@ -281,120 +328,191 @@ layui.define('component', function(exports) {
|
||||
bodys.filter(FILTER).remove();
|
||||
}
|
||||
|
||||
that.roll('auto');
|
||||
|
||||
// 回调
|
||||
var params = that.data();
|
||||
typeof options.close === 'function' && options.close(params);
|
||||
layui.event.call(params.thisHeader[0], component.CONST.MOD_NAME, 'close('+ options.id +')', params); // 事件
|
||||
|
||||
// 标签关闭后的事件
|
||||
layui.event.call(
|
||||
params.thisHeaderItem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'afterClose('+ options.id +')',
|
||||
params
|
||||
);
|
||||
};
|
||||
|
||||
// 切换标签
|
||||
Class.prototype.change = function(thisHeader) {
|
||||
if (!thisHeader || !thisHeader[0]) return;
|
||||
/**
|
||||
* 切换标签
|
||||
* @param {Object} thisHeaderItem - 当前标签头部项元素
|
||||
* @param {boolean} [force=false] - 是否强制切换
|
||||
* @returns
|
||||
*/
|
||||
Class.prototype.change = function(thisHeaderItem, force) {
|
||||
if (!thisHeaderItem || !thisHeaderItem[0]) return;
|
||||
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var index = thisHeader.index();
|
||||
var thatA = thisHeader.find('a');
|
||||
var isLink = typeof thatA.attr('href') === 'string' && thatA.attr('target') === '_blank'; // 是否存在跳转链接
|
||||
var unselect = typeof thisHeader.attr('lay-unselect') === 'string'; // 是否禁用选中
|
||||
var index = thisHeaderItem.index();
|
||||
var thatA = thisHeaderItem.find('a');
|
||||
// 是否存在跳转链接
|
||||
var isLink = typeof thatA.attr('href') === 'string' && thatA.attr('target') === '_blank';
|
||||
// 是否不允许选中
|
||||
var unselect = typeof thisHeaderItem.attr('lay-unselect') === 'string';
|
||||
|
||||
// 执行切换
|
||||
if (!(isLink || unselect)) {
|
||||
// 头部
|
||||
thisHeader.addClass(component.CONST.CLASS_THIS).siblings()
|
||||
.removeClass(component.CONST.CLASS_THIS);
|
||||
// 内容
|
||||
that.findBodyItem(index).addClass(component.CONST.CLASS_SHOW)
|
||||
.siblings().removeClass(component.CONST.CLASS_SHOW);
|
||||
// 不满足切换的条件
|
||||
if (isLink || unselect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 当前标签相关数据
|
||||
var params = that.data();
|
||||
|
||||
// 标签关闭前的事件。若非强制关闭,可则根据事件的返回结果决定是否关闭
|
||||
if (!force) {
|
||||
var enable = layui.event.call(
|
||||
thisHeaderItem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'beforeChange('+ options.id +')',
|
||||
$.extend(params, {
|
||||
from: {
|
||||
index: params.index,
|
||||
headerItem: params.thisHeaderItem
|
||||
},
|
||||
to: {
|
||||
index: thisHeaderItem.index(),
|
||||
headerItem: thisHeaderItem
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 是否阻止切换
|
||||
if (enable === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 执行标签头部切换
|
||||
thisHeaderItem.addClass(component.CONST.CLASS_THIS).siblings()
|
||||
.removeClass(component.CONST.CLASS_THIS);
|
||||
|
||||
// 执行标签内容切换
|
||||
that.findBodyItem(index).addClass(component.CONST.CLASS_SHOW)
|
||||
.siblings().removeClass(component.CONST.CLASS_SHOW);
|
||||
|
||||
that.roll('auto', index);
|
||||
|
||||
// 回调
|
||||
// 重新获取标签相关数据
|
||||
var params = that.data();
|
||||
typeof options.change === 'function' && options.change(params);
|
||||
layui.event.call(thisHeader[0], component.CONST.MOD_NAME, 'change('+ options.id +')', params); // 事件
|
||||
|
||||
// 标签切换后的事件
|
||||
layui.event.call(
|
||||
params.thisHeaderItem[0],
|
||||
component.CONST.MOD_NAME,
|
||||
'afterChange('+ options.id +')',
|
||||
params
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染标签头部项
|
||||
Class.prototype.renderHeaderItem = function(obj){
|
||||
/**
|
||||
* 渲染标签头部项
|
||||
* @param {Object} opts - 标签项配置信息
|
||||
*/
|
||||
Class.prototype.renderHeaderItem = function(opts) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = $(obj.headerItem || options.headerItem || '<li></li>');
|
||||
var headerItem = $(opts.headerItem || options.headerItem || '<li></li>');
|
||||
|
||||
elemItem.html(obj.title || 'New Tab');
|
||||
headerItem.html(opts.title || 'New Tab');
|
||||
|
||||
// 追加属性
|
||||
layui.each(obj, function(key, value){
|
||||
layui.each(opts, function(key, value){
|
||||
if(/^(title|content|mode|done)$/.test(key)) return;
|
||||
elemItem.attr('lay-'+ key, value);
|
||||
headerItem.attr('lay-'+ key, value);
|
||||
});
|
||||
|
||||
// 追加标签关闭元素
|
||||
that.appendClose(elemItem, obj);
|
||||
that.appendClose(headerItem, opts);
|
||||
|
||||
return elemItem;
|
||||
return headerItem;
|
||||
};
|
||||
|
||||
// 渲染标签内容项
|
||||
Class.prototype.renderBodyItem = function(obj) {
|
||||
/**
|
||||
* 渲染标签内容项
|
||||
* @param {Object} opts - 标签项配置信息
|
||||
*/
|
||||
Class.prototype.renderBodyItem = function(opts) {
|
||||
var that = this
|
||||
var options = that.config
|
||||
var elemItem = $(obj.bodyItem || options.bodyItem || '<div class="'+ component.CONST.ITEM +'"></div>');
|
||||
var bodyItem = $(opts.bodyItem || options.bodyItem || '<div class="'+ component.CONST.ITEM +'"></div>');
|
||||
|
||||
elemItem.html(obj.content || '');
|
||||
return elemItem;
|
||||
bodyItem.html(opts.content || '');
|
||||
return bodyItem;
|
||||
};
|
||||
|
||||
// 给某一个标签项追加可关闭元素
|
||||
Class.prototype.appendClose = function(othis, obj) {
|
||||
/**
|
||||
* 给某一个标签项追加可关闭元素
|
||||
* @param {Object} headerItem - 标签项元素
|
||||
* @param {Object} opts - 标签项配置信息
|
||||
*/
|
||||
Class.prototype.appendClose = function(headerItem, opts) {
|
||||
var that = this
|
||||
var options = that.config;
|
||||
|
||||
if(!options.closable) return;
|
||||
|
||||
obj = obj || {};
|
||||
if (obj.unclosed || othis.attr('lay-unclosed')) return; // 不可关闭项
|
||||
opts = opts || {};
|
||||
|
||||
if (!othis.find('.'+ component.CONST.CLOSE)[0]) {
|
||||
// 不可关闭项
|
||||
if (opts.closable === 'false' || headerItem.attr('lay-closable') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 可关闭项追加关闭按钮
|
||||
if (!headerItem.find('.'+ component.CONST.CLOSE)[0]) {
|
||||
var close = $('<i class="layui-icon layui-icon-close layui-unselect '+ component.CONST.CLOSE +'"></i>');
|
||||
close.on('click', function(){
|
||||
that.close($(this).parent());
|
||||
return false;
|
||||
});
|
||||
othis.append(close);
|
||||
headerItem.append(close);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染标签可关闭元素
|
||||
Class.prototype.renderClose = function(othis) {
|
||||
Class.prototype.renderClose = function() {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var container = that.getContainer();
|
||||
var hasDel = that.cache('close');
|
||||
|
||||
// 是否开启关闭
|
||||
if (options.closable) {
|
||||
if (!hasDel) {
|
||||
elemItem.header.items.each(function(){
|
||||
container.header.items.each(function(){
|
||||
that.appendClose($(this));
|
||||
});
|
||||
that.cache('close', true);
|
||||
}
|
||||
} else if(hasDel) {
|
||||
elemItem.header.items.each(function() {
|
||||
container.header.items.each(function() {
|
||||
$(this).find('.'+ component.CONST.CLOSE).remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 滚动标签
|
||||
Class.prototype.roll = function(type, index) {
|
||||
/**
|
||||
* 标签头滚动
|
||||
* @param {('auto'|'prev'|'next'|'init')} [mode='next'] - 滚动方式
|
||||
* @param {number} index - 标签索引。默认取当前选中标签的索引值
|
||||
* @returns
|
||||
*/
|
||||
Class.prototype.roll = function(mode, index) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var headerElem = elemItem.header.elem;
|
||||
var headerItems = elemItem.header.items;
|
||||
var container = that.getContainer();
|
||||
var headerElem = container.header.elem;
|
||||
var headerItems = container.header.items;
|
||||
var scrollWidth = headerElem.prop('scrollWidth'); // 实际总长度
|
||||
var outerWidth = Math.ceil(headerElem.outerWidth()); // 可视区域的长度
|
||||
var tabsLeft = headerElem.data('left') || 0;
|
||||
@ -418,7 +536,7 @@ layui.define('component', function(exports) {
|
||||
// 左侧临界值
|
||||
if (tabsLeft + countWidth < 0) {
|
||||
tabsLeft = countWidth >= 0 ? countWidth : 0; // 标签的复原位移不能超出 0
|
||||
return headerElem.css('left', -tabsLeft).data('left', -tabsLeft);;
|
||||
return headerElem.css('left', -tabsLeft).data('left', -tabsLeft);
|
||||
}
|
||||
|
||||
// 当选中标签溢出在可视区域「右侧」时,
|
||||
@ -478,7 +596,8 @@ layui.define('component', function(exports) {
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'init') return;
|
||||
// 初始化滚动模式
|
||||
if (mode === 'init') return;
|
||||
|
||||
// 重新获取
|
||||
scrollWidth = headerElem.prop('scrollWidth') // 实际总长度
|
||||
@ -486,7 +605,7 @@ layui.define('component', function(exports) {
|
||||
elemScroll = headerElem.parent('.'+ CLASS_SCROLL);
|
||||
|
||||
// 左箭头(往右滚动)
|
||||
if (type === 'prev') {
|
||||
if (mode === 'prev') {
|
||||
// 当前的 left 减去可视宽度,用于与上一轮的页签比较
|
||||
var prevLeft = -tabsLeft - outerWidth;
|
||||
if(prevLeft < 0) prevLeft = 0;
|
||||
@ -499,9 +618,9 @@ layui.define('component', function(exports) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else if(type === 'auto') { // 自动识别滚动
|
||||
} else if(mode === 'auto') { // 自动识别滚动
|
||||
rollToVisibleArea();
|
||||
} else { // 右箭头(往左滚动)
|
||||
} else { // 右箭头(往左滚动) 默认 next
|
||||
headerItems.each(function(i, item){
|
||||
var li = $(item);
|
||||
var left = Math.ceil(li.position().left);
|
||||
@ -528,96 +647,101 @@ layui.define('component', function(exports) {
|
||||
](component.CONST.CLASS_DISABLED);
|
||||
};
|
||||
|
||||
// 根据 id 或 index 获取相关标签头部项
|
||||
/**
|
||||
* 根据 id 或 index 获取相关标签头部项
|
||||
* @param {number|string} index - 标签索引或 id
|
||||
*/
|
||||
Class.prototype.findHeaderItem = function(index) {
|
||||
if(!(
|
||||
typeof index === 'number'
|
||||
|| (typeof index === 'string' && index)
|
||||
)) return;
|
||||
var headerItems = this.elemItem().header.items;
|
||||
var headerItems = this.getContainer().header.items;
|
||||
var item = headerItems.filter('[lay-id="'+ index +'"]');
|
||||
return item[0] ? item : headerItems.eq(index);
|
||||
};
|
||||
|
||||
// 根据 index 获取相关标签内容项
|
||||
/**
|
||||
* 根据 index 获取相关标签内容项
|
||||
* @param {number} index - 标签索引
|
||||
*/
|
||||
Class.prototype.findBodyItem = function(index) {
|
||||
return this.elemItem().body.items.eq(index);
|
||||
return this.getContainer().body.items.eq(index);
|
||||
};
|
||||
|
||||
// 返回给回调的公共信息
|
||||
/**
|
||||
* 返回给回调的公共信息
|
||||
* @returns
|
||||
*/
|
||||
Class.prototype.data = function() {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var elemItem = that.elemItem();
|
||||
var thisHeader = elemItem.header.items.filter('.'+ component.CONST.CLASS_THIS);
|
||||
var index = thisHeader.index();
|
||||
var container = that.getContainer();
|
||||
var thisHeaderItem = container.header.items.filter('.'+ component.CONST.CLASS_THIS);
|
||||
var index = thisHeaderItem.index();
|
||||
|
||||
return {
|
||||
options: options, // 标签配置信息
|
||||
elemItem: elemItem,
|
||||
thisHeader: thisHeader, // 当前标签头部项
|
||||
thisBody: that.findBodyItem(index), // 当前标签内容项
|
||||
index: index, // 当前标签下标
|
||||
length: elemItem.header.items.length // 当前标签数
|
||||
container: container, // 标签容器的相关元素
|
||||
thisHeaderItem: thisHeaderItem, // 当前标签头部项
|
||||
thisBodyItem: that.findBodyItem(index), // 当前标签内容项
|
||||
index: index, // 当前标签索引
|
||||
length: container.header.items.length // 当前标签数
|
||||
}
|
||||
};
|
||||
|
||||
// 扩展组件接口
|
||||
$.extend(component, {
|
||||
/**
|
||||
* 增加标签
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {object} obj - 标签配置信息
|
||||
* @returns
|
||||
* 添加标签
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {Object} opts - 添加标签的配置项,详见 Class.prototype.add
|
||||
*/
|
||||
add: function(id, obj) {
|
||||
add: function(id, opts) {
|
||||
var that = component.getThis(id);
|
||||
if(!that) return this;
|
||||
that.add(obj);
|
||||
if(!that) return;
|
||||
that.add(opts);
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭标签
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {number} index - 标签下标
|
||||
* @returns
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {number} index - 标签索引
|
||||
* @param {boolean} [force=false] - 是否强制关闭
|
||||
*/
|
||||
close: function(id, index) {
|
||||
close: function(id, index, force) {
|
||||
var that = component.getThis(id);
|
||||
if(!that) return this;
|
||||
if(!that) return;
|
||||
if(index === undefined) index = that.data().index; // index 若不传,则表示关闭当前标签
|
||||
that.close(that.findHeaderItem(index));
|
||||
that.close(that.findHeaderItem(index), force);
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭多个标签
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {string} type - 关闭类型,可选值:left、right、other、all
|
||||
* @param {number} index - 标签下标。若传入,则按 index 所在标签为事件执行关闭操作
|
||||
* @returns
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {('other'|'right'|'all')} [mode="all"] - 关闭方式
|
||||
* @param {number} index - 活动标签的索引,默认取当前选中标签的索引。一般用于标签右键事件
|
||||
*/
|
||||
closeMore: function(id, type, index) {
|
||||
closeMult: function(id, mode, index, force) {
|
||||
var that = component.getThis(id);
|
||||
if(!that) return this;
|
||||
that.closeMore(type, index);
|
||||
if(!that) return;
|
||||
that.closeMult(mode, index, force);
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换标签
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {number} index - 标签下标
|
||||
* @returns
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {number} index - 标签索引
|
||||
*/
|
||||
change: function(id, index) {
|
||||
change: function(id, index, force) {
|
||||
var that = component.getThis(id);
|
||||
if(!that) return this;
|
||||
that.change(that.findHeaderItem(index));
|
||||
if(!that) return;
|
||||
that.change(that.findHeaderItem(index), force);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取标签信息
|
||||
* @param {string} id - 标签 ID
|
||||
* @returns
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
*/
|
||||
data: function(id) {
|
||||
var that = component.getThis(id);
|
||||
@ -626,34 +750,35 @@ layui.define('component', function(exports) {
|
||||
|
||||
/**
|
||||
* 获取标签指定头部项
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {number} index - 标签下标
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {number} index - 标签索引
|
||||
* @returns
|
||||
*/
|
||||
headerItem: function(id, index) {
|
||||
getHeaderItem: function(id, index) {
|
||||
var that = component.getThis(id);
|
||||
return that ? that.findHeaderItem(index) : this;
|
||||
if(!that) return;
|
||||
return that.findHeaderItem(index);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取标签指定内容项
|
||||
* @param {string} id - 标签 ID
|
||||
* @param {number} index - 标签下标
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
* @param {number} index - 标签索引
|
||||
* @returns
|
||||
*/
|
||||
bodyItem: function(id, index) {
|
||||
getBodyItem: function(id, index) {
|
||||
var that = component.getThis(id);
|
||||
return that ? that.findBodyItem(index) : this;
|
||||
if(!that) return;
|
||||
return that.findBodyItem(index);
|
||||
},
|
||||
|
||||
/**
|
||||
* 调整视图结构
|
||||
* @param {string} id - 标签 ID
|
||||
* @returns
|
||||
* 刷新标签视图结构
|
||||
* @param {string} id - 渲染时的实例 ID
|
||||
*/
|
||||
setView: function(id) {
|
||||
refresh: function(id) {
|
||||
var that = component.getThis(id);
|
||||
if (!that) return this;
|
||||
if (!that) return;
|
||||
that.roll('auto');
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user