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

refactor(tabs): Change syntax, add features #287

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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