Skip to content

Commit

Permalink
refact(carousel): Change to using active binding for current slide
Browse files Browse the repository at this point in the history
  • Loading branch information
ajoslin committed Jan 15, 2013
1 parent 6058e18 commit 27c872a
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 116 deletions.
2 changes: 1 addition & 1 deletion misc/demo-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="description" content="AngularJS native directives for Twitter Bootstrap">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

<script src="http://ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion%>/angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
<script src="ui-bootstrap-tpls-<%= version%>.min.js"></script>
<script src="assets/plunker.js"></script>
<script>
Expand Down
121 changes: 66 additions & 55 deletions src/carousel/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@
*
* The carousel has all of the function that the original Bootstrap carousel has, except for animations.
*
* For no interval set the interval to non-number
* Template: <carousel interval="none"><slide active="slide.active">{{anything}}</slide></carousel>
* To change the carousel from outside the carousel itself add in the current-index attribute
* Template: <carousel interval="none" current-index="int"><slide active="slide.active">{{anything}}</slide></carousel>
* For no interval set the interval to non-number, or milliseconds of desired interval
* Template: <carousel interval="none"><slide>{{anything}}</slide></carousel>
* To change the carousel's active slide set the active attribute to true
* Template: <carousel interval="none"><slide active="someModel">{{anything}}</slide></carousel>
*/
angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
var slides = $scope.slides = [],
self = this,
currentTimeout, currentSlide, isPlaying;
$scope.currentIndex = -1;
var self = this,
slides = self.slides = [],
currentIndex = -1,
currentTimeout, isPlaying;
self.currentSlide = null;

/* direction: "prev" or "next" */
self.select = function(index, direction) {
var nextSlide = slides[index];
if (nextSlide && nextSlide !== currentSlide) {
self.select = function(nextSlide, direction) {
var nextIndex = slides.indexOf(nextSlide);
//Decide direction if it's not given
if (direction === undefined) {
direction = nextIndex > currentIndex ? "next" : "prev";
}
if (nextSlide && nextSlide !== self.currentSlide) {
if ($scope.$currentTransition) {
$scope.$currentTransition.cancel();
//Timeout so ng-class in template has time to fix classes for finished slide
Expand All @@ -30,63 +35,73 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
}
function goNext() {
//If we have a slide to transition from and we have a transition type and we're allowed, go
if (currentSlide && angular.isString(direction) && !$scope.noTransition) {
if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
//We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
nextSlide.$element.addClass(direction);
nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow
angular.extend(nextSlide, {direction: direction, sliding: true});
angular.extend(currentSlide||{}, {direction: direction});

//Set all other slides to stop doing their stuff for the new transition
angular.forEach(slides, function(slide) {
angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
});
angular.extend(nextSlide, {direction: direction, active: true, entering: true});
angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});

$scope.$currentTransition = $transition(nextSlide.$element, {});
//We have to create new pointers inside a closure so next/current don't change
//We have to create new pointers inside a closure since next & current will change
(function(next,current) {
$scope.$currentTransition.then(
function(){ transitionDone(next, current); },
function(){ transitionDone(next, current); }
);
}(nextSlide, currentSlide));
}(nextSlide, self.currentSlide));
} else {
transitionDone(nextSlide, currentSlide);
transitionDone(nextSlide, self.currentSlide);
}
currentSlide = nextSlide;
$scope.currentIndex = self.selectedIndex = index;
self.currentSlide = nextSlide;
currentIndex = nextIndex;
//every time you change slides, reset the timer
restartTimer();
}
function transitionDone(next, current) {
angular.extend(next, {active: true, sliding: false, direction: null});
angular.extend(current||{}, {active: false, direction: null});
angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
$scope.$currentTransition = null;
}
};

/* Allow outside people to call indexOf on slides array */
self.indexOfSlide = function(slide) {
return slides.indexOf(slide);
};

$scope.next = function() {
var newIndex = ($scope.currentIndex + 1) % slides.length;
return self.select(newIndex, 'next');
var newIndex = (currentIndex + 1) % slides.length;
return self.select(slides[newIndex], 'next');
};

$scope.prev = function() {
var newIndex = $scope.currentIndex - 1 < 0 ? slides.length - 1 : $scope.currentIndex - 1;
return self.select(newIndex, 'prev');
var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
return self.select(slides[newIndex], 'prev');
};

function getInterval() {
var i = +$scope.interval;
return (isNaN(i) || i<=0) ? 5000 : i;
}
function playCarousel() {
if (isPlaying) {
$scope.next();
restartTimer();
} else {
$scope.pause();
}
}
$scope.$watch('interval', restartTimer);
function restartTimer() {
if (currentTimeout) {
$timeout.cancel(currentTimeout);
}
currentTimeout = $timeout(playCarousel, getInterval());
function go() {
if (isPlaying) {
$scope.next();
restartTimer();
} else {
$scope.pause();
}
}
var interval = +$scope.interval;
if (!isNaN(interval) && interval>=0) {
currentTimeout = $timeout(go, interval);
}
}
$scope.play = function() {
if (!isPlaying) {
Expand All @@ -102,11 +117,11 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
};

self.addSlide = function(slide, element) {
angular.extend(slide, {$element: element});
slide.$element = element;
slides.push(slide);
//if this is the first slide or the slide is set to active, select it
if(slides.length === 1 || slide.active) {
self.select(slides.length - 1);
self.select(slides[slides.length-1]);
if (slides.length == 1) {
$scope.play();
}
Expand All @@ -121,9 +136,9 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
slides.splice(index, 1);
if (slides.length > 0 && slide.active) {
if (index >= slides.length) {
self.select(index - 1);
self.select(slides[index-1]);
} else {
self.select(index);
self.select(slides[index]);
}
}
};
Expand All @@ -137,18 +152,8 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
require: 'carousel',
templateUrl: 'template/carousel/carousel.html',
scope: {
currentIndex: '=',
interval: '=',
noTransition: '=' //This really exists so unit tests can test without transitions
},
link: function(scope, elm, attrs, carouselCtrl) {
scope.$watch('currentIndex', function(idx) {
//If it's a number and it's not same as current selection, change selection
if (+idx == idx && idx != carouselCtrl.selectedIndex) {
var selected = carouselCtrl.selectedIndex;
carouselCtrl.select(idx, idx > selected ? "next" : "prev");
}
});
noTransition: '='
}
};
}])
Expand All @@ -160,14 +165,20 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
replace: true,
templateUrl: 'template/carousel/slide.html',
scope: {
active:'='
active: '='
},
link: function (scope, element, attrs, carouselCtrl) {
carouselCtrl.addSlide(scope, element);
//the scope is destroyed then remove the slide from the current slides array
//when the scope is destroyed then remove the slide from the current slides array
scope.$on('$destroy', function() {
carouselCtrl.removeSlide(scope);
});

scope.$watch('active', function(active) {
if (active) {
carouselCtrl.select(scope);
}
});
}
};
}]);
28 changes: 21 additions & 7 deletions src/carousel/docs/demo.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
<div ng-controller="CarouselDemoCtrl">
<carousel current-index="selected" interval="5000">
<slide ng-repeat="s in slides" active="s.active">
<img ng-src="{{s.image}}" style="margin:auto;">
<carousel interval="myInterval">
<slide ng-repeat="slide in slides" active="slide.active">
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>Slide {{$index}}</h4>
<p>{{s.text}}</p>
<p>{{slide.text}}</p>
</div>
</slide>
</carousel>
Select slide:
<a ng-repeat="i in slides" ng-click="selectSlide($index)" class="btn btn-mini">{{$index}}</a>
<br /><a class="btn btn-primary" ng-click="addSlide()">Add Slide</a>
<div class="row-fluid">
<div class="span6">
<ul>
<li><a class="btn btn-primary" ng-click="addSlide()">Add Slide</a></li>
<li ng-repeat="slide in slides">
{{$index}}: {{slide.text}}
<a ng-click="slide.active = true">[select]</a>
<a ng-click="slides.splice($index, 1)">[remove]</a>
<span ng-show="slide.active" style="color:green;">&#10003;</a>
</li>
</ul>
</div>
<div class="span6">
Interval, in milliseconds: <input ng-model="myInterval">
<br />Enter a negative or non-number to stop the interval.
</div>
</div>
</div>
22 changes: 10 additions & 12 deletions src/carousel/docs/demo.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
function CarouselDemoCtrl($scope) {
var slides = $scope.slides = [
{image: 'http://placekitten.com/325/200',text: 'Kitten.'},
{image: 'http://placekitten.com/275/200',text: 'Kitty!'},
{image: 'http://placekitten.com/375/200',text: 'Cat.'},
{image: 'http://placekitten.com/250/200',text: 'Feline!'}
$scope.myInterval = 5000;
$scope.slides = [
{image: 'http://placekitten.com/200/200',text: 'Kitten.'},
{image: 'http://placekitten.com/225/200',text: 'Kitty!'},
{image: 'http://placekitten.com/250/200',text: 'Cat.'},
{image: 'http://placekitten.com/275/200',text: 'Feline!'}
];
$scope.selectSlide = function(i) {
$scope.selected = i;
};
$scope.addSlide = function() {
slides.push({
image: 'http://placekitten.com/'+(200+20*slides.length)+'/200',
text: ['More','Extra','Additional','Surplus'][Math.floor(Math.random()*4)] + ' ' +
['Cats', 'Kittys', 'Felines', 'Cute Things'][Math.floor(Math.random()*4)]
$scope.slides.push({
image: 'http://placekitten.com/'+(200+25*Math.floor(Math.random()*4))+'/200',
text: ['More','Extra','Lots of','Surplus'][Math.floor(Math.random()*4)] + ' ' +
['Cats', 'Kittys', 'Felines', 'Cutes'][Math.floor(Math.random()*4)]
});
};
}
Loading

0 comments on commit 27c872a

Please sign in to comment.