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.
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
- Loading branch information
Showing
10 changed files
with
520 additions
and
280 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,10 @@ | ||
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() { | ||
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,174 @@ | ||
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; | ||
|
||
.controller('TabsetController', ['$scope', '$element', | ||
function TabsetCtrl($scope, $element) { | ||
var ctrl = this, | ||
tabs = ctrl.tabs = []; | ||
|
||
ctrl.select = function(tab) { | ||
angular.forEach(tabs, ctrl.deselect); | ||
tab.active = true; | ||
ctrl.activeTab = tab; | ||
}; | ||
|
||
this.addPane = function addPane(pane) { | ||
if (!panes.length) { | ||
$scope.select(pane); | ||
ctrl.deselect = function(tab) { | ||
if (ctrl.activeTab === tab) { | ||
ctrl.activeTab = null; | ||
} | ||
panes.push(pane); | ||
tab.active = false; | ||
}; | ||
|
||
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.addTab = function addTab(tab) { | ||
if (!tabs.length) { | ||
ctrl.select(tab); | ||
} | ||
tabs.push(tab); | ||
}; | ||
|
||
ctrl.removeTab = function removeTab(tab) { | ||
var index = tabs.indexOf(tab); | ||
tabs.splice(index, 1); | ||
//Select a new tab if removed tab was selected | ||
if (tab.active && tabs.length > 0) { | ||
ctrl.select(tabs[index < tabs.length ? index : 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: '@' | ||
}, | ||
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); | ||
controller: ['$scope', function TabCtrl($scope) { | ||
this.getHeadingElement = function() { | ||
return $scope.headingElement; | ||
}; | ||
}], | ||
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.$watch(function watchActive() { | ||
return getActive(scope.$parent); | ||
}, 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); | ||
if (attrs.select) { | ||
scope.$parent.$eval(attrs.select); | ||
} | ||
} else { | ||
tabsetCtrl.deselect(scope); | ||
} | ||
}); | ||
|
||
scope.select = function() { | ||
scope.active = true; | ||
}; | ||
|
||
tabsetCtrl.addTab(scope); | ||
scope.$on('$destroy', function() { | ||
tabsetCtrl.removeTab(scope); | ||
}); | ||
//If the tabset sets this guy to active, set the parent too. We have to | ||
//do this because the parent watcher will fire and set active to false | ||
//again before the watcher on line 82 can go | ||
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); | ||
} | ||
}); | ||
if (heading) { | ||
scope.headingElement = angular.element(heading); | ||
} | ||
//Everything else is the content we need, since we removed | ||
scope.contentElement = angular.element(contents); | ||
}); | ||
}; | ||
} | ||
}; | ||
}]) | ||
|
||
.directive('tabHeadingTransclude', [function() { | ||
return { | ||
restrict: 'A', | ||
require: '^tab', | ||
link: function(scope, elm, attrs, tabCtrl) { | ||
scope.$watch(function getHeadingElement() { | ||
return tabCtrl.getHeadingElement(); | ||
}, function(heading) { | ||
if (heading) { | ||
elm.html(''); | ||
elm.append(heading); | ||
} | ||
}); | ||
} | ||
}; | ||
}]) | ||
|
||
tabsCtrl.addPane(scope); | ||
scope.$on('$destroy', function() { | ||
tabsCtrl.removePane(scope); | ||
.directive('tabContentTransclude', [function() { | ||
return { | ||
restrict: 'A', | ||
require: '^tabset', | ||
link: function(scope, elm, attrs, tabsetCtrl) { | ||
scope.$watch(function() { | ||
return tabsetCtrl.activeTab; | ||
}, function(activeTab) { | ||
elm.html(''); | ||
if (activeTab) { | ||
elm.append(activeTab.contentElement); | ||
} | ||
}); | ||
}, | ||
templateUrl: 'template/tabs/pane.html', | ||
replace: true | ||
} | ||
}; | ||
}]); | ||
}]) | ||
|
||
; | ||
|
Oops, something went wrong.