Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit 8ee76b4

Browse files
committed
feat(tabs): Change directive name, add features
* Rename 'tabs' directive to 'tabset', and 'pane' directive to 'tab'. The new syntax is more intuitive; The word pane does not obviously represent a subset of a tab group. (Closes #186) * Add 'tab-heading' directive, which is a child of a 'tab'. Allows HTML in tab headings. (Closes #124) * Add option for a 'select' attribute callback when a tab is selected. (Closes #141) * Tabs transclude to title elements instead of content elements. Now the ordering of tab titles is always correct. (Closes #153) * Only the active tab's content is ever present in the DOM. Provides an increase in performance. BREAKING CHANGE: The 'tabs' directive has been renamed to 'tabset', and the 'pane' directive has been renamed to 'tab'. To migrate your code, follow the example below. Before: <tabs> <pane heading="one"> First Content </pane> <pane ng-repeat="apple in basket" heading="{{apple.heading}}"> {{apple.content}} </pane> </tabs> After: <tabset> <tab heading="one"> First Content </tab> <tab ng-repeat="apple in basket" heading="{{apple.heading}}"> {{apple.content}} </tab> </tabset>
1 parent f698717 commit 8ee76b4

File tree

11 files changed

+571
-280
lines changed

11 files changed

+571
-280
lines changed

Diff for: misc/demo-assets/app.js

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ function MainCtrl($scope, $http, orderByFilter) {
1919
}
2020
};
2121

22+
//Tabs lazily insert code into the dom, so we have to color the javascript
23+
//code lazily too.
24+
$scope.onCodeTabSelected = function() {
25+
Rainbow.color();
26+
};
27+
2228
$scope.downloadBuild = function() {
2329
var downloadUrl = url + "/api/bootstrap/download?";
2430
angular.forEach($scope.selectedModules, function(module) {

Diff for: misc/demo-template.html

+8-4
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,14 @@ <h1><%= module.displayName %><small>
182182
<div class="pull-right">
183183
<button class="btn btn-info" id="plunk-btn" ng-click="edit('<%= ngversion%>', '<%= bsversion %>', '<%= version%>', '<%= module.name %>')"><i class="icon-edit icon-white"></i> Edit in plunker</button>
184184
</div>
185-
<tabs>
186-
<pane heading="Markup" plunker-content="markup"><pre ng-non-bindable><code data-language="html"><%- module.html %></code></pre></pane>
187-
<pane heading="JavaScript" plunker-content="javascript"><pre ng-non-bindable><code data-language="javascript"><%- module.js %></code></pre></pane>
188-
</tabs>
185+
<tabset>
186+
<tab heading="Markup" plunker-content="markup">
187+
<pre ng-non-bindable><code data-language="html"><%- module.html %></code></pre>
188+
</tab>
189+
<tab heading="JavaScript" plunker-content="javascript" select="onCodeTabSelected()">
190+
<pre ng-non-bindable><code data-language="javascript"><%- module.js %></code></pre>
191+
</tab>
192+
</tabset>
189193
</div>
190194
</div>
191195
</section>

Diff for: src/tabs/docs/demo.html

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
<div ng-controller="TabsDemoCtrl">
2-
<tabs>
3-
<pane heading="Static title">Static content</pane>
4-
<pane ng-repeat="pane in panes" heading="{{pane.title}}" active="pane.active">{{pane.content}}</pane>
5-
</tabs>
6-
<div class="row-fluid">
7-
<button class="btn" ng-click="panes[0].active = true">Select second tab</button>
8-
<button class="btn" ng-click="panes[1].active = true">Select third tab</button>
9-
</div>
10-
</div>
2+
Select a tab by setting active binding to true:
3+
<br />
4+
<button class="btn" ng-click="tabs[0].active = true">
5+
Select second tab
6+
</button>
7+
<button class="btn" ng-click="tabs[1].active = true">
8+
Select third tab
9+
</button>
10+
<br /><br />
11+
<tabset>
12+
<tab heading="Static title">Static content</tab>
13+
<tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="tab.active">
14+
{{tab.content}}
15+
</tab>
16+
<tab select="alertMe()">
17+
<tab-heading>
18+
<i class="icon-bell"></i> Select me for alert!
19+
</tab-heading>
20+
I've got an HTML heading, and a select callback. Pretty cool!
21+
</tab>
22+
</tabset>
23+
</div>

Diff for: src/tabs/docs/demo.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
var TabsDemoCtrl = function ($scope) {
2-
$scope.panes = [
2+
$scope.tabs = [
33
{ title:"Dynamic Title 1", content:"Dynamic content 1" },
44
{ title:"Dynamic Title 2", content:"Dynamic content 2" }
55
];
6-
};
6+
7+
$scope.alertMe = function() {
8+
alert("You've selected the alert tab!");
9+
};
10+
};

Diff for: src/tabs/docs/readme.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
AngularJS version of the tabs directive.
1+
AngularJS version of the tabs directive.
2+
3+
Allows a `select` callback attribute, and `active` binding attribute.
4+
5+
Allows either `heading` text-heading as an attribute, or a `<tab-heading>` element inside as the heading.

Diff for: src/tabs/tabs.js

+163-51
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,187 @@
11
angular.module('ui.bootstrap.tabs', [])
2-
.controller('TabsController', ['$scope', '$element', function($scope, $element) {
3-
var panes = $scope.panes = [];
4-
5-
this.select = $scope.select = function selectPane(pane) {
6-
angular.forEach(panes, function(pane) {
7-
pane.selected = false;
8-
});
9-
pane.selected = true;
2+
3+
.directive('tabs', function() {
4+
return function() {
5+
throw new Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md");
106
};
7+
})
118

12-
this.addPane = function addPane(pane) {
13-
if (!panes.length) {
14-
$scope.select(pane);
9+
.controller('TabsetController', ['$scope', '$element',
10+
function TabsetCtrl($scope, $element) {
11+
var ctrl = this,
12+
tabs = ctrl.tabs = [];
13+
14+
ctrl.select = function(tab) {
15+
angular.forEach(tabs, ctrl.deselect);
16+
tab.active = true;
17+
ctrl.activeTab = tab;
18+
};
19+
20+
ctrl.deselect = function(tab) {
21+
if (ctrl.activeTab === tab) {
22+
ctrl.activeTab = null;
1523
}
16-
panes.push(pane);
24+
tab.active = false;
1725
};
1826

19-
this.removePane = function removePane(pane) {
20-
var index = panes.indexOf(pane);
21-
panes.splice(index, 1);
22-
//Select a new pane if removed pane was selected
23-
if (pane.selected && panes.length > 0) {
24-
$scope.select(panes[index < panes.length ? index : index-1]);
27+
28+
ctrl.addTab = function addTab(tab) {
29+
tabs.push(tab);
30+
if (tabs.length == 1) {
31+
ctrl.select(tab);
2532
}
2633
};
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
38+
if (tab.active && tabs.length > 1) {
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+
};
2745
}])
28-
.directive('tabs', function() {
46+
47+
.directive('tabset', function() {
2948
return {
3049
restrict: 'EA',
3150
transclude: true,
32-
scope: {},
33-
controller: 'TabsController',
34-
templateUrl: 'template/tabs/tabs.html',
35-
replace: true
51+
controller: 'TabsetController',
52+
templateUrl: 'template/tabs/tabset.html'
3653
};
3754
})
38-
.directive('pane', ['$parse', function($parse) {
55+
56+
.directive('tab', ['$parse', '$http', '$templateCache', '$compile',
57+
function($parse, $http, $templateCache, $compile) {
3958
return {
40-
require: '^tabs',
59+
require: '^tabset',
4160
restrict: 'EA',
61+
replace: true,
62+
templateUrl: 'template/tabs/tab.html',
4263
transclude: true,
43-
scope:{
44-
heading:'@'
64+
scope: {
65+
heading: '@',
66+
onSelect: '&select' //This callback is called in contentHeadingTransclude
67+
//once it inserts the tab's content into the dom
4568
},
46-
link: function(scope, element, attrs, tabsCtrl) {
47-
var getSelected, setSelected;
48-
scope.selected = false;
49-
if (attrs.active) {
50-
getSelected = $parse(attrs.active);
51-
setSelected = getSelected.assign;
52-
scope.$watch(
53-
function watchSelected() {return getSelected(scope.$parent);},
54-
function updateSelected(value) {scope.selected = value;}
55-
);
56-
scope.selected = getSelected ? getSelected(scope.$parent) : false;
57-
}
58-
scope.$watch('selected', function(selected) {
59-
if(selected) {
60-
tabsCtrl.select(scope);
69+
controller: ['$scope', function TabCtrl($scope) {
70+
this.getHeadingElement = function() {
71+
return $scope.headingElement;
72+
};
73+
}],
74+
compile: function(elm, attrs, transclude) {
75+
return function postLink(scope, elm, attrs, tabsetCtrl) {
76+
var getActive, setActive;
77+
scope.active = false; // default value
78+
if (attrs.active) {
79+
getActive = $parse(attrs.active);
80+
setActive = getActive.assign;
81+
scope.$parent.$watch(getActive, function updateActive(value) {
82+
scope.active = !!value;
83+
});
84+
} else {
85+
setActive = getActive = angular.noop;
6186
}
62-
if(setSelected) {
63-
setSelected(scope.$parent, selected);
87+
88+
scope.$watch('active', function(active) {
89+
setActive(scope.$parent, active);
90+
if (active) {
91+
tabsetCtrl.select(scope);
92+
} else {
93+
tabsetCtrl.deselect(scope);
94+
}
95+
});
96+
97+
scope.select = function() {
98+
scope.active = true;
99+
};
100+
101+
tabsetCtrl.addTab(scope);
102+
scope.$on('$destroy', function() {
103+
tabsetCtrl.removeTab(scope);
104+
});
105+
//If the tabset sets this tab to active, set the parent scope's active
106+
//binding too. We do this so the watch for the parent's initial active
107+
//value won't overwrite what is initially set by the tabset
108+
if (scope.active) {
109+
setActive(scope.$parent, true);
110+
}
111+
112+
//Transclude the collection of sibling elements. Use forEach to find
113+
//the heading if it exists. We don't use a directive for tab-heading
114+
//because it is problematic. Discussion @ http://git.io/MSNPwQ
115+
transclude(scope.$parent, function(clone) {
116+
//Look at every element in the clone collection. If it's tab-heading,
117+
//mark it as that. If it's not tab-heading, mark it as tab contents
118+
var contents = [], heading;
119+
angular.forEach(clone, function(el) {
120+
//See if it's a tab-heading attr or element directive
121+
//First make sure it's a normal element, one that has a tagName
122+
if (el.tagName &&
123+
(el.hasAttribute("tab-heading") ||
124+
el.hasAttribute("data-tab-heading") ||
125+
el.tagName.toLowerCase() == "tab-heading" ||
126+
el.tagName.toLowerCase() == "data-tab-heading"
127+
)) {
128+
heading = el;
129+
} else {
130+
contents.push(el);
131+
}
132+
});
133+
//Share what we found on the scope, so our tabHeadingTransclude and
134+
//tabContentTransclude directives can find out what the heading and
135+
//contents are.
136+
if (heading) {
137+
scope.headingElement = angular.element(heading);
138+
}
139+
scope.contentElement = angular.element(contents);
140+
});
141+
};
142+
}
143+
};
144+
}])
145+
146+
.directive('tabHeadingTransclude', [function() {
147+
return {
148+
restrict: 'A',
149+
require: '^tab',
150+
link: function(scope, elm, attrs, tabCtrl) {
151+
scope.$watch(function getHeadingElement() {
152+
return tabCtrl.getHeadingElement();
153+
}, function updateHeadingElement(heading) {
154+
if (heading) {
155+
elm.html('');
156+
elm.append(heading);
64157
}
65158
});
159+
}
160+
};
161+
}])
66162

67-
tabsCtrl.addPane(scope);
68-
scope.$on('$destroy', function() {
69-
tabsCtrl.removePane(scope);
163+
.directive('tabContentTransclude', [function() {
164+
return {
165+
restrict: 'A',
166+
require: '^tabset',
167+
link: function(scope, elm, attrs, tabsetCtrl) {
168+
scope.$watch(function getActiveTab() {
169+
return tabsetCtrl.activeTab;
170+
}, function updateActiveTab(activeTab, previousActiveTab) {
171+
if (previousActiveTab) {
172+
previousActiveTab.contentElement.remove();
173+
}
174+
if (activeTab) {
175+
elm.append(activeTab.contentElement);
176+
//We call onSelect here so we can be sure it is called after the
177+
//tab's element is inserted into the DOM, and all values will be
178+
//up-to-date
179+
activeTab.onSelect();
180+
}
70181
});
71-
},
72-
templateUrl: 'template/tabs/pane.html',
73-
replace: true
182+
}
74183
};
75-
}]);
184+
}])
185+
186+
;
187+

0 commit comments

Comments
 (0)