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

Commit d53855f

Browse files
committed
refactor(tabs): Change syntax, add features
* Tabs transclude to title elements instead of content elements, so the ordering is always correct (#153) * Rename `<tabs>` to `<tabset>`, `<pane>` to `<tab>` (#186) * Add `<tab-heading>` directive, which is a child of a `<tab>`. Allows HTML in tab headings (#124) * Add `select` attribute callback when tab is selected (#141) * Only the active tab's content is actually ever in the DOM
1 parent 194c848 commit d53855f

File tree

10 files changed

+365
-273
lines changed

10 files changed

+365
-273
lines changed

Diff for: misc/demo-template.html

+4-4
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,10 @@ <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"><pre ng-non-bindable><code data-language="html"><%- module.html %></code></pre></tab>
187+
<tab heading="JavaScript" plunker-content="javascript"><pre ng-non-bindable><code data-language="javascript"><%- module.js %></code></pre></tab>
188+
</tabset>
189189
</div>
190190
</div>
191191
</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

+149-51
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,173 @@
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+
.controller('TabsetController', ['$scope', '$element',
4+
function TabsetCtrl($scope, $element) {
5+
var ctrl = this,
6+
tabs = ctrl.tabs = [];
7+
8+
ctrl.select = function(tab) {
9+
angular.forEach(tabs, ctrl.deselect);
10+
tab.active = true;
11+
ctrl.activeTab = tab;
12+
};
13+
14+
ctrl.deselect = function(tab) {
15+
if (ctrl.activeTab === tab) {
16+
ctrl.activeTab = null;
17+
}
18+
tab.active = false;
1019
};
1120

12-
this.addPane = function addPane(pane) {
13-
if (!panes.length) {
14-
$scope.select(pane);
21+
22+
ctrl.addTab = function addTab(tab) {
23+
if (!tabs.length) {
24+
ctrl.select(tab);
1525
}
16-
panes.push(pane);
26+
tabs.push(tab);
1727
};
1828

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]);
29+
ctrl.removeTab = function removeTab(tab) {
30+
var index = tabs.indexOf(tab);
31+
tabs.splice(index, 1);
32+
//Select a new tab if removed tab was selected
33+
if (tab.active && tabs.length > 0) {
34+
ctrl.select(tabs[index < tabs.length ? index : index-1]);
2535
}
2636
};
2737
}])
28-
.directive('tabs', function() {
38+
39+
.directive('tabset', function() {
2940
return {
3041
restrict: 'EA',
3142
transclude: true,
32-
scope: {},
33-
controller: 'TabsController',
34-
templateUrl: 'template/tabs/tabs.html',
35-
replace: true
43+
controller: 'TabsetController',
44+
templateUrl: 'template/tabs/tabset.html'
3645
};
3746
})
38-
.directive('pane', ['$parse', function($parse) {
47+
48+
.directive('tab', ['$parse', '$http', '$templateCache', '$compile',
49+
function($parse, $http, $templateCache, $compile) {
3950
return {
40-
require: '^tabs',
51+
require: '^tabset',
4152
restrict: 'EA',
53+
replace: true,
54+
templateUrl: 'template/tabs/tab.html',
4255
transclude: true,
43-
scope:{
44-
heading:'@'
56+
scope: {
57+
heading: '@'
4558
},
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);
59+
controller: ['$scope', function TabCtrl($scope) {
60+
this.getHeadingElement = function() {
61+
return $scope.headingElement;
62+
};
63+
}],
64+
compile: function(elm, attrs, transclude) {
65+
return function postLink(scope, elm, attrs, tabsetCtrl) {
66+
var getActive, setActive;
67+
scope.active = false; // default value
68+
if (attrs.active) {
69+
getActive = $parse(attrs.active);
70+
setActive = getActive.assign;
71+
scope.$watch(function watchActive() {
72+
return getActive(scope.$parent);
73+
}, 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+
if (attrs.select) {
85+
scope.$parent.$eval(attrs.select);
86+
}
87+
} else {
88+
tabsetCtrl.deselect(scope);
89+
}
90+
});
91+
92+
scope.select = function() {
93+
scope.active = true;
94+
};
95+
96+
tabsetCtrl.addTab(scope);
97+
scope.$on('$destroy', function() {
98+
tabsetCtrl.removeTab(scope);
99+
});
100+
//If the tabset sets this guy to active, set the parent too
101+
//We have to do this because the parent watcher will fire and
102+
//set active to false again before the watcher on line 82
103+
//can go
104+
if (scope.active) {
105+
setActive(scope.$parent, true);
106+
}
107+
108+
transclude(scope.$parent, function(clone) {
109+
//Create a wrapper around clone - originally clone is just a
110+
//collection of sibling elements with no parent.
111+
//The wrapper lets us easily find things without looping through
112+
//a collection of elements.
113+
var wrapper = angular.element('<div />');
114+
wrapper.append(clone);
115+
116+
//Find the heading if it exists
117+
//We don't use a directive for tab-heading because
118+
//it presents too many problems.
119+
//Some of them are expounded upon here
120+
//https://github.com/angular-ui/bootstrap/pull/186/
121+
var headingElm = wrapper.find(
122+
'[tab-heading],[data-tab-heading],tab-heading,data-tab-heading'
123+
);
124+
if (headingElm.length) {
125+
headingElm.remove();
126+
scope.headingElement = headingElm.contents();
127+
}
128+
129+
//Everything else is the content we need, since we removed
130+
//the heading element if it existed
131+
scope.contentElement = wrapper.contents();
132+
});
133+
};
134+
}
135+
};
136+
}])
137+
138+
.directive('tabHeadingTransclude', [function() {
139+
return {
140+
restrict: 'A',
141+
require: '^tab',
142+
link: function(scope, elm, attrs, tabCtrl) {
143+
scope.$watch(function getHeadingElement() {
144+
return tabCtrl.getHeadingElement();
145+
}, function(heading) {
146+
if (heading) {
147+
elm.html('');
148+
elm.append(heading);
64149
}
65150
});
151+
}
152+
};
153+
}])
66154

67-
tabsCtrl.addPane(scope);
68-
scope.$on('$destroy', function() {
69-
tabsCtrl.removePane(scope);
155+
.directive('tabContentTransclude', [function() {
156+
return {
157+
restrict: 'A',
158+
require: '^tabset',
159+
link: function(scope, elm, attrs, tabsetCtrl) {
160+
scope.$watch(function() {
161+
return tabsetCtrl.activeTab;
162+
}, function(activeTab) {
163+
elm.html('');
164+
if (activeTab) {
165+
elm.append(activeTab.contentElement);
166+
}
70167
});
71-
},
72-
templateUrl: 'template/tabs/pane.html',
73-
replace: true
168+
}
74169
};
75-
}]);
170+
}])
171+
172+
;
173+

0 commit comments

Comments
 (0)