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

Commit 224bc2f

Browse files
committed
fix(tabs): fix tab content compiling wrong (Closes #599, #631, #574)
* Before, tab content was being transcluded before the tab content area was ready. This forced us to disconnect the tab contents from the DOM temporarily, then reocnnect them later. This caused a lot of problems. * Now, neither the tab content or header are transcluded until both the heading and content areas are loaded. This is simpler and fixes many weird compilation bugs.
1 parent f5b37f6 commit 224bc2f

File tree

2 files changed

+70
-47
lines changed

2 files changed

+70
-47
lines changed

src/tabs/tabs.js

+31-47
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,16 @@ angular.module('ui.bootstrap.tabs', [])
1515
};
1616
})
1717

18-
.controller('TabsetController', ['$scope', '$element',
18+
.controller('TabsetController', ['$scope', '$element',
1919
function TabsetCtrl($scope, $element) {
2020

21-
//Expose the outer scope for tab content compiling, so it can compile
22-
//on outer scope like it should
23-
this.$outerScope = $scope.$parent;
24-
2521
var ctrl = this,
2622
tabs = ctrl.tabs = $scope.tabs = [];
2723

2824
ctrl.select = function(tab) {
2925
angular.forEach(tabs, function(tab) {
3026
tab.active = false;
31-
});
27+
});
3228
tab.active = true;
3329
};
3430

@@ -39,7 +35,7 @@ function TabsetCtrl($scope, $element) {
3935
}
4036
};
4137

42-
ctrl.removeTab = function removeTab(tab) {
38+
ctrl.removeTab = function removeTab(tab) {
4339
var index = tabs.indexOf(tab);
4440
//Select a new tab if the tab to be removed is selected
4541
if (tab.active && tabs.length > 1) {
@@ -101,7 +97,7 @@ function TabsetCtrl($scope, $element) {
10197
* @param {boolean=} active A binding, telling whether or not this tab is selected.
10298
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
10399
*
104-
* @description
100+
* @description
105101
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
106102
*
107103
* @example
@@ -235,37 +231,11 @@ function($parse, $http, $templateCache, $compile) {
235231
//value won't overwrite what is initially set by the tabset
236232
if (scope.active) {
237233
setActive(scope.$parent, true);
238-
}
234+
}
239235

240-
//Transclude the collection of sibling elements. Use forEach to find
241-
//the heading if it exists. We don't use a directive for tab-heading
242-
//because it is problematic. Discussion @ http://git.io/MSNPwQ
243-
transclude(scope.$parent, function(clone) {
244-
//Look at every element in the clone collection. If it's tab-heading,
245-
//mark it as that. If it's not tab-heading, mark it as tab contents
246-
var contents = [], heading;
247-
angular.forEach(clone, function(el) {
248-
//See if it's a tab-heading attr or element directive
249-
//First make sure it's a normal element, one that has a tagName
250-
if (el.tagName &&
251-
(el.hasAttribute("tab-heading") ||
252-
el.hasAttribute("data-tab-heading") ||
253-
el.tagName.toLowerCase() == "tab-heading" ||
254-
el.tagName.toLowerCase() == "data-tab-heading"
255-
)) {
256-
heading = el;
257-
} else {
258-
contents.push(el);
259-
}
260-
});
261-
//Share what we found on the scope, so our tabHeadingTransclude and
262-
//tabContentTransclude directives can find out what the heading and
263-
//contents are.
264-
if (heading) {
265-
scope.headingElement = angular.element(heading);
266-
}
267-
scope.contentElement = angular.element(contents);
268-
});
236+
//We need to transclude later, once the content container is ready.
237+
//when this link happens, we're inside a tab heading.
238+
scope.$transcludeFn = transclude;
269239
};
270240
}
271241
};
@@ -274,7 +244,7 @@ function($parse, $http, $templateCache, $compile) {
274244
.directive('tabHeadingTransclude', [function() {
275245
return {
276246
restrict: 'A',
277-
require: '^tab',
247+
require: '^tab',
278248
link: function(scope, elm, attrs, tabCtrl) {
279249
scope.$watch('headingElement', function updateHeadingElement(heading) {
280250
if (heading) {
@@ -290,17 +260,31 @@ function($parse, $http, $templateCache, $compile) {
290260
return {
291261
restrict: 'A',
292262
require: '^tabset',
293-
link: function(scope, elm, attrs, tabsetCtrl) {
294-
var outerScope = tabsetCtrl.$outerScope;
295-
scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
296-
elm.html('');
297-
if (tab) {
298-
elm.append(tab.contentElement);
299-
$compile(tab.contentElement)(outerScope);
300-
}
263+
link: function(scope, elm, attrs) {
264+
var tab = scope.$eval(attrs.tabContentTransclude);
265+
266+
//Now our tab is ready to be transcluded: both the tab heading area
267+
//and the tab content area are loaded. Transclude 'em both.
268+
tab.$transcludeFn(tab.$parent, function(contents) {
269+
angular.forEach(contents, function(node) {
270+
if (isTabHeading(node)) {
271+
//Let tabHeadingTransclude know.
272+
tab.headingElement = node;
273+
} else {
274+
elm.append(node);
275+
}
276+
});
301277
});
302278
}
303279
};
280+
function isTabHeading(node) {
281+
return node.tagName && (
282+
node.hasAttribute('tab-heading') ||
283+
node.hasAttribute('data-tab-heading') ||
284+
node.tagName.toLowerCase() === 'tab-heading' ||
285+
node.tagName.toLowerCase() === 'data-tab-heading'
286+
);
287+
}
304288
}])
305289

306290
;

src/tabs/test/tabsSpec.js

+39
Original file line numberDiff line numberDiff line change
@@ -495,4 +495,43 @@ describe('tabs', function() {
495495
expect(tabChild.inheritedData('$tabsetController')).toBeTruthy();
496496
});
497497
});
498+
499+
//https://github.com/angular-ui/bootstrap/issues/631
500+
describe('ng-options in content', function() {
501+
var elm;
502+
it('should render correct amount of options', inject(function($compile, $rootScope) {
503+
var scope = $rootScope.$new();
504+
elm = $compile('<tabset><tab><select ng-model="foo" ng-options="i for i in [1,2,3]"></tab>')(scope);
505+
scope.$apply();
506+
507+
var select = elm.find('select');
508+
scope.$apply();
509+
expect(select.children().length).toBe(4);
510+
}));
511+
});
512+
513+
//https://github.com/angular-ui/bootstrap/issues/599
514+
describe('ng-repeat in content', function() {
515+
var elm;
516+
it('should render ng-repeat', inject(function($compile, $rootScope) {
517+
var scope = $rootScope.$new();
518+
scope.tabs = [
519+
{title:'a', array:[1,2,3]},
520+
{title:'b', array:[2,3,4]},
521+
{title:'c', array:[3,4,5]}
522+
];
523+
elm = $compile('<div><tabset>' +
524+
'<tab ng-repeat="tab in tabs" heading="{{tab.title}}">' +
525+
'<tab-heading>{{$index}}</tab-heading>' +
526+
'<span ng-repeat="a in tab.array">{{a}},</span>' +
527+
'</tab>' +
528+
'</tabset></div>')(scope);
529+
scope.$apply();
530+
531+
var contents = elm.find('.tab-pane');
532+
expect(contents.eq(0).text().trim()).toEqual('1,2,3,');
533+
expect(contents.eq(1).text().trim()).toEqual('2,3,4,');
534+
expect(contents.eq(2).text().trim()).toEqual('3,4,5,');
535+
}));
536+
});
498537
});

0 commit comments

Comments
 (0)