How to Implement a Smart Chunking Bootstrap Carousel With AngularJS
Read on and learn how to create a "smart chunking carousel" (one that displays the optimal amount of images based on a user's screen size) by following Matt Raible's tutorial.
Join the DZone community and get the full member experience.
Join For FreeI've been helping a client develop a project management application for the last several months. One of the features I implemented uses UI Bootstrap's carousel directive to display a list of project templates to choose from when creating a new project. Rather than displaying one at a time, we wanted to display as many as the user's screen would allow. That is, if they were on a large monitor, we wanted to display five templates, a medium size monitor would display three and so on. This is a story of how I implemented a smart chunking carousel.
To begin, I made it possible to show groups of items in the carousel using array chunking.
function chunk(arr, size) {
var newArr = [];
var arrayLength = arr.length;
for (var i = 0; i < arrayLength; i += size) {
newArr.push(arr.slice(i, i + size));
}
return newArr;
}
Using UI Bootstrap's example code, I created $scope.chunkedSlides
from $scope.slides
.
$scope.chunkSize = 5;
// chunk slides so there's two per chunk by default
$scope.chunkedSlides = chunk($scope.slides, $scope.chunkSize);
Next, I changed the HTML template to read the grouped slides, and show each one.
<uib-carousel active="active" interval="0" no-wrap="true">
<uib-slide ng-repeat="row in chunkedSlides">
<div class="row">
<div ng-repeat="slide in row track by $index" class="slide">
<img ng-src="{{slide.image}}">
<div class="carousel-caption">
<h4>Slide {{slide.id}}</h4>
<p>{{slide.text}}</p>
</div>
</div>
</div>
</uib-slide>
</uib-carousel>
This was enough to get five squares on a large monitor.
However, I wanted to go further and reduce the number per group on smaller monitors. I created aSmartChunking
service that defined how many per group for each possible width.
angular.module('app').service('SmartChunking', function() {
var large = 1600;
var medium = 1200;
var small = 1024;
var xsmall = 800;
this.getChunkSize = function(width) {
var chunkSize;
if (width >= large) {
chunkSize = 5;
} else if (width >= medium) {
chunkSize = 4;
} else if (width >= small) {
chunkSize = 3;
} else if (width >= xsmall) {
chunkSize = 2;
} else {
chunkSize = 1;
}
return chunkSize;
}
});
I wrote a smart-chunking
directive to fire an event with the chunk size.
angular.module('app').directive('smartChunking', function($window, SmartChunking) {
return {
restrict: 'A',
link: function($scope) {
var w = angular.element($window);
// window.outerWidth works on desktop, screen.height on iPad (width returns 768)
var width = ($window.outerWidth > 0) ? $window.outerWidth : screen.height;
var chunkSize = SmartChunking.getChunkSize(width);
if (chunkSize !== 5) {
$scope.$emit('change-chunk-size', chunkSize);
}
$scope.getWidth = function() {
return ($window.outerWidth > 0) ? $window.outerWidth : screen.width;
};
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
if (newValue !== oldValue) {
var chunkSize = SmartChunking.getChunkSize(newValue);
$scope.$emit('change-chunk-size', chunkSize);
}
});
w.bind('resize', function() {
$scope.$apply();
});
}
}
});
Then I added a listener for this in the controller that populated the carousel.
$scope.$on('change-chunk-size', function(event, data) {
if (data !== $scope.chunkSize) {
$scope.chunkedSlides = chunk($scope.slides, data);
$scope.chunkSize = data;
}
});
The final step was adding the smark-chunking
directive to each slide and dynamically determining its col-sm-*
class.
<div ng-repeat="slide in row track by $index" class="slide" ng-class="getSlideClass(chunkSize)" smart-chunking>
The controller contains a map of classes that map to chunk sizes:
var classMap = {
5: 'col-sm-2',
4: 'col-sm-3',
3: 'col-sm-4',
2: 'col-sm-5',
};
$scope.getSlideClass = function(chunkSize) {
if (classMap[chunkSize]) {
return classMap[chunkSize];
} else {
return 'col-sm-10';
}
}
I did find that adding some CSS made things look quite a bit better.
.carousel-caption {
padding-bottom: 0;
}
.carousel-control.left,
.carousel-control.right {
background-image: none;
}
.carousel-indicators {
display: none;
}
.carousel-inner {
padding-left: 10%;
overflow: visible;
}
.carousel-control .glyphicon-chevron-left,
.carousel-control .glyphicon-chevron-right {
font-size: 100px;
margin-top: -60px;
font-style: normal;
font-weight: 100;
}
.carousel-control .glyphicon-chevron-left {
margin-left: -100px;
}
.carousel-control .glyphicon-chevron-right {
margin-right: -40px;
}
/* make slide widths more responsive */
@media only screen and (min-width: 1600px) {
.col-sm-2 {
width: 18%;
}
}
@media only screen and (min-width: 1200px) {
.col-sm-3 {
width: 22%;
}
.carousel-control .glyphicon-chevron-left {
margin-left: -70px;
}
.carousel-control .glyphicon-chevron-right {
margin-right: -20px;
}
}
@media only screen and (max-width: 1200px) {
.col-sm-4 {
width: 29%;
}
.carousel-control .glyphicon-chevron-left {
margin-left: -70px;
}
.carousel-control .glyphicon-chevron-right {
margin-right: -20px;
}
}
@media only screen and (max-width: 800px) {
.col-sm-10 {
width: 90%;
}
}
I hope this tip helps you if you need to implement a similar feature. I've published a demo on Plunkr (best experienced in embedded view).
Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments