import layui from '../core.js'; import $ from 'jquery'; import { component as component$1 } from './component.js'; /** * tabs * 标签页组件 */ // 创建组件 var component = component$1({ name: 'tabs', // 组件名 // 默认配置 config: { elem: '.layui-tabs', trigger: 'click', // 标签切换的触发事件 headerMode: 'auto' // 标签头部的显示模式 auto | scroll | normal }, CONST: { ELEM: 'layui-tabs', HEADER: 'layui-tabs-header', CLOSE: 'layui-tabs-close', BODY: 'layui-tabs-body', ITEM: 'layui-tabs-item', CARD: 'layui-tabs-card' }, // 渲染 render: function() { var that = this; var options = that.config; // 标签页元素项 that.headerElem = ['.'+ component.CONST.HEADER + ':eq(0)', '>li']; that.bodyElem = ['.'+ component.CONST.BODY + ':eq(0)', '>.'+ component.CONST.ITEM]; // 获取标签容器中的 header body 相关元素 that.getContainer = function() { var elem = that.documentElem || options.elem; return { header: { elem: elem.find(that.headerElem[0]), items: elem.find(that.headerElem.join('')) }, body: { elem: elem.find(that.bodyElem[0]), items: elem.find(that.bodyElem.join('')) } }; }; // 若 header 选项类型为数组 if (layui.type(options.header) === 'array') { // if (options.header.length === 0) return; // 给任意元素绑定 tabs 切换功能 if (typeof options.header[0] === 'string') { that.headerElem = options.header.concat(); that.documentElem = $(document); } else { // 方法传值渲染 that.elemView = $('
'); if (options.className) that.elemView.addClass(options.className); var headerElem = $(''); var bodyElem = $('
'); // 生成标签项 layui.each(options.header, function(i, item){ var elemHeaderItem = that.renderHeaderItem(item); headerElem.append(elemHeaderItem); }); layui.each(options.body, function(i, item){ var elemBodyItem = that.renderBodyItem(item); bodyElem.append(elemBodyItem); }); that.elemView.append(headerElem).append(bodyElem); options.elem.html(that.elemView); } } else { that.renderClose(); // 初始化标签关闭结构 } // 若 body 选项类型为数组 if (layui.type(options.body) === 'array') { if (typeof options.body[0] === 'string') { 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), true); } else if (data.index === -1) { // 初始选中项为空时,默认选中第一个 that.change(that.findHeaderItem(0), true); } // 初始化滚动结构 that.roll('auto'); // 清除隐藏占位 if (options.elem.hasClass(component.CONST.CLASS_HIDEV)) { options.elem.removeClass(component.CONST.CLASS_HIDEV); } // 回调 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 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; // 标签头部事件 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)); }); // 窗口 resize 事件 if (!inner.onresize) { var timer; $(window).on('resize', function() { clearTimeout(timer); timer = setTimeout(function(){ layui.each(component.cache.id, function(key) { var that = component.getInst(key); if(!that) return; that.roll('init'); }); }, 50); }); inner.onresize = true; } } }); // 内部变量集 var inner = {}; /** * 扩展组件原型方法 */ var Class = component.Class; /** * 增加标签 * @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 {boolean} [opts.active] - 是否将新增项设置为活动标签 * @param {boolean} [opts.closable] - 标签是否可关闭。初始值取决于 options.closable * @param {string} [opts.headerItem] - 自定义标签头部元素 * @param {string} [opts.bodyItem] - 自定义标签内容元素 * @param {Function} [opts.done] - 标签添加成功后执行的回调函数 */ Class.prototype.add = function(opts) { var that = this; that.config; var container = that.getContainer(); var newHeaderItem = that.renderHeaderItem(opts); var newBodyItem = that.renderBodyItem(opts); var data = that.data(); // 选项默认值 opts = $.extend({ active: true }, opts); // 插入方式 if (/(before|after)/.test(opts.mode)) { // 在活动标签前后插入 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' // 插入标签到最后 })[opts.mode || 'append'] || 'append'; container.header.elem[mode](newHeaderItem); container.body.elem[mode](newBodyItem); } // 是否将新增项设置为活动标签 if (opts.active) { that.change(newHeaderItem, true); } else { that.roll('auto'); } // 回调 typeof opts.done === 'function' && opts.done( $.extend(data, { headerItem: newHeaderItem, bodyItem: newBodyItem }) ); }; /** * 关闭指定标签 * @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 layid = thisHeaderItem.attr('lay-id'); var index = thisHeaderItem.index(); // 标签是否不可关闭 if (thisHeaderItem.attr('lay-closable') === 'false') { return; } // 当前标签相关数据 var data = that.data(); // 标签关闭前的事件。若非强制关闭,可则根据事件的返回结果决定是否关闭 if (!force) { var closable = layui.event.call( thisHeaderItem[0], component.CONST.MOD_NAME, 'beforeClose('+ options.id +')', $.extend(data, { index: 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); } } // 移除元素 that.findBodyItem(layid || index).remove(); thisHeaderItem.remove(); that.roll('auto', index); // 获取当前标签相关数据 var data = that.data(); // 标签关闭后的事件 layui.event.call( data.thisHeaderItem[0], component.CONST.MOD_NAME, 'afterClose('+ options.id +')', data ); }; /** * 批量关闭标签 * @see tabs.close */ Class.prototype.closeMult = function(mode, index) { var that = this; var options = that.config; var container = that.getContainer(); var data = that.data(); var headers = container.header.items; container.body.items; var DISABLED_CLOSE_SELECTOR = '[lay-closable="false"]'; // 不可关闭标签选择器 index = index === undefined ? data.index : index; var headerItem = that.findHeaderItem(index); that.findBodyItem(index); var itemIndex = headerItem.index(); // 若当前选中标签也允许关闭,则尝试寻找不可关闭的标签并将其选中 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, true); } else if(prevHeader[0]) { that.change(prevHeader, true); } } else if(index !== data.index) { // 自动切换到活动标签 that.change(headerItem, true); } } // 执行批量关闭标签 headers.each(function(i) { var $this = $(this); var layid = $this.attr('lay-id'); var bodyItem = that.findBodyItem(layid || i); // 标签是否不可关闭 if ($this.attr('lay-closable') === 'false') { return; } // 批量关闭方式 var isCloseOther = mode === 'other' && i !== itemIndex; // 关闭其他标签 var isCloseRight = mode === 'right' && i > itemIndex; // 关闭右侧标签 var isCloseLeft = mode === 'left' && i < itemIndex; // 关闭左侧标签(不推荐) var isCloseAll = mode === 'all'; // 关闭所有标签 if (isCloseOther || isCloseRight || isCloseLeft || isCloseAll) { $this.remove(); bodyItem.remove(); } }); that.roll('auto'); // 回调 var data = that.data(); // 标签关闭后的事件 layui.event.call( data.thisHeaderItem[0], component.CONST.MOD_NAME, 'afterClose('+ options.id +')', data ); }; /** * 切换标签 * @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 layid = thisHeaderItem.attr('lay-id'); 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) { return; } // 当前标签相关数据 var data = that.data(); // 标签关闭前的事件。若非强制关闭,可则根据事件的返回结果决定是否关闭 if (!force) { var enable = layui.event.call( thisHeaderItem[0], component.CONST.MOD_NAME, 'beforeChange('+ options.id +')', $.extend(data, { from: { index: data.index, headerItem: data.thisHeaderItem }, to: { index: index, headerItem: thisHeaderItem } }) ); // 是否阻止切换 if (enable === false) { return; } } // 执行标签头部切换 thisHeaderItem.addClass(component.CONST.CLASS_THIS).siblings() .removeClass(component.CONST.CLASS_THIS); // 执行标签内容切换 that.findBodyItem(layid || index).addClass(component.CONST.CLASS_SHOW) .siblings().removeClass(component.CONST.CLASS_SHOW); that.roll('auto', index); // 重新获取标签相关数据 var data = that.data(); // 标签切换后的事件 layui.event.call( data.thisHeaderItem[0], component.CONST.MOD_NAME, 'afterChange('+ options.id +')', data ); }; /** * 渲染标签头部项 * @param {Object} opts - 标签项配置信息 */ Class.prototype.renderHeaderItem = function(opts) { var that = this; var options = that.config; var headerItem = $(opts.headerItem || options.headerItem || '
  • '); headerItem.html(opts.title || 'New Tab').attr('lay-id', opts.id); that.appendClose(headerItem, opts); // 追加标签关闭元素 return headerItem; }; /** * 渲染标签内容项 * @param {Object} opts - 标签项配置信息 */ Class.prototype.renderBodyItem = function(opts) { var that = this; var options = that.config; var bodyItem = $(opts.bodyItem || options.bodyItem || '
    '); bodyItem.html(opts.content || '').attr('lay-id', opts.id); return bodyItem; }; /** * 给某一个标签项追加可关闭元素 * @param {Object} headerItem - 标签项元素 * @param {Object} opts - 标签项配置信息 */ Class.prototype.appendClose = function(headerItem, opts) { var that = this; var options = that.config; if (!options.closable) return; opts = opts || {}; // 不可关闭项 if (opts.closable == false) { headerItem.attr('lay-closable', 'false'); } if (headerItem.attr('lay-closable') === 'false') { return; } // 可关闭项追加关闭按钮 if (!headerItem.find('.'+ component.CONST.CLOSE)[0]) { var close = $(''); close.on('click', function(){ that.close($(this).parent()); return false; }); headerItem.append(close); } }; // 渲染标签可关闭元素 Class.prototype.renderClose = function() { var that = this; var options = that.config; var container = that.getContainer(); container.header.items.each(function() { var $this = $(this); // 是否开启关闭 if (options.closable) { that.appendClose($this); } else { $this.find('.'+ component.CONST.CLOSE).remove(); } }); }; /** * 标签头滚动 * @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 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; var scrollMode = options.headerMode === 'scroll'; // 标签头部是否始终保持滚动模式 // 让选中标签始终保持在可视区域 var rollToVisibleArea = function() { index = isNaN(index) ? that.data().index : index; var thisItemElem = headerItems.eq(index); if (!thisItemElem[0]) return; // 当前标签的相对水平坐标值 var thisLeft = Math.ceil(thisItemElem.position().left); var padding = 1; // 让边界额外保持一定间距 // 当选中标签溢出在可视区域「左侧」时 var countWidth = thisLeft - (thisItemElem.prev().outerWidth() || 0); // 始终空出上一个标签 if (countWidth > 0) countWidth = countWidth - padding; // 左侧临界值 if (tabsLeft + countWidth < 0) { tabsLeft = countWidth >= 0 ? countWidth : 0; // 标签的复原位移不能超出 0 return headerElem.css('left', -tabsLeft).data('left', -tabsLeft); } // 当选中标签溢出在可视区域「右侧」时, var countWidth = thisLeft + thisItemElem.outerWidth() + (thisItemElem.next().outerWidth() || 0) + padding; // 始终空出下一个标签 // 右侧临界值 if (tabsLeft + countWidth - outerWidth > 0) { tabsLeft = countWidth - outerWidth; headerElem.css('left', -tabsLeft).data('left', -tabsLeft); } }; // css 类名 var CLASS_SCROLL = 'layui-tabs-scroll'; var CLASS_BAR = 'layui-tabs-bar'; var CLASS_BAR_ICON = ['layui-icon-prev', 'layui-icon-next']; // 滚动结构 var rollElem = { elem: $('
    '), bar: $([ '
    ', '', '', '
    ' ].join('')) }; // 不渲染头部滚动结构 if (options.headerMode === 'normal') return; // 是否渲染滚动结构 var elemScroll = headerElem.parent('.'+ CLASS_SCROLL); if (scrollMode || (!scrollMode && scrollWidth > outerWidth)) { if (!elemScroll[0]) { if (options.elem.hasClass(component.CONST.CARD)) { rollElem.elem.addClass(component.CONST.CARD); } headerElem.wrap(rollElem.elem); headerElem.after(rollElem.bar); // 点击左右箭头 rollElem.bar.children().on('click', function(){ var othis = $(this); var mode = othis.attr('lay-mode'); if ($(this).hasClass(component.CONST.CLASS_DISABLED)) return; mode && that.roll(mode); }); } } else if(!scrollMode) { if (elemScroll[0]) { elemScroll.find('.'+ CLASS_BAR).remove(); headerElem.unwrap().css('left', 0).data('left', 0); } else { return; } } // 初始化滚动模式 if (mode === 'init') return; // 重新获取 scrollWidth = headerElem.prop('scrollWidth'); // 实际总长度 outerWidth = headerElem.outerWidth(); // 可视区域的长度 elemScroll = headerElem.parent('.'+ CLASS_SCROLL); // 左箭头(往右滚动) if (mode === 'prev') { // 当前的 left 减去可视宽度,用于与上一轮的页签比较 var prevLeft = -tabsLeft - outerWidth; if(prevLeft < 0) prevLeft = 0; headerItems.each(function(i, item){ var li = $(item); var left = Math.ceil(li.position().left); if (left >= prevLeft) { headerElem.css('left', -left).data('left', -left); return false; } }); } else if(mode === 'auto') { // 自动识别滚动 rollToVisibleArea(); } else { // 右箭头(往左滚动) 默认 next headerItems.each(function(i, item){ var li = $(item); var left = Math.ceil(li.position().left); if (left + li.outerWidth() >= outerWidth - tabsLeft) { headerElem.css('left', -left).data('left', -left); return false; } }); } // 同步箭头状态 tabsLeft = headerElem.data('left') || 0; // 左 elemScroll.find('.'+ CLASS_BAR_ICON[0])[ tabsLeft < 0 ? 'removeClass' : 'addClass' ](component.CONST.CLASS_DISABLED); // 右 elemScroll.find('.'+ CLASS_BAR_ICON[1])[ parseFloat(tabsLeft + scrollWidth) - outerWidth > 0 ? 'removeClass' : 'addClass' ](component.CONST.CLASS_DISABLED); }; /** * 获取标签头部项 * @param {number|string} index - 标签索引或 lay-id */ Class.prototype.findHeaderItem = function(index) { var container = this.getContainer(); var headerItems = container.header.items; // 根据 lay-id 匹配 if (typeof index === 'string') { return headerItems.filter('[lay-id="'+ index +'"]'); } return headerItems.eq(index); }; /** * 获取标签内容项 * @param {number|string} index - 标签索引或 lay-id */ Class.prototype.findBodyItem = function(index) { var container = this.getContainer(); var bodyItems = container.body.items; // 根据 lay-id 匹配 if (typeof index === 'string') { var bodyItem = bodyItems.filter('[lay-id="'+ index +'"]'); return bodyItem[0] ? bodyItem : function() { // 若未匹配到 lay-id 对应内容项,则通过对应头部项的索引匹配内容项 var headerItems = container.header.items; var headerItemIndex = headerItems.filter('[lay-id="'+ index +'"]').index(); return headerItemIndex !== -1 ? bodyItems.eq(headerItemIndex) : bodyItem; }(); } return bodyItems.eq(index); }; /** * 返回给回调的公共信息 * @returns */ Class.prototype.data = function() { var that = this; var options = that.config; var container = that.getContainer(); var thisHeaderItem = container.header.items.filter('.'+ component.CONST.CLASS_THIS); var index = thisHeaderItem.index(); var layid = thisHeaderItem.attr('lay-id'); return { options: options, // 标签配置信息 container: container, // 标签容器的相关元素 thisHeaderItem: thisHeaderItem, // 当前活动标签头部项 thisBodyItem: that.findBodyItem(layid || index), // 当前活动标签内容项 index: index, // 当前活动标签索引 length: container.header.items.length // 标签数量 }; }; // 扩展组件接口 $.extend(component, { /** * 添加标签 * @param {string} id - 渲染时的实例 ID * @param {Object} opts - 添加标签的配置项,详见 Class.prototype.add */ add: function(id, opts) { var that = component.getInst(id); if(!that) return; that.add(opts); }, /** * 关闭标签 * @param {string} id - 渲染时的实例 ID * @param {number} index - 标签索引 * @param {boolean} [force=false] - 是否强制关闭 */ close: function(id, index, force) { var that = component.getInst(id); if (!that) return; // index 若不传,则表示关闭当前标签 if (index === undefined) { index = that.data().index; } that.close(that.findHeaderItem(index), force); }, /** * 关闭多个标签 * @param {string} id - 渲染时的实例 ID * @param {('other'|'right'|'all')} [mode="all"] - 关闭方式 * @param {number} index - 活动标签的索引,默认取当前选中标签的索引。一般用于标签右键事件 */ closeMult: function(id, mode, index) { var that = component.getInst(id); if(!that) return; that.closeMult(mode, index); }, /** * 切换标签 * @param {string} id - 渲染时的实例 ID * @param {number} index - 标签索引 */ change: function(id, index, force) { var that = component.getInst(id); if(!that) return; that.change(that.findHeaderItem(index), force); }, /** * 获取标签信息 * @param {string} id - 渲染时的实例 ID */ data: function(id) { var that = component.getInst(id); return that ? that.data() : {}; }, /** * 获取标签指定头部项 * @param {string} id - 渲染时的实例 ID * @param {number} index - 标签索引或 lay-id 值 * @returns */ getHeaderItem: function(id, index) { var that = component.getInst(id); if(!that) return; return that.findHeaderItem(index); }, /** * 获取标签指定内容项 * @param {string} id - 渲染时的实例 ID * @param {number} index - 标签索引或 lay-id 值 * @returns */ getBodyItem: function(id, index) { var that = component.getInst(id); if(!that) return; return that.findBodyItem(index); }, /** * 刷新标签视图结构 * @param {string} id - 渲染时的实例 ID */ refresh: function(id) { var that = component.getInst(id); if (!that) return; that.roll('auto'); } }); // 初始化渲染 $(function() { component.render(); }); export { component as default, component as tabs }; //# sourceMappingURL=tabs.js.map