/* * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ * Version: 0.11.0-SNAPSHOT - 2014-03-17 * License: MIT */ /** * @ngdoc overview * @name ui.bootstrap.tabs * * @description * AngularJS version of the tabs directive. */ angular.module('ui.bootstrap.tabs', []) .controller('TabsetController', ['$scope', function TabsetCtrl($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.active = true; selectedTab.onSelect(); }; 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 = true; } else if (tab.active) { ctrl.select(tab); } }; ctrl.removeTab = function removeTab(tab) { var index = tabs.indexOf(tab); //Select a new tab if the tab to be removed is selected if (tab.active && tabs.length > 1) { //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); }; }]) /** * @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 First Content! Second Content!
First Vertical Content! Second Vertical Content! First Justified Content! Second Justified Content!
*/ .directive('tabset', function() { return { restrict: 'EA', transclude: true, replace: true, scope: { type: '@' }, controller: 'TabsetController', 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

First Tab Alert me! Second Tab, with alert callback and html heading! {{item.content}}
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!"); }); }; };
*/ /** * @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 HTML in my titles?! And some content, too! Icon heading?!? That's right. */ .directive('tab', ['$parse', function($parse) { 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 }, compile: function(elm, attrs, transclude) { return function postLink(scope, elm, attrs, tabsetCtrl) { scope.$watch('active', function(active) { if (active) { tabsetCtrl.select(scope); } }); scope.disabled = false; if ( attrs.disabled ) { scope.$parent.$watch($parse(attrs.disabled), 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', [function() { return { restrict: 'A', require: '^tab', link: function(scope, elm, attrs, tabCtrl) { scope.$watch('headingElement', function updateHeadingElement(heading) { if (heading) { elm.html(''); elm.append(heading); } }); } }; }]) .directive('tabContentTransclude', function() { return { restrict: 'A', require: '^tabset', link: function(scope, elm, attrs) { 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.tagName.toLowerCase() === 'tab-heading' || node.tagName.toLowerCase() === 'data-tab-heading' ); } }) ;