diff --git a/src/tabs/docs/demo.html b/src/tabs/docs/demo.html index 3730e4ebaa..8a848d398f 100644 --- a/src/tabs/docs/demo.html +++ b/src/tabs/docs/demo.html @@ -9,31 +9,31 @@


- - Static content - + + Static content + {{tab.content}} - - - + + + Alert! - + I've got an HTML heading, and a select callback. Pretty cool! - - + +
- - Vertical content 1 - Vertical content 2 - + + Vertical content 1 + Vertical content 2 +
- - Justified content - Short Labeled Justified content - Long Labeled Justified content - + + Justified content + Short Labeled Justified content + Long Labeled Justified content + diff --git a/src/tabs/docs/readme.md b/src/tabs/docs/readme.md index e43fe2c5c8..d00461c479 100644 --- a/src/tabs/docs/readme.md +++ b/src/tabs/docs/readme.md @@ -2,7 +2,7 @@ AngularJS version of the tabs directive. ### Settings ### -#### `` #### +#### `` #### * `vertical` _(Defaults: false)_ : @@ -16,9 +16,9 @@ AngularJS version of the tabs directive. _(Defaults: 'tabs')_ : Navigation type. Possible values are 'tabs' and 'pills'. -#### `` #### +#### `` #### - * `heading` or `` + * `heading` or `` : Heading text or HTML markup. diff --git a/src/tabs/tabs.js b/src/tabs/tabs.js index cbdcd619f3..c158474e9f 100644 --- a/src/tabs/tabs.js +++ b/src/tabs/tabs.js @@ -9,7 +9,7 @@ angular.module('ui.bootstrap.tabs', []) -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { +.controller('UibTabsetController', ['$scope', function ($scope) { var ctrl = this, tabs = ctrl.tabs = $scope.tabs = []; @@ -73,23 +73,23 @@ angular.module('ui.bootstrap.tabs', []) * @example - - First Content! - Second Content! - + + First Content! + Second Content! +
- - First Vertical Content! - Second Vertical Content! - - - First Justified Content! - Second Justified Content! - + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! +
*/ -.directive('tabset', function() { +.directive('uibTabset', function() { return { restrict: 'EA', transclude: true, @@ -97,7 +97,7 @@ angular.module('ui.bootstrap.tabs', []) scope: { type: '@' }, - controller: 'TabsetController', + controller: 'UibTabsetController', templateUrl: 'template/tabs/tabset.html', link: function(scope, element, attrs) { scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; @@ -130,19 +130,19 @@ angular.module('ui.bootstrap.tabs', []) Enable/disable item 2, using disabled binding
- - First Tab - - Alert me! + + First Tab + + Alert me!
Second Tab, with alert callback and html heading! -
- + {{item.content}} - -
+ + @@ -173,22 +173,22 @@ angular.module('ui.bootstrap.tabs', []) * @example - - - HTML in my titles?! + + + HTML in my titles?! And some content, too! - - - Icon heading?!? + + + Icon heading?!? That's right. - - + + */ -.directive('tab', ['$parse', function($parse) { +.directive('uibTab', ['$parse', function($parse) { return { - require: '^tabset', + require: '^uibTabset', restrict: 'EA', replace: true, templateUrl: 'template/tabs/tab.html', @@ -235,11 +235,11 @@ angular.module('ui.bootstrap.tabs', []) }; }]) -.directive('tabHeadingTransclude', function() { +.directive('uibTabHeadingTransclude', function() { return { restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { + require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal + link: function(scope, elm) { scope.$watch('headingElement', function updateHeadingElement(heading) { if (heading) { elm.html(''); @@ -250,12 +250,12 @@ angular.module('ui.bootstrap.tabs', []) }; }) -.directive('tabContentTransclude', function() { +.directive('uibTabContentTransclude', function() { return { restrict: 'A', - require: '^tabset', + require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); + var tab = scope.$eval(attrs.uibTabContentTransclude); //Now our tab is ready to be transcluded: both the tab heading area //and the tab content area are loaded. Transclude 'em both. @@ -274,12 +274,156 @@ angular.module('ui.bootstrap.tabs', []) function isTabHeading(node) { return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.hasAttribute('x-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' || - node.tagName.toLowerCase() === 'x-tab-heading' + node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal + node.hasAttribute('uib-tab-heading') || + node.hasAttribute('data-uib-tab-heading') || + node.hasAttribute('x-uib-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal + node.tagName.toLowerCase() === 'uib-tab-heading' || + node.tagName.toLowerCase() === 'data-uib-tab-heading' || + node.tagName.toLowerCase() === 'x-uib-tab-heading' ); } }); + +/* deprecated tabs below */ + +angular.module('ui.bootstrap.tabs') + + .value('$tabsSuppressWarning', false) + + .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'UibTabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + + if (!$tabsSuppressWarning) { + $log.warn('tabset is now deprecated. Use uib-tabset instead.'); + } + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; + }]) + + .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + link: function(scope, elm, attrs, tabsetCtrl, transclude) { + if (!$tabsSuppressWarning) { + $log.warn('tab is now deprecated. Use uib-tab instead.'); + } + + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if (attrs.disable) { + scope.$parent.$watch($parse(attrs.disable), function(value) { + scope.disabled = !!value; + }); + } + + scope.select = function() { + if (!scope.disabled) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + } + }; + }]) + + .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm) { + if (!$tabsSuppressWarning) { + $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.'); + } + + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; + }]) + + .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + if (!$tabsSuppressWarning) { + $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.'); + } + + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } + else { + elm.append(node); + } + }); + }); + } + }; + + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.hasAttribute('x-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' || + node.tagName.toLowerCase() === 'x-tab-heading' + ); + } + }]); diff --git a/src/tabs/test/tabs.spec.js b/src/tabs/test/tabs.spec.js index 320735eefe..10e07bb059 100644 --- a/src/tabs/test/tabs.spec.js +++ b/src/tabs/test/tabs.spec.js @@ -1,7 +1,10 @@ describe('tabs', function() { - beforeEach(module('ui.bootstrap.tabs', 'template/tabs/tabset.html', 'template/tabs/tab.html')); - var elm, scope; + + beforeEach(module('ui.bootstrap.tabs')); + beforeEach(module('template/tabs/tabset.html')); + beforeEach(module('template/tabs/tab.html')); + function titles() { return elm.find('ul.nav-tabs li'); } @@ -25,7 +28,6 @@ describe('tabs', function() { } } - describe('basics', function() { beforeEach(inject(function($compile, $rootScope) { scope = $rootScope.$new(); @@ -37,15 +39,15 @@ describe('tabs', function() { scope.deselectFirst = jasmine.createSpy(); scope.deselectSecond = jasmine.createSpy(); elm = $compile([ - '', - ' ', + '', + ' ', ' first content is {{first}}', - ' ', - ' ', - ' Second Tab {{second}}', + ' ', + ' ', + ' Second Tab {{second}}', ' second content is {{second}}', - ' ', - '' + ' ', + '' ].join('\n'))(scope); scope.$apply(); return elm; @@ -60,8 +62,8 @@ describe('tabs', function() { var t = titles(); expect(t.length).toBe(2); expect(t.find('a').eq(0).text()).toBe('First Tab 1'); - //It should put the tab-heading element into the 'a' title - expect(t.find('a').eq(1).children().is('tab-heading')).toBe(true); + //It should put the uib-tab-heading element into the 'a' title + expect(t.find('a').eq(1).children().is('uib-tab-heading')).toBe(true); expect(t.find('a').eq(1).children().html()).toBe('Second Tab 2'); }); @@ -112,16 +114,16 @@ describe('tabs', function() { makeTab(), makeTab(), makeTab(true), makeTab() ]; elm = $compile([ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '' + '', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + '' ].join('\n'))(scope); scope.$apply(); })); @@ -161,10 +163,10 @@ describe('tabs', function() { elm = $compile([ '
', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', '
' ].join('\n'))(scope); scope.$apply(); @@ -201,12 +203,12 @@ describe('tabs', function() { makeTab(), makeTab(), makeTab(true), makeTab() ]; elm = $compile([ - '', - ' ', - ' heading {{index}}', + '', + ' ', + ' heading {{index}}', ' content {{$index}}', - ' ', - '' + ' ', + '' ].join('\n'))(scope); scope.$apply(); })); @@ -279,16 +281,16 @@ describe('tabs', function() { scope.foo = {active: true}; scope.select = jasmine.createSpy(); elm = $compile([ - '', - ' ', - ' heading {{index}}', + '', + ' ', + ' heading {{index}}', ' content {{$index}}', - ' ', - ' ', - ' heading foo', + ' ', + ' ', + ' heading foo', ' content foo', - ' ', - '' + ' ', + '' ].join('\n'))(scope); scope.$apply(); @@ -296,21 +298,20 @@ describe('tabs', function() { }); }); - describe('advanced tab-heading element', function() { + describe('advanced uib-tab-heading element', function() { beforeEach(inject(function($compile, $rootScope, $sce) { scope = $rootScope.$new(); scope.myHtml = $sce.trustAsHtml('hello, there!'); scope.value = true; elm = $compile([ - '', - ' ', - ' ', - ' ', - ' ', - ' 1', - '
2
', - '
3
', - '
' + '', + ' ', + ' ', + ' ', + ' 1', + '
2
', + '
3
', + '
' ].join('\n'))(scope); scope.$apply(); })); @@ -331,7 +332,7 @@ describe('tabs', function() { expect(heading().eq(0)).not.toBeHidden(); }); - it('should have a tab-heading no matter what syntax was used', function() { + it('should have a uib-tab-heading no matter what syntax was used', function() { expect(heading().eq(1).text()).toBe('1'); expect(heading().eq(2).text()).toBe('2'); expect(heading().eq(3).text()).toBe('3'); @@ -349,26 +350,26 @@ describe('tabs', function() { { title:'Title 3', available:true } ]; elm = $compile([ - '', + '', ' ', '
div that makes troubles
', - ' First Static', + ' First Static', '
another div that may do evil
', - ' some content', + ' some content', ' ', - ' Mid Static', + ' Mid Static', ' a text node', ' ', ' yet another span that may do evil', - ' some content', + ' some content', ' a text node', ' yet another span that may do evil', ' ', - ' Last Static', + ' Last Static', ' a text node', ' yet another span that may do evil', ' ', - '
' + '' ].join('\n'))(scope); scope.tabIsAvailable = function(tab) { @@ -410,7 +411,7 @@ describe('tabs', function() { }); }); - describe('tabset controller', function() { + describe('uib-tabset controller', function() { function mockTab(isActive) { var _isActive; if (isActive || isActive === false) { @@ -428,7 +429,7 @@ describe('tabs', function() { beforeEach(inject(function($controller, $rootScope) { scope = $rootScope; //instantiate the controller stand-alone, without the directive - ctrl = $controller('TabsetController', {$scope: scope}); + ctrl = $controller('UibTabsetController', {$scope: scope}); })); describe('select', function() { @@ -510,7 +511,7 @@ describe('tabs', function() { describe('remove', function() { it('should remove title tabs when elements are destroyed and change selection', inject(function($controller, $compile, $rootScope) { scope = $rootScope.$new(); - elm = $compile('Hellocontent {{i}}')(scope); + elm = $compile('Hellocontent {{i}}')(scope); scope.$apply(); expectTitles(['1']); @@ -570,12 +571,12 @@ describe('tabs', function() { getTab(false) ]; elm = $compile([ - '', - ' ', - ' heading {{index}}', + '', + ' ', + ' heading {{index}}', ' content {{$index}}', - ' ', - '' + ' ', + '' ].join('\n'))(scope); scope.$apply(); @@ -604,12 +605,12 @@ describe('tabs', function() { makeTab(false), makeTab(true), makeTab(false), makeTab(true) ]; elm = $compile([ - '', - ' ', - ' heading {{index}}', + '', + ' ', + ' heading {{index}}', ' content {{$index}}', - ' ', - '' + ' ', + '' ].join('\n'))(scope); scope.$apply(); })); @@ -653,7 +654,7 @@ describe('tabs', function() { beforeEach(inject(function($compile, $rootScope) { scope = $rootScope.$new(); scope.vertical = true; - elm = $compile('')(scope); + elm = $compile('')(scope); scope.$apply(); })); @@ -666,7 +667,7 @@ describe('tabs', function() { beforeEach(inject(function($compile, $rootScope) { scope = $rootScope.$new(); scope.justified = true; - elm = $compile('')(scope); + elm = $compile('')(scope); scope.$apply(); })); @@ -680,7 +681,7 @@ describe('tabs', function() { scope = $rootScope.$new(); scope.navType = 'pills'; - elm = $compile('')(scope); + elm = $compile('')(scope); scope.$apply(); })); @@ -694,13 +695,13 @@ describe('tabs', function() { describe('child compilation', function() { var elm; beforeEach(inject(function($compile, $rootScope) { - elm = $compile('
')($rootScope.$new()); + elm = $compile('
')($rootScope.$new()); $rootScope.$apply(); })); it('should hookup the tab\'s children to the tab with $compile', function() { var tabChild = $('.tab-pane', elm).children().first(); - expect(tabChild.inheritedData('$tabsetController')).toBeTruthy(); + expect(tabChild.inheritedData('$uibTabsetController')).toBeTruthy(); }); }); @@ -709,7 +710,7 @@ describe('tabs', function() { var elm; it('should render correct amount of options', inject(function($compile, $rootScope) { var scope = $rootScope.$new(); - elm = $compile('')(scope); scope.$apply(); var select = elm.find('select'); @@ -728,12 +729,12 @@ describe('tabs', function() { {title:'b', array:[2,3,4]}, {title:'c', array:[3,4,5]} ]; - elm = $compile('
' + - '' + - '{{$index}}' + + elm = $compile('
' + + '' + + '{{$index}}' + '{{a}},' + - '' + - '
')(scope); + '' + + '
')(scope); scope.$apply(); var contents = elm.find('.tab-pane'); @@ -750,20 +751,20 @@ describe('tabs', function() { var scope = $rootScope.$new(); elm = $compile([ '
', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', '
' ].join('\n'))(scope); scope.$apply(); @@ -780,27 +781,27 @@ describe('tabs', function() { scope.tab2aaText = '456'; elm = $compile([ '
', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', ' {{ tab1aText }}', - ' ', - ' ', + ' ', + ' ', ' {{ tab1Text }}', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', ' {{ tab2aaText }}', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', '
' ].join('\n'))(scope); scope.$apply(); @@ -832,16 +833,16 @@ describe('tabs', function() { ]; elm = $compile([ '
', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', ' {{ innerTab.content }}', - ' ', - ' ', + ' ', + ' ', ' {{ tab.content }}', - ' ', - ' ', + ' ', + ' ', '
' ].join('\n'))(scope); scope.$apply(); @@ -852,3 +853,70 @@ describe('tabs', function() { })); }); }); + +/* deprecation tests below */ + +describe('tab deprecation', function() { + beforeEach(module('ui.bootstrap.tabs')); + beforeEach(module('template/tabs/tabset.html')); + beforeEach(module('template/tabs/tab.html')); + + it('should suppress warning', function() { + module(function($provide) { + $provide.value('$tabsSuppressWarning', true); + }); + + inject(function($compile, $log, $rootScope) { + spyOn($log, 'warn'); + + var element = + '' + + '' + + 'Tab Heading One' + + '
Tab One Contents
' + + '
' + + '' + + '
Tab Two Contents
' + + '
' + + '
'; + $compile(element)($rootScope); + $rootScope.$digest(); + expect($log.warn.calls.count()).toBe(0); + }); + }); + + it('should give warning by default', inject(function($templateCache, $compile, $log, $rootScope) { + spyOn($log, 'warn'); + + var tabSetTemplate = + '
' + + '' + + '
' + + '
' + + '
' + + '
'; + $templateCache.put('template/tabs/tabset.html', tabSetTemplate); + + var tabTemplate = + '
  • ' + + '{{heading}}' + + '
  • '; + $templateCache.put('template/tabs/tab.html', tabTemplate); + + var element = + '' + + '' + + 'Tab Heading One' + + '
    Tab One Contents
    ' + + '
    ' + + '
    '; + $compile(element)($rootScope); + $rootScope.$digest(); + + expect($log.warn.calls.count()).toBe(4); + expect($log.warn.calls.argsFor(0)).toEqual(['tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.']); + expect($log.warn.calls.argsFor(1)).toEqual(['tab is now deprecated. Use uib-tab instead.']); + expect($log.warn.calls.argsFor(2)).toEqual(['tabset is now deprecated. Use uib-tabset instead.']); + expect($log.warn.calls.argsFor(3)).toEqual(['tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.']); + })); +}); diff --git a/template/tabs/tab.html b/template/tabs/tab.html index d76dd67caf..0d8a42ed6b 100644 --- a/template/tabs/tab.html +++ b/template/tabs/tab.html @@ -1,3 +1,3 @@
  • - {{heading}} + {{heading}}
  • diff --git a/template/tabs/tabset.html b/template/tabs/tabset.html index b953a49161..294c86a78b 100644 --- a/template/tabs/tabset.html +++ b/template/tabs/tabset.html @@ -4,7 +4,7 @@
    + uib-tab-content-transclude="tab">