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

Commit

Permalink
feat(tabs): Change directive name, add features
Browse files Browse the repository at this point in the history
* 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
ajoslin committed May 9, 2013
1 parent 39d7661 commit c532659
Show file tree
Hide file tree
Showing 10 changed files with 525 additions and 275 deletions.
12 changes: 8 additions & 4 deletions misc/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,14 @@ <h1><%= module.displayName %><small>
<div class="pull-right">
<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>
</div>
<tabs>
<pane heading="Markup" plunker-content="markup"><pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre></pane>
<pane heading="JavaScript" plunker-content="javascript"><pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre></pane>
</tabs>
<tabset>
<tab heading="Markup" plunker-content="markup">
<pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre>
</tab>
<tab heading="JavaScript" plunker-content="javascript">
<pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
</tab>
</tabset>
</div>
</div>
</section>
Expand Down
31 changes: 22 additions & 9 deletions src/tabs/docs/demo.html
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>
10 changes: 8 additions & 2 deletions src/tabs/docs/demo.js
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!");
});
};
};
6 changes: 5 additions & 1 deletion src/tabs/docs/readme.md
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.
191 changes: 142 additions & 49 deletions src/tabs/tabs.js
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
}
};
}]);
}])

;

Loading

0 comments on commit c532659

Please sign in to comment.