This repository has been archived by the owner on May 29, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
10 changed files
with
525 additions
and
275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,23 @@ | ||
<div ng-controller="TabsDemoCtrl"> | ||
<tabs> | ||
<pane heading="Static title">Static content</pane> | ||
<pane ng-repeat="pane in panes" heading="{{pane.title}}" active="pane.active">{{pane.content}}</pane> | ||
</tabs> | ||
<div class="row-fluid"> | ||
<button class="btn" ng-click="panes[0].active = true">Select second tab</button> | ||
<button class="btn" ng-click="panes[1].active = true">Select third tab</button> | ||
</div> | ||
</div> | ||
Select a tab by setting active binding to true: | ||
<br /> | ||
<button class="btn" ng-click="tabs[0].active = true"> | ||
Select second tab | ||
</button> | ||
<button class="btn" ng-click="tabs[1].active = true"> | ||
Select third tab | ||
</button> | ||
<br /><br /> | ||
<tabset> | ||
<tab heading="Static title">Static content</tab> | ||
<tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="tab.active"> | ||
{{tab.content}} | ||
</tab> | ||
<tab select="alertMe()"> | ||
<tab-heading> | ||
<i class="icon-bell"></i> Select me for alert! | ||
</tab-heading> | ||
I've got an HTML heading, and a select callback. Pretty cool! | ||
</tab> | ||
</tabset> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
var TabsDemoCtrl = function ($scope) { | ||
$scope.panes = [ | ||
$scope.tabs = [ | ||
{ title:"Dynamic Title 1", content:"Dynamic content 1" }, | ||
{ title:"Dynamic Title 2", content:"Dynamic content 2" } | ||
]; | ||
}; | ||
|
||
$scope.alertMe = function() { | ||
setTimeout(function() { | ||
alert("You've selected the alert tab!"); | ||
}); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
AngularJS version of the tabs directive. | ||
AngularJS version of the tabs directive. | ||
|
||
Allows a `select` callback attribute, and `active` binding attribute. | ||
|
||
Allows either `heading` text-heading as an attribute, or a `<tab-heading>` element inside as the heading. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,168 @@ | ||
angular.module('ui.bootstrap.tabs', []) | ||
.controller('TabsController', ['$scope', '$element', function($scope, $element) { | ||
var panes = $scope.panes = []; | ||
|
||
this.select = $scope.select = function selectPane(pane) { | ||
angular.forEach(panes, function(pane) { | ||
pane.selected = false; | ||
}); | ||
pane.selected = true; | ||
.directive('tabs', function() { | ||
return function() { | ||
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"); | ||
}; | ||
}) | ||
|
||
this.addPane = function addPane(pane) { | ||
if (!panes.length) { | ||
$scope.select(pane); | ||
.controller('TabsetController', ['$scope', '$element', | ||
function TabsetCtrl($scope, $element) { | ||
var ctrl = this, | ||
tabs = ctrl.tabs = $scope.tabs = []; | ||
|
||
ctrl.select = function(tab) { | ||
angular.forEach(tabs, function(tab) { | ||
tab.active = false; | ||
}); | ||
tab.active = true; | ||
}; | ||
|
||
ctrl.addTab = function addTab(tab) { | ||
tabs.push(tab); | ||
if (tabs.length == 1) { | ||
ctrl.select(tab); | ||
} | ||
panes.push(pane); | ||
}; | ||
|
||
this.removePane = function removePane(pane) { | ||
var index = panes.indexOf(pane); | ||
panes.splice(index, 1); | ||
//Select a new pane if removed pane was selected | ||
if (pane.selected && panes.length > 0) { | ||
$scope.select(panes[index < panes.length ? index : index-1]); | ||
ctrl.removeTab = function removeTab(tab) { | ||
var index = tabs.indexOf(tab); | ||
//Select a new tab if the tab to be removed is selected | ||
if (tab.active && tabs.length > 1) { | ||
//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); | ||
}; | ||
}]) | ||
.directive('tabs', function() { | ||
|
||
.directive('tabset', function() { | ||
return { | ||
restrict: 'EA', | ||
transclude: true, | ||
scope: {}, | ||
controller: 'TabsController', | ||
templateUrl: 'template/tabs/tabs.html', | ||
replace: true | ||
controller: 'TabsetController', | ||
templateUrl: 'template/tabs/tabset.html' | ||
}; | ||
}) | ||
.directive('pane', ['$parse', function($parse) { | ||
|
||
.directive('tab', ['$parse', '$http', '$templateCache', '$compile', | ||
function($parse, $http, $templateCache, $compile) { | ||
return { | ||
require: '^tabs', | ||
require: '^tabset', | ||
restrict: 'EA', | ||
replace: true, | ||
templateUrl: 'template/tabs/tab.html', | ||
transclude: true, | ||
scope:{ | ||
heading:'@' | ||
scope: { | ||
heading: '@', | ||
onSelect: '&select' //This callback is called in contentHeadingTransclude | ||
//once it inserts the tab's content into the dom | ||
}, | ||
controller: function() { | ||
//Empty controller so other directives can require being 'under' a tab | ||
}, | ||
link: function(scope, element, attrs, tabsCtrl) { | ||
var getSelected, setSelected; | ||
scope.selected = false; | ||
if (attrs.active) { | ||
getSelected = $parse(attrs.active); | ||
setSelected = getSelected.assign; | ||
scope.$watch( | ||
function watchSelected() {return getSelected(scope.$parent);}, | ||
function updateSelected(value) {scope.selected = value;} | ||
); | ||
scope.selected = getSelected ? getSelected(scope.$parent) : false; | ||
} | ||
scope.$watch('selected', function(selected) { | ||
if(selected) { | ||
tabsCtrl.select(scope); | ||
compile: function(elm, attrs, transclude) { | ||
return function postLink(scope, elm, attrs, tabsetCtrl) { | ||
var getActive, setActive; | ||
scope.active = false; // default value | ||
if (attrs.active) { | ||
getActive = $parse(attrs.active); | ||
setActive = getActive.assign; | ||
scope.$parent.$watch(getActive, function updateActive(value) { | ||
scope.active = !!value; | ||
}); | ||
} else { | ||
setActive = getActive = angular.noop; | ||
} | ||
if(setSelected) { | ||
setSelected(scope.$parent, selected); | ||
|
||
scope.$watch('active', function(active) { | ||
setActive(scope.$parent, active); | ||
if (active) { | ||
tabsetCtrl.select(scope); | ||
scope.onSelect(); | ||
} | ||
}); | ||
|
||
scope.select = function() { | ||
scope.active = true; | ||
}; | ||
|
||
tabsetCtrl.addTab(scope); | ||
scope.$on('$destroy', function() { | ||
tabsetCtrl.removeTab(scope); | ||
}); | ||
//If the tabset sets this tab to active, set the parent scope's active | ||
//binding too. We do this so the watch for the parent's initial active | ||
//value won't overwrite what is initially set by the tabset | ||
if (scope.active) { | ||
setActive(scope.$parent, true); | ||
} | ||
|
||
//Transclude the collection of sibling elements. Use forEach to find | ||
//the heading if it exists. We don't use a directive for tab-heading | ||
//because it is problematic. Discussion @ http://git.io/MSNPwQ | ||
transclude(scope.$parent, function(clone) { | ||
//Look at every element in the clone collection. If it's tab-heading, | ||
//mark it as that. If it's not tab-heading, mark it as tab contents | ||
var contents = [], heading; | ||
angular.forEach(clone, function(el) { | ||
//See if it's a tab-heading attr or element directive | ||
//First make sure it's a normal element, one that has a tagName | ||
if (el.tagName && | ||
(el.hasAttribute("tab-heading") || | ||
el.hasAttribute("data-tab-heading") || | ||
el.tagName.toLowerCase() == "tab-heading" || | ||
el.tagName.toLowerCase() == "data-tab-heading" | ||
)) { | ||
heading = el; | ||
} else { | ||
contents.push(el); | ||
} | ||
}); | ||
//Share what we found on the scope, so our tabHeadingTransclude and | ||
//tabContentTransclude directives can find out what the heading and | ||
//contents are. | ||
if (heading) { | ||
scope.headingElement = angular.element(heading); | ||
} | ||
scope.contentElement = angular.element(contents); | ||
}); | ||
}; | ||
} | ||
}; | ||
}]) | ||
|
||
.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); | ||
} | ||
}); | ||
} | ||
}; | ||
}]) | ||
|
||
tabsCtrl.addPane(scope); | ||
scope.$on('$destroy', function() { | ||
tabsCtrl.removePane(scope); | ||
.directive('tabContentTransclude', ['$parse', function($parse) { | ||
return { | ||
restrict: 'A', | ||
require: '^tabset', | ||
link: function(scope, elm, attrs, tabsetCtrl) { | ||
scope.$watch($parse(attrs.tabContentTransclude), function(tab) { | ||
elm.html(''); | ||
if (tab) { | ||
elm.append(tab.contentElement); | ||
} | ||
}); | ||
}, | ||
templateUrl: 'template/tabs/pane.html', | ||
replace: true | ||
} | ||
}; | ||
}]); | ||
}]) | ||
|
||
; | ||
|
Oops, something went wrong.