Server IP : 103.53.40.154 / Your IP : 18.226.82.90 Web Server : Apache System : Linux md-in-35.webhostbox.net 4.19.286-203.ELK.el7.x86_64 #1 SMP Wed Jun 14 04:33:55 CDT 2023 x86_64 User : ppcad7no ( 715) PHP Version : 8.2.25 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON Directory (0755) : /home2/ppcad7no/umrahguide.in/js/ |
[ Home ] | [ C0mmand ] | [ Upload File ] |
---|
/** * jquery-bootstrap-scrolling-tabs * @version v0.7.1 * @link https://github.com/mikejacobson/jquery-bootstrap-scrolling-tabs * @author Mike Jacobson <michaeljjacobson1@gmail.com> * @license MIT License, http://www.opensource.org/licenses/MIT * * jQuery plugin version of Angular directive angular-bootstrap-scrolling-tabs: * https://github.com/mikejacobson/angular-bootstrap-scrolling-tabs * * Usage: * * Use case #1: HTML-defined tabs * ------------------------------ * Demo: http://plnkr.co/edit/thyD0grCxIjyU4PoTt4x?p=preview * * Sample HTML: * * <!-- Nav tabs --> * <ul class="nav nav-tabs" role="tablist"> * <li role="presentation" class="active"><a href="#tab1" role="tab" data-toggle="tab">Tab Number 1</a></li> * <li role="presentation"><a href="#tab2" role="tab" data-toggle="tab">Tab Number 2</a></li> * <li role="presentation"><a href="#tab3" role="tab" data-toggle="tab">Tab Number 3</a></li> * <li role="presentation"><a href="#tab4" role="tab" data-toggle="tab">Tab Number 4</a></li> * </ul> * * <!-- Tab panes --> * <div class="tab-content"> * <div role="tabpanel" class="tab-pane active" id="tab1">Tab 1 content...</div> * <div role="tabpanel" class="tab-pane" id="tab2">Tab 2 content...</div> * <div role="tabpanel" class="tab-pane" id="tab3">Tab 3 content...</div> * <div role="tabpanel" class="tab-pane" id="tab4">Tab 4 content...</div> * </div> * * * JavaScript: * * $('.nav-tabs').scrollingTabs(); * * * Use Case #2: Data-driven tabs * ----------------------------- * Demo: http://plnkr.co/edit/MWBjLnTvJeetjU3NEimg?p=preview * * Sample HTML: * * <!-- build .nav-tabs and .tab-content in here --> * <div id="tabs-inside-here"></div> * * * JavaScript: * * $('#tabs-inside-here').scrollingTabs({ * tabs: tabs, // required * propPaneId: 'paneId', // optional * propTitle: 'title', // optional * propActive: 'active', // optional * propDisabled: 'disabled', // optional * propContent: 'content', // optional * ignoreTabPanes: false, // optional * scrollToTabEdge: false, // optional * disableScrollArrowsOnFullyScrolled: false, // optional * reverseScroll: false // optional * }); * * Settings/Options: * * tabs: tabs data array * prop*: name of your tab object's property name that * corresponds to that required tab property if * your property name is different than the * standard name (paneId, title, etc.) * ignoreTabPanes: relevant for data-driven tabs only--set to true if * you want the plugin to only touch the tabs * and to not generate the tab pane elements * that go in .tab-content. By default, the plugin * will generate the tab panes based on the content * property in your tab data, if a content property * is present. * scrollToTabEdge: set to true if you want to force full-width tabs * to display at the left scroll arrow. i.e., if the * scrolling stops with only half a tab showing, * it will snap the tab to its edge so the full tab * shows. * disableScrollArrowsOnFullyScrolled: * set to true if you want the left scroll arrow to * disable when the tabs are scrolled fully left, * and the right scroll arrow to disable when the tabs * are scrolled fully right. * reverseScroll: * set to true if you want the left scroll arrow to * slide the tabs left instead of right, and the right * scroll arrow to slide the tabs right. * widthMultiplier: * set to a value less than 1 if you want the tabs * container to be less than the full width of its * parent element. For example, set it to 0.5 if you * want the tabs container to be half the width of * its parent. * tabClickHandler: * a callback function to execute any time a tab is clicked. * The function is simply passed as the event handler * to jQuery's .on(), so the function will receive * the jQuery event as an argument, and the 'this' * inside the function will be the clicked tab's anchor * element. * cssClassLeftArrow, cssClassRightArrow: * custom values for the class attributes for the * left and right scroll arrows. The defaults are * 'glyphicon glyphicon-chevron-left' and * 'glyphicon glyphicon-chevron-right'. * Using different icons might require you to add * custom styling to the arrows to position the icons * correctly; the arrows can be targeted with these * selectors: * .scrtabs-tab-scroll-arrow * .scrtabs-tab-scroll-arrow-left * .scrtabs-tab-scroll-arrow-right * * * On tabs data change: * * $('#tabs-inside-here').scrollingTabs('refresh'); * * On tabs data change, if you want the active tab to be set based on * the updated tabs data (i.e., you want to override the current * active tab setting selected by the user), for example, if you * added a new tab and you want it to be the active tab: * * $('#tabs-inside-here').scrollingTabs('refresh', { * forceActiveTab: true * }); * * Any options that can be passed into the plugin can be set on the * plugin's 'defaults' object instead so you don't have to pass them in: * * $.fn.scrollingTabs.defaults.tabs = tabs; * $.fn.scrollingTabs.defaults.forceActiveTab = true; * $.fn.scrollingTabs.defaults.scrollToTabEdge = true; * $.fn.scrollingTabs.defaults.disableScrollArrowsOnFullyScrolled = true; * $.fn.scrollingTabs.defaults.reverseScroll = true; * $.fn.scrollingTabs.defaults.widthMultiplier = 0.5; * $.fn.scrollingTabs.defaults.tabClickHandler = function () { }; * * * Methods * ----------------------------- * - refresh * On window resize, the tabs should refresh themselves, but to force a refresh: * * $('.nav-tabs').scrollingTabs('refresh'); * * - scrollToActiveTab * On window resize, the active tab will automatically be scrolled to * if it ends up offscreen, but you can also programmatically force a * scroll to the active tab any time (if, for example, you're * programmatically setting the active tab) by calling the * 'scrollToActiveTab' method: * * $('.nav-tabs').scrollingTabs('scrollToActiveTab'); * * * Events * ----------------------------- * The plugin triggers event 'ready.scrtabs' when the tabs have * been wrapped in the scroller and are ready for viewing: * * $('.nav-tabs') * .scrollingTabs() * .on('ready.scrtabs', function() { * // tabs ready, do my other stuff... * }); * * $('#tabs-inside-here') * .scrollingTabs({ tabs: tabs }) * .on('ready.scrtabs', function() { * // tabs ready, do my other stuff... * }); * * * Destroying * ----------------------------- * To destroy: * * $('.nav-tabs').scrollingTabs('destroy'); * * $('#tabs-inside-here').scrollingTabs('destroy'); * * If you were wrapping markup, the markup will be restored; if your tabs * were data-driven, the tabs will be destroyed along with the plugin. * */ ;(function ($, window) { 'use strict'; var CONSTANTS = { CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL: 50, // timeout interval for repeatedly moving the tabs container // by one increment while the mouse is held down--decrease to // make mousedown continous scrolling faster SCROLL_ARROW_WIDTH: 20, SCROLL_OFFSET_FRACTION: 6, // each click moves the container this fraction of the fixed container--decrease // to make the tabs scroll farther per click DATA_KEY_DDMENU_MODIFIED: 'scrtabsddmenumodified', DATA_KEY_IS_MOUSEDOWN: 'scrtabsismousedown', CSS_CLASSES: { SCROLL_ARROW_DISABLE: 'scrtabs-disable' }, SLIDE_DIRECTION: { LEFT: 1, RIGHT: 2 }, EVENTS: { CLICK: 'click.scrtabs', DROPDOWN_MENU_HIDE: 'hide.bs.dropdown.scrtabs', DROPDOWN_MENU_SHOW: 'show.bs.dropdown.scrtabs', FORCE_REFRESH: 'forcerefresh.scrtabs', MOUSEDOWN: 'mousedown.scrtabs touchstart.scrtabs', MOUSEUP: 'mouseup.scrtabs touchend.scrtabs', WINDOW_RESIZE: 'resize.scrtabs', TABS_READY: 'ready.scrtabs' } }; // smartresize from Paul Irish (debounced window resize) (function (sr) { var debounce = function (func, threshold, execAsap) { var timeout; return function debounced() { var obj = this, args = arguments; function delayed() { if (!execAsap) { func.apply(obj, args); } timeout = null; } if (timeout) { clearTimeout(timeout); } else if (execAsap) { func.apply(obj, args); } timeout = setTimeout(delayed, threshold || 100); }; }; $.fn[sr] = function (fn) { return fn ? this.bind(CONSTANTS.EVENTS.WINDOW_RESIZE, debounce(fn)) : this.trigger(sr); }; })('smartresize'); /* *********************************************************************************** * ElementsHandler - Class that each instance of ScrollingTabsControl will instantiate * **********************************************************************************/ function ElementsHandler(scrollingTabsControl) { var ehd = this; ehd.stc = scrollingTabsControl; } // ElementsHandler prototype methods (function (p) { p.initElements = function (options) { var ehd = this; ehd.setElementReferences(); ehd.setEventListeners(); }; p.refreshAllElementSizes = function () { var ehd = this, stc = ehd.stc, smv = stc.scrollMovement, scrollArrowsWereVisible = stc.scrollArrowsVisible, actionsTaken = { didScrollToActiveTab: false }, isPerformingSlideAnim = false, minPos; ehd.setElementWidths(); ehd.setScrollArrowVisibility(); // this could have been a window resize or the removal of a // dynamic tab, so make sure the movable container is positioned // correctly because, if it is far to the left and we increased the // window width, it's possible that the tabs will be too far left, // beyond the min pos. if (stc.scrollArrowsVisible) { // make sure container not too far left minPos = smv.getMinPos(); isPerformingSlideAnim = smv.scrollToActiveTab({ isOnWindowResize: true }); if (!isPerformingSlideAnim) { smv.refreshScrollArrowsDisabledState(); if (stc.movableContainerLeftPos < minPos) { smv.incrementMovableContainerRight(minPos); } } actionsTaken.didScrollToActiveTab = true; } else if (scrollArrowsWereVisible) { // scroll arrows went away after resize, so position movable container at 0 stc.movableContainerLeftPos = 0; smv.slideMovableContainerToLeftPos(); } return actionsTaken; }; p.setElementReferences = function () { var ehd = this, stc = ehd.stc, $tabsContainer = stc.$tabsContainer, $leftArrow = $tabsContainer.find('.scrtabs-tab-scroll-arrow-left'), $rightArrow = $tabsContainer.find('.scrtabs-tab-scroll-arrow-right'); stc.isNavPills = false; stc.$fixedContainer = $tabsContainer.find('.scrtabs-tabs-fixed-container'); stc.$movableContainer = $tabsContainer.find('.scrtabs-tabs-movable-container'); stc.$tabsUl = $tabsContainer.find('.nav-tabs'); // check for pills if (!stc.$tabsUl.length) { stc.$tabsUl = $tabsContainer.find('.nav-pills'); if (stc.$tabsUl.length) { stc.isNavPills = true; } } stc.$tabsLiCollection = stc.$tabsUl.find('> li'); stc.$slideLeftArrow = stc.reverseScroll ? $leftArrow : $rightArrow; stc.$slideRightArrow = stc.reverseScroll ? $rightArrow : $leftArrow; stc.$scrollArrows = stc.$slideLeftArrow.add(stc.$slideRightArrow); stc.$win = $(window); }; p.setElementWidths = function () { var ehd = this, stc = ehd.stc; stc.winWidth = stc.$win.width(); stc.scrollArrowsCombinedWidth = stc.$slideLeftArrow.outerWidth() + stc.$slideRightArrow.outerWidth(); ehd.setFixedContainerWidth(); ehd.setMovableContainerWidth(); }; p.setEventListeners = function () { var ehd = this, stc = ehd.stc, evh = stc.eventHandlers, ev = CONSTANTS.EVENTS; stc.$slideLeftArrow .off('.scrtabs') .on(ev.MOUSEDOWN, function (e) { evh.handleMousedownOnSlideMovContainerLeftArrow.call(evh, e); }) .on(ev.MOUSEUP, function (e) { evh.handleMouseupOnSlideMovContainerLeftArrow.call(evh, e); }) .on(ev.CLICK, function (e) { evh.handleClickOnSlideMovContainerLeftArrow.call(evh, e); }); stc.$slideRightArrow .off('.scrtabs') .on(ev.MOUSEDOWN, function (e) { evh.handleMousedownOnSlideMovContainerRightArrow.call(evh, e); }) .on(ev.MOUSEUP, function (e) { evh.handleMouseupOnSlideMovContainerRightArrow.call(evh, e); }) .on(ev.CLICK, function (e) { evh.handleClickOnSlideMovContainerRightArrow.call(evh, e); }); if (stc.tabClickHandler) { stc.$tabsLiCollection .find('a[data-toggle="tab"]') .off(ev.CLICK) .on(ev.CLICK, stc.tabClickHandler); } stc.$win.off('.scrtabs').smartresize(function (e) { evh.handleWindowResize.call(evh, e); }); $('body').on(CONSTANTS.EVENTS.FORCE_REFRESH, stc.elementsHandler.refreshAllElementSizes.bind(stc.elementsHandler)); }; p.setFixedContainerWidth = function () { var ehd = this, stc = ehd.stc, tabsContainerRect = stc.$tabsContainer.get(0).getBoundingClientRect(); /** * @author poletaew * It solves problem with rounding by jQuery.outerWidth * If we have real width 100.5 px, jQuery.outerWidth returns us 101 px and we get layout's fail */ stc.fixedContainerWidth = tabsContainerRect.width || (tabsContainerRect.right - tabsContainerRect.left); stc.fixedContainerWidth = stc.fixedContainerWidth * stc.widthMultiplier; stc.$fixedContainer.width(stc.fixedContainerWidth); }; p.setFixedContainerWidthForHiddenScrollArrows = function () { var ehd = this, stc = ehd.stc; stc.$fixedContainer.width(stc.fixedContainerWidth); }; p.setFixedContainerWidthForVisibleScrollArrows = function () { var ehd = this, stc = ehd.stc; stc.$fixedContainer.width(stc.fixedContainerWidth - stc.scrollArrowsCombinedWidth); }; p.setMovableContainerWidth = function () { var ehd = this, stc = ehd.stc, $tabLi = stc.$tabsUl.find('> li'); stc.movableContainerWidth = 0; if ($tabLi.length) { $tabLi.each(function __getLiWidth() { var $li = $(this), totalMargin = 0; if (stc.isNavPills) { // pills have a margin-left, tabs have no margin totalMargin = parseInt($li.css('margin-left'), 10) + parseInt($li.css('margin-right'), 10); } stc.movableContainerWidth += ($li.outerWidth() + totalMargin); }); stc.movableContainerWidth += 1; // if the tabs don't span the width of the page, force the // movable container width to full page width so the bottom // border spans the page width instead of just spanning the // width of the tabs if (stc.movableContainerWidth < stc.fixedContainerWidth) { stc.movableContainerWidth = stc.fixedContainerWidth; } } stc.$movableContainer.width(stc.movableContainerWidth); }; p.setScrollArrowVisibility = function () { var ehd = this, stc = ehd.stc, shouldBeVisible = stc.movableContainerWidth > stc.fixedContainerWidth; if (shouldBeVisible && !stc.scrollArrowsVisible) { stc.$scrollArrows.show(); stc.scrollArrowsVisible = true; } else if (!shouldBeVisible && stc.scrollArrowsVisible) { stc.$scrollArrows.hide(); stc.scrollArrowsVisible = false; } if (stc.scrollArrowsVisible) { ehd.setFixedContainerWidthForVisibleScrollArrows(); } else { ehd.setFixedContainerWidthForHiddenScrollArrows(); } }; }(ElementsHandler.prototype)); /* *********************************************************************************** * EventHandlers - Class that each instance of ScrollingTabsControl will instantiate * **********************************************************************************/ function EventHandlers(scrollingTabsControl) { var evh = this; evh.stc = scrollingTabsControl; } // prototype methods (function (p){ p.handleClickOnSlideMovContainerLeftArrow = function (e) { var evh = this, stc = evh.stc; stc.scrollMovement.incrementMovableContainerLeft(); }; p.handleClickOnSlideMovContainerRightArrow = function (e) { var evh = this, stc = evh.stc; stc.scrollMovement.incrementMovableContainerRight(); }; p.handleMousedownOnSlideMovContainerLeftArrow = function (e) { var evh = this, stc = evh.stc; stc.$slideLeftArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true); stc.scrollMovement.continueSlideMovableContainerLeft(); }; p.handleMousedownOnSlideMovContainerRightArrow = function (e) { var evh = this, stc = evh.stc; stc.$slideRightArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true); stc.scrollMovement.continueSlideMovableContainerRight(); }; p.handleMouseupOnSlideMovContainerLeftArrow = function (e) { var evh = this, stc = evh.stc; stc.$slideLeftArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false); }; p.handleMouseupOnSlideMovContainerRightArrow = function (e) { var evh = this, stc = evh.stc; stc.$slideRightArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false); }; p.handleWindowResize = function (e) { var evh = this, stc = evh.stc, newWinWidth = stc.$win.width(); if (newWinWidth === stc.winWidth) { return false; } stc.winWidth = newWinWidth; stc.elementsHandler.refreshAllElementSizes(); }; }(EventHandlers.prototype)); /* *********************************************************************************** * ScrollMovement - Class that each instance of ScrollingTabsControl will instantiate * **********************************************************************************/ function ScrollMovement(scrollingTabsControl) { var smv = this; smv.stc = scrollingTabsControl; } // prototype methods (function (p) { p.continueSlideMovableContainerLeft = function () { var smv = this, stc = smv.stc; setTimeout(function() { if (stc.movableContainerLeftPos <= smv.getMinPos() || !stc.$slideLeftArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN)) { return; } if (!smv.incrementMovableContainerLeft()) { // haven't reached max left smv.continueSlideMovableContainerLeft(); } }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL); }; p.continueSlideMovableContainerRight = function () { var smv = this, stc = smv.stc; setTimeout(function() { if (stc.movableContainerLeftPos >= 0 || !stc.$slideRightArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN)) { return; } if (!smv.incrementMovableContainerRight()) { // haven't reached max right smv.continueSlideMovableContainerRight(); } }, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL); }; p.decrementMovableContainerLeftPos = function (minPos) { var smv = this, stc = smv.stc; stc.movableContainerLeftPos -= (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION); if (stc.movableContainerLeftPos < minPos) { stc.movableContainerLeftPos = minPos; } else if (stc.scrollToTabEdge) { smv.setMovableContainerLeftPosToTabEdge(CONSTANTS.SLIDE_DIRECTION.LEFT); if (stc.movableContainerLeftPos < minPos) { stc.movableContainerLeftPos = minPos; } } }; p.disableSlideLeftArrow = function () { var smv = this, stc = smv.stc; if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) { return; } stc.$slideLeftArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE); }; p.disableSlideRightArrow = function () { var smv = this, stc = smv.stc; if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) { return; } stc.$slideRightArrow.addClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE); }; p.enableSlideLeftArrow = function () { var smv = this, stc = smv.stc; if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) { return; } stc.$slideLeftArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE); }; p.enableSlideRightArrow = function () { var smv = this, stc = smv.stc; if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) { return; } stc.$slideRightArrow.removeClass(CONSTANTS.CSS_CLASSES.SCROLL_ARROW_DISABLE); }; p.getMinPos = function () { var smv = this, stc = smv.stc; return stc.scrollArrowsVisible ? (stc.fixedContainerWidth - stc.movableContainerWidth - stc.scrollArrowsCombinedWidth) : 0; }; p.getMovableContainerCssLeftVal = function () { var smv = this, stc = smv.stc; return (stc.movableContainerLeftPos === 0) ? '0' : stc.movableContainerLeftPos + 'px'; }; p.incrementMovableContainerLeft = function () { var smv = this, stc = smv.stc, minPos = smv.getMinPos(); smv.decrementMovableContainerLeftPos(minPos); smv.slideMovableContainerToLeftPos(); smv.enableSlideRightArrow(); // return true if we're fully left, false otherwise return (stc.movableContainerLeftPos === minPos); }; p.incrementMovableContainerRight = function (minPos) { var smv = this, stc = smv.stc; // if minPos passed in, the movable container was beyond the minPos if (minPos) { stc.movableContainerLeftPos = minPos; } else { stc.movableContainerLeftPos += (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION); if (stc.movableContainerLeftPos > 0) { stc.movableContainerLeftPos = 0; } else if (stc.scrollToTabEdge) { smv.setMovableContainerLeftPosToTabEdge(CONSTANTS.SLIDE_DIRECTION.RIGHT); } } smv.slideMovableContainerToLeftPos(); smv.enableSlideLeftArrow(); // return true if we're fully right, false otherwise // left pos of 0 is the movable container's max position (farthest right) return (stc.movableContainerLeftPos === 0); }; p.refreshScrollArrowsDisabledState = function() { var smv = this, stc = smv.stc; if (!stc.disableScrollArrowsOnFullyScrolled || !stc.scrollArrowsVisible) { return; } if (stc.movableContainerLeftPos >= 0) { // movable container fully right smv.disableSlideRightArrow(); smv.enableSlideLeftArrow(); return; } if (stc.movableContainerLeftPos <= smv.getMinPos()) { // fully left smv.disableSlideLeftArrow(); smv.enableSlideRightArrow(); return; } smv.enableSlideLeftArrow(); smv.enableSlideRightArrow(); }; p.scrollToActiveTab = function (options) { var smv = this, stc = smv.stc, RIGHT_OFFSET_BUFFER = 20, $activeTab, activeTabLeftPos, activeTabRightPos, rightArrowLeftPos; if (!stc.scrollArrowsVisible) { return; } $activeTab = stc.$tabsUl.find('li.active'); if (!$activeTab.length) { return; } /** * @author poletaew * We need relative offset (depends on $fixedContainer), don't absolute */ activeTabLeftPos = $activeTab.offset().left - stc.$fixedContainer.offset().left; activeTabRightPos = activeTabLeftPos + $activeTab.outerWidth(); rightArrowLeftPos = stc.fixedContainerWidth - RIGHT_OFFSET_BUFFER; if (activeTabRightPos > rightArrowLeftPos) { // active tab off right side stc.movableContainerLeftPos -= (activeTabRightPos - rightArrowLeftPos + CONSTANTS.SCROLL_ARROW_WIDTH); smv.slideMovableContainerToLeftPos(); return true; } else if (activeTabLeftPos < CONSTANTS.SCROLL_ARROW_WIDTH) { // active tab off left side stc.movableContainerLeftPos += CONSTANTS.SCROLL_ARROW_WIDTH - activeTabLeftPos; smv.slideMovableContainerToLeftPos(); return true; } return false; }; p.setMovableContainerLeftPosToTabEdge = function (slideDirection) { var smv = this, stc = smv.stc, offscreenWidth = -stc.movableContainerLeftPos, totalTabWidth = 0; // make sure LeftPos is set so that a tab edge will be against the // left scroll arrow so we won't have a partial, cut-off tab stc.$tabsLiCollection.each(function (index) { var tabWidth = $(this).width(); totalTabWidth += tabWidth; if (totalTabWidth > offscreenWidth) { stc.movableContainerLeftPos = (slideDirection === CONSTANTS.SLIDE_DIRECTION.RIGHT) ? -(totalTabWidth - tabWidth) : -totalTabWidth; return false; // exit .each() loop } }); }; p.slideMovableContainerToLeftPos = function () { var smv = this, stc = smv.stc, minPos = smv.getMinPos(), leftVal; if (stc.movableContainerLeftPos > 0) { stc.movableContainerLeftPos = 0; } else if (stc.movableContainerLeftPos < minPos) { stc.movableContainerLeftPos = minPos; } stc.movableContainerLeftPos = stc.movableContainerLeftPos / 1; leftVal = smv.getMovableContainerCssLeftVal(); smv.performingSlideAnim = true; stc.$movableContainer.stop().animate({ left: leftVal }, 'slow', function __slideAnimComplete() { var newMinPos = smv.getMinPos(); smv.performingSlideAnim = false; // if we slid past the min pos--which can happen if you resize the window // quickly--move back into position if (stc.movableContainerLeftPos < newMinPos) { smv.decrementMovableContainerLeftPos(newMinPos); stc.$movableContainer.stop().animate({ left: smv.getMovableContainerCssLeftVal() }, 'fast', function() { smv.refreshScrollArrowsDisabledState(); }); } else { smv.refreshScrollArrowsDisabledState(); } }); }; }(ScrollMovement.prototype)); /* ********************************************************************** * ScrollingTabsControl - Class that each directive will instantiate * **********************************************************************/ function ScrollingTabsControl($tabsContainer) { var stc = this; stc.$tabsContainer = $tabsContainer; stc.movableContainerLeftPos = 0; stc.scrollArrowsVisible = false; stc.scrollToTabEdge = false; stc.disableScrollArrowsOnFullyScrolled = false; stc.reverseScroll = false; stc.widthMultiplier = 1; stc.scrollMovement = new ScrollMovement(stc); stc.eventHandlers = new EventHandlers(stc); stc.elementsHandler = new ElementsHandler(stc); } // prototype methods (function (p) { p.initTabs = function (options, $scroller, readyCallback, attachTabContentToDomCallback) { var stc = this, elementsHandler = stc.elementsHandler, num; if (options.scrollToTabEdge) { stc.scrollToTabEdge = true; } if (options.disableScrollArrowsOnFullyScrolled) { stc.disableScrollArrowsOnFullyScrolled = true; } if (options.reverseScroll) { stc.reverseScroll = true; } if (options.widthMultiplier !== 1) { num = Number(options.widthMultiplier); // handle string value if (!isNaN(num)) { stc.widthMultiplier = num; } } setTimeout(initTabsAfterTimeout, 100); function initTabsAfterTimeout() { var actionsTaken; // if we're just wrapping non-data-driven tabs, the user might // have the .nav-tabs hidden to prevent the clunky flash of // multi-line tabs on page refresh, so we need to make sure // they're visible before trying to wrap them $scroller.find('.nav-tabs').show(); elementsHandler.initElements(options); actionsTaken = elementsHandler.refreshAllElementSizes(); $scroller.css('visibility', 'visible'); if (attachTabContentToDomCallback) { attachTabContentToDomCallback(); } if (readyCallback) { readyCallback(); } } }; p.scrollToActiveTab = function(options) { var stc = this, smv = stc.scrollMovement; smv.scrollToActiveTab(options); }; }(ScrollingTabsControl.prototype)); //////////////////////////////////////////// // // plugin-specific stuff // //////////////////////////////////////////// var tabElements = (function () { return { getElTabPaneForLi: getElTabPaneForLi, getNewElNavTabs: getNewElNavTabs, getNewElScrollerElementWrappingNavTabsInstance: getNewElScrollerElementWrappingNavTabsInstance, getNewElTabAnchor: getNewElTabAnchor, getNewElTabContent: getNewElTabContent, getNewElTabLi: getNewElTabLi, getNewElTabPane: getNewElTabPane }; /////////////////// // ---- retrieve existing elements from the DOM ---------- function getElTabPaneForLi($li) { return $($li.find('a').attr('href')); } // ---- create new elements ---------- function getNewElNavTabs() { return $('<ul class="nav nav-tabs" role="tablist"></ul>'); } function getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance, settings) { var $tabsContainer = $('<div class="scrtabs-tab-container"></div>'), $leftArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-tab-scroll-arrow-left"><span class="' + settings.cssClassLeftArrow + '"></span></div>'), $rightArrow = $('<div class="scrtabs-tab-scroll-arrow scrtabs-tab-scroll-arrow-right"><span class="' + settings.cssClassRightArrow + '"></span></div>'), $fixedContainer = $('<div class="scrtabs-tabs-fixed-container"></div>'), $movableContainer = $('<div class="scrtabs-tabs-movable-container"></div>'); if (settings.disableScrollArrowsOnFullyScrolled) { $leftArrow.add($rightArrow).addClass('scrtabs-disable'); } return $tabsContainer .append($leftArrow, $fixedContainer.append($movableContainer.append($navTabsInstance)), $rightArrow); } function getNewElTabAnchor(tab, propNames) { return $('<a role="tab" data-toggle="tab"></a>') .attr('href', '#' + tab[propNames.paneId]) .html(tab[propNames.title]); } function getNewElTabContent() { return $('<div class="tab-content"></div>'); } function getNewElTabLi(tab, propNames, forceActiveTab) { var $li = $('<li role="presentation" class=""></li>'), $a = getNewElTabAnchor(tab, propNames).appendTo($li); if (tab[propNames.disabled]) { $li.addClass('disabled'); $a.attr('data-toggle', ''); } else if (forceActiveTab && tab[propNames.active]) { $li.addClass('active'); } return $li; } function getNewElTabPane(tab, propNames, forceActiveTab) { var $pane = $('<div role="tabpanel" class="tab-pane"></div>') .attr('id', tab[propNames.paneId]) .html(tab[propNames.content]); if (forceActiveTab && tab[propNames.active]) { $pane.addClass('active'); } return $pane; } }()); // tabElements var tabUtils = (function () { return { didTabOrderChange: didTabOrderChange, getIndexOfClosestEnabledTab: getIndexOfClosestEnabledTab, getTabIndexByPaneId: getTabIndexByPaneId, storeDataOnLiEl: storeDataOnLiEl }; /////////////////// function didTabOrderChange($currTabLis, updatedTabs, propNames) { var isTabOrderChanged = false; $currTabLis.each(function (currDomIdx) { var newIdx = getTabIndexByPaneId(updatedTabs, propNames.paneId, $(this).data('tab')[propNames.paneId]); if ((newIdx > -1) && (newIdx !== currDomIdx)) { // tab moved isTabOrderChanged = true; return false; // exit .each() loop } }); return isTabOrderChanged; } function getIndexOfClosestEnabledTab($currTabLis, startIndex) { var lastIndex = $currTabLis.length - 1, closestIdx = -1, incrementFromStartIndex = 0, testIdx = 0; // expand out from the current tab looking for an enabled tab; // we prefer the tab after us over the tab before while ((closestIdx === -1) && (testIdx >= 0)) { if ( (((testIdx = startIndex + (++incrementFromStartIndex)) <= lastIndex) && !$currTabLis.eq(testIdx).hasClass('disabled')) || (((testIdx = startIndex - incrementFromStartIndex) >= 0) && !$currTabLis.eq(testIdx).hasClass('disabled')) ) { closestIdx = testIdx; } } return closestIdx; } function getTabIndexByPaneId(tabs, paneIdPropName, paneId) { var idx = -1; tabs.some(function (tab, i) { if (tab[paneIdPropName] === paneId) { idx = i; return true; // exit loop } }); return idx; } function storeDataOnLiEl($li, tabs, index) { $li.data({ tab: $.extend({}, tabs[index]), // store a clone so we can check for changes index: index }); } }()); // tabUtils function buildNavTabsAndTabContentForTargetElementInstance($targetElInstance, settings, readyCallback) { var tabs = settings.tabs, propNames = { paneId: settings.propPaneId, title: settings.propTitle, active: settings.propActive, disabled: settings.propDisabled, content: settings.propContent }, ignoreTabPanes = settings.ignoreTabPanes, hasTabContent = tabs.length && tabs[0][propNames.content] !== undefined, $navTabs = tabElements.getNewElNavTabs(), $tabContent = tabElements.getNewElTabContent(), $scroller, attachTabContentToDomCallback = ignoreTabPanes ? null : function() { $scroller.after($tabContent); }; if (!tabs.length) { return; } tabs.forEach(function(tab, index) { tabElements .getNewElTabLi(tab, propNames, true) // true -> forceActiveTab .appendTo($navTabs); // build the tab panes if we weren't told to ignore them and there's // tab content data available if (!ignoreTabPanes && hasTabContent) { tabElements .getNewElTabPane(tab, propNames, true) // true -> forceActiveTab .appendTo($tabContent); } }); $scroller = wrapNavTabsInstanceInScroller($navTabs, settings, readyCallback, attachTabContentToDomCallback); $scroller.appendTo($targetElInstance); $targetElInstance.data({ scrtabs: { tabs: tabs, propNames: propNames, ignoreTabPanes: ignoreTabPanes, hasTabContent: hasTabContent, scroller: $scroller } }); // once the nav-tabs are wrapped in the scroller, attach each tab's // data to it for reference later; we need to wait till they're // wrapped in the scroller because we wrap a *clone* of the nav-tabs // we built above, not the original nav-tabs $scroller.find('.nav-tabs > li').each(function (index) { tabUtils.storeDataOnLiEl($(this), tabs, index); }); } function checkForTabAdded(refreshData) { var updatedTabsArray = refreshData.updatedTabsArray, propNames = refreshData.propNames, ignoreTabPanes = refreshData.ignoreTabPanes, options = refreshData.options, $currTabLis = refreshData.$currTabLis, $navTabs = refreshData.$navTabs, $currTabContentPanesContainer = ignoreTabPanes ? null : refreshData.$currTabContentPanesContainer, $currTabContentPanes = ignoreTabPanes ? null : refreshData.$currTabContentPanes, isInitTabsRequired = false; // make sure each tab in the updated tabs array has a corresponding DOM element updatedTabsArray.forEach(function (tab, idx) { var $li = $currTabLis.find('a[href="#' + tab[propNames.paneId] + '"]'), isTabIdxPastCurrTabs = (idx >= $currTabLis.length), $pane; if (!$li.length) { // new tab isInitTabsRequired = true; // add the tab, add its pane (if necessary), and refresh the scroller $li = tabElements.getNewElTabLi(tab, propNames, options.forceActiveTab); tabUtils.storeDataOnLiEl($li, updatedTabsArray, idx); if (isTabIdxPastCurrTabs) { // append to end of current tabs $li.appendTo($navTabs); } else { // insert in middle of current tabs $li.insertBefore($currTabLis.eq(idx)); } if (!ignoreTabPanes && tab[propNames.content] !== undefined) { $pane = tabElements.getNewElTabPane(tab, propNames, options.forceActiveTab); if (isTabIdxPastCurrTabs) { // append to end of current tabs $pane.appendTo($currTabContentPanesContainer); } else { // insert in middle of current tabs $pane.insertBefore($currTabContentPanes.eq(idx)); } } } }); return isInitTabsRequired; } function checkForTabPropertiesUpdated(refreshData) { var tabLiData = refreshData.tabLi, ignoreTabPanes = refreshData.ignoreTabPanes, $li = tabLiData.$li, $contentPane = tabLiData.$contentPane, origTabData = tabLiData.origTabData, newTabData = tabLiData.newTabData, propNames = refreshData.propNames, isInitTabsRequired = false; // update tab title if necessary if (origTabData[propNames.title] !== newTabData[propNames.title]) { $li.find('a[role="tab"]') .html(origTabData[propNames.title] = newTabData[propNames.title]); isInitTabsRequired = true; } // update tab active state if necessary if (refreshData.options.forceActiveTab) { // set the active tab based on the tabs array regardless of the current // DOM state, which could have been changed by the user clicking a tab // without those changes being reflected back to the tab data $li[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active'); $contentPane[newTabData[propNames.active] ? 'addClass' : 'removeClass']('active'); origTabData[propNames.active] = newTabData[propNames.active]; isInitTabsRequired = true; } // update tab content pane if necessary if (!ignoreTabPanes && origTabData[propNames.content] !== newTabData[propNames.content]) { $contentPane.html(origTabData[propNames.content] = newTabData[propNames.content]); isInitTabsRequired = true; } return isInitTabsRequired; } function checkForTabRemoved(refreshData) { var tabLiData = refreshData.tabLi, ignoreTabPanes = refreshData.ignoreTabPanes, $li = tabLiData.$li, idxToMakeActive; if (tabLiData.newIdx !== -1) { // tab was not removed--it has a valid index return false; } // if this was the active tab, make the closest enabled tab active if ($li.hasClass('active')) { idxToMakeActive = tabUtils.getIndexOfClosestEnabledTab(refreshData.$currTabLis, tabLiData.currDomIdx); if (idxToMakeActive > -1) { refreshData.$currTabLis .eq(idxToMakeActive) .addClass('active'); if (!ignoreTabPanes) { refreshData.$currTabContentPanes .eq(idxToMakeActive) .addClass('active'); } } } $li.remove(); if (!ignoreTabPanes) { tabLiData.$contentPane.remove(); } return true; } function checkForTabsOrderChanged(refreshData) { var $currTabLis = refreshData.$currTabLis, updatedTabsArray = refreshData.updatedTabsArray, propNames = refreshData.propNames, ignoreTabPanes = refreshData.ignoreTabPanes, newTabsCollection = [], newTabPanesCollection = ignoreTabPanes ? null : []; if (!tabUtils.didTabOrderChange($currTabLis, updatedTabsArray, propNames)) { return false; } // the tab order changed... updatedTabsArray.forEach(function (t, i) { var paneId = t[propNames.paneId]; newTabsCollection.push( $currTabLis .find('a[role="tab"][href="#' + paneId + '"]') .parent('li') ); if (!ignoreTabPanes) { newTabPanesCollection.push($('#' + paneId)); } }); refreshData.$navTabs.append(newTabsCollection); if (!ignoreTabPanes) { refreshData.$currTabContentPanesContainer.append(newTabPanesCollection); } return true; } function checkForTabsRemovedOrUpdated(refreshData) { var $currTabLis = refreshData.$currTabLis, updatedTabsArray = refreshData.updatedTabsArray, propNames = refreshData.propNames, isInitTabsRequired = false; $currTabLis.each(function (currDomIdx) { var $li = $(this), origTabData = $li.data('tab'), newIdx = tabUtils.getTabIndexByPaneId(updatedTabsArray, propNames.paneId, origTabData[propNames.paneId]), newTabData = (newIdx > -1) ? updatedTabsArray[newIdx] : null; refreshData.tabLi = { $li: $li, currDomIdx: currDomIdx, newIdx: newIdx, $contentPane: tabElements.getElTabPaneForLi($li), origTabData: origTabData, newTabData: newTabData }; if (checkForTabRemoved(refreshData)) { isInitTabsRequired = true; return; // continue to next $li in .each() since we removed this tab } if (checkForTabPropertiesUpdated(refreshData)) { isInitTabsRequired = true; } }); return isInitTabsRequired; } function destroyPlugin() { var $targetElInstance = $(this), scrtabsData = $targetElInstance.data('scrtabs'), $tabsContainer; if (!scrtabsData) { return; } scrtabsData.scroller .off(CONSTANTS.EVENTS.DROPDOWN_MENU_SHOW) .off(CONSTANTS.EVENTS.DROPDOWN_MENU_HIDE); // if there were any dropdown menus opened, remove the css we added to // them so they would display correctly scrtabsData.scroller .find('[data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED + ']') .css({ display: '', left: '', top: '' }) .off(CONSTANTS.EVENTS.CLICK) .removeAttr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED); if (scrtabsData.scroller.hasTabClickHandler) { $targetElInstance .find('a[data-toggle="tab"]') .off('.scrtabs'); } if (scrtabsData.isWrapperOnly) { // we just wrapped nav-tabs markup, so restore it // $targetElInstance is the ul.nav-tabs $tabsContainer = $targetElInstance.parents('.scrtabs-tab-container'); if ($tabsContainer.length) { $tabsContainer.replaceWith($targetElInstance); } } else { // we generated the tabs from data so destroy everything we created if (scrtabsData.scroller && scrtabsData.scroller.initTabs) { scrtabsData.scroller.initTabs = null; } // $targetElInstance is the container for the ul.nav-tabs we generated $targetElInstance .find('.scrtabs-tab-container') .add('.tab-content') .remove(); } $targetElInstance.removeData('scrtabs'); $(window).off(CONSTANTS.EVENTS.WINDOW_RESIZE); $('body').off(CONSTANTS.EVENTS.FORCE_REFRESH); } function listenForDropdownMenuTabs($scroller) { var $ddMenu; // for dropdown menus to show, we need to move them out of the // scroller and append them to the body $scroller .on(CONSTANTS.EVENTS.DROPDOWN_MENU_SHOW, handleDropdownShow) .on(CONSTANTS.EVENTS.DROPDOWN_MENU_HIDE, handleDropdownHide); function handleDropdownHide(e) { // move the dropdown menu back into its tab $(e.target).append($ddMenu.off(CONSTANTS.EVENTS.CLICK)); } function handleDropdownShow(e) { var $ddParentTabLi = $(e.target), ddLiOffset = $ddParentTabLi.offset(), $currActiveTab = $scroller.find('li[role="presentation"].active'), ddMenuRightX, tabsContainerMaxX, ddMenuTargetLeft; $ddMenu = $ddParentTabLi .find('.dropdown-menu') .attr('data-' + CONSTANTS.DATA_KEY_DDMENU_MODIFIED, true); // if the dropdown's parent tab li isn't already active, // we need to deactivate any active menu item in the dropdown if ($currActiveTab[0] !== $ddParentTabLi[0]) { $ddMenu.find('li.active').removeClass('active'); } // we need to do our own click handling because the built-in // bootstrap handlers won't work since we moved the dropdown // menu outside the tabs container $ddMenu.on(CONSTANTS.EVENTS.CLICK, 'a[role="tab"]', handleClickOnDropdownMenuItem); $('body').append($ddMenu); // make sure the menu doesn't go off the right side of the page ddMenuRightX = $ddMenu.width() + ddLiOffset.left; tabsContainerMaxX = $scroller.width() - (CONSTANTS.SCROLL_ARROW_WIDTH + 1); ddMenuTargetLeft = ddLiOffset.left; if (ddMenuRightX > tabsContainerMaxX) { ddMenuTargetLeft -= (ddMenuRightX - tabsContainerMaxX); } $ddMenu.css({ 'display': 'block', 'top': ddLiOffset.top + $ddParentTabLi.outerHeight() - 2, 'left': ddMenuTargetLeft }); function handleClickOnDropdownMenuItem(e) { var $selectedMenuItemAnc = $(this), $selectedMenuItemLi = $selectedMenuItemAnc.parent('li'), $selectedMenuItemDropdownMenu = $selectedMenuItemLi.parent('.dropdown-menu'), targetPaneId = $selectedMenuItemAnc.attr('href'); if ($selectedMenuItemLi.hasClass('active')) { return; } // once we select a menu item from the dropdown, deactivate // the current tab (unless it's our parent tab), deactivate // any active dropdown menu item, make our parent tab active // (if it's not already), and activate the selected menu item $scroller .find('li.active') .not($ddParentTabLi) .add($selectedMenuItemDropdownMenu.find('li.active')) .removeClass('active'); $ddParentTabLi .add($selectedMenuItemLi) .addClass('active'); // manually deactivate current active pane and activate our pane $('.tab-content .tab-pane.active').removeClass('active'); $(targetPaneId).addClass('active'); } } } function refreshDataDrivenTabs($container, options) { var instanceData = $container.data().scrtabs, scroller = instanceData.scroller, $navTabs = $container.find('.scrtabs-tab-container .nav-tabs'), $currTabContentPanesContainer = $container.find('.tab-content'), isInitTabsRequired = false, refreshData = { options: options, updatedTabsArray: instanceData.tabs, propNames: instanceData.propNames, ignoreTabPanes: instanceData.ignoreTabPanes, $navTabs: $navTabs, $currTabLis: $navTabs.find('> li'), $currTabContentPanesContainer: $currTabContentPanesContainer, $currTabContentPanes: $currTabContentPanesContainer.find('.tab-pane') }; // to preserve the tab positions if we're just adding or removing // a tab, don't completely rebuild the tab structure, but check // for differences between the new tabs array and the old if (checkForTabAdded(refreshData)) { isInitTabsRequired = true; } if (checkForTabsOrderChanged(refreshData)) { isInitTabsRequired = true; } if (checkForTabsRemovedOrUpdated(refreshData)) { isInitTabsRequired = true; } if (isInitTabsRequired) { scroller.initTabs(); } return isInitTabsRequired; } function refreshTargetElementInstance($container, options) { if (!$container.data('scrtabs')) { // target element doesn't have plugin on it return; } // force a refresh if the tabs are static html or they're data-driven // but the data didn't change so we didn't call initTabs() if ($container.data('scrtabs').isWrapperOnly || !refreshDataDrivenTabs($container, options)) { $('body').trigger(CONSTANTS.EVENTS.FORCE_REFRESH); } } function scrollToActiveTab() { var $targetElInstance = $(this), scrtabsData = $targetElInstance.data('scrtabs'); if (!scrtabsData) { return; } scrtabsData.scroller.scrollToActiveTab(); } function wrapNavTabsInstanceInScroller($navTabsInstance, settings, readyCallback, attachTabContentToDomCallback) { var $scroller = tabElements.getNewElScrollerElementWrappingNavTabsInstance($navTabsInstance.clone(true), settings), // use clone because we replaceWith later scrollingTabsControl = new ScrollingTabsControl($scroller), navTabsInstanceData = $navTabsInstance.data('scrtabs'); if (!navTabsInstanceData) { $navTabsInstance.data('scrtabs', { scroller: $scroller }); } else { navTabsInstanceData.scroller = $scroller; } $navTabsInstance.replaceWith($scroller.css('visibility', 'hidden')); if (settings.tabClickHandler && (typeof settings.tabClickHandler === 'function')) { $scroller.hasTabClickHandler = true; scrollingTabsControl.tabClickHandler = settings.tabClickHandler; } $scroller.initTabs = function () { scrollingTabsControl.initTabs(settings, $scroller, readyCallback, attachTabContentToDomCallback); }; $scroller.scrollToActiveTab = function() { scrollingTabsControl.scrollToActiveTab(settings); }; $scroller.initTabs(); listenForDropdownMenuTabs($scroller); return $scroller; } //////////////////////// var methods = { destroy: function() { var $targetEls = this; return $targetEls.each(destroyPlugin); }, init: function(options) { var $targetEls = this, targetElsLastIndex = $targetEls.length - 1, settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {}); // ---- tabs NOT data-driven ------------------------- if (!settings.tabs) { // just wrap the selected .nav-tabs element(s) in the scroller return $targetEls.each(function(index) { var dataObj = { isWrapperOnly: true }, $targetEl = $(this).data({ scrtabs: dataObj }), readyCallback = (index < targetElsLastIndex) ? null : function() { $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY); }; wrapNavTabsInstanceInScroller($targetEl, settings, readyCallback); }); } // ---- tabs data-driven ------------------------- return $targetEls.each(function (index) { var $targetEl = $(this), readyCallback = (index < targetElsLastIndex) ? null : function() { $targetEls.trigger(CONSTANTS.EVENTS.TABS_READY); }; buildNavTabsAndTabContentForTargetElementInstance($targetEl, settings, readyCallback); }); }, refresh: function(options) { var $targetEls = this, settings = $.extend({}, $.fn.scrollingTabs.defaults, options || {}); return $targetEls.each(function () { refreshTargetElementInstance($(this), settings); }); }, scrollToActiveTab: function() { return this.each(scrollToActiveTab); } }; $.fn.scrollingTabs = function(methodOrOptions) { if (methods[methodOrOptions]) { return methods[methodOrOptions].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (!methodOrOptions || (typeof methodOrOptions === 'object')) { return methods.init.apply(this, arguments); } else { $.error('Method ' + methodOrOptions + ' does not exist on $.scrollingTabs.'); } }; $.fn.scrollingTabs.defaults = { tabs: null, propPaneId: 'paneId', propTitle: 'title', propActive: 'active', propDisabled: 'disabled', propContent: 'content', ignoreTabPanes: false, scrollToTabEdge: false, disableScrollArrowsOnFullyScrolled: false, forceActiveTab: false, reverseScroll: false, widthMultiplier: 1, tabClickHandler: null, cssClassLeftArrow: 'glyphicon glyphicon-chevron-left', cssClassRightArrow: 'glyphicon glyphicon-chevron-right' }; }(jQuery, window));