|
|
@@ -0,0 +1,293 @@
|
|
|
+
|
|
|
+/**
|
|
|
+ * @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 !== 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">
|
|
|
+ <tabset>
|
|
|
+ <tab heading="Tab 1"><b>First</b> Content!</tab>
|
|
|
+ <tab heading="Tab 2"><i>Second</i> Content!</tab>
|
|
|
+ </tabset>
|
|
|
+ <hr />
|
|
|
+ <tabset vertical="true">
|
|
|
+ <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
|
|
|
+ <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
|
|
|
+ </tabset>
|
|
|
+ <tabset justified="true">
|
|
|
+ <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
|
|
|
+ <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
|
|
|
+ </tabset>
|
|
|
+ </file>
|
|
|
+</example>
|
|
|
+ */
|
|
|
+.directive('tabset', function() {
|
|
|
+ return {
|
|
|
+ restrict: 'EA',
|
|
|
+ transclude: true,
|
|
|
+ replace: true,
|
|
|
+ scope: {
|
|
|
+ type: '@'
|
|
|
+ },
|
|
|
+ controller: 'TabsetController',
|
|
|
+ templateUrl: 'app/partials/bootstrap/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 />
|
|
|
+ <tabset>
|
|
|
+ <tab heading="Tab 1">First Tab</tab>
|
|
|
+ <tab select="alertMe()">
|
|
|
+ <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
|
|
|
+ Second Tab, with alert callback and html heading!
|
|
|
+ </tab>
|
|
|
+ <tab ng-repeat="item in items"
|
|
|
+ heading="{{item.title}}"
|
|
|
+ disabled="item.disabled"
|
|
|
+ active="item.active">
|
|
|
+ {{item.content}}
|
|
|
+ </tab>
|
|
|
+ </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">
|
|
|
+ <tabset>
|
|
|
+ <tab>
|
|
|
+ <tab-heading><b>HTML</b> in my titles?!</tab-heading>
|
|
|
+ And some content, too!
|
|
|
+ </tab>
|
|
|
+ <tab>
|
|
|
+ <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
|
|
|
+ That's right.
|
|
|
+ </tab>
|
|
|
+ </tabset>
|
|
|
+ </file>
|
|
|
+</example>
|
|
|
+ */
|
|
|
+.directive('tab', ['$parse', '$log', function($parse, $log) {
|
|
|
+ return {
|
|
|
+ require: '^tabset',
|
|
|
+ restrict: 'EA',
|
|
|
+ replace: true,
|
|
|
+ templateUrl: 'app/partials/bootstrap/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.disable ) {
|
|
|
+ scope.$parent.$watch($parse(attrs.disable), function(value) {
|
|
|
+ scope.disabled = !! value;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Deprecation support of "disabled" parameter
|
|
|
+ // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
|
|
|
+ // This code is duplicated from the lines above to make it easy to remove once
|
|
|
+ // the feature has been completely deprecated
|
|
|
+ if ( attrs.disabled ) {
|
|
|
+ $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
|
|
|
+ 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'
|
|
|
+ );
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+;
|