feat(slideBox): add on-slide-start callback

This commit is contained in:
Andrew
2014-12-01 16:10:53 -07:00
parent 55b35b5409
commit cd5aaa5df2
7 changed files with 95 additions and 31 deletions

View File

@@ -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.

View File

@@ -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);

View File

@@ -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: '<div class="slider-slides" ng-transclude></div>',
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) {

View File

@@ -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,
});

View File

@@ -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);
}
}

View File

@@ -37,6 +37,12 @@ describe('ionSlideBox', function() {
describe('directive', function() {
it('should error with nested ion-slides', function() {
expect(function() {
setup('', '<ion-slide> <ion-slide>Nested</ion-slide> </ion-slide>');
}).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']);

View File

@@ -59,7 +59,6 @@ describe('<ion-slide-pager> 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);