From 519de698fe5142b7b44d1c6bfc42909738c06b4c Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sun, 5 Apr 2015 20:04:49 +0100 Subject: [PATCH 1/8] feat(carousel): add wrap control option --- src/carousel/carousel.js | 90 ++++++++++++++++++++++++++++----- template/carousel/carousel.html | 4 +- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/carousel/carousel.js b/src/carousel/carousel.js index 14dd3a6bca..6ef74d5295 100644 --- a/src/carousel/carousel.js +++ b/src/carousel/carousel.js @@ -14,17 +14,23 @@ angular.module('ui.bootstrap.carousel', []) currentInterval, isPlaying; self.currentSlide = null; + // setting wrap as false if undefined + $scope.wrap = angular.isUndefined($scope.wrap)?true:$scope.wrap; + $scope.nextActive = true; + $scope.prevActive = true; + var destroyed = false; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { var nextIndex = self.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { - direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; + direction = nextIndex > $scope.getCurrentIndex() ? 'next' : 'prev'; } if (nextSlide && nextSlide !== self.currentSlide) { goNext(); } + self.updateCarouselControls(); function goNext() { // Scope has been destroyed, stop here. if (destroyed) { return; } @@ -53,40 +59,96 @@ angular.module('ui.bootstrap.carousel', []) return slides[index]; } var i, len = slides.length; - for (i = 0; i < slides.length; ++i) { + for (i = 0; i < len; ++i) { if (slides[i].index == index) { return slides[i]; } } } - self.getCurrentIndex = function() { + $scope.getCurrentIndex = function() { if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { return +self.currentSlide.index; } return currentIndex; }; + self.updateNextActive = function(){ + // if wrap and there are slides -> true + // if wrap false and there are next slides -> true + // otherwise -> false + if (slides.length !== 0) + { + if ($scope.wrap) + { + $scope.nextActive = true; + return; + } + else if (!$scope.wrap && ($scope.getCurrentIndex() + 1 < slides.length)) + { + $scope.nextActive = true; + return; + } + } + + $scope.nextActive = false; + }; + + self.updatePrevActive = function(){ + // if wrap and there are slides -> true + // if wrap false and there are previous slides -> true + // otherwise -> false + if (slides.length !== 0) + { + if ($scope.wrap) + { + $scope.prevActive = true; + return; + } + else if (!$scope.wrap && ($scope.getCurrentIndex() - 1 >= 0)) + { + $scope.prevActive = true; + return; + } + } + + $scope.prevActive = false; + }; + + self.updateCarouselControls = function (){ + self.updatePrevActive(); + self.updateNextActive(); + }; + /* Allow outside people to call indexOf on slides array */ self.indexOfSlide = function(slide) { return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); }; $scope.next = function() { - var newIndex = (self.getCurrentIndex() + 1) % slides.length; + // if wrap is not active stops the next action + + if ($scope.nextActive) + { + var newIndex = ($scope.getCurrentIndex() + 1) % slides.length; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(getSlideByIndex(newIndex), 'next'); + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(getSlideByIndex(newIndex), 'next'); + } } }; $scope.prev = function() { - var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; + // if wrap is not active stops the next action + if ($scope.prevActive) + { + var newIndex = $scope.getCurrentIndex() - 1 < 0 ? slides.length - 1 : $scope.getCurrentIndex() - 1; - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(getSlideByIndex(newIndex), 'prev'); + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(getSlideByIndex(newIndex), 'prev'); + } } }; @@ -146,6 +208,7 @@ angular.module('ui.bootstrap.carousel', []) } else { slide.active = false; } + self.updateCarouselControls(); }; self.removeSlide = function(slide) { @@ -166,6 +229,7 @@ angular.module('ui.bootstrap.carousel', []) } else if (currentIndex > index) { currentIndex--; } + self.updateCarouselControls(); }; }]) @@ -181,6 +245,7 @@ angular.module('ui.bootstrap.carousel', []) * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. * @param {boolean=} noTransition Whether to disable transitions on the carousel. * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * €param {boolean=} wrap Whether the carousel should cycle continuously or have hard stops * * @example @@ -219,7 +284,8 @@ angular.module('ui.bootstrap.carousel', []) scope: { interval: '=', noTransition: '=', - noPause: '=' + noPause: '=', + wrap: '=' } }; }]) diff --git a/template/carousel/carousel.html b/template/carousel/carousel.html index 3b26a25814..eac72cda9a 100644 --- a/template/carousel/carousel.html +++ b/template/carousel/carousel.html @@ -3,6 +3,6 @@
  • - - + + From 7d7eacc56bc8a3747575290275f2a14e92de7d75 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sun, 5 Apr 2015 22:18:14 +0100 Subject: [PATCH 2/8] test(carousel): refactor to support the wrap option --- src/carousel/test/carousel.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/carousel/test/carousel.spec.js b/src/carousel/test/carousel.spec.js index 86e5a0735a..bb190a5f07 100644 --- a/src/carousel/test/carousel.spec.js +++ b/src/carousel/test/carousel.spec.js @@ -32,7 +32,7 @@ describe('carousel', function() { {active:false,content:'three'} ]; elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + @@ -77,7 +77,7 @@ describe('carousel', function() { scope.slides=[{active:false,content:'one'}]; scope.$apply(); elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + @@ -276,7 +276,7 @@ describe('carousel', function() { {active:false,content:'three', id:3} ]; elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + From aca7b3bb78aef456d7ff57d727a0ab60f916a66a Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 17:46:27 +0100 Subject: [PATCH 3/8] feat(carousel): changing wrap attr to two way binding --- src/carousel/carousel.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/carousel/carousel.js b/src/carousel/carousel.js index 6ef74d5295..c48699f8e0 100644 --- a/src/carousel/carousel.js +++ b/src/carousel/carousel.js @@ -13,9 +13,9 @@ angular.module('ui.bootstrap.carousel', []) currentIndex = -1, currentInterval, isPlaying; self.currentSlide = null; - + // setting wrap as false if undefined - $scope.wrap = angular.isUndefined($scope.wrap)?true:$scope.wrap; + $scope.wrapValue = angular.isUndefined($scope.wrap)?true:$scope.wrap; $scope.nextActive = true; $scope.prevActive = true; @@ -29,8 +29,9 @@ angular.module('ui.bootstrap.carousel', []) } if (nextSlide && nextSlide !== self.currentSlide) { goNext(); + self.updateCarouselControls(); } - self.updateCarouselControls(); + function goNext() { // Scope has been destroyed, stop here. if (destroyed) { return; } @@ -79,12 +80,12 @@ angular.module('ui.bootstrap.carousel', []) // otherwise -> false if (slides.length !== 0) { - if ($scope.wrap) + if ($scope.wrapValue) { $scope.nextActive = true; return; } - else if (!$scope.wrap && ($scope.getCurrentIndex() + 1 < slides.length)) + else if ($scope.getCurrentIndex() + 1 < slides.length) { $scope.nextActive = true; return; @@ -100,12 +101,12 @@ angular.module('ui.bootstrap.carousel', []) // otherwise -> false if (slides.length !== 0) { - if ($scope.wrap) + if ($scope.wrapValue) { $scope.prevActive = true; return; } - else if (!$scope.wrap && ($scope.getCurrentIndex() - 1 >= 0)) + else if ($scope.getCurrentIndex() - 1 >= 0) { $scope.prevActive = true; return; @@ -126,8 +127,7 @@ angular.module('ui.bootstrap.carousel', []) }; $scope.next = function() { - // if wrap is not active stops the next action - + // if wrap is not active stops the next action if ($scope.nextActive) { var newIndex = ($scope.getCurrentIndex() + 1) % slides.length; From 746b85a9f427efc3e576b655f0a2c7a40360b146 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 17:53:26 +0100 Subject: [PATCH 4/8] test(carousel): adding tests for the wrap option --- src/carousel/test/carousel.spec.js | 68 ++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/carousel/test/carousel.spec.js b/src/carousel/test/carousel.spec.js index bb190a5f07..d3bb9d1b4b 100644 --- a/src/carousel/test/carousel.spec.js +++ b/src/carousel/test/carousel.spec.js @@ -32,7 +32,7 @@ describe('carousel', function() { {active:false,content:'three'} ]; elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + @@ -77,7 +77,7 @@ describe('carousel', function() { scope.slides=[{active:false,content:'one'}]; scope.$apply(); elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + @@ -254,6 +254,68 @@ describe('carousel', function() { testSlideActive(1); }); + it('should stop navigation when wrap false', function () { + scope.slides = [ + {active:false,content:'one'}, + {active:false,content:'two'}, + {active:false,content:'three'} + ]; + elm = $compile( + '' + + '' + + '{{slide.content}}' + + '' + + '' + )(scope); + scope.$apply(); + + var navPrev = elm.find('a.left'); + var navNext = elm.find('a.right'); + + testSlideActive(0); + navPrev.click(); + testSlideActive(0); + navNext.click(); + testSlideActive(1); + navNext.click(); + testSlideActive(2); + navNext.click(); + testSlideActive(2); + }); + + it('should hide navigation when wrap false', function () { + scope.slides = [ + {active:false,content:'one'}, + {active:false,content:'two'}, + {active:false,content:'three'} + ]; + elm = $compile( + '' + + '' + + '{{slide.content}}' + + '' + + '' + )(scope); + scope.$apply(); + + var navPrev = elm.find('a.left'); + var navNext = elm.find('a.right'); + + testSlideActive(0); + expect(navPrev.hasClass('ng-hide')).toBe(true); + expect(navNext.hasClass('ng-hide')).toBe(false); + navNext.click(); + expect(navPrev.hasClass('ng-hide')).toBe(false); + expect(navNext.hasClass('ng-hide')).toBe(false); + testSlideActive(1); + expect(navPrev.hasClass('ng-hide')).toBe(false); + expect(navNext.hasClass('ng-hide')).toBe(false); + navNext.click(); + testSlideActive(2); + expect(navPrev.hasClass('ng-hide')).toBe(false); + expect(navNext.hasClass('ng-hide')).toBe(true); + }); + it('issue 1414 - should not continue running timers after scope is destroyed', function() { testSlideActive(0); $interval.flush(scope.interval); @@ -276,7 +338,7 @@ describe('carousel', function() { {active:false,content:'three', id:3} ]; elm = $compile( - '' + + '' + '' + '{{slide.content}}' + '' + From 43dfadb082069bf8dab02380f746e39e588f7e64 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 21:43:11 +0100 Subject: [PATCH 5/8] docs(carousel): updating the example with the wrap option --- src/carousel/docs/README.md | 2 ++ src/carousel/docs/demo.html | 9 ++++++++- src/carousel/docs/demo.js | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/carousel/docs/README.md b/src/carousel/docs/README.md index 2c7d173adc..1a07573f6d 100644 --- a/src/carousel/docs/README.md +++ b/src/carousel/docs/README.md @@ -3,3 +3,5 @@ Carousel creates a carousel similar to bootstrap's image carousel. The carousel also offers support for touchscreen devices in the form of swiping. To enable swiping, load the `ngTouch` module as a dependency. Use a `` element with `` elements inside it. It will automatically cycle through the slides at a given rate, and a current-index variable will be kept in sync with the currently visible slide. + +Use the attribute `wrap` set to `false` (the default value is true) to had hard stops to the carousel. diff --git a/src/carousel/docs/demo.html b/src/carousel/docs/demo.html index 3f7d13b25b..4c6bc8abdb 100644 --- a/src/carousel/docs/demo.html +++ b/src/carousel/docs/demo.html @@ -1,6 +1,6 @@
    - + +
    +
    +
    diff --git a/src/carousel/docs/demo.js b/src/carousel/docs/demo.js index 7aed52da0b..db081f0639 100644 --- a/src/carousel/docs/demo.js +++ b/src/carousel/docs/demo.js @@ -1,5 +1,6 @@ angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) { $scope.myInterval = 5000; + $scope.myWrap = true; var slides = $scope.slides = []; $scope.addSlide = function() { var newWidth = 600 + slides.length + 1; From 516de15688c47603afa31c10b1a99bfd22ba61c6 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 21:45:00 +0100 Subject: [PATCH 6/8] test(carousel): fix typo --- src/carousel/test/carousel.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/carousel/test/carousel.spec.js b/src/carousel/test/carousel.spec.js index d3bb9d1b4b..fe907debea 100644 --- a/src/carousel/test/carousel.spec.js +++ b/src/carousel/test/carousel.spec.js @@ -254,7 +254,7 @@ describe('carousel', function() { testSlideActive(1); }); - it('should stop navigation when wrap false', function () { + it('should stop navigation when wrap is false', function () { scope.slides = [ {active:false,content:'one'}, {active:false,content:'two'}, @@ -283,7 +283,7 @@ describe('carousel', function() { testSlideActive(2); }); - it('should hide navigation when wrap false', function () { + it('should hide navigation when wrap is false', function () { scope.slides = [ {active:false,content:'one'}, {active:false,content:'two'}, From 5bf9aa559ee6ccc3591fe6bf19b175c40b47c902 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 21:47:13 +0100 Subject: [PATCH 7/8] feat(carousel): reacting to wrap changes --- src/carousel/carousel.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/carousel/carousel.js b/src/carousel/carousel.js index c48699f8e0..a851a7d700 100644 --- a/src/carousel/carousel.js +++ b/src/carousel/carousel.js @@ -25,7 +25,7 @@ angular.module('ui.bootstrap.carousel', []) var nextIndex = self.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { - direction = nextIndex > $scope.getCurrentIndex() ? 'next' : 'prev'; + direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } if (nextSlide && nextSlide !== self.currentSlide) { goNext(); @@ -67,7 +67,7 @@ angular.module('ui.bootstrap.carousel', []) } } - $scope.getCurrentIndex = function() { + self.getCurrentIndex = function() { if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { return +self.currentSlide.index; } @@ -85,7 +85,7 @@ angular.module('ui.bootstrap.carousel', []) $scope.nextActive = true; return; } - else if ($scope.getCurrentIndex() + 1 < slides.length) + else if (self.getCurrentIndex() + 1 < slides.length) { $scope.nextActive = true; return; @@ -106,7 +106,7 @@ angular.module('ui.bootstrap.carousel', []) $scope.prevActive = true; return; } - else if ($scope.getCurrentIndex() - 1 >= 0) + else if (self.getCurrentIndex() - 1 >= 0) { $scope.prevActive = true; return; @@ -130,7 +130,7 @@ angular.module('ui.bootstrap.carousel', []) // if wrap is not active stops the next action if ($scope.nextActive) { - var newIndex = ($scope.getCurrentIndex() + 1) % slides.length; + var newIndex = (self.getCurrentIndex() + 1) % slides.length; //Prevent this user-triggered transition from occurring if there is already one in progress if (!$scope.$currentTransition) { @@ -143,7 +143,7 @@ angular.module('ui.bootstrap.carousel', []) // if wrap is not active stops the next action if ($scope.prevActive) { - var newIndex = $scope.getCurrentIndex() - 1 < 0 ? slides.length - 1 : $scope.getCurrentIndex() - 1; + var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; //Prevent this user-triggered transition from occurring if there is already one in progress if (!$scope.$currentTransition) { @@ -156,6 +156,13 @@ angular.module('ui.bootstrap.carousel', []) return self.currentSlide === slide; }; + $scope.$watch('wrap', updateWrapStatus); + + function updateWrapStatus() { + $scope.wrapValue = angular.isUndefined($scope.wrap)?true:$scope.wrap; + self.updateCarouselControls(); + } + $scope.$watch('interval', restartTimer); $scope.$on('$destroy', resetTimer); From b206f4129f7cfd4183000214b216207dadea4cd8 Mon Sep 17 00:00:00 2001 From: Rafael Pereira Date: Sat, 11 Apr 2015 22:07:47 +0100 Subject: [PATCH 8/8] fix(carousel): fix typo --- src/carousel/carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/carousel/carousel.js b/src/carousel/carousel.js index a851a7d700..012e3da979 100644 --- a/src/carousel/carousel.js +++ b/src/carousel/carousel.js @@ -252,7 +252,7 @@ angular.module('ui.bootstrap.carousel', []) * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. * @param {boolean=} noTransition Whether to disable transitions on the carousel. * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). - * €param {boolean=} wrap Whether the carousel should cycle continuously or have hard stops + * @param {boolean=} wrap Whether the carousel should cycle continuously or have hard stops * * @example