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

Commit c532659

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) 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 39d7661 commit c532659

File tree

10 files changed

+525
-275
lines changed

10 files changed

+525
-275
lines changed

misc/demo/index.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 %>', '<%= pkg.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.docs.html %></code></pre></pane>
187-
<pane heading="JavaScript" plunker-content="javascript"><pre ng-non-bindable><code data-language="javascript"><%- module.docs.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.docs.html %></code></pre>
188+
</tab>
189+
<tab heading="JavaScript" plunker-content="javascript">
190+
<pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
191+
</tab>
192+
</tabset>
189193
</div>
190194
</div>
191195
</section>

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>

src/tabs/docs/demo.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
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+
setTimeout(function() {
9+
alert("You've selected the alert tab!");
10+
});
11+
};
12+
};

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.

src/tabs/tabs.js

+142-49
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,168 @@
11
angular.module('ui.bootstrap.tabs', [])
2-
.controller('TabsController', ['$scope', '$element', function($scope, $element) {
3-
var panes = $scope.panes = [];
42

5-
this.select = $scope.select = function selectPane(pane) {
6-
angular.forEach(panes, function(pane) {
7-
pane.selected = false;
8-
});
9-
pane.selected = true;
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 = $scope.tabs = [];
13+
14+
ctrl.select = function(tab) {
15+
angular.forEach(tabs, function(tab) {
16+
tab.active = false;
17+
});
18+
tab.active = true;
19+
};
20+
21+
ctrl.addTab = function addTab(tab) {
22+
tabs.push(tab);
23+
if (tabs.length == 1) {
24+
ctrl.select(tab);
1525
}
16-
panes.push(pane);
1726
};
1827

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]);
28+
ctrl.removeTab = function removeTab(tab) {
29+
var index = tabs.indexOf(tab);
30+
//Select a new tab if the tab to be removed is selected
31+
if (tab.active && tabs.length > 1) {
32+
//If this is the last tab, select the previous tab. else, the next tab.
33+
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
34+
ctrl.select(tabs[newActiveIndex]);
2535
}
36+
tabs.splice(index, 1);
2637
};
2738
}])
28-
.directive('tabs', function() {
39+
40+
.directive('tabset', function() {
2941
return {
3042
restrict: 'EA',
3143
transclude: true,
3244
scope: {},
33-
controller: 'TabsController',
34-
templateUrl: 'template/tabs/tabs.html',
35-
replace: true
45+
controller: 'TabsetController',
46+
templateUrl: 'template/tabs/tabset.html'
3647
};
3748
})
38-
.directive('pane', ['$parse', function($parse) {
49+
50+
.directive('tab', ['$parse', '$http', '$templateCache', '$compile',
51+
function($parse, $http, $templateCache, $compile) {
3952
return {
40-
require: '^tabs',
53+
require: '^tabset',
4154
restrict: 'EA',
55+
replace: true,
56+
templateUrl: 'template/tabs/tab.html',
4257
transclude: true,
43-
scope:{
44-
heading:'@'
58+
scope: {
59+
heading: '@',
60+
onSelect: '&select' //This callback is called in contentHeadingTransclude
61+
//once it inserts the tab's content into the dom
62+
},
63+
controller: function() {
64+
//Empty controller so other directives can require being 'under' a tab
4565
},
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);
66+
compile: function(elm, attrs, transclude) {
67+
return function postLink(scope, elm, attrs, tabsetCtrl) {
68+
var getActive, setActive;
69+
scope.active = false; // default value
70+
if (attrs.active) {
71+
getActive = $parse(attrs.active);
72+
setActive = getActive.assign;
73+
scope.$parent.$watch(getActive, function updateActive(value) {
74+
scope.active = !!value;
75+
});
76+
} else {
77+
setActive = getActive = angular.noop;
6178
}
62-
if(setSelected) {
63-
setSelected(scope.$parent, selected);
79+
80+
scope.$watch('active', function(active) {
81+
setActive(scope.$parent, active);
82+
if (active) {
83+
tabsetCtrl.select(scope);
84+
scope.onSelect();
85+
}
86+
});
87+
88+
scope.select = function() {
89+
scope.active = true;
90+
};
91+
92+
tabsetCtrl.addTab(scope);
93+
scope.$on('$destroy', function() {
94+
tabsetCtrl.removeTab(scope);
95+
});
96+
//If the tabset sets this tab to active, set the parent scope's active
97+
//binding too. We do this so the watch for the parent's initial active
98+
//value won't overwrite what is initially set by the tabset
99+
if (scope.active) {
100+
setActive(scope.$parent, true);
101+
}
102+
103+
//Transclude the collection of sibling elements. Use forEach to find
104+
//the heading if it exists. We don't use a directive for tab-heading
105+
//because it is problematic. Discussion @ http://git.io/MSNPwQ
106+
transclude(scope.$parent, function(clone) {
107+
//Look at every element in the clone collection. If it's tab-heading,
108+
//mark it as that. If it's not tab-heading, mark it as tab contents
109+
var contents = [], heading;
110+
angular.forEach(clone, function(el) {
111+
//See if it's a tab-heading attr or element directive
112+
//First make sure it's a normal element, one that has a tagName
113+
if (el.tagName &&
114+
(el.hasAttribute("tab-heading") ||
115+
el.hasAttribute("data-tab-heading") ||
116+
el.tagName.toLowerCase() == "tab-heading" ||
117+
el.tagName.toLowerCase() == "data-tab-heading"
118+
)) {
119+
heading = el;
120+
} else {
121+
contents.push(el);
122+
}
123+
});
124+
//Share what we found on the scope, so our tabHeadingTransclude and
125+
//tabContentTransclude directives can find out what the heading and
126+
//contents are.
127+
if (heading) {
128+
scope.headingElement = angular.element(heading);
129+
}
130+
scope.contentElement = angular.element(contents);
131+
});
132+
};
133+
}
134+
};
135+
}])
136+
137+
.directive('tabHeadingTransclude', [function() {
138+
return {
139+
restrict: 'A',
140+
require: '^tab',
141+
link: function(scope, elm, attrs, tabCtrl) {
142+
scope.$watch('headingElement', function updateHeadingElement(heading) {
143+
if (heading) {
144+
elm.html('');
145+
elm.append(heading);
64146
}
65147
});
148+
}
149+
};
150+
}])
66151

67-
tabsCtrl.addPane(scope);
68-
scope.$on('$destroy', function() {
69-
tabsCtrl.removePane(scope);
152+
.directive('tabContentTransclude', ['$parse', function($parse) {
153+
return {
154+
restrict: 'A',
155+
require: '^tabset',
156+
link: function(scope, elm, attrs, tabsetCtrl) {
157+
scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
158+
elm.html('');
159+
if (tab) {
160+
elm.append(tab.contentElement);
161+
}
70162
});
71-
},
72-
templateUrl: 'template/tabs/pane.html',
73-
replace: true
163+
}
74164
};
75-
}]);
165+
}])
166+
167+
;
168+

0 commit comments

Comments
 (0)