tabs.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /**
  2. * @ngdoc overview
  3. * @name ui.bootstrap.tabs
  4. *
  5. * @description
  6. * AngularJS version of the tabs directive.
  7. */
  8. angular.module('ui.bootstrap.tabs', [])
  9. .controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
  10. var ctrl = this,
  11. tabs = ctrl.tabs = $scope.tabs = [];
  12. ctrl.select = function(selectedTab) {
  13. angular.forEach(tabs, function(tab) {
  14. if (tab.active && tab !== selectedTab) {
  15. tab.active = false;
  16. tab.onDeselect();
  17. }
  18. });
  19. selectedTab.active = true;
  20. selectedTab.onSelect();
  21. };
  22. ctrl.addTab = function addTab(tab) {
  23. tabs.push(tab);
  24. // we can't run the select function on the first tab
  25. // since that would select it twice
  26. if (tabs.length === 1 && tab.active !== false) {
  27. tab.active = true;
  28. } else if (tab.active) {
  29. ctrl.select(tab);
  30. }
  31. else {
  32. tab.active = false;
  33. }
  34. };
  35. ctrl.removeTab = function removeTab(tab) {
  36. var index = tabs.indexOf(tab);
  37. //Select a new tab if the tab to be removed is selected and not destroyed
  38. if (tab.active && tabs.length > 1 && !destroyed) {
  39. //If this is the last tab, select the previous tab. else, the next tab.
  40. var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
  41. ctrl.select(tabs[newActiveIndex]);
  42. }
  43. tabs.splice(index, 1);
  44. };
  45. var destroyed;
  46. $scope.$on('$destroy', function() {
  47. destroyed = true;
  48. });
  49. }])
  50. /**
  51. * @ngdoc directive
  52. * @name ui.bootstrap.tabs.directive:tabset
  53. * @restrict EA
  54. *
  55. * @description
  56. * Tabset is the outer container for the tabs directive
  57. *
  58. * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
  59. * @param {boolean=} justified Whether or not to use justified styling for the tabs.
  60. *
  61. * @example
  62. <example module="ui.bootstrap">
  63. <file name="index.html">
  64. <tabset>
  65. <tab heading="Tab 1"><b>First</b> Content!</tab>
  66. <tab heading="Tab 2"><i>Second</i> Content!</tab>
  67. </tabset>
  68. <hr />
  69. <tabset vertical="true">
  70. <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
  71. <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
  72. </tabset>
  73. <tabset justified="true">
  74. <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
  75. <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
  76. </tabset>
  77. </file>
  78. </example>
  79. */
  80. .directive('tabset', function() {
  81. return {
  82. restrict: 'EA',
  83. transclude: true,
  84. replace: true,
  85. scope: {
  86. type: '@'
  87. },
  88. controller: 'TabsetController',
  89. templateUrl: 'app/partials/bootstrap/tabset.html',
  90. link: function(scope, element, attrs) {
  91. scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
  92. scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
  93. }
  94. };
  95. })
  96. /**
  97. * @ngdoc directive
  98. * @name ui.bootstrap.tabs.directive:tab
  99. * @restrict EA
  100. *
  101. * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
  102. * @param {string=} select An expression to evaluate when the tab is selected.
  103. * @param {boolean=} active A binding, telling whether or not this tab is selected.
  104. * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
  105. *
  106. * @description
  107. * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
  108. *
  109. * @example
  110. <example module="ui.bootstrap">
  111. <file name="index.html">
  112. <div ng-controller="TabsDemoCtrl">
  113. <button class="btn btn-small" ng-click="items[0].active = true">
  114. Select item 1, using active binding
  115. </button>
  116. <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
  117. Enable/disable item 2, using disabled binding
  118. </button>
  119. <br />
  120. <tabset>
  121. <tab heading="Tab 1">First Tab</tab>
  122. <tab select="alertMe()">
  123. <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
  124. Second Tab, with alert callback and html heading!
  125. </tab>
  126. <tab ng-repeat="item in items"
  127. heading="{{item.title}}"
  128. disabled="item.disabled"
  129. active="item.active">
  130. {{item.content}}
  131. </tab>
  132. </tabset>
  133. </div>
  134. </file>
  135. <file name="script.js">
  136. function TabsDemoCtrl($scope) {
  137. $scope.items = [
  138. { title:"Dynamic Title 1", content:"Dynamic Item 0" },
  139. { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
  140. ];
  141. $scope.alertMe = function() {
  142. setTimeout(function() {
  143. alert("You've selected the alert tab!");
  144. });
  145. };
  146. };
  147. </file>
  148. </example>
  149. */
  150. /**
  151. * @ngdoc directive
  152. * @name ui.bootstrap.tabs.directive:tabHeading
  153. * @restrict EA
  154. *
  155. * @description
  156. * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
  157. *
  158. * @example
  159. <example module="ui.bootstrap">
  160. <file name="index.html">
  161. <tabset>
  162. <tab>
  163. <tab-heading><b>HTML</b> in my titles?!</tab-heading>
  164. And some content, too!
  165. </tab>
  166. <tab>
  167. <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
  168. That's right.
  169. </tab>
  170. </tabset>
  171. </file>
  172. </example>
  173. */
  174. .directive('tab', ['$parse', '$log', function($parse, $log) {
  175. return {
  176. require: '^tabset',
  177. restrict: 'EA',
  178. replace: true,
  179. templateUrl: 'app/partials/bootstrap/tab.html',
  180. transclude: true,
  181. scope: {
  182. active: '=?',
  183. heading: '@',
  184. onSelect: '&select', //This callback is called in contentHeadingTransclude
  185. //once it inserts the tab's content into the dom
  186. onDeselect: '&deselect'
  187. },
  188. controller: function() {
  189. //Empty controller so other directives can require being 'under' a tab
  190. },
  191. compile: function(elm, attrs, transclude) {
  192. return function postLink(scope, elm, attrs, tabsetCtrl) {
  193. scope.$watch('active', function(active) {
  194. if (active) {
  195. tabsetCtrl.select(scope);
  196. }
  197. });
  198. scope.disabled = false;
  199. if ( attrs.disable ) {
  200. scope.$parent.$watch($parse(attrs.disable), function(value) {
  201. scope.disabled = !! value;
  202. });
  203. }
  204. // Deprecation support of "disabled" parameter
  205. // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
  206. // This code is duplicated from the lines above to make it easy to remove once
  207. // the feature has been completely deprecated
  208. if ( attrs.disabled ) {
  209. $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
  210. scope.$parent.$watch($parse(attrs.disabled), function(value) {
  211. scope.disabled = !! value;
  212. });
  213. }
  214. scope.select = function() {
  215. if ( !scope.disabled ) {
  216. scope.active = true;
  217. }
  218. };
  219. tabsetCtrl.addTab(scope);
  220. scope.$on('$destroy', function() {
  221. tabsetCtrl.removeTab(scope);
  222. });
  223. //We need to transclude later, once the content container is ready.
  224. //when this link happens, we're inside a tab heading.
  225. scope.$transcludeFn = transclude;
  226. };
  227. }
  228. };
  229. }])
  230. .directive('tabHeadingTransclude', [function() {
  231. return {
  232. restrict: 'A',
  233. require: '^tab',
  234. link: function(scope, elm, attrs, tabCtrl) {
  235. scope.$watch('headingElement', function updateHeadingElement(heading) {
  236. if (heading) {
  237. elm.html('');
  238. elm.append(heading);
  239. }
  240. });
  241. }
  242. };
  243. }])
  244. .directive('tabContentTransclude', function() {
  245. return {
  246. restrict: 'A',
  247. require: '^tabset',
  248. link: function(scope, elm, attrs) {
  249. var tab = scope.$eval(attrs.tabContentTransclude);
  250. //Now our tab is ready to be transcluded: both the tab heading area
  251. //and the tab content area are loaded. Transclude 'em both.
  252. tab.$transcludeFn(tab.$parent, function(contents) {
  253. angular.forEach(contents, function(node) {
  254. if (isTabHeading(node)) {
  255. //Let tabHeadingTransclude know.
  256. tab.headingElement = node;
  257. } else {
  258. elm.append(node);
  259. }
  260. });
  261. });
  262. }
  263. };
  264. function isTabHeading(node) {
  265. return node.tagName && (
  266. node.hasAttribute('tab-heading') ||
  267. node.hasAttribute('data-tab-heading') ||
  268. node.tagName.toLowerCase() === 'tab-heading' ||
  269. node.tagName.toLowerCase() === 'data-tab-heading'
  270. );
  271. }
  272. })
  273. ;