<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="scrolling-tabs.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="scrolling-tabs-header">Angular Bootstrap Scrolling Tabs</div>
<div ng-app="myapp">
<div ng-controller="MainController as main">
<button ng-click="main.myJahIf = 42">don't panic</button>
<div>main.myJahIf: {{main.myJahIf}}</div>
<div scrolling-tabs-wrapper>
<tabset>
<tab>
<tab-heading>TAB heading 111</tab-heading>
<tab-content>
<div>
TAB 111
</div>
</tab-content>
</tab>
<tab ng-if="main.myJahIf == 42">
<tab-heading>TAB heading 222</tab-heading>
<tab-content>
<div>
TAB 222
</div>
</tab-content>
</tab>
<tab ng-if="main.myJahIf == 42">
<tab-heading>TAB heading 333</tab-heading>
<tab-content>
<div>
TAB 333
</div>
</tab-content>
</tab>
<tab>
<tab-heading>TAB heading 444</tab-heading>
<tab-content>
<div>
TAB 444
</div>
</tab-content>
</tab>
<tab>
<tab-heading>TAB heading 555</tab-heading>
<tab-content>
<div>
TAB 555
</div>
</tab-content>
</tab>
</tabset>
</div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.0.min.js"></script>
<script src="scrolling-tabs.js"></script>
<script src="app.js"></script>
<script src="MainService.js"></script>
<script src="MainController.js"></script>
</body>
</html>
// Code goes here
.scrolling-tabs-header {
background-color: #666666;
color: white;
font-size: 24px;
height: 50px;
padding: 8px 24px; }
.scrolling-tabs-subheader {
background-color: #f0f0f0;
color: #333;
font-size: 16px;
height: 65px;
padding: 8px 24px;
}
.scrolling-tabs-subheader div:first-child {
font-weight: 700;
margin-bottom: 2px;
}
;(function () {
'use strict';
angular.module('myapp', ['ui.bootstrap', 'mj.scrollingTabs']);
}());
;(function () {
'use strict';
function MainController(MainService) {
var ctrl = this;
ctrl.myJahIf = MainService.data.myJahIf;
ctrl.handleClickOnTab = function (e, idx, tab) {
};
}
MainController.$inject = ['MainService'];
angular.module('myapp').controller('MainController', MainController);
}());
;(function () {
'use strict';
var myJahIf = 555;
function MainService($timeout) {
var svc = this;
svc.data = {
myJahIf: myJahIf
};
}
MainService.$inject = ['$timeout'];
angular.module('myapp').service('MainService', MainService);
}());
.scrtabs-tab-container * {
box-sizing: border-box;
}
.scrtabs-tab-container {
height: 42px;
}
.scrtabs-tabs-fixed-container {
float: left;
height: 42px;
overflow: hidden;
width: 100%;
}
.scrtabs-tabs-movable-container {
position: relative;
}
.scrtabs-tab-scroll-arrow {
border: 1px solid #dddddd;
border-top: none;
color: #428bca;
cursor: pointer;
float: left;
font-size: 12px;
height: 42px;
margin-bottom: -1px;
padding-left: 2px;
padding-top: 13px;
width: 20px;
}
.scrtabs-tab-scroll-arrow:hover {
background-color: #eeeeee;
}
.scrtabs-tabs-fixed-container ul.nav-tabs {
height: 41px;
}
.scrtabs-tabs-fixed-container ul.nav-tabs > li {
white-space: nowrap;
}
.scrtabs-tab-content-hidden {
display: none;
}
;(function () {
'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_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_IS_MOUSEDOWN: 'ismousedown'
},
scrollingTabsModule = angular.module('mj.scrollingTabs', []),
/* *************************************************************
* scrolling-tabs element directive template
* *************************************************************/
// plunk: http://plnkr.co/edit/YhKiIhuAPkpAyacu6tuk
scrollingTabsTemplate = [
'<div class="scrtabs-tab-container">',
' <div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-left"><span class="glyphicon glyphicon-chevron-left"></span></div>',
' <div class="scrtabs-tabs-fixed-container">',
' <div class="scrtabs-tabs-movable-container">',
' <ul class="nav nav-tabs" role="tablist">',
' <li ng-class="{ \'active\': tab[propActive || \'active\'], ',
' \'disabled\': tab[propDisabled || \'disabled\'] }" ',
' data-tab="{{tab}}" data-index="{{$index}}" ng-repeat="tab in tabsArr">',
' <a ng-href="{{\'#\' + tab[propPaneId || \'paneId\']}}" role="tab"',
' data-toggle="{{tab[propDisabled || \'disabled\'] ? \'\' : \'tab\'}}" ',
' ng-bind-html="sanitize(tab[propTitle || \'title\']);">',
' </a>',
' </li>',
' </ul>',
' </div>',
' </div>',
' <div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-right"><span class="glyphicon glyphicon-chevron-right"></span></div>',
'</div>'
].join(''),
/* *************************************************************
* scrolling-tabs-wrapper element directive template
* *************************************************************/
// plunk: http://plnkr.co/edit/lWeQxxecKPudK7xlQxS3
scrollingTabsWrapperTemplate = [
'<div class="scrtabs-tab-container">',
' <div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-left"><span class="glyphicon glyphicon-chevron-left"></span></div>',
' <div class="scrtabs-tabs-fixed-container">',
' <div class="scrtabs-tabs-movable-container" ng-transclude></div>',
' </div>',
' <div class="scrtabs-tab-scroll-arrow scrtabs-js-tab-scroll-arrow-right"><span class="glyphicon glyphicon-chevron-right"></span></div>',
'</div>'
].join('');
// 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);
};
};
jQuery.fn[sr] = function (fn) { return fn ? this.bind('resize.scrtabs', debounce(fn)) : this.trigger(sr); };
})(jQuery, 'smartresize');
/* ***********************************************************************************
* EventHandlers - Class that each instance of ScrollingTabsControl will instantiate
* **********************************************************************************/
function EventHandlers(scrollingTabsControl) {
var evh = this;
evh.stc = scrollingTabsControl;
}
// prototype methods
(function (p){
p.handleClickOnLeftScrollArrow = function (e) {
var evh = this,
stc = evh.stc;
stc.scrollMovement.incrementScrollLeft();
};
p.handleClickOnRightScrollArrow = function (e) {
var evh = this,
stc = evh.stc,
scrollMovement = stc.scrollMovement;
scrollMovement.incrementScrollRight(scrollMovement.getMinPos());
};
p.handleMousedownOnLeftScrollArrow = function (e) {
var evh = this,
stc = evh.stc;
stc.scrollMovement.startScrollLeft();
};
p.handleMousedownOnRightScrollArrow = function (e) {
var evh = this,
stc = evh.stc;
stc.scrollMovement.startScrollRight();
};
p.handleMouseupOnLeftScrollArrow = function (e) {
var evh = this,
stc = evh.stc;
stc.scrollMovement.stopScrollLeft();
};
p.handleMouseupOnRightScrollArrow = function (e) {
var evh = this,
stc = evh.stc;
stc.scrollMovement.stopScrollRight();
};
p.handleWindowResize = function (e) {
var evh = this,
stc = evh.stc,
newWinWidth = stc.$win.width();
if (newWinWidth === stc.winWidth) {
return false; // false alarm
}
stc.winWidth = newWinWidth;
stc.elementsHandler.refreshAllElementSizes(true); // true -> check for scroll arrows not being necessary anymore
};
}(EventHandlers.prototype));
/* ***********************************************************************************
* 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,
stc = ehd.stc,
$tabsContainer = stc.$tabsContainer;
ehd.setElementReferences();
if (options.isWrappingAngularUITabset) {
ehd.moveTabContentOutsideScrollContainer(options);
}
ehd.setEventListeners();
};
p.moveTabContentOutsideScrollContainer = function (options) {
var ehd = this,
stc = ehd.stc,
$tabsContainer = stc.$tabsContainer,
tabContentCloneCssClass = 'scrtabs-tab-content-clone',
tabContentHiddenCssClass = 'scrtabs-tab-content-hidden',
$tabContent = $tabsContainer.find('.tab-content').not('.' + tabContentCloneCssClass),
$currTcClone,
$newTcClone;
// if the tabs array won't be changing, we can just move the
// the .tab-content outside the scrolling container right now
if (!options.isWatchingTabsArray) {
$tabContent.appendTo($tabsContainer);
return;
}
/* if we're watching the tabs array for changes, we can't just
* move the .tab-content outside the scrolling container because
* that will break the angular-ui directive dependencies, and
* an error will be thrown as soon as the tabs array changes;
* so we leave the .tab-content where it is but hide it, then
* make a clone and move the clone outside the scroll container,
* which will be the visible .tab-content.
*/
// hide the original .tab-content if it's not already hidden
if (!$tabContent.hasClass(tabContentHiddenCssClass)) {
$tabContent.addClass(tabContentHiddenCssClass);
}
// create new clone
$newTcClone = $tabContent
.clone()
.removeClass(tabContentHiddenCssClass)
.addClass(tabContentCloneCssClass);
// get the current clone, if it exists
$currTcClone = $tabsContainer.find('.' + tabContentCloneCssClass);
if ($currTcClone.length) { // already a clone there so replace it
$currTcClone.replaceWith($newTcClone);
} else {
$tabsContainer.append($newTcClone);
}
};
p.refreshAllElementSizes = function (isPossibleArrowVisibilityChange) {
var ehd = this,
stc = ehd.stc,
smv = stc.scrollMovement,
scrollArrowsWereVisible = stc.scrollArrowsVisible,
minPos;
ehd.setElementWidths();
ehd.setScrollArrowVisibility();
if (stc.scrollArrowsVisible) {
ehd.setFixedContainerWidthForJustVisibleScrollArrows();
}
// if this was a window resize, 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 (isPossibleArrowVisibilityChange && (stc.scrollArrowsVisible || scrollArrowsWereVisible)) {
if (stc.scrollArrowsVisible) {
// make sure container not too far left
minPos = smv.getMinPos();
if (stc.movableContainerLeftPos < minPos) {
smv.incrementScrollRight(minPos);
} else {
smv.scrollToActiveTab({
isOnWindowResize: true
});
}
} else {
// scroll arrows went away after resize, so position movable container at 0
stc.movableContainerLeftPos = 0;
smv.slideMovableContainerToLeftPos();
}
}
};
p.setElementReferences = function () {
var ehd = this,
stc = ehd.stc,
$tabsContainer = stc.$tabsContainer;
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.$leftScrollArrow = $tabsContainer.find('.scrtabs-js-tab-scroll-arrow-left');
stc.$rightScrollArrow = $tabsContainer.find('.scrtabs-js-tab-scroll-arrow-right');
stc.$scrollArrows = stc.$leftScrollArrow.add(stc.$rightScrollArrow);
stc.$win = $(window);
};
p.setElementWidths = function () {
var ehd = this,
stc = ehd.stc;
stc.containerWidth = stc.$tabsContainer.outerWidth();
stc.winWidth = stc.$win.width();
stc.scrollArrowsCombinedWidth = stc.$leftScrollArrow.outerWidth() + stc.$rightScrollArrow.outerWidth();
ehd.setFixedContainerWidth();
ehd.setMovableContainerWidth();
};
p.setEventListeners = function () {
var ehd = this,
stc = ehd.stc,
evh = stc.eventHandlers; // eventHandlers
stc.$leftScrollArrow.off('.scrtabs').on({
'mousedown.scrtabs': function (e) { evh.handleMousedownOnLeftScrollArrow.call(evh, e); },
'mouseup.scrtabs': function (e) { evh.handleMouseupOnLeftScrollArrow.call(evh, e); },
'click.scrtabs': function (e) { evh.handleClickOnLeftScrollArrow.call(evh, e); }
});
stc.$rightScrollArrow.off('.scrtabs').on({
'mousedown.scrtabs': function (e) { evh.handleMousedownOnRightScrollArrow.call(evh, e); },
'mouseup.scrtabs': function (e) { evh.handleMouseupOnRightScrollArrow.call(evh, e); },
'click.scrtabs': function (e) { evh.handleClickOnRightScrollArrow.call(evh, e); }
});
stc.$win.smartresize(function (e) { evh.handleWindowResize.call(evh, e); });
};
p.setFixedContainerWidth = function () {
var ehd = this,
stc = ehd.stc;
stc.$fixedContainer.width(stc.fixedContainerWidth = stc.$tabsContainer.outerWidth());
};
p.setFixedContainerWidthForJustHiddenScrollArrows = function () {
var ehd = this,
stc = ehd.stc;
stc.$fixedContainer.width(stc.fixedContainerWidth);
};
p.setFixedContainerWidthForJustVisibleScrollArrows = function () {
var ehd = this,
stc = ehd.stc;
stc.$fixedContainer.width(stc.fixedContainerWidth - stc.scrollArrowsCombinedWidth);
};
p.setMovableContainerWidth = function () {
var ehd = this,
stc = ehd.stc;
stc.movableContainerWidth = 0;
stc.$tabsUl.find('li').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.$movableContainer.width(stc.movableContainerWidth += 1);
};
p.setScrollArrowVisibility = function () {
var ehd = this,
stc = ehd.stc,
shouldBeVisible = stc.movableContainerWidth > stc.fixedContainerWidth;
if (shouldBeVisible && !stc.scrollArrowsVisible) {
stc.$scrollArrows.show();
stc.scrollArrowsVisible = true;
ehd.setFixedContainerWidthForJustVisibleScrollArrows();
} else if (!shouldBeVisible && stc.scrollArrowsVisible) {
stc.$scrollArrows.hide();
stc.scrollArrowsVisible = false;
ehd.setFixedContainerWidthForJustHiddenScrollArrows();
}
};
}(ElementsHandler.prototype));
/* ***********************************************************************************
* ScrollMovement - Class that each instance of ScrollingTabsControl will instantiate
* **********************************************************************************/
function ScrollMovement(scrollingTabsControl) {
var smv = this;
smv.stc = scrollingTabsControl;
}
// prototype methods
(function (p) {
p.continueScrollLeft = function () {
var smv = this,
stc = smv.stc;
stc.$timeout(function() {
if (stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN) && (stc.movableContainerLeftPos < 0)) {
if (!smv.incrementScrollLeft()) { // scroll limit not reached, so keep scrolling
smv.continueScrollLeft();
}
}
}, CONSTANTS.CONTINUOUS_SCROLLING_TIMEOUT_INTERVAL);
};
p.continueScrollRight = function (minPos) {
var smv = this,
stc = smv.stc;
stc.$timeout(function() {
if (stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN) && (stc.movableContainerLeftPos > minPos)) {
// slide tabs LEFT -> decrease movable container's left position
// min value is (movableContainerWidth - $tabHeader width)
if (!smv.incrementScrollRight(minPos)) {
smv.continueScrollRight(minPos);
}
}
}, 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('right');
if (stc.movableContainerLeftPos < minPos) {
stc.movableContainerLeftPos = minPos;
}
}
};
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.incrementScrollLeft = function () {
var smv = this,
stc = smv.stc;
stc.movableContainerLeftPos += (stc.fixedContainerWidth / CONSTANTS.SCROLL_OFFSET_FRACTION);
if (stc.movableContainerLeftPos > 0) {
stc.movableContainerLeftPos = 0;
} else if (stc.scrollToTabEdge) {
smv.setMovableContainerLeftPosToTabEdge('left');
if (stc.movableContainerLeftPos > 0) {
stc.movableContainerLeftPos = 0;
}
}
smv.slideMovableContainerToLeftPos();
return (stc.movableContainerLeftPos === 0); // indicates scroll limit reached
};
p.incrementScrollRight = function (minPos) {
var smv = this,
stc = smv.stc;
smv.decrementMovableContainerLeftPos(minPos);
smv.slideMovableContainerToLeftPos();
return (stc.movableContainerLeftPos === minPos);
};
p.scrollToActiveTab = function (options) {
var smv = this,
stc = smv.stc,
$activeTab,
activeTabWidth,
activeTabLeftPos,
rightArrowLeftPos,
overlap;
// if the active tab is not fully visible, scroll till it is
if (!stc.scrollArrowsVisible) {
return;
}
$activeTab = stc.$tabsUl.find('li.active');
if (!$activeTab.length) {
return;
}
activeTabWidth = $activeTab.outerWidth();
activeTabLeftPos = $activeTab.offset().left;
rightArrowLeftPos = stc.$rightScrollArrow.offset().left;
overlap = activeTabLeftPos + activeTabWidth - rightArrowLeftPos;
if (overlap > 0) {
stc.movableContainerLeftPos = (options.isOnWindowResize || options.isOnTabsRefresh) ? (stc.movableContainerLeftPos - overlap) : -overlap;
smv.slideMovableContainerToLeftPos();
}
};
p.setMovableContainerLeftPosToTabEdge = function (scrollArrowClicked) {
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 = (scrollArrowClicked === 'left') ? -(totalTabWidth - tabWidth) : -totalTabWidth;
return false; // exit .each() loop
}
});
};
p.slideMovableContainerToLeftPos = function () {
var smv = this,
stc = smv.stc,
leftVal;
stc.movableContainerLeftPos = stc.movableContainerLeftPos / 1;
leftVal = smv.getMovableContainerCssLeftVal();
stc.$movableContainer.stop().animate({ left: leftVal }, 'slow', function __slideAnimComplete() {
var newMinPos = smv.getMinPos();
// 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');
}
});
};
p.startScrollLeft = function () {
var smv = this,
stc = smv.stc;
stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
smv.continueScrollLeft();
};
p.startScrollRight = function () {
var smv = this,
stc = smv.stc;
stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, true);
smv.continueScrollRight(smv.getMinPos());
};
p.stopScrollLeft = function () {
var smv = this,
stc = smv.stc;
stc.$leftScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
};
p.stopScrollRight = function () {
var smv = this,
stc = smv.stc;
stc.$rightScrollArrow.data(CONSTANTS.DATA_KEY_IS_MOUSEDOWN, false);
};
}(ScrollMovement.prototype));
/* **********************************************************************
* ScrollingTabsControl - Class that each directive will instantiate
* **********************************************************************/
function ScrollingTabsControl($tabsContainer, $timeout) {
var stc = this;
stc.$tabsContainer = $tabsContainer;
stc.$timeout = $timeout;
stc.movableContainerLeftPos = 0;
stc.scrollArrowsVisible = true;
stc.scrollToTabEdge = false;
stc.scrollMovement = new ScrollMovement(stc);
stc.eventHandlers = new EventHandlers(stc);
stc.elementsHandler = new ElementsHandler(stc);
}
// prototype methods
(function (p) {
p.initTabs = function (options) {
var stc = this,
elementsHandler = stc.elementsHandler,
scrollMovement = stc.scrollMovement;
if (options.scrollToTabEdge) {
stc.scrollToTabEdge = true;
}
stc.$timeout(function __initTabsAfterTimeout() {
elementsHandler.initElements(options);
elementsHandler.refreshAllElementSizes();
scrollMovement.scrollToActiveTab({
isOnTabsRefresh: options.isWatchingTabsArray
});
}, 100);
};
}(ScrollingTabsControl.prototype));
/* ********************************************************
* scrolling-tabs Directive
* ********************************************************/
function scrollingTabsDirective($timeout, $sce) {
function sanitize (html) {
return $sce.trustAsHtml(html);
}
// ------------ Directive Object ---------------------------
return {
restrict: 'E',
template: scrollingTabsTemplate,
transclude: false,
replace: true,
scope: {
tabs: '@',
watchTabs: '=',
propPaneId: '@',
propTitle: '@',
propActive: '@',
propDisabled: '@',
localTabClick: '&tabClick'
},
link: function(scope, element, attrs) {
var scrollingTabsControl = new ScrollingTabsControl(element, $timeout),
scrollToTabEdge = attrs.scrollToTabEdge && attrs.scrollToTabEdge.toLowerCase() === 'true';
scope.tabsArr = scope.$eval(scope.tabs);
scope.propPaneId = scope.propPaneId || 'paneId';
scope.propTitle = scope.propTitle || 'title';
scope.propActive = scope.propActive || 'active';
scope.propDisabled = scope.propDisabled || 'disabled';
scope.sanitize = sanitize;
element.on('click.scrollingTabs', '.nav-tabs > li', function __handleClickOnTab(e) {
var clickedTabElData = $(this).data();
scope.localTabClick({
$event: e,
$index: clickedTabElData.index,
tab: clickedTabElData.tab
});
});
if (!attrs.watchTabs) {
// we're not watching the tabs array for changes so just init
// the tabs without adding a watch
scrollingTabsControl.initTabs({
isWrapperDirective: false,
scrollToTabEdge: scrollToTabEdge
});
return;
}
// we're watching the tabs array for changes...
scope.$watch('watchTabs', function (latestTabsArray, prevTabsArray) {
var $activeTabLi,
activeIndex;
scope.tabsArr = scope.$eval(scope.tabs);
if (latestTabsArray.length && latestTabsArray[latestTabsArray.length - 1].active) { // new tab should be active
// the tab we just added should be active, so, after giving the
// elements time to render (thus the $timeout), force a click on it so
// bootstrap's built-in tab/pane activation can do its thing, otherwise
// the tab will show as active but its content pane won't be
$timeout(function () {
element.find('ul.nav-tabs > li:last').removeClass('active').find('a[role="tab"]').click();
}, 0);
} else { // --------- preserve the currently active tab
// we've replaced the nav-tabs so, to get the currently active tab
// we need to get it from the DOM because clicking a tab doesn't update
// the tabs array (Bootstrap's js just makes the clicked tab and its
// corresponding tab-content pane active); then we need to update
// the tabs array so it reflects the currently active tab before we
// call initTabs() because initTabs() generates the tab elements based
// on the array data.
// get the index of the currently active tab
$activeTabLi = element.find('.nav-tabs > li.active');
if ($activeTabLi.length) {
activeIndex = $activeTabLi.data('index');
scope.tabsArr.some(function __forEachTabsArrItem(t) {
if (t[scope.propActive]) {
t[scope.propActive] = false;
return true; // exit loop
}
});
scope.tabsArr[activeIndex][scope.propActive] = true;
}
}
scrollingTabsControl.initTabs({
isWrapperDirective: false,
isWatchingTabsArray: true,
scrollToTabEdge: scrollToTabEdge
});
}, true);
}
};
}
/* ********************************************************
* scrolling-tabs-wrapper Directive
* ********************************************************/
function scrollingTabsWrapperDirective($timeout) {
// ------------ Directive Object ---------------------------
return {
restrict: 'A',
template: scrollingTabsWrapperTemplate,
transclude: true,
replace: true,
link: function(scope, element, attrs) {
var scrollingTabsControl = new ScrollingTabsControl(element, $timeout),
isWrappingAngularUITabset = element.find('tabset').length > 0,
scrollToTabEdge = attrs.scrollToTabEdge && attrs.scrollToTabEdge.toLowerCase() === 'true';
if (!attrs.watchTabs) {
// we don't need to watch the tabs array for changes, so just
// init the tabs control and return
scrollingTabsControl.initTabs({
isWrapperDirective: true,
isWrappingAngularUITabset: isWrappingAngularUITabset,
scrollToTabEdge: scrollToTabEdge
});
return;
}
// watch the tabs array for changes and refresh the tabs
// control any time it changes (whether the change is a
// new tab or just a change in which tab is selected)
scope.$watch(attrs.watchTabs, function (latestTabsArray, prevTabsArray) {
if (!isWrappingAngularUITabset) { // wrapping regular bootstrap nav-tabs
// if we're wrapping regular bootstrap nav-tabs, we need to
// manually track the active tab because, if a tab is clicked,
// the tabs array doesn't update to reflect the new active tab;
// so if we make each dynamically added tab the new active tab,
// we need to deactivate whatever the currently active tab is,
// and we have no way of knowing that from the state of the tabs
// array--we need to check the tab elements on the page
// listen for tab clicks and update our tabs array accordingly because
// bootstrap doesn't do that
if (latestTabsArray.length && latestTabsArray[latestTabsArray.length - 1].active) {
// the tab we just added should be active, so, after giving the
// elements time to render (thus the $timeout), force a click on it so
// bootstrap's built-in tab/pane activation can do its thing, otherwise
// the tab will show as active but its content pane won't be
$timeout(function () {
element.find('ul.nav-tabs > li:last').removeClass('active').find('a[role="tab"]').click();
}, 0);
}
}
scrollingTabsControl.initTabs({
isWrapperDirective: true,
isWrappingAngularUITabset: isWrappingAngularUITabset,
isWatchingTabsArray: true,
scrollToTabEdge: scrollToTabEdge
});
}, true);
}
};
}
scrollingTabsDirective.$inject = ['$timeout', '$sce'];
scrollingTabsWrapperDirective.$inject = ['$timeout'];
scrollingTabsModule.directive('scrollingTabs', scrollingTabsDirective);
scrollingTabsModule.directive('scrollingTabsWrapper', scrollingTabsWrapperDirective);
}());