From cd5aaa5df203c2a509723405e3d851dd39ae59b1 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 Dec 2014 16:10:53 -0700 Subject: [PATCH] feat(slideBox): add on-slide-start callback --- js/angular/controller/slideBoxController.js | 6 ++-- js/angular/directive/slide.js | 14 +++++++-- js/angular/directive/slideBox.js | 27 ++++++++++++---- js/angular/directive/slideBoxPager.js | 26 ++++++++++++---- scss/_slide-box.scss | 21 ++++++------- test/unit/angular/directive/slideBox.unit.js | 31 ++++++++++++++++++- .../unit/angular/directive/slidePager.unit.js | 1 - 7 files changed, 95 insertions(+), 31 deletions(-) diff --git a/js/angular/controller/slideBoxController.js b/js/angular/controller/slideBoxController.js index d42ffceb92..03f3745331 100644 --- a/js/angular/controller/slideBoxController.js +++ b/js/angular/controller/slideBoxController.js @@ -240,6 +240,8 @@ function(scope, element, $log, $document, $$q, $timeout, $interval, $$ionicAttac var direction; var translatePx; + element.triggerHandler('$ionSlideBox.slide', newIndex); + // We're interested in isDrag, because a failed drag is the only case // where we want to run a slide animation yet have no change in selectedIndex if (!isDrag && (delta === 0 || selectedIndex === -1)) { @@ -284,8 +286,8 @@ function(scope, element, $log, $document, $$q, $timeout, $interval, $$ionicAttac function setSelectedSlide(newIndex) { selectedIndex = newIndex; - container.css(ionic.CSS.TRANSFORM, ''); setDisplayedSlides(self.previous(newIndex), newIndex, self.next(newIndex)); + container.css(ionic.CSS.TRANSFORM, ''); } /** @@ -375,7 +377,7 @@ function(scope, element, $log, $document, $$q, $timeout, $interval, $$ionicAttac if (isSuccess) { var distanceRemaining = (1 - Math.abs(percent)) * dragWidth; - var transitionDuration = Math.min(distanceRemaining / velocity, SLIDE_TRANSITION_DURATION); + var transitionDuration = Math.min((distanceRemaining / velocity) - 34, SLIDE_TRANSITION_DURATION); self.select(function getIndex() { // This will be called once this dragend is reached in the select queue. diff --git a/js/angular/directive/slide.js b/js/angular/directive/slide.js index 5907f99468..26f730faa0 100644 --- a/js/angular/directive/slide.js +++ b/js/angular/directive/slide.js @@ -21,12 +21,20 @@ IonicModule .directive('ionSlide', ['$timeout', function($timeout) { return { restrict: 'E', - require: '^ionSlideBox', + require: ['^ionSlideBox', '^^?ionSlide'], transclude: true, + controller: angular.noop, link: postLink }; - function postLink(scope, element, attr, slideBoxCtrl, transclude) { + function postLink(scope, element, attr, ctrls, transclude) { + var slideBoxCtrl = ctrls[0]; + var slideCtrl = ctrls[1]; + + if (slideCtrl) { + throw new Error('You cannot have an ion-slide within another ion-slide!'); + } + element.addClass('slider-slide'); slideBoxCtrl.onAddSlide(); @@ -35,7 +43,7 @@ IonicModule element.data('$ionSlideScope', childScope); // Disconnect by default, will be reconnected if shown - ionic.Utils.disconnectScope(childScope); + // ionic.Utils.disconnectScope(childScope); transclude(childScope, function(contents) { element.append(contents); diff --git a/js/angular/directive/slideBox.js b/js/angular/directive/slideBox.js index c40d6acc23..37a1e4e609 100644 --- a/js/angular/directive/slideBox.js +++ b/js/angular/directive/slideBox.js @@ -30,18 +30,22 @@ * * @param {expression=} selected A model bound to the selected slide index. * @param {boolean=} loop Whether the slide box should loop. Default false. - * @param {number=} auto-play If a positive number, then every time the given number of milliseconds have passed, slideBox will go to the next slide. Set to a non-positive number to disable. Default: -1. - * @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed a '$slideIndex' variable. + * @param {number=} auto-play If a positive number, then every time the given number of + * milliseconds have passed, slideBox will go to the next slide. Set to a non-positive number + * to disable. Default: -1. + * @param {expression=} on-slide-changed Expression called when all currently queued slide + * animations finish. Is passed a '$slideIndex' variable. + * @param {expression=} on-slide-start Expression called whenever a slide animation starts. + * Is passed a '$slideIndex' variable. * @param {string=} delegate-handle The handle used to identify this slideBox with * {@link ionic.service:$ionicSlideBoxDelegate}. */ IonicModule .directive('ionSlideBox', [ '$ionicSlideBoxDelegate', - '$window', '$ionicHistory', - '$parse', -function($ionicSlideBoxDelegate, $window, $ionicHistory, $parse) { + '$timeout', +function($ionicSlideBoxDelegate, $ionicHistory, $timeout) { return { restrict: 'E', @@ -50,7 +54,8 @@ function($ionicSlideBoxDelegate, $window, $ionicHistory, $parse) { transclude: true, scope: { selected: '=?', - onSlideChanged: '&' + onSlideChanged: '&', + onSlideStart: '&' }, template: '
', compile: compile @@ -72,6 +77,7 @@ function($ionicSlideBoxDelegate, $window, $ionicHistory, $parse) { } ); + listenForSlide(); watchSelected(); isDefined(attr.loop) && watchLoop(); isDefined(attr.autoPlay) && watchAutoPlay(); @@ -82,6 +88,15 @@ function($ionicSlideBoxDelegate, $window, $ionicHistory, $parse) { // Methods // *** + function listenForSlide() { + element.on('$ionSlideBox.slide', function(ev, index) { + scope.onSlideStart({ + $slideIndex: index + }); + $timeout(angular.noop); + }); + } + function watchSelected() { scope.$watch('selected', function(index) { if (slideBoxCtrl.selected() !== index) { diff --git a/js/angular/directive/slideBoxPager.js b/js/angular/directive/slideBoxPager.js index 12bbe7aaa8..743a510a44 100644 --- a/js/angular/directive/slideBoxPager.js +++ b/js/angular/directive/slideBoxPager.js @@ -48,6 +48,7 @@ function($parse) { return { restrict: 'E', require: '^ionSlideBox', + scope: {}, link: postLink }; @@ -60,16 +61,27 @@ function($parse) { var node = element[0]; // Put it outside the slides container it was transcluded into - slideBoxCtrl.element.append(element); + slideBoxCtrl.element.prepend(element); - element.addClass('slider-pager'); - scope.slideBoxCtrl = slideBoxCtrl; - scope.pages = []; element.on('click', onPagerClicked); scope.$watch(slideBoxCtrl.count, watchCountAction); scope.$watch(slideBoxCtrl.selected, watchSelectedAction); + slideBoxCtrl.element.on('$ionSlideBox.slide', onSlideStart); + scope.$on('$destroy', function() { + slideBoxCtrl.element.off('$ionSlideBox.slide', onSlideStart); + }); + + element.addClass('ng-hide'); + ionic.requestAnimationFrame(function() { + element.removeClass('ng-hide').addClass('slider-pager'); + }); + + function onSlideStart(ev, index) { + watchSelectedAction(index); + } + function onPagerClicked(ev) { for (var i = 0, pager; (pager = node.children[i]); i++) { if (pager === ev.target) { @@ -88,18 +100,20 @@ function($parse) { } } - function watchSelectedAction(selected, oldSelected) { + var oldSelected; + function watchSelectedAction(selected) { var old = node.children[oldSelected]; if (old) old.classList.remove('active'); var current = node.children[selected]; if (current) current.classList.add('active'); + oldSelected = selected; } //* Extra methods *// function doClick(index) { scope.$apply(function() { - clickFn(scope, { + clickFn(scope.$parent, { index: index, // DEPRECATED `index` $slideIndex: index, }); diff --git a/scss/_slide-box.scss b/scss/_slide-box.scss index 51305bed9a..0e92cb0083 100644 --- a/scss/_slide-box.scss +++ b/scss/_slide-box.scss @@ -10,34 +10,31 @@ .slider-slides { width: 100%; - position: relative; @include translate3d(0,0,0); - &.transition { - -webkit-transition: linear -webkit-transform; - transition: linear transform; - } + -webkit-transition: linear -webkit-transform; + transition: linear transform; } .slider-slide { width: 100%; height: 100%; - &:not([slide-display]) { - display: none; - } + display: none; + @include translate3d(0,0,0); + &[slide-display] { display: inline-block; } - &[slide-display="selected"] { - } &[slide-display="previous"] { position: absolute; - left: -100%; top: 0; + left: 0; + @include translate3d(-100%,0,0); } &[slide-display="next"] { position: absolute; - left: 100%; top: 0; + left: 0; + @include translate3d(100%,0,0); } } diff --git a/test/unit/angular/directive/slideBox.unit.js b/test/unit/angular/directive/slideBox.unit.js index 41dee4d6f7..6bf3fb22f0 100644 --- a/test/unit/angular/directive/slideBox.unit.js +++ b/test/unit/angular/directive/slideBox.unit.js @@ -37,6 +37,12 @@ describe('ionSlideBox', function() { describe('directive', function() { + it('should error with nested ion-slides', function() { + expect(function() { + setup('', ' Nested '); + }).toThrow(); + }); + describe('selection', function() { it('should select first slide automatically', inject(function($rootScope, $timeout) { var el = setup('selected="$root.current"'); @@ -140,16 +146,28 @@ describe('ionSlideBox', function() { it('should call when selected changes', inject(function($rootScope, $timeout) { $rootScope.changed = jasmine.createSpy('changed'); var el = setup('selected="$root.current" on-slide-changed="changed($slideIndex)"'); + expect($rootScope.changed).not.toHaveBeenCalled(); $timeout.flush(); expect($rootScope.changed).toHaveBeenCalledWith(0); $rootScope.changed.reset(); $rootScope.$apply('current = 2'); + expect($rootScope.changed).not.toHaveBeenCalled(); $timeout.flush(); expect($rootScope.changed).toHaveBeenCalledWith(2); })); }); + describe('onSlideStart', function() { + it('should call when animation starts', inject(function($rootScope, $timeout) { + $rootScope.start = jasmine.createSpy('start'); + $rootScope.current = 1; + var el = setup('selected="$root.current" on-slide-start="$root.start($slideIndex)">'); + expect($rootScope.start).toHaveBeenCalledWith(1); + $timeout.flush(); + })); + }); + }); describe('delegate', function() { @@ -324,27 +342,38 @@ describe('ionSlideBox', function() { it('when queueing, should only publish after final slide', inject(function($timeout, $rootScope) { $rootScope.changed = jasmine.createSpy('changed'); - var el = setup('selected="$root.current" on-slide-changed="$root.changed($slideIndex)"'); + $rootScope.start = jasmine.createSpy('start'); + var el = setup('selected="$root.current" on-slide-changed="$root.changed($slideIndex)" on-slide-start="$root.start($slideIndex)"'); $timeout.flush(); $rootScope.changed.reset(); + $rootScope.start.reset(); $del.select(1); $del.select(2); $del.select(1); + expect($rootScope.start).toHaveBeenCalledWith(1); + $rootScope.start.reset(); $timeout.flush(); + // Scope data bindings not published expect($rootScope.changed).not.toHaveBeenCalled(); expect($rootScope.current).toBe(0); // Elements look different, though. expect(slideDisplays(el)).toEqual(['previous', 'selected', 'next']); + expect($rootScope.start).toHaveBeenCalledWith(2); + $rootScope.start.reset(); $timeout.flush(); + expect($rootScope.changed).not.toHaveBeenCalled(); expect($rootScope.current).toBe(0); expect(slideDisplays(el)).toEqual(['', 'previous', 'selected']); + expect($rootScope.start).toHaveBeenCalledWith(1); + $rootScope.start.reset(); $timeout.flush(); + expect($rootScope.changed).toHaveBeenCalledWith(1); expect($rootScope.current).toBe(1); expect(slideDisplays(el)).toEqual(['previous', 'selected', 'next']); diff --git a/test/unit/angular/directive/slidePager.unit.js b/test/unit/angular/directive/slidePager.unit.js index 5355e9def6..55cad971c0 100644 --- a/test/unit/angular/directive/slidePager.unit.js +++ b/test/unit/angular/directive/slidePager.unit.js @@ -59,7 +59,6 @@ describe(' directive', function() { var slideBoxCtrl = el.controller('ionSlideBox'); var pagers = el.find('.slider-pager-page'); - expect(slideBoxCtrl.selected()).toBe(0); pagers.eq(1).click(); expect(slideBoxCtrl.selected()).toBe(0); expect($rootScope.click).toHaveBeenCalledWith(1);