Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Add wrap option to carousel #3528

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 83 additions & 10 deletions src/carousel/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ angular.module('ui.bootstrap.carousel', [])
currentIndex = -1,
currentInterval, isPlaying;
self.currentSlide = null;

// setting wrap as false if undefined
$scope.wrapValue = angular.isUndefined($scope.wrap)?true:$scope.wrap;
$scope.nextActive = true;
$scope.prevActive = true;

var destroyed = false;
/* direction: "prev" or "next" */
Expand All @@ -24,7 +29,9 @@ angular.module('ui.bootstrap.carousel', [])
}
if (nextSlide && nextSlide !== self.currentSlide) {
goNext();
self.updateCarouselControls();
}

function goNext() {
// Scope has been destroyed, stop here.
if (destroyed) { return; }
Expand Down Expand Up @@ -53,7 +60,7 @@ 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];
}
Expand All @@ -67,33 +74,95 @@ angular.module('ui.bootstrap.carousel', [])
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.wrapValue)
{
$scope.nextActive = true;
return;
}
else if (self.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.wrapValue)
{
$scope.prevActive = true;
return;
}
else if (self.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 = (self.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 = 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) {
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');
}
}
};

$scope.isActive = function(slide) {
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);

Expand Down Expand Up @@ -146,6 +215,7 @@ angular.module('ui.bootstrap.carousel', [])
} else {
slide.active = false;
}
self.updateCarouselControls();
};

self.removeSlide = function(slide) {
Expand All @@ -166,6 +236,7 @@ angular.module('ui.bootstrap.carousel', [])
} else if (currentIndex > index) {
currentIndex--;
}
self.updateCarouselControls();
};

}])
Expand All @@ -181,6 +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
*
* @example
<example module="ui.bootstrap">
Expand Down Expand Up @@ -219,7 +291,8 @@ angular.module('ui.bootstrap.carousel', [])
scope: {
interval: '=',
noTransition: '=',
noPause: '='
noPause: '=',
wrap: '='
}
};
}])
Expand Down
2 changes: 2 additions & 0 deletions src/carousel/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<carousel>` element with `<slide>` 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.
9 changes: 8 additions & 1 deletion src/carousel/docs/demo.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div ng-controller="CarouselDemoCtrl">
<div style="height: 305px">
<carousel interval="myInterval">
<carousel interval="myInterval" wrap="myWrap">
<slide ng-repeat="slide in slides" active="slide.active">
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
Expand All @@ -17,6 +17,13 @@ <h4>Slide {{$index}}</h4>
<div class="col-md-6">
Interval, in milliseconds: <input type="number" class="form-control" ng-model="myInterval">
<br />Enter a negative number or 0 to stop the interval.
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="checkbox">
Wrap (cycle continuously) <input type="checkbox" ng-model="myWrap">
</label>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions src/carousel/docs/demo.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
62 changes: 62 additions & 0 deletions src/carousel/test/carousel.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,68 @@ describe('carousel', function() {
testSlideActive(1);
});

it('should stop navigation when wrap is false', function () {
scope.slides = [
{active:false,content:'one'},
{active:false,content:'two'},
{active:false,content:'three'}
];
elm = $compile(
'<carousel interval="interval" wrap="false">' +
'<slide ng-repeat="slide in slides" active="slide.active">' +
'{{slide.content}}' +
'</slide>' +
'</carousel>'
)(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 is false', function () {
scope.slides = [
{active:false,content:'one'},
{active:false,content:'two'},
{active:false,content:'three'}
];
elm = $compile(
'<carousel interval="interval" wrap="false">' +
'<slide ng-repeat="slide in slides" active="slide.active">' +
'{{slide.content}}' +
'</slide>' +
'</carousel>'
)(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);
Expand Down
4 changes: 2 additions & 2 deletions template/carousel/carousel.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<li ng-repeat="slide in slides | orderBy:'index' track by $index" ng-class="{active: isActive(slide)}" ng-click="select(slide)"></li>
</ol>
<div class="carousel-inner" ng-transclude></div>
<a class="left carousel-control" ng-click="prev()" ng-show="slides.length > 1"><span class="glyphicon glyphicon-chevron-left"></span></a>
<a class="right carousel-control" ng-click="next()" ng-show="slides.length > 1"><span class="glyphicon glyphicon-chevron-right"></span></a>
<a class="left carousel-control" ng-click="prev()" ng-show="prevActive && slides.length > 0"><span class="glyphicon glyphicon-chevron-left"></span></a>
<a class="right carousel-control" ng-click="next()" ng-show="nextActive && slides.length > 0"><span class="glyphicon glyphicon-chevron-right"></span></a>
</div>