Matt RaibleMatt Raible is a Web Architecture Consultant specializing in open source frameworks.

The JHipster Mini-Book The JHipster Mini-Book is a guide to getting started with hip technologies today: AngularJS, Bootstrap, and Spring Boot. All of these frameworks are wrapped up in an easy-to-use project called JHipster.

This book shows you how to build an app with JHipster, and guides you through the plethora of tools, techniques and options you can use. Furthermore, it explains the UI and API building blocks so you understand the underpinnings of your great application.

For book updates, follow @jhipster-book on Twitter.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

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.

Carousel Diagram

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).

Smark Chunking Carousel

Posted in The Web at Mar 15 2016, 09:47:30 AM MDT Add a Comment
Comments:

Post a Comment:
  • HTML Syntax: Allowed