| |
| /** |
| * @ngdoc overview |
| * @name ui.bootstrap.tabs |
| * |
| * @description |
| * AngularJS version of the tabs directive. |
| */ |
| |
| angular.module('ui.bootstrap.tabs', []) |
| |
| .controller('UibTabsetController', ['$scope', function ($scope) { |
| var ctrl = this, |
| tabs = ctrl.tabs = $scope.tabs = []; |
| |
| ctrl.select = function(selectedTab) { |
| angular.forEach(tabs, function(tab) { |
| if (tab.active && tab !== selectedTab) { |
| tab.active = false; |
| tab.onDeselect(); |
| selectedTab.selectCalled = false; |
| } |
| }); |
| selectedTab.active = true; |
| // only call select if it has not already been called |
| if (!selectedTab.selectCalled) { |
| selectedTab.onSelect(); |
| selectedTab.selectCalled = true; |
| } |
| }; |
| |
| ctrl.addTab = function addTab(tab) { |
| tabs.push(tab); |
| // we can't run the select function on the first tab |
| // since that would select it twice |
| if (tabs.length === 1 && tab.active !== false) { |
| tab.active = true; |
| } else if (tab.active) { |
| ctrl.select(tab); |
| } else { |
| tab.active = false; |
| } |
| }; |
| |
| ctrl.removeTab = function removeTab(tab) { |
| var index = tabs.indexOf(tab); |
| //Select a new tab if the tab to be removed is selected and not destroyed |
| if (tab.active && tabs.length > 1 && !destroyed) { |
| //If this is the last tab, select the previous tab. else, the next tab. |
| var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; |
| ctrl.select(tabs[newActiveIndex]); |
| } |
| tabs.splice(index, 1); |
| }; |
| |
| var destroyed; |
| $scope.$on('$destroy', function() { |
| destroyed = true; |
| }); |
| }]) |
| |
| /** |
| * @ngdoc directive |
| * @name ui.bootstrap.tabs.directive:tabset |
| * @restrict EA |
| * |
| * @description |
| * Tabset is the outer container for the tabs directive |
| * |
| * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. |
| * @param {boolean=} justified Whether or not to use justified styling for the tabs. |
| * |
| * @example |
| <example module="ui.bootstrap"> |
| <file name="index.html"> |
| <uib-tabset> |
| <uib-tab heading="Tab 1"><b>First</b> Content!</uib-tab> |
| <uib-tab heading="Tab 2"><i>Second</i> Content!</uib-tab> |
| </uib-tabset> |
| <hr /> |
| <uib-tabset vertical="true"> |
| <uib-tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</uib-tab> |
| <uib-tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</uib-tab> |
| </uib-tabset> |
| <uib-tabset justified="true"> |
| <uib-tab heading="Justified Tab 1"><b>First</b> Justified Content!</uib-tab> |
| <uib-tab heading="Justified Tab 2"><i>Second</i> Justified Content!</uib-tab> |
| </uib-tabset> |
| </file> |
| </example> |
| */ |
| .directive('uibTabset', function() { |
| return { |
| restrict: 'EA', |
| transclude: true, |
| replace: true, |
| scope: { |
| type: '@' |
| }, |
| controller: 'UibTabsetController', |
| templateUrl: 'template/tabs/tabset.html', |
| link: function(scope, element, attrs) { |
| scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; |
| scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; |
| } |
| }; |
| }) |
| |
| /** |
| * @ngdoc directive |
| * @name ui.bootstrap.tabs.directive:tab |
| * @restrict EA |
| * |
| * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. |
| * @param {string=} select An expression to evaluate when the tab is selected. |
| * @param {boolean=} active A binding, telling whether or not this tab is selected. |
| * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. |
| * |
| * @description |
| * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. |
| * |
| * @example |
| <example module="ui.bootstrap"> |
| <file name="index.html"> |
| <div ng-controller="TabsDemoCtrl"> |
| <button class="btn btn-small" ng-click="items[0].active = true"> |
| Select item 1, using active binding |
| </button> |
| <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled"> |
| Enable/disable item 2, using disabled binding |
| </button> |
| <br /> |
| <uib-tabset> |
| <uib-tab heading="Tab 1">First Tab</uib-tab> |
| <uib-tab select="alertMe()"> |
| <uib-tab-heading><i class="icon-bell"></i> Alert me!</tab-heading> |
| Second Tab, with alert callback and html heading! |
| </uib-tab> |
| <uib-tab ng-repeat="item in items" |
| heading="{{item.title}}" |
| disabled="item.disabled" |
| active="item.active"> |
| {{item.content}} |
| </uib-tab> |
| </uib-tabset> |
| </div> |
| </file> |
| <file name="script.js"> |
| function TabsDemoCtrl($scope) { |
| $scope.items = [ |
| { title:"Dynamic Title 1", content:"Dynamic Item 0" }, |
| { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } |
| ]; |
| |
| $scope.alertMe = function() { |
| setTimeout(function() { |
| alert("You've selected the alert tab!"); |
| }); |
| }; |
| }; |
| </file> |
| </example> |
| */ |
| |
| /** |
| * @ngdoc directive |
| * @name ui.bootstrap.tabs.directive:tabHeading |
| * @restrict EA |
| * |
| * @description |
| * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. |
| * |
| * @example |
| <example module="ui.bootstrap"> |
| <file name="index.html"> |
| <uib-tabset> |
| <uib-tab> |
| <uib-tab-heading><b>HTML</b> in my titles?!</tab-heading> |
| And some content, too! |
| </uib-tab> |
| <uib-tab> |
| <uib-tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading> |
| That's right. |
| </uib-tab> |
| </uib-tabset> |
| </file> |
| </example> |
| */ |
| .directive('uibTab', ['$parse', function($parse) { |
| return { |
| require: '^uibTabset', |
| restrict: 'EA', |
| replace: true, |
| templateUrl: 'template/tabs/tab.html', |
| transclude: true, |
| scope: { |
| active: '=?', |
| heading: '@', |
| onSelect: '&select', //This callback is called in contentHeadingTransclude |
| //once it inserts the tab's content into the dom |
| onDeselect: '&deselect' |
| }, |
| controller: function() { |
| //Empty controller so other directives can require being 'under' a tab |
| }, |
| link: function(scope, elm, attrs, tabsetCtrl, transclude) { |
| scope.$watch('active', function(active) { |
| if (active) { |
| tabsetCtrl.select(scope); |
| } |
| }); |
| |
| scope.disabled = false; |
| if (attrs.disable) { |
| scope.$parent.$watch($parse(attrs.disable), function(value) { |
| scope.disabled = !! value; |
| }); |
| } |
| |
| scope.select = function() { |
| if (!scope.disabled) { |
| scope.active = true; |
| } |
| }; |
| |
| tabsetCtrl.addTab(scope); |
| scope.$on('$destroy', function() { |
| tabsetCtrl.removeTab(scope); |
| }); |
| |
| //We need to transclude later, once the content container is ready. |
| //when this link happens, we're inside a tab heading. |
| scope.$transcludeFn = transclude; |
| } |
| }; |
| }]) |
| |
| .directive('uibTabHeadingTransclude', function() { |
| return { |
| restrict: 'A', |
| require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal |
| link: function(scope, elm) { |
| scope.$watch('headingElement', function updateHeadingElement(heading) { |
| if (heading) { |
| elm.html(''); |
| elm.append(heading); |
| } |
| }); |
| } |
| }; |
| }) |
| |
| .directive('uibTabContentTransclude', function() { |
| return { |
| restrict: 'A', |
| require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal |
| link: function(scope, elm, attrs) { |
| var tab = scope.$eval(attrs.uibTabContentTransclude); |
| |
| //Now our tab is ready to be transcluded: both the tab heading area |
| //and the tab content area are loaded. Transclude 'em both. |
| tab.$transcludeFn(tab.$parent, function(contents) { |
| angular.forEach(contents, function(node) { |
| if (isTabHeading(node)) { |
| //Let tabHeadingTransclude know. |
| tab.headingElement = node; |
| } else { |
| elm.append(node); |
| } |
| }); |
| }); |
| } |
| }; |
| |
| function isTabHeading(node) { |
| return node.tagName && ( |
| node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal |
| node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal |
| node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal |
| node.hasAttribute('uib-tab-heading') || |
| node.hasAttribute('data-uib-tab-heading') || |
| node.hasAttribute('x-uib-tab-heading') || |
| node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal |
| node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal |
| node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal |
| node.tagName.toLowerCase() === 'uib-tab-heading' || |
| node.tagName.toLowerCase() === 'data-uib-tab-heading' || |
| node.tagName.toLowerCase() === 'x-uib-tab-heading' |
| ); |
| } |
| }); |
| |
| /* deprecated tabs below */ |
| |
| angular.module('ui.bootstrap.tabs') |
| |
| .value('$tabsSuppressWarning', false) |
| |
| .controller('TabsetController', ['$scope', '$controller', '$log', '$tabsSuppressWarning', function($scope, $controller, $log, $tabsSuppressWarning) { |
| if (!$tabsSuppressWarning) { |
| $log.warn('TabsetController is now deprecated. Use UibTabsetController instead.'); |
| } |
| |
| angular.extend(this, $controller('UibTabsetController', { |
| $scope: $scope |
| })); |
| }]) |
| |
| .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { |
| return { |
| restrict: 'EA', |
| transclude: true, |
| replace: true, |
| scope: { |
| type: '@' |
| }, |
| controller: 'TabsetController', |
| templateUrl: 'template/tabs/tabset.html', |
| link: function(scope, element, attrs) { |
| |
| if (!$tabsSuppressWarning) { |
| $log.warn('tabset is now deprecated. Use uib-tabset instead.'); |
| } |
| scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; |
| scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; |
| } |
| }; |
| }]) |
| |
| .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) { |
| return { |
| require: '^tabset', |
| restrict: 'EA', |
| replace: true, |
| templateUrl: 'template/tabs/tab.html', |
| transclude: true, |
| scope: { |
| active: '=?', |
| heading: '@', |
| onSelect: '&select', //This callback is called in contentHeadingTransclude |
| //once it inserts the tab's content into the dom |
| onDeselect: '&deselect' |
| }, |
| controller: function() { |
| //Empty controller so other directives can require being 'under' a tab |
| }, |
| link: function(scope, elm, attrs, tabsetCtrl, transclude) { |
| if (!$tabsSuppressWarning) { |
| $log.warn('tab is now deprecated. Use uib-tab instead.'); |
| } |
| |
| scope.$watch('active', function(active) { |
| if (active) { |
| tabsetCtrl.select(scope); |
| } |
| }); |
| |
| scope.disabled = false; |
| if (attrs.disable) { |
| scope.$parent.$watch($parse(attrs.disable), function(value) { |
| scope.disabled = !!value; |
| }); |
| } |
| |
| scope.select = function() { |
| if (!scope.disabled) { |
| scope.active = true; |
| } |
| }; |
| |
| tabsetCtrl.addTab(scope); |
| scope.$on('$destroy', function() { |
| tabsetCtrl.removeTab(scope); |
| }); |
| |
| //We need to transclude later, once the content container is ready. |
| //when this link happens, we're inside a tab heading. |
| scope.$transcludeFn = transclude; |
| } |
| }; |
| }]) |
| |
| .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { |
| return { |
| restrict: 'A', |
| require: '^tab', |
| link: function(scope, elm) { |
| if (!$tabsSuppressWarning) { |
| $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.'); |
| } |
| |
| scope.$watch('headingElement', function updateHeadingElement(heading) { |
| if (heading) { |
| elm.html(''); |
| elm.append(heading); |
| } |
| }); |
| } |
| }; |
| }]) |
| |
| .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { |
| return { |
| restrict: 'A', |
| require: '^tabset', |
| link: function(scope, elm, attrs) { |
| if (!$tabsSuppressWarning) { |
| $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.'); |
| } |
| |
| var tab = scope.$eval(attrs.tabContentTransclude); |
| |
| //Now our tab is ready to be transcluded: both the tab heading area |
| //and the tab content area are loaded. Transclude 'em both. |
| tab.$transcludeFn(tab.$parent, function(contents) { |
| angular.forEach(contents, function(node) { |
| if (isTabHeading(node)) { |
| //Let tabHeadingTransclude know. |
| tab.headingElement = node; |
| } |
| else { |
| elm.append(node); |
| } |
| }); |
| }); |
| } |
| }; |
| |
| function isTabHeading(node) { |
| return node.tagName && ( |
| node.hasAttribute('tab-heading') || |
| node.hasAttribute('data-tab-heading') || |
| node.hasAttribute('x-tab-heading') || |
| node.tagName.toLowerCase() === 'tab-heading' || |
| node.tagName.toLowerCase() === 'data-tab-heading' || |
| node.tagName.toLowerCase() === 'x-tab-heading' |
| ); |
| } |
| }]); |