angular-ui-tab-scroll
Version:
A scrollable tab plugin intended for scrolling UI Bootstrap tabset.
389 lines (336 loc) • 16.7 kB
JavaScript
/*
* angular-ui-tab-scroll
* https://github.com/VersifitTechnologies/angular-ui-tab-scroll
*
* Version: 2.3.5
* License: MIT
*/
angular.module('ui.tab.scroll', [])
.provider('scrollableTabsetConfig', function(){
//the default options
var defaultConfig = {
showDropDown: true,
showTooltips: true,
tooltipLeftPlacement: 'right',
tooltipRightPlacement: 'left',
scrollBy: '50',
autoRecalculate: false,
leftScrollAddition: '0'
};
var config = angular.extend({}, defaultConfig);
return {
setShowDropDown : function(value){
config.showDropDown = (value == true);
},
setShowTooltips : function(value){
config.showTooltips = (value == true);
},
setTooltipLeftPlacement : function(value){
config.tooltipLeftPlacement = value;
},
setTooltipRightPlacement : function(value){
config.tooltipRightPlacement = value;
},
setScrollBy : function(value){
config.scrollBy = value;
},
setAutoRecalculate : function(value){
config.autoRecalculate = (value == true);
},
setLeftScrollAddition : function(value){
config.leftScrollAddition = value;
},
$get: function(){
return {
showDropDown: config.showDropDown,
showTooltips: config.showTooltips,
tooltipLeftPlacement: config.tooltipLeftPlacement,
tooltipRightPlacement: config.tooltipRightPlacement,
scrollBy: config.scrollBy,
autoRecalculate: config.autoRecalculate,
leftScrollAddition: config.leftScrollAddition
};
}
};
}
)
.directive('scrollableTabset', [
'scrollableTabsetConfig', '$window', '$interval', '$timeout','$sce',
function(scrollableTabsetConfig, $window, $interval, $timeout, $sce) {
return {
restrict: 'AE',
transclude: true,
scope: {
showDropDown: '@',
showTooltips: '@',
tooltipLeftPlacement: '@',
tooltipRightPlacement: '@',
scrollBy: '@',
autoRecalculate: '@',
isButtonsVisible: '=?',
dropDownClass: '@?',
dropDownMenuClass: '@?',
dropDownHeaderTemplateUrl: '@?',
dropDownHeaderClass: '@?',
api: '=?',
leftScrollAddition: '@'
},
template: [
'<div class="ui-tabs-scrollable" ng-class="{\'show-drop-down\': !hideDropDown}">',
'<button type="button" ng-mousedown="scrollButtonDown(\'left\', $event)" ng-mouseup="scrollButtonUp()" ng-hide="hideButtons"' +
' ng-disabled="disableLeft" class="btn nav-button left-nav-button"' +
' tooltip-placement="{{tooltipLeftDirection}}" uib-tooltip-html="tooltipLeftHtml"></button>',
'<div class="spacer" ng-class="{\'hidden-buttons\': hideButtons}" ng-transclude></div>',
'<button type="button" ng-mousedown="scrollButtonDown(\'right\', $event)" ng-mouseup="scrollButtonUp()" ng-hide="hideButtons"' +
' ng-disabled="disableRight" class="btn nav-button right-nav-button"' +
' tooltip-placement="{{tooltipRightDirection}}" uib-tooltip-html="tooltipRightHtml"></button>',
'<div class="btn-group" ng-class="::dropDownClass" uib-dropdown dropdown-append-to-body ng-hide="hideDropDown">',
'<button type="button" class="btn" uib-dropdown-toggle></button>',
'<ul class="dropdown-menu" uib-dropdown-menu role="menu" ng-class="::dropDownMenuClass||\'dropdown-menu-right\'">',
'<li ng-class="::dropDownHeaderClass" ng-include="::dropDownHeaderTemplateUrl"></li>',
'<li role="menuitem" ng-repeat="tab in dropdownTabs" ng-class="{\'disabled\': tab.disabled, \'active\': tab.active}" ng-click="activateTab(tab)">',
'<a href><span class="dropDownTabActiveMark" ng-style="{\'visibility\': tab.active?\'visible\':\'hidden\'}"></span>{{tab.tabScrollTitle}}</a>',
'</li>',
'</ul>',
'</div>',
'</div>'
].join(''),
link: function($scope, $el) {
$scope.dropdownTabs = [];
$scope.hideButtons = true;
$scope.hideDropDown = true;
$scope.tooltipRightHtml = '';
$scope.tooltipLeftHtml = '';
$scope.disableLeft = true;
$scope.disableRight = true;
$scope.tooltipLeftDirection = $scope.tooltipLeftPlacement ? $scope.tooltipLeftPlacement : scrollableTabsetConfig.tooltipLeftPlacement;
$scope.tooltipRightDirection = $scope.tooltipRightPlacement ? $scope.tooltipRightPlacement : scrollableTabsetConfig.tooltipRightPlacement;
$scope.mouseDownInterval = null;
$scope.isHolding = false;
$scope.winResizeTimeout;
$scope.userShowDropDown = $scope.showDropDown ? $scope.showDropDown === 'true' : scrollableTabsetConfig.showDropDown;
$scope.userShowTooltips = $scope.showTooltips ? $scope.showTooltips === 'true' : scrollableTabsetConfig.showTooltips == true;
$scope.scrollByPixels = parseInt($scope.scrollBy ? $scope.scrollBy : scrollableTabsetConfig.scrollBy);
$scope.leftScrollAdditionPixels = parseInt($scope.leftScrollAddition ? $scope.leftScrollAddition : scrollableTabsetConfig.leftScrollAddition);
$scope.api = {
doRecalculate: function(){
$timeout(function(){$scope.reCalcAll()});
},
scrollTabIntoView: function(arg){
$timeout(function(){$scope.scrollTabIntoView(arg)});
}
};
$scope.scrollTo = function(element, change, duration, callback, isLinear) {
var start = element.scrollLeft;
var increment = 20;
var position = 0;
var animateScroll = function(elapsedTime) {
elapsedTime += increment;
if(isLinear === true) {
position = $scope.linearTween(elapsedTime, start, change, duration);
} else {
position = $scope.easeInOutQuad(elapsedTime, start, change, duration);
}
element.scrollLeft = position;
if (elapsedTime < duration) {
setTimeout(function() {
animateScroll(elapsedTime);
}, increment);
}else{
callback();
}
};
animateScroll(0);
}
$scope.linearTween = function (currentTime, start, change, duration) {
return change * currentTime / duration + start;
};
$scope.easeInOutQuad = function(currentTime, start, change, duration) {
currentTime /= duration / 2;
if (currentTime < 1) {
return change / 2 * currentTime * currentTime + start;
}
currentTime --;
return -change / 2 * (currentTime * (currentTime - 2) - 1) + start;
}
$scope.onWindowResize = function() {
// delay for a bit to avoid running lots of times.
clearTimeout($scope.winResizeTimeout);
$scope.winResizeTimeout = setTimeout(function(){
$scope.reCalcAll();
$scope.scrollTabIntoView();
$scope.$apply();
}, 250);
};
$scope.cancelMouseDownInterval = function() {
$scope.isHolding = false;
if($scope.mouseDownInterval) {
$interval.cancel($scope.mouseDownInterval);
$scope.mouseDownInterval = null;
}
};
$scope.scrollButtonDown = function(direction, event) {
event.stopPropagation();
$scope.isHolding = true;
var realScroll = direction === 'left' ? 0 - $scope.scrollByPixels : $scope.scrollByPixels;
$scope.scrollTo($scope.tabContainer, realScroll, 150, function(){
$timeout(function(){
$scope.reCalcSides();
});
}, true);
$scope.mouseDownInterval = $interval(function() {
if($scope.isHolding) {
$scope.scrollTo($scope.tabContainer, realScroll, 150, function(){
$timeout(function(){
$scope.reCalcSides();
});
}, true);
if(event.target.disabled) {
$scope.cancelMouseDownInterval();
}
}
}, 100);
}
$scope.scrollButtonUp = function() {
$scope.cancelMouseDownInterval();
}
$scope.activateTab = function(tab) {
if(tab.disabled)return;
tab.select();
$timeout(function () {
$scope.scrollTabIntoView();
});
}
$scope.reCalcSides = function() {
if(!$scope.tabContainer || $scope.hideButtons)return;
$scope.disableRight = $scope.tabContainer.scrollLeft >= $scope.tabContainer.scrollWidth - $scope.tabContainer.offsetWidth;
$scope.disableLeft = $scope.tabContainer.scrollLeft <= 0;
if($scope.userShowTooltips){
$scope.reCalcTooltips();
}
};
$scope.reCalcTooltips = function(){
if(!$scope.tabContainer || $scope.hideButtons)return;
var rightTooltips = [];
var leftTooltips = [];
var allTabs = $scope.tabContainer.querySelectorAll('ul.nav-tabs > li');
angular.forEach(allTabs, function(tab) {
var rightPosition = parseInt(tab.getBoundingClientRect().left + tab.getBoundingClientRect().width - $scope.tabContainer.getBoundingClientRect().left);
var leftPosition = tab.getBoundingClientRect().left - $scope.tabContainer.getBoundingClientRect().left;
var heading = tab.getAttribute("data-tabScrollHeading");
var ignore = tab.getAttribute("data-tabScrollIgnore");
if(rightPosition > $scope.tabContainer.offsetWidth && !ignore ) {
if(heading) {
rightTooltips.push(heading)
} else if (tab.textContent)rightTooltips.push(tab.textContent);
}
if (leftPosition < 0 && !ignore ) {
if(heading) {
leftTooltips.push(heading)
} else if (tab.textContent)leftTooltips.push(tab.textContent);
}
});
var rightTooltipsHtml = rightTooltips.join('<br>');
$scope.tooltipRightHtml = $sce.trustAsHtml(rightTooltipsHtml);
var leftTooltipsHtml = leftTooltips.join('<br>');
$scope.tooltipLeftHtml = $sce.trustAsHtml(leftTooltipsHtml);
};
$scope.scrollTabIntoView = function(arg){
if(!$scope.tabContainer || $scope.hideButtons)return;
var argInt = parseInt(arg);
var tabToScroll;
// first we find the tab element.
if(argInt) { // scroll tab index into view
var allTabs = $scope.tabContainer.querySelectorAll('ul.nav-tabs > li');
if(allTabs.length > argInt) { // only if its really exist
tabToScroll = allTabs[argInt];
}
} else { // scroll selected tab into view
var activeTab = $scope.tabContainer.querySelector('li.active');
if(activeTab) {
tabToScroll = activeTab;
}
}
// now let's scroll it into view.
if(tabToScroll) {
var rightPosition = parseInt(tabToScroll.getBoundingClientRect().left + tabToScroll.getBoundingClientRect().width - $scope.tabContainer.getBoundingClientRect().left);
var leftPosition = tabToScroll.getBoundingClientRect().left - $scope.tabContainer.getBoundingClientRect().left;
if (leftPosition - $scope.leftScrollAdditionPixels < 0) {
var dif = leftPosition - 20 - $scope.leftScrollAdditionPixels;
$scope.scrollTo($scope.tabContainer, dif, 700, function(){
$timeout(function(){
$scope.reCalcSides();
});
});
} else if(rightPosition > $scope.tabContainer.offsetWidth){
var dif = rightPosition - $scope.tabContainer.offsetWidth + 20;
$scope.scrollTo($scope.tabContainer, dif, 700, function(){
$timeout(function(){
$scope.reCalcSides();
});
});
}
}
};
// init is called only once!
$scope.init = function() {
$scope.tabContainer = $el[0].querySelector('.spacer ul.nav-tabs');
if(!$scope.tabContainer)return;
var autoRecalc = $scope.autoRecalculate ? $scope.autoRecalculate === 'true' : scrollableTabsetConfig.autoRecalculate;
if(autoRecalc) {
var tabsetElement = angular.element($el[0].querySelector('.spacer div'));
$scope.$watchCollection(
function () {
return tabsetElement.isolateScope() ? tabsetElement.isolateScope().tabs : false;
},
function () {
$timeout(function () {
$scope.reCalcAll()
});
}
);
}
$scope.reCalcAll();
// attaching event to window resize.
angular.element($window).on('resize', $scope.onWindowResize);
};
// re-calculate if the scroll buttons are needed, than call re-calculate for both buttons.
$scope.reCalcAll = function() {
if(!$scope.tabContainer)return;
$scope.hideButtons = $scope.tabContainer.scrollWidth <= $scope.tabContainer.offsetWidth;
$scope.hideDropDown = $scope.userShowDropDown ? $scope.hideButtons : true;
$scope.isButtonsVisible = !$scope.hideButtons;
if(!$scope.hideButtons) {
if(!$scope.hideDropDown) {
var allTabs = $scope.tabContainer.querySelectorAll('ul.nav-tabs > li');
$scope.dropdownTabs = [];
angular.forEach(allTabs, function (tab) {
var ignore = tab.getAttribute("data-tabScrollIgnore");
if(!ignore){
var heading = tab.getAttribute("data-tabScrollHeading");
var tabScope = angular.element(tab).isolateScope();
//push new field to use as title in the drop down.
tabScope.tabScrollTitle = heading ? heading : tab.textContent.trim();
$scope.dropdownTabs.push(tabScope);
}
});
} else {
$scope.dropdownTabs = [];
}
$scope.reCalcSides();
} else {
$scope.dropdownTabs = [];
}
};
// this is how we init for the first time.
$timeout(function(){
$scope.init();
});
// when scope destroyed
$scope.$on('$destroy', function () {
angular.element($window).off('resize', $scope.onWindowResize);
$scope.dropdownTabs = [];
});
}
};
}]);