diff --git a/js/ext/angular/src/directive/ionicBar.js b/js/ext/angular/src/directive/ionicBar.js index 5696fb4b1e..2f92b495e5 100644 --- a/js/ext/angular/src/directive/ionicBar.js +++ b/js/ext/angular/src/directive/ionicBar.js @@ -112,25 +112,47 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize']) .directive('ionFooterBar', barDirective(false)); function barDirective(isHeader) { - var BAR_TEMPLATE = isHeader ? - '
' : - ''; - var BAR_MODEL_DEFAULT = isHeader ? - '$ionicHeaderBarController' : - '$ionicFooterBarController'; return ['$parse', function($parse) { return { restrict: 'E', - replace: true, - transclude: true, - template: BAR_TEMPLATE, - link: function($scope, $element, $attr) { - var hb = new ionic.views.HeaderBar({ - el: $element[0], - alignTitle: $attr.alignTitle || 'center' - }); + compile: function($element, $attr) { + $element.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer'); + return { pre: prelink }; + function prelink($scope, $element, $attr) { + var hb = new ionic.views.HeaderBar({ + el: $element[0], + alignTitle: $attr.alignTitle || 'center' + }); - $parse($attr.controllerBind || BAR_MODEL_DEFAULT).assign($scope, hb); + $parse($attr.controllerBind || + (isHeader ? '$ionicHeaderBarController' : '$ionicFooterBarController') + ).assign($scope, hb); + + var el = $element[0]; + + if (isHeader) { + $scope.$watch(function() { return el.className; }, function(value) { + var isSubheader = value.indexOf('bar-subheader') !== -1; + $scope.$parent.$hasHeader = !isSubheader; + $scope.$parent.$hasSubheader = isSubheader; + }); + $scope.$on('$destroy', function() { + $scope.$parent.$hasHeader = $scope.$parent.$hasSubheader = null; + }); + } else { + $scope.$watch(function() { return el.className; }, function(value) { + var isSubfooter = value.indexOf('bar-subfooter') !== -1; + $scope.$parent.$hasFooter = !isSubfooter; + $scope.$parent.$hasSubfooter = isSubfooter; + }); + $scope.$on('$destroy', function() { + $scope.$parent.$hasFooter = $scope.$parent.$hasSubfooter = null; + }); + $scope.$watch('$hasTabs', function(val) { + $element.toggleClass('has-tabs', !!val); + }); + } + } } }; }]; diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 08ddc4aa33..f25443212a 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -44,9 +44,6 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll']) * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll} * directive. * - * Use the classes 'has-header', 'has-subheader', 'has-footer', and 'has-tabs' - * to modify the positioning of the ion-content relative to surrounding elements. - * * @param {string=} controller-bind The scope variable to bind this element's scrollView's * {@link ionic.controller:ionicScroll ionicScroll controller} to. * Default: $scope.$ionicScrollController. @@ -68,24 +65,31 @@ angular.module('ionic.ui.content', ['ionic.ui.scroll']) function($parse, $timeout, $controller, $ionicBind) { return { restrict: 'E', - replace: true, - transclude: true, require: '^?ionNavView', scope: true, - template: - '
' + - '
' + - '
', - compile: function(element, attr, transclude) { - return { - //Prelink so it can compile before other directives compile. - //Then other directives can require ionicScrollCtrl - pre: prelink - }; + compile: function(element, attr) { + element.addClass('scroll-content'); + //We cannot transclude here because it breaks element.data() inheritance on compile + var innerElement = angular.element('
'); + innerElement.append(element.contents()); + element.append(innerElement); + + return { pre: prelink }; function prelink($scope, $element, $attr, navViewCtrl) { - var clone, sc, scrollView, scrollCtrl, - scrollContent = angular.element($element[0].querySelector('.scroll')); + var clone, sc, scrollView, scrollCtrl; + + $scope.$watch(function() { + return ($scope.$hasHeader ? ' has-header' : '') + + ($scope.$hasSubheader ? ' has-subheader' : '') + + ($scope.$hasFooter ? ' has-footer' : '') + + ($scope.$hasSubfooter ? ' has-subfooter' : '') + + ($scope.$hasTabs ? ' has-tabs' : '') + + ($scope.$hasTabsTop ? ' has-tabs-top' : ''); + }, function(className, oldClassName) { + $element.removeClass(oldClassName); + $element.addClass(className); + }); $ionicBind($scope, $attr, { $onScroll: '&onScroll', @@ -104,7 +108,7 @@ function($parse, $timeout, $controller, $ionicBind) { if (angular.isDefined($attr.padding)) { $scope.$watch($attr.padding, function(newVal) { - scrollContent.toggleClass('padding', !!newVal); + innerElement.toggleClass('padding', !!newVal); }); } @@ -139,13 +143,6 @@ function($parse, $timeout, $controller, $ionicBind) { scrollView = scrollCtrl.scrollView; } - transclude($scope, function(clone) { - if (scrollCtrl) { - clone.data('$$ionicScrollController', scrollCtrl); - } - scrollContent.append(clone); - }); - } } }; diff --git a/js/ext/angular/src/directive/ionicNavBar.js b/js/ext/angular/src/directive/ionicNavBar.js index cd2b39dc42..1a05a738a8 100644 --- a/js/ext/angular/src/directive/ionicNavBar.js +++ b/js/ext/angular/src/directive/ionicNavBar.js @@ -215,20 +215,22 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) { return { restrict: 'E', - replace: true, - transclude: true, controller: '$ionicNavBar', - template: - '', - compile: function(tElement, tAttrs, transclude) { + scope: true, + compile: function(tElement, tAttrs) { + //We cannot transclude here because it breaks element.data() inheritance on compile + tElement + .addClass('bar bar-header nav-bar') + .append( + '
' + + '
' + + '

' + + '
' + + '
' + ); - return function link($scope, $element, $attr, navBarCtrl) { + return { pre: prelink }; + function prelink($scope, $element, $attr, navBarCtrl) { navBarCtrl._headerBarView = new ionic.views.HeaderBar({ el: $element[0], alignTitle: $attr.alignTitle || 'center' @@ -237,23 +239,23 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) { $parse($attr.controllerBind || '$ionicNavBarController') .assign($scope, navBarCtrl); - //Put transcluded content (usually a back button) before the rest - transclude($scope, function(clone) { - $element.prepend(clone); - }); - //defaults $scope.backButtonShown = false; $scope.shouldAnimate = true; $scope.isReverse = false; $scope.isInvisible = true; + $scope.$parent.$hasHeader = true; - $scope.navBarClass = function() { + $scope.$watch(function() { return ($scope.isReverse ? ' reverse' : '') + ($scope.isInvisible ? ' invisible' : '') + (!$scope.shouldAnimate ? ' no-animation' : ''); - }; - }; + }, function(className, oldClassName) { + $element.removeClass(oldClassName); + $element.addClass(className); + }); + + } } }; }]) @@ -311,32 +313,30 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) { return { restrict: 'E', require: '^ionNavBar', - replace: true, - transclude: true, - template: - '', - link: function($scope, $element, $attr, navBarCtrl) { - $scope.$navBack = navBarCtrl.back; - if (!$attr.ngClick) { - $ionicNgClick($scope, $element, '$navBack($event)'); - } - - //If the current viewstate does not allow a back button, - //always hide it. - var deregisterListener = $scope.$parent.$on( - '$viewHistory.historyChange', - function(e, data) { - $scope.hasBackButton = !!data.showBack; + compile: function(tElement, tAttrs) { + tElement.addClass('button back-button'); + return function($scope, $element, $attr, navBarCtrl) { + $scope.$navBack = navBarCtrl.back; + if (!$attr.ngClick) { + $ionicNgClick($scope, $element, '$navBack($event)'); } - ); - $scope.$on('$destroy', deregisterListener); - //Make sure both that a backButton is allowed in the first place, - //and that it is shown by the current view. - $scope.$watch('!!(backButtonShown && hasBackButton)', function(val) { - $element.toggleClass('hide', !val); - }); + //If the current viewstate does not allow a back button, + //always hide it. + var deregisterListener = $scope.$parent.$on( + '$viewHistory.historyChange', + function(e, data) { + $scope.hasBackButton = !!data.showBack; + } + ); + $scope.$on('$destroy', deregisterListener); + + //Make sure both that a backButton is allowed in the first place, + //and that it is shown by the current view. + $scope.$watch('!!(backButtonShown && hasBackButton)', function(val) { + $element.toggleClass('hide', !val); + }); + }; } }; }]) @@ -379,9 +379,8 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) { .directive('ionNavButtons', ['$compile', '$animate', function($compile, $animate) { return { require: '^ionNavBar', - transclude: true, restrict: 'E', - compile: function($element, $attrs, transclude) { + compile: function($element, $attrs) { return function($scope, $element, $attrs, navBarCtrl) { var navElement = $attrs.side === 'right' ? navBarCtrl.rightButtonsElement : @@ -391,13 +390,13 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) { //so we can remove them all when this element dies - //even if the buttons have changed through an ng-repeat or the like, //we just remove their div parent and they are gone. - var clone = angular.element('
').append(transclude($scope)); - $animate.enter(clone, navElement); + var buttons = angular.element('
').append($element.contents().remove()); + $animate.enter(buttons, navElement); //When our ion-nav-buttons container is destroyed, //destroy everything in the navbar $scope.$on('$destroy', function() { - $animate.leave(clone); + $animate.leave(buttons); }); // The original element is just a completely empty element. diff --git a/js/ext/angular/src/directive/ionicPopup.js b/js/ext/angular/src/directive/ionicPopup.js index b1902e6ecb..f841a4553b 100644 --- a/js/ext/angular/src/directive/ionicPopup.js +++ b/js/ext/angular/src/directive/ionicPopup.js @@ -23,6 +23,18 @@ angular.module('ionic.ui.popup', []) replace: true, transclude: true, scope: true, + template: + '', link: function($scope, $element, $attr) { $ionicBind($scope, $attr, { title: '@', @@ -45,18 +57,7 @@ angular.module('ionic.ui.popup', []) } $scope.$onButtonTap({button: button, event: event}); } - }, - template: '' + } }; }]); diff --git a/js/ext/angular/src/directive/ionicScroll.js b/js/ext/angular/src/directive/ionicScroll.js index 73b6376194..dd45ff34db 100644 --- a/js/ext/angular/src/directive/ionicScroll.js +++ b/js/ext/angular/src/directive/ionicScroll.js @@ -26,9 +26,6 @@ angular.module('ionic.ui.scroll', []) .directive('ionScroll', ['$parse', '$timeout', '$controller', function($parse, $timeout, $controller) { return { restrict: 'E', - replace: true, - template: '
', - transclude: true, scope: { direction: '@', paging: '@', @@ -39,23 +36,25 @@ angular.module('ionic.ui.scroll', []) scrollbarY: '@', }, controller: function() {}, - compile: function(element, attr, transclude) { + compile: function(element, attr) { + element.addClass('scroll-view'); - return { - //Prelink so it can compile before other directives compile. - //Then other directives can require ionicScrollCtrl - pre: prelink - }; + //We cannot transclude here because it breaks element.data() inheritance on compile + var innerElement = angular.element('
'); + innerElement.append(element.contents()); + element.append(innerElement); + return { pre: prelink }; function prelink($scope, $element, $attr) { - var scrollView, scrollCtrl, - sc = $element[0].children[0]; + var scrollView, scrollCtrl; - if(attr.padding == "true") { - sc.classList.add('padding'); + if (angular.isDefined($attr.padding)) { + $scope.$watch($attr.padding, function(newVal) { + innerElement.toggleClass('padding', !!newVal); + }); } if($scope.$eval($scope.paging) === true) { - sc.classList.add('scroll-paging'); + innerElement.addClass('scroll-paging'); } if(!$scope.direction) { $scope.direction = 'y'; } diff --git a/js/ext/angular/src/directive/ionicSideMenu.js b/js/ext/angular/src/directive/ionicSideMenu.js index 57be388b94..d46e9ae1af 100644 --- a/js/ext/angular/src/directive/ionicSideMenu.js +++ b/js/ext/angular/src/directive/ionicSideMenu.js @@ -157,8 +157,9 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie restrict: 'AC', require: '^ionSideMenus', scope: true, - compile: function(element, attr, transclude) { - return function($scope, $element, $attr, sideMenuCtrl) { + compile: function(element, attr) { + return { pre: prelink }; + function prelink($scope, $element, $attr, sideMenuCtrl) { $element.addClass('menu-content'); @@ -248,7 +249,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie $ionicGesture.off(releaseGesture, 'release', dragReleaseFn); ionic.off('tap', contentTap, $element[0]); }); - }; + } } }; }]) @@ -282,14 +283,13 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie return { restrict: 'E', require: '^ionSideMenus', - replace: true, - transclude: true, scope: true, - template: '', - compile: function(element, attr, transclude) { + compile: function(element, attr) { angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true'); angular.isUndefined(attr.width) && attr.$set('width', '275'); + element.addClass('menu menu-' + attr.side); + return function($scope, $element, $attr, sideMenuCtrl) { $scope.side = $attr.side || 'left'; @@ -308,10 +308,6 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie $scope.$watch($attr.isEnabled, function(val) { sideMenu.setIsEnabled(!!val); }); - - transclude($scope, function(clone) { - $element.append(clone); - }); }; } }; diff --git a/js/ext/angular/src/directive/ionicTabBar.js b/js/ext/angular/src/directive/ionicTabBar.js index 8aaf1d5209..64904c8674 100644 --- a/js/ext/angular/src/directive/ionicTabBar.js +++ b/js/ext/angular/src/directive/ionicTabBar.js @@ -172,28 +172,33 @@ angular.module('ionic.ui.tabs', ['ionic.service.view']) .directive('ionTabs', ['$ionicViewService', '$parse', function($ionicViewService, $parse) { return { restrict: 'E', - replace: true, scope: true, - transclude: true, controller: 'ionicTabs', - template: - '
' + - '
' + - '
' + - '
', - compile: function(element, attr, transclude) { + compile: function(element, attr) { + element.addClass('view'); + //We cannot transclude here because it breaks element.data() inheritance on compile + var innerElement = angular.element('
'); + innerElement.append(element.contents()); + element.append(innerElement); - return function link($scope, $element, $attr, tabsCtrl) { + return { pre: prelink }; + function prelink($scope, $element, $attr, tabsCtrl) { $parse(attr.model || '$ionicTabsController').assign($scope, tabsCtrl); tabsCtrl.$scope = $scope; tabsCtrl.$element = $element; tabsCtrl.$tabsElement = angular.element($element[0].querySelector('.tabs')); - transclude($scope, function(clone) { - $element.append(clone); + var el = $element[0]; + $scope.$watch(function() { return el.className; }, function(value) { + var isTabsTop = value.indexOf('tabs-top') !== -1; + $scope.$hasTabs = !isTabsTop; + $scope.$hasTabsTop = isTabsTop; }); - }; + $scope.$on('$destroy', function() { + $scope.$hasTabs = $scope.$hasTabsTop = null; + }); + } } }; }]) diff --git a/js/ext/angular/test/directive/ionicBar.unit.js b/js/ext/angular/test/directive/ionicBar.unit.js index 76211794c2..b3091e26db 100644 --- a/js/ext/angular/test/directive/ionicBar.unit.js +++ b/js/ext/angular/test/directive/ionicBar.unit.js @@ -55,11 +55,11 @@ describe('bar directives', function() { angular.forEach([{ tag: 'ion-header-bar', - element: 'header', + className: 'bar bar-header', controllerBind: '$ionicHeaderBarController' }, { tag: 'ion-footer-bar', - element: 'footer', + className: 'bar bar-footer', controllerBind: '$ionicFooterBarController' }], function(data) { describe(data.tag, function() { @@ -78,9 +78,50 @@ describe('bar directives', function() { return el; } - it('should compile to ' + data.element, function() { + if (data.tag === 'ion-header-bar') { + it('$hasHeader $hasSubheader', function() { + var el = setup(); + var scope = el.scope().$parent; + expect(scope.$hasHeader).toEqual(true); + expect(scope.$hasSubheader).toEqual(false); + el.addClass('bar-subheader'); + scope.$apply(); + expect(scope.$hasHeader).toEqual(false); + expect(scope.$hasSubheader).toEqual(true); + el.removeClass('bar-subheader'); + scope.$apply(); + expect(scope.$hasHeader).toEqual(true); + expect(scope.$hasSubheader).toEqual(false); + }); + } else { + it('$hasFooter $hasSubheader', function() { + var el = setup(); + var scope = el.scope().$parent; + expect(scope.$hasFooter).toEqual(true); + expect(scope.$hasSubfooter).toEqual(false); + el.addClass('bar-subfooter'); + scope.$apply(); + expect(scope.$hasFooter).toEqual(false); + expect(scope.$hasSubfooter).toEqual(true); + el.removeClass('bar-subfooter'); + scope.$apply(); + expect(scope.$hasFooter).toEqual(true); + expect(scope.$hasSubfooter).toEqual(false); + }); + it('.has-tabs', function() { + var el = setup(); + var scope = el.scope().$parent; + expect(el.hasClass('has-tabs')).toBe(false); + scope.$apply('$hasTabs = true'); + expect(el.hasClass('has-tabs')).toBe(true); + scope.$apply('$hasTabs = false'); + expect(el.hasClass('has-tabs')).toBe(false); + }); + } + + it('should compile to ' + data.className, function() { var el = setup(); - expect(el[0].tagName.toLowerCase()).toBe(data.element); + expect(el.hasClass(data.className)).toBe(true); }); it('should assign views.HeaderBar to default controllerBind', function() { diff --git a/js/ext/angular/test/directive/ionicNavBar.unit.js b/js/ext/angular/test/directive/ionicNavBar.unit.js index 95aafa385a..ca9b8d5604 100644 --- a/js/ext/angular/test/directive/ionicNavBar.unit.js +++ b/js/ext/angular/test/directive/ionicNavBar.unit.js @@ -229,6 +229,11 @@ describe('ionNavBar', function() { expect(el.children().eq(0).html()).toBe('super content 4'); }); + it('should $parent.$hasHeader', function() { + var el = setup(); + expect(el.scope().$parent.$hasHeader).toBe(true); + }); + it('should assign $scope.navBarController by default', function() { var el = setup(); expect(el.controller('ionNavBar')).toBeTruthy(); //sanity diff --git a/js/ext/angular/test/directive/ionicScroll.unit.js b/js/ext/angular/test/directive/ionicScroll.unit.js index f2ae0519b4..a482ec7259 100644 --- a/js/ext/angular/test/directive/ionicScroll.unit.js +++ b/js/ext/angular/test/directive/ionicScroll.unit.js @@ -26,6 +26,7 @@ describe('Ionic Scroll Directive', function() { it('should add padding classname', function() { element = compile('')(scope); + scope.$apply(); expect(element.children().eq(0).hasClass('padding')).toEqual(true); var scrollElement = element.find('.scroll'); expect(scrollElement.hasClass('padding')).toEqual(true); diff --git a/js/ext/angular/test/directive/ionicTabBar.unit.js b/js/ext/angular/test/directive/ionicTabBar.unit.js index e5a9d28ff2..7093d8605c 100644 --- a/js/ext/angular/test/directive/ionicTabBar.unit.js +++ b/js/ext/angular/test/directive/ionicTabBar.unit.js @@ -224,6 +224,21 @@ describe('tabs', function() { return element; } + it('should $hasTabs and $hasTabsTop', function() { + var el = setup(); + var scope = el.scope(); + expect(scope.$hasTabs).toBe(true); + expect(scope.$hasTabsTop).toBe(false); + el.addClass('tabs-top'); + scope.$apply(); + expect(scope.$hasTabs).toBe(false); + expect(scope.$hasTabsTop).toBe(true); + el.removeClass('tabs-top'); + scope.$apply(); + expect(scope.$hasTabs).toBe(true); + expect(scope.$hasTabsTop).toBe(false); + }); + it('should bind controller to scope.$ionicTabsController by default', function() { var el = setup(); expect(el.controller('ionTabs')).toBeTruthy(); //sanity @@ -238,8 +253,7 @@ describe('tabs', function() { it('should transclude content with same scope', function() { var el = setup('', '
'); - expect(el.children().eq(1).hasClass('content')).toBe(true); - expect(el.children().eq(1).scope()).toBe(el.scope()); + expect(el[0].querySelector('.tabs .content')).toBeTruthy(); }); }); diff --git a/js/ext/angular/test/list-fit.html b/js/ext/angular/test/list-fit.html index ce29f6a1a1..ba78e419d8 100644 --- a/js/ext/angular/test/list-fit.html +++ b/js/ext/angular/test/list-fit.html @@ -16,7 +16,12 @@

Header!

- + + +
+    $hasHeader: {{!!$hasHeader}}
+    $hasSubheader: {{!!$hasSubheader}}
+    
diff --git a/js/ext/angular/test/viewState.html b/js/ext/angular/test/viewState.html index 16792a2a6f..32e22a1f18 100644 --- a/js/ext/angular/test/viewState.html +++ b/js/ext/angular/test/viewState.html @@ -33,7 +33,7 @@
- +