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

Commit

Permalink
fix(tabs): initial tab selection
Browse files Browse the repository at this point in the history
Closes #834, Fixes #747

- Avoid re-initializing `tab.active`
- `setActive` only when all `tab.active` are set up (on the next
  digest cycle)

Before I go to explain, there are up to two expressions that indicate
whether a tab is active:

  1. `active` variable in the isolate scope of the tab directive
  2. The expression from the active attribute (`attrs.active`) if it
     is set, I'll call this `getActive`, as that's the variable that
     refers to it.

During initial linking (adding of tabs), the `active` variable in the
tab's isolate scope tracks the active tab. When the first tab is
added, it's `active` is set to true since there's no way to know if
subsequent tabs are active since they haven't been added yet. As such,
at this point, it is not meaningful to set assign the `getActive`
with the value of `active`. At least not until all the tabs have been
added. Hence, a good time would be to wait until the next $digest
cycle.

A watcher is called asynchronously after initialization even if there
is no change on the expression's value.

As such, we can leave that to the watcher for the `active` expression
to initialize getActive by calling setActive during its initlization
cycle.

However, there is a chance (if the $digest cycles and planets
align...) that the `active` variable gets initialized a second time
using the `getActive` (in the watcher for `getActive`). Since we're
already setting `active` to `getActive`, and the `active` variable
should now be carrying the *truth*. Avoid this re-initialization.
  • Loading branch information
chrisirhc authored and ajoslin committed Sep 23, 2013
1 parent 509357e commit a08173e
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 5 deletions.
14 changes: 9 additions & 5 deletions src/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,22 @@ angular.module('ui.bootstrap.tabs', [])
if (attrs.active) {
getActive = $parse(attrs.active);
setActive = getActive.assign;
scope.$parent.$watch(getActive, function updateActive(value) {
scope.active = !!value;
scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
// Avoid re-initializing scope.active as it is already initialized
// below. (watcher is called async during init with value ===
// oldVal)
if (value !== oldVal) {
scope.active = !!value;
}
});
scope.active = getActive(scope.$parent);
} else {
setActive = getActive = angular.noop;
}

scope.$watch('active', function(active) {
// Note this watcher also initializes and assigns scope.active to the
// attrs.active expression.
setActive(scope.$parent, active);
if (active) {
tabsetCtrl.select(scope);
Expand All @@ -231,9 +238,6 @@ angular.module('ui.bootstrap.tabs', [])
scope.$on('$destroy', function() {
tabsetCtrl.removeTab(scope);
});
if (scope.active) {
setActive(scope.$parent, true);
}


//We need to transclude later, once the content container is ready.
Expand Down
51 changes: 51 additions & 0 deletions src/tabs/test/tabsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,57 @@ describe('tabs', function() {

});

describe('basics with initial active tab', function() {

beforeEach(inject(function($compile, $rootScope) {
scope = $rootScope.$new();

function makeTab(active) {
return {
active: !!active,
select: jasmine.createSpy()
};
}
scope.tabs = [
makeTab(), makeTab(), makeTab(true), makeTab()
];
elm = $compile([
'<tabset>',
' <tab active="tabs[0].active" select="tabs[0].select()">',
' </tab>',
' <tab active="tabs[1].active" select="tabs[1].select()">',
' </tab>',
' <tab active="tabs[2].active" select="tabs[2].select()">',
' </tab>',
' <tab active="tabs[3].active" select="tabs[3].select()">',
' </tab>',
'</tabset>'
].join('\n'))(scope);
scope.$apply();
}));

function expectTabActive(activeTab) {
var _titles = titles();
angular.forEach(scope.tabs, function(tab, i) {
if (activeTab === tab) {
expect(tab.active).toBe(true);
//It should only call select ONCE for each select
expect(tab.select).toHaveBeenCalled();
expect(_titles.eq(i)).toHaveClass('active');
expect(contents().eq(i)).toHaveClass('active');
} else {
expect(tab.active).toBe(false);
expect(_titles.eq(i)).not.toHaveClass('active');
}
});
}

it('should make tab titles and set active tab active', function() {
expect(titles().length).toBe(scope.tabs.length);
expectTabActive(scope.tabs[2]);
});
});

describe('ng-repeat', function() {

beforeEach(inject(function($compile, $rootScope) {
Expand Down

0 comments on commit a08173e

Please sign in to comment.