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

fix(tabs): Make nested tabs work #1362

Closed
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
34 changes: 4 additions & 30 deletions src/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ angular.module('ui.bootstrap.tabs', [])
* Tabset is the outer container for the tabs directive
*
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
* @param {string=} direction What direction the tabs should be rendered. Available:
* 'right', 'left', 'below'.
*
* @example
<example module="ui.bootstrap">
Expand All @@ -77,19 +75,12 @@ angular.module('ui.bootstrap.tabs', [])
restrict: 'EA',
transclude: true,
replace: true,
require: '^tabset',
scope: {},
controller: 'TabsetController',
templateUrl: 'template/tabs/tabset.html',
compile: function(elm, attrs, transclude) {
return function(scope, element, attrs, tabsetCtrl) {
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
scope.direction = angular.isDefined(attrs.direction) ? scope.$parent.$eval(attrs.direction) : 'top';
scope.tabsAbove = (scope.direction != 'below');
tabsetCtrl.$scope = scope;
tabsetCtrl.$transcludeFn = transclude;
};
link: function(scope, element, attrs) {
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
}
};
})
Expand Down Expand Up @@ -294,21 +285,4 @@ angular.module('ui.bootstrap.tabs', [])
}
})

.directive('tabsetTitles', function() {
return {
restrict: 'A',
require: '^tabset',
templateUrl: 'template/tabs/tabset-titles.html',
replace: true,
link: function(scope, elm, attrs, tabsetCtrl) {
if (!scope.$eval(attrs.tabsetTitles)) {
elm.remove();
} else {
//now that tabs location has been decided, transclude the tab titles in
tabsetCtrl.$transcludeFn(tabsetCtrl.$scope.$parent, function(node) {
elm.append(node);
});
}
}
};
});
;
161 changes: 110 additions & 51 deletions src/tabs/test/tabs.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
describe('tabs', function() {
beforeEach(module('ui.bootstrap.tabs', 'template/tabs/tabset.html', 'template/tabs/tab.html', 'template/tabs/tabset-titles.html'));
beforeEach(module('ui.bootstrap.tabs', 'template/tabs/tabset.html', 'template/tabs/tab.html'));

var elm, scope;
function titles() {
Expand Down Expand Up @@ -544,56 +544,6 @@ describe('tabs', function() {
});
});

describe('direction', function() {
it('should not have `tab-left`, `tab-right` nor `tabs-below` classes if the direction is undefined', inject(function($compile, $rootScope) {
scope = $rootScope.$new();
scope.direction = undefined;

elm = $compile('<tabset direction="direction"></tabset>')(scope);
scope.$apply();
expect(elm).not.toHaveClass('tabs-left');
expect(elm).not.toHaveClass('tabs-right');
expect(elm).not.toHaveClass('tabs-below');
expect(elm.find('.nav + .tab-content').length).toBe(1);
}));

it('should only have the `tab-left` direction class if the direction is "left"', inject(function($compile, $rootScope) {
scope = $rootScope.$new();
scope.direction = 'left';

elm = $compile('<tabset direction="direction"></tabset>')(scope);
scope.$apply();
expect(elm).toHaveClass('tabs-left');
expect(elm).not.toHaveClass('tabs-right');
expect(elm).not.toHaveClass('tabs-below');
expect(elm.find('.nav + .tab-content').length).toBe(1);
}));

it('should only have the `tab-right direction class if the direction is "right"', inject(function($compile, $rootScope) {
scope = $rootScope.$new();
scope.direction = 'right';

elm = $compile('<tabset direction="direction"></tabset>')(scope);
scope.$apply();
expect(elm).not.toHaveClass('tabs-left');
expect(elm).toHaveClass('tabs-right');
expect(elm).not.toHaveClass('tabs-below');
expect(elm.find('.nav + .tab-content').length).toBe(1);
}));

it('should only have the `tab-below direction class if the direction is "below"', inject(function($compile, $rootScope) {
scope = $rootScope.$new();
scope.direction = 'below';

elm = $compile('<tabset direction="direction"></tabset>')(scope);
scope.$apply();
expect(elm).not.toHaveClass('tabs-left');
expect(elm).not.toHaveClass('tabs-right');
expect(elm).toHaveClass('tabs-below');
expect(elm.find('.tab-content + .nav').length).toBe(1);
}));
});

//https://github.com/angular-ui/bootstrap/issues/524
describe('child compilation', function() {

Expand Down Expand Up @@ -647,4 +597,113 @@ describe('tabs', function() {
expect(contents.eq(2).text().trim()).toEqual('3,4,5,');
}));
});

//https://github.com/angular-ui/bootstrap/issues/783
describe('nested tabs', function() {
var elm;
it('should render without errors', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
elm = $compile([
'<div>',
' <tabset>',
' <tab heading="Tab 1">',
' <tabset>',
' <tab heading="Tab 1A">',
' </tab>',
' </tabset>',
' </tab>',
' <tab heading="Tab 2">',
' <tabset>',
' <tab heading="Tab 2A">',
' </tab>',
' </tabset>',
' </tab>',
' </tabset>',
'</div>'
].join('\n'))(scope);
scope.$apply();

// 1 outside tabset, 2 nested tabsets
expect(elm.find('.tabbable').length).toEqual(3);
}));

it('should render with the correct scopes', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
scope.tab1Text = 'abc';
scope.tab1aText = '123';
scope.tab1aHead = '123';
scope.tab2aaText = '456';
elm = $compile([
'<div>',
' <tabset>',
' <tab heading="Tab 1">',
' <tabset>',
' <tab heading="{{ tab1aHead }}">',
' {{ tab1aText }}',
' </tab>',
' </tabset>',
' <span class="tab-1">{{ tab1Text }}</span>',
' </tab>',
' <tab heading="Tab 2">',
' <tabset>',
' <tab heading="Tab 2A">',
' <tabset>',
' <tab heading="Tab 2AA">',
' <span class="tab-2aa">{{ tab2aaText }}</span>',
' </tab>',
' </tabset>',
' </tab>',
' </tabset>',
' </tab>',
' </tabset>',
'</div>'
].join('\n'))(scope);
scope.$apply();

var outsideTabset = elm.find('.tabbable').eq(0);
var nestedTabset = outsideTabset.find('.tabbable');

expect(elm.find('.tabbable').length).toEqual(4);
expect(outsideTabset.find('.tab-pane').eq(0).find('.tab-1').text().trim()).toEqual(scope.tab1Text);
expect(nestedTabset.find('.tab-pane').eq(0).text().trim()).toEqual(scope.tab1aText);
expect(nestedTabset.find('ul.nav-tabs li').eq(0).text().trim()).toEqual(scope.tab1aHead);
expect(nestedTabset.eq(2).find('.tab-pane').eq(0).find('.tab-2aa').text().trim()).toEqual(scope.tab2aaText);
}));

it('ng-repeat works with nested tabs', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
scope.tabs = [
{
tabs: [
{
content: 'tab1a'
},
{
content: 'tab2a'
}
],
content: 'tab1'
}
];
elm = $compile([
'<div>',
' <tabset>',
' <tab ng-repeat="tab in tabs">',
' <tabset>',
' <tab ng-repeat="innerTab in tab.tabs">',
' <span class="inner-tab-content">{{ innerTab.content }}</span>',
' </tab>',
' </tabset>',
' <span class="outer-tab-content">{{ tab.content }}</span>',
' </tab>',
' </tabset>',
'</div>'
].join('\n'))(scope);
scope.$apply();

expect(elm.find('.inner-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].tabs[0].content);
expect(elm.find('.inner-tab-content').eq(1).text().trim()).toEqual(scope.tabs[0].tabs[1].content);
expect(elm.find('.outer-tab-content').eq(0).text().trim()).toEqual(scope.tabs[0].content);
}));
});
});
2 changes: 0 additions & 2 deletions template/tabs/tabset-titles.html

This file was deleted.

6 changes: 3 additions & 3 deletions template/tabs/tabset.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

<div class="tabbable" ng-class="{'tabs-right': direction == 'right', 'tabs-left': direction == 'left', 'tabs-below': direction == 'below'}">
<div tabset-titles="tabsAbove"></div>
<div class="tabbable">
<ul class="nav {{type && 'nav-' + type}}" ng-class="{'nav-stacked': vertical}" ng-transclude>
</ul>
<div class="tab-content">
<div class="tab-pane"
ng-repeat="tab in tabs"
ng-class="{active: tab.active}"
tab-content-transclude="tab">
</div>
</div>
<div tabset-titles="!tabsAbove"></div>
</div>