How to Implement a Smart Chunking Bootstrap Carousel with AngularJS
I'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 a SmartChunking
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).



Posted by SmartAsh on January 23, 2018 at 01:03 AM MST #
Posted by Matt Raible on February 11, 2018 at 07:18 PM MST #