//Make sure jQuery has been loaded if (typeof jQuery === "undefined") { throw new Error("MultiTabs requires jQuery"); }((function ($) { "use strict"; var NAMESPACE, tabIndex; //variable var MultiTabs, handler, getTabIndex, isExtUrl, sumDomWidth, trimText, supportStorage; //function var defaultLayoutTemplates, defaultInit; //default variable NAMESPACE = '.multitabs'; // namespace for on() function /** * splice namespace for on() function, and bind it * @param $selector jQuery selector * @param event event * @param childSelector child selector (string), same as on() function * @param fn function * @param skipNS bool. If true skip splice namespace */ handler = function ($selector, event, childSelector, fn, skipNS) { var ev = skipNS ? event : event.split(' ').join(NAMESPACE + ' ') + NAMESPACE; $selector.off(ev, childSelector, fn).on(ev, childSelector, fn); }; /** * get index for tab * @param content content type, for 'main' tab just can be 1 * @param capacity capacity of tab, except 'main' tab * @returns int return index */ getTabIndex = function (content, capacity) { if (content === 'main') return 0; capacity = capacity || 8; //capacity of maximum tab quantity, the tab will be cover if more than it tabIndex = tabIndex || 0; tabIndex++; tabIndex = tabIndex % capacity; return tabIndex; }; /** * trim text, remove the extra space, and trim text with maxLength, add '...' after trim. * @param text the text need to trim * @param maxLength max length for text * @returns {string} return trimed text */ trimText = function (text, maxLength) { maxLength = maxLength || $.fn.multitabs.defaults.navTab.maxTitleLength; var words = (text + "").split(' '); var t = ''; for (var i = 0; i < words.length; i++) { var w = $.trim(words[i]); t += w ? (w + ' ') : ''; } if (t.length > maxLength) { t = t.substr(0, maxLength); t += '...' } return t; }; supportStorage = function (is_cache) { return !(sessionStorage === undefined) && is_cache; }; /** * Calculate the total width * @param JqueryDomObjList the object list for calculate * @returns {number} return total object width (int) */ sumDomWidth = function (JqueryDomObjList) { var width = 0; $(JqueryDomObjList).each(function () { width += $(this).outerWidth(true) }); return width }; /** * Judgment is external URL * @param url URL for judgment * @returns {boolean} external URL return true, local return false */ isExtUrl = function (url) { var absUrl = (function (url) { var a = document.createElement('a'); a.href = url; return a.href; })(url); var webRoot = window.location.protocol + '//' + window.location.host + '/'; var urlRoot = absUrl.substr(0, webRoot.length); return (!(urlRoot === webRoot)); }; /** * Layout Templates */ defaultLayoutTemplates = { /** * Main Layout */ default: '
' + '
' + '
' + '' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
', classic: '
' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
', simple: '
' + '
' + '' + '
' + '
' + '
', navTab: '{title}', closeBtn: ' ', ajaxTabPane: '
{content}
', iframeTabPane: '' }; /** * Default init page * @type {*[]} */ defaultInit = [{ //default tabs in initial; type: 'main', //default is info; title: 'main', //default title; content: '

Demo page

Welcome to use bootstrap multi-tabs :)

' //default content }]; /** * multitabs constructor * @param element Primary container * @param options options * @constructor */ MultiTabs = function (element, options) { var self = this; self.$element = $(element); self._init(options)._listen()._final(); }; /** * MultiTabs's function */ MultiTabs.prototype = { /** * constructor */ constructor: MultiTabs, /** * create tab and return this. * @param obj the obj to trigger multitabs * @param active if true, active tab after create * @returns this Chain structure. */ create: function (obj, active) { var options = this.options; var param, $navTab; if (!(param = this._getParam(obj))) { return this; //return multitabs obj when is invaid obj } $navTab = this._exist(param); if ($navTab && !param.refresh) { this.active($navTab); return this; } param.active = !param.active ? active : param.active; //nav tab create $navTab = this._createNavTab(param); //tab-pane create this._createTabPane(param); //add tab to storage this._storage(param.did, param); if (param.active) { this.active($navTab); } return this; }, /** * Create tab pane * @param param * @param index * @returns {*|{}} * @private */ _createTabPane: function (param) { var self = this, $el = self.$element; $el.tabContent.append(self._getTabPaneHtml(param)); return $el.tabContent.find('#' + param.did); }, /** * get tab pane html * @param param * @param index * @returns {string} * @private */ _getTabPaneHtml: function (param) { var self = this, options = self.options; if (!param.content && param.iframe) { return defaultLayoutTemplates.iframeTabPane .replace('{class}', options.content.iframe.class) .replace('{tabPaneId}', param.did); } else { return defaultLayoutTemplates.ajaxTabPane .replace('{class}', options.content.ajax.class) .replace('{tabPaneId}', param.did) .replace('{content}', param.content); } }, /** * create nav tab * @param param * @param index * @returns {*|{}} * @private */ _createNavTab: function (param) { var self = this, $el = self.$element; var navTabHtml = self._getNavTabHtml(param); var $navTabLi = $el.navPanelList.find('a[data-type="' + param.type + '"][data-index="' + param.index + '"]').parent('li'); if ($navTabLi.length) { $navTabLi.html(navTabHtml); self._getTabPane($navTabLi.find('a:first')).remove(); //remove old content pane directly } else { $el.navPanelList.append('
  • ' + navTabHtml + '
  • '); } return $el.navPanelList.find('a[data-type="' + param.type + '"][data-index="' + param.index + '"]:first'); }, /** * get nav tab html * @param param * @param index * @returns {string} * @private */ _getNavTabHtml: function (param) { var self = this, options = self.options; var closeBtnHtml, display; display = options.nav.showCloseOnHover ? '' : 'display:inline;'; closeBtnHtml = (param.type === 'main') ? '' : defaultLayoutTemplates.closeBtn.replace('{style}', display); //main content can not colse. return defaultLayoutTemplates.navTab .replace('{index}', param.index) .replace('{navTabId}', param.did) .replace('{url}', param.url) .replace('{title}', param.title) .replace('{type}', param.type) + closeBtnHtml; }, /** * generate tab pane's id * @param param * @param index * @returns {string} * @private */ _generateId: function (param) { return 'multitabs_' + param.type + '_' + param.index; }, /** * active navTab * @param navTab * @returns self Chain structure. */ active: function (navTab) { var self = this, $el = self.$element; var $navTab = self._getNavTab(navTab), $tabPane = self._getTabPane($navTab), $prevActivedTab = $el.navPanelList.find('li.active:first a'); var prevNavTabParam = $prevActivedTab.length ? self._getParam($prevActivedTab) : {}; var navTabParam = $navTab.length ? self._getParam($navTab) : {}; //change storage active status var storage = self._storage(); if (storage[prevNavTabParam.id]) { storage[prevNavTabParam.id].active = false; } if (storage[navTabParam.id]) { storage[navTabParam.id].active = true; } self._resetStorage(storage); //active navTab and tabPane $prevActivedTab.parent('li').removeClass('active'); $navTab.parent('li').addClass('active'); self._fixTabPosition($navTab); self._getTabPane($prevActivedTab).removeClass('active'); $tabPane.addClass('active'); self._fixTabContentLayout($tabPane); //fill tab pane self._fillTabPane($tabPane, navTabParam); return self; }, /** * fill tab pane * @private */ _fillTabPane: function (tabPane, param) { var self = this, options = self.options; var $tabPane = $(tabPane); //if navTab-pane empty, load content if (!$tabPane.html()) { if ($tabPane.is('iframe')) { if (!$tabPane.attr('src')) { $tabPane.attr('src', param.url); } } else { $.ajax({ url: param.url, dataType: "html", success: function (callback) { $tabPane.html(options.content.ajax.success(callback)); }, error: function (callback) { $tabPane.html(options.content.ajax.error(callback)); } }); } } }, /** * move left * @return self */ moveLeft: function () { var self = this, $el = self.$element, navPanelListMarginLeft = Math.abs(parseInt($el.navPanelList.css("margin-left"))), navPanelWidth = $el.navPanel.outerWidth(true), sumTabsWidth = sumDomWidth($el.navPanelList.children('li')), leftWidth = 0, marginLeft = 0, $navTabLi; if (sumTabsWidth < navPanelWidth) { return self } else { $navTabLi = $el.navPanelList.children('li:first'); while ((marginLeft + $navTabLi.width()) <= navPanelListMarginLeft) { marginLeft += $navTabLi.outerWidth(true); $navTabLi = $navTabLi.next(); } marginLeft = 0; if (sumDomWidth($navTabLi.prevAll()) > navPanelWidth) { while (((marginLeft + $navTabLi.width()) < navPanelWidth) && $navTabLi.length > 0) { marginLeft += $navTabLi.outerWidth(true); $navTabLi = $navTabLi.prev(); } leftWidth = sumDomWidth($navTabLi.prevAll()); } } $el.navPanelList.animate({ marginLeft: 0 - leftWidth + "px" }, "fast"); return self; }, /** * move right * @return self */ moveRight: function () { var self = this, $el = self.$element, navPanelListMarginLeft = Math.abs(parseInt($el.navPanelList.css("margin-left"))), navPanelWidth = $el.navPanel.outerWidth(true), sumTabsWidth = sumDomWidth($el.navPanelList.children('li')), leftWidth = 0, $navTabLi, marginLeft; if (sumTabsWidth < navPanelWidth) { return self; } else { $navTabLi = $el.navPanelList.children('li:first'); marginLeft = 0; while ((marginLeft + $navTabLi.width()) <= navPanelListMarginLeft) { marginLeft += $navTabLi.outerWidth(true); $navTabLi = $navTabLi.next(); } marginLeft = 0; while (((marginLeft + $navTabLi.width()) < navPanelWidth) && $navTabLi.length > 0) { marginLeft += $navTabLi.outerWidth(true); $navTabLi = $navTabLi.next(); } leftWidth = sumDomWidth($navTabLi.prevAll()); if (leftWidth > 0) { $el.navPanelList.animate({ marginLeft: 0 - leftWidth + "px" }, "fast"); } } return self; }, /** * close navTab * @param navTab * @return self Chain structure. */ close: function (navTab) { var self = this, $tabPane; var $navTab = self._getNavTab(navTab), $navTabLi = $navTab.parent('li'); $tabPane = self._getTabPane($navTab); //close unsave tab confirm if ($navTabLi.length && $tabPane.length && $tabPane.hasClass('unsave') && !self._unsaveConfirm()) { return self; } if ($navTabLi.hasClass("active")) { var $nextLi = $navTabLi.next("li:first"), $prevLi = $navTabLi.prev("li:last"); //if ($nextLi.size()) { if ($nextLi.length) { self.active($nextLi); self.activeMenu($nextLi.find('a')); //} else if ($prevLi.size()) { } else if ($prevLi.length) { self.active($prevLi); self.activeMenu($prevLi.find('a')); } } self._delStorage($navTab.attr('data-id')); //remove tab from session storage $navTabLi.remove(); $tabPane.remove(); return self; }, /** * close others tab * @return self Chain structure. */ closeOthers: function (retainTab) { var self = this, $el = self.$element, findTab; if (!retainTab) { findTab = $el.navPanelList.find('li:not(.active)').find('a:not([data-type="main"])'); } else { findTab = $el.navPanelList.find('a:not([data-type="main"])').filter(function(index){ if (retainTab != $(this).data('index')) return this; }); } findTab.each(function () { var $navTab = $(this); self._delStorage($navTab.attr('data-id')); //remove tab from session storage self._getTabPane($navTab).remove(); //remove tab-content $navTab.parent('li').remove(); //remove navtab }); if (retainTab) { self.active($el.navPanelList.find('a[data-index="' + retainTab + '"]')); self.activeMenu($el.navPanelList.find('a[data-index="' + retainTab + '"]')); } $el.navPanelList.css("margin-left", "0"); return self; }, /** * focus actived tab * @return self Chain structure. */ showActive: function () { var self = this, $el = self.$element; var navTab = $el.navPanelList.find('li.active:first a'); self._fixTabPosition(navTab); return self; }, /** * close all tabs, (except main tab) * @return self Chain structure. */ closeAll: function () { var self = this, $el = self.$element; $el.navPanelList.find('a:not([data-type="main"])').each(function () { var $navTab = $(this); self._delStorage($navTab.attr('data-id')); //remove tab from session storage self._getTabPane($navTab).remove(); //remove tab-content $navTab.parent('li').remove(); //remove navtab }); self.active($el.navPanelList.find('a[data-type="main"]:first').parent('li')); self.activeMenu($el.navPanelList.find('a[data-type="main"]:first')); return self; }, /** * 左侧导航变化 */ activeMenu: function(navTab) { // 点击选项卡时,左侧菜单栏跟随变化 var $navObj = $("a[href$='" + $(navTab).data('url') + "']"), // 当前url对应的左侧导航对象 $navHasSubnav = $navObj.parents('.nav-item'), $viSubHeight = $navHasSubnav.siblings().find('.nav-subnav:visible').outerHeight(); $('.nav-item').each(function(i){ if ($(this).hasClass('active') && !$navObj.parents('.nav-item').last().hasClass('active')) { $(this).removeClass('active').removeClass('open'); $(this).find('.nav-subnav:visible').slideUp(500); if (window.innerWidth > 1024 && $('body').hasClass('lyear-layout-sidebar-close')) { $(this).find('.nav-subnav').hide(); } } }); $('.nav-drawer').find('li').removeClass('active'); $navObj.parent('li').addClass('active'); $navHasSubnav.first().addClass('active'); // 当前菜单无子菜单 if (!$navObj.parents('.nav-item').first().is('.nav-item-has-subnav')) { var hht = 48 * ( $navObj.parents('.nav-item').first().prevAll().length - 1 ); $('.lyear-layout-sidebar-scroll').animate({scrollTop: hht}, 300); } if ($navObj.parents('ul.nav-subnav').last().is(':hidden')) { $navObj.parents('ul.nav-subnav').last().slideDown(500, function(){ $navHasSubnav.last().addClass('open'); var scrollHeight = 0, $scrollBox = $('.lyear-layout-sidebar-scroll'), pervTotal = $navHasSubnav.last().prevAll().length, boxHeight = $scrollBox.outerHeight(), innerHeight = $('.sidebar-main').outerHeight(), thisScroll = $scrollBox.scrollTop(), thisSubHeight = $(this).outerHeight(), footHeight = 121; if (footHeight + innerHeight - boxHeight >= (pervTotal * 48)) { scrollHeight = pervTotal * 48; } if ($navHasSubnav.length == 1) { $scrollBox.animate({scrollTop: scrollHeight}, 300); } else { // 子菜单操作 if (typeof($viSubHeight) != 'undefined' && $viSubHeight != null) { scrollHeight = thisScroll + thisSubHeight - $viSubHeight; $scrollBox.animate({scrollTop: scrollHeight}, 300); } else { if ((thisScroll + boxHeight - $scrollBox[0].scrollHeight) == 0) { scrollHeight = thisScroll - thisSubHeight; $scrollBox.animate({scrollTop: scrollHeight}, 300); } } } }); } }, /** * init function * @param options * @returns self * @private */ _init: function (options) { var self = this, $el = self.$element; $el.html(defaultLayoutTemplates[options.nav.layout] .replace('{mainClass}', options.class) .replace('{navClass}', options.nav.class) .replace(/\{nav-tabs\}/g, options.nav.style) .replace(/\{backgroundColor\}/g, options.nav.backgroundColor) .replace('{dropdown}', options.language.nav.dropdown) .replace('{showActivedTab}', options.language.nav.showActivedTab) .replace('{closeAllTabs}', options.language.nav.closeAllTabs) .replace('{closeOtherTabs}', options.language.nav.closeOtherTabs) ); $el.wrapper = $el.find('.mt-wrapper:first'); $el.nav = $el.find('.mt-nav-bar:first'); $el.navToolsLeft = $el.nav.find('.mt-nav-tools-left:first'); $el.navPanel = $el.nav.find('.mt-nav-panel:first'); $el.navPanelList = $el.nav.find('.mt-nav-panel:first ul'); //$el.navTabMain = $('#multitabs_main_0'); $el.navToolsRight = $el.nav.find('.mt-nav-tools-right:first'); $el.tabContent = $el.find('.tab-content:first'); //hide tab-header if maxTabs less than 1 if (options.nav.maxTabs <= 1) { options.nav.maxTabs = 1; $el.nav.hide(); } //set the nav-panel width var toolWidth = $el.nav.find('.mt-nav-tools-left:visible:first').width() + $el.nav.find('.mt-nav-tools-right:visible:first').width(); $el.navPanel.css('width', 'calc(100% - ' + toolWidth + 'px)'); self.options = options; return self; }, /** * final funcion for after init Multitabs * @returns self * @private */ _final: function () { var self = this, $el = self.$element, options = self.options, storage, init = options.init, param; if (supportStorage(options.cache)) { storage = self._storage(); self._resetStorage({}); $.each(storage, function (k, v) { self.create(v, false); }) } if ($.isEmptyObject(storage)) { init = (!$.isEmptyObject(init) && init instanceof Array) ? init : defaultInit; for (var i = 0; i < init.length; i++) { param = self._getParam(init[i]); if (param) { self.create(param); } } } //if no any tab actived, active the main tab if (!$el.navPanelList.children('li.active').length) { self.active($el.navPanelList.find('[data-type="main"]:first')); } return self; }, /** * bind action * @return self * @private */ _listen: function () { var self = this, $el = self.$element, options = self.options; //create tab handler($(document), 'click', options.selector, function () { self.create(this, true); if (!$(this).parent().parent('ul').hasClass('dropdown-menu')) { // 20190402改,下拉菜单中的网址采用data-url,并且不阻止后面的动作 return false; //Prevent the default selector action } }); //active tab handler($el.nav, 'click', '.mt-nav-tab', function () { self.active(this); self.activeMenu(this); }); //drag tab if (options.nav.draggable) { handler($el.navPanelList, 'mousedown', '.mt-nav-tab', function (event) { var $navTab = $(this), $navTabLi = $navTab.closest('li'); var $prevNavTabLi = $navTabLi.prev(); var dragMode = true, moved = false, isMain = ($navTab.data('type') === "main"); var tmpId = 'mt_tmp_id_' + new Date().getTime(), navTabBlankHtml = '
  • '; var abs_x = event.pageX - $navTabLi.offset().left + $el.nav.offset().left; $navTabLi.before(navTabBlankHtml); $navTabLi.addClass('mt-dragging mt-dragging-tab').css({ 'left': event.pageX - abs_x + 'px' }); $(document).on('mousemove', function (event) { if (dragMode && !isMain) { $navTabLi.css({ 'left': event.pageX - abs_x + 'px' }); $el.navPanelList.children('li:not(".mt-dragging")').each(function () { var leftWidth = $(this).offset().left + $(this).outerWidth() + 20; //20 px more for gap if (leftWidth > $navTabLi.offset().left) { if ($(this).next().attr('id') !== tmpId) { moved = true; $prevNavTabLi = $(this); $('#' + tmpId).remove(); $prevNavTabLi.after(navTabBlankHtml); } return false; } }); } }).on("selectstart", function () { //disable text selection if (dragMode) { return false; } }).on('mouseup', function () { if (dragMode) { $navTabLi.removeClass('mt-dragging mt-dragging-tab').css({'left': 'auto'}); if (moved) { $prevNavTabLi.after($navTabLi); } $('#' + tmpId).remove(); } dragMode = false; }); }); } // 右键菜单 handler($el.nav, 'contextmenu', '.mt-nav-tab', function (event) { event.preventDefault(); var menu = $('