Multi checkbox directive

So,lately I was working on different  areas of my application and suddenly  I’ve noticed something weird,there were lot’s of places where multi-checkbox template was used over and over again.

This made me thinking that what if I have a common template for  multiple check-boxes which functions similar to what my app requires and can be so generic that it can handle any type of list data source.Then I started writing a custom directive which reduces my JavaScript logic, redundantly written every time when someone needs to perform this function.

Below are the few key steps which we require to build our custom multi checkbox directive:

  • Let’s decide what type of HTML component our directive should behave,let’s make it act as both element and as attribute here.
mainApp.directive('multiCheckbox', ['$log', '$filter', '$timeout', function($log, $filter, $timeout) {

return {

restrict: 'EA',  // E-element & A - attribute
  • Now let’s insert out custom HTML template.which will replace our directive when compiled.
templateUrl: multiCheckBoxTemplate.html,
replace: true,  //to replace our custom template in place of tag 
transclude: false,//make it true if we want to insert anything  btw directive tags
  • One of the master step is creating a scope,so there are 3 different ways of handling a directive scope like  .
scope:false //parent scope is used by directive
scope:true // child scope is created 
// for our case we need isolate scope to re use this directive again ..
scope: {  //isolate scope created 
            selectedlist: '=', // dummy list
            orginallist: '=', // data source 'list'
            value: '@', //object to go into selected list 
            label: '@', // object to display with checkbox
            all: '@', // whether to display 'All' option or not
            sortBy: '@' // sort the list by..
        },

As of now,I’m not explaining how many different ways we can build custom directive scope but for this objective we are using ‘=’ for two-way data binding and ‘@’ which reads the attribute value.

  • Moving to our next big step which is to manipulate DOM elements where directive is present., for this we will be using link function.
 link: function($scope, element, attrs) {
       $scope.checkbox = {};
       $scope.checkbox.all = false; //set 'All' checkbox to false
       //to show/hide 'All' checkbox
       $scope.checkbox.showAll = $scope.all == 'true' ? true : false;
                    
 //function called on click of check box
  $scope.toggle = function(index) {
        var item = $scope.list[index];
 var i =$scope.selectedlist.length > 0 ?      $scope.selectedlist.indexOf(item.value):-1;
        item.checked = !item.checked;
  if (!item.checked) {
       $scope.selectedlist.splice(i, 1);//remove item if unchecked
       $scope.checkbox.all = false;//make 'All' to uncheck too
 } else if (item.checked) {
        $scope.selectedlist.push(item.value);//add item if checked
 }
     };
 //function called when 'All' checkbox is checked
   $scope.selectAll = function() {
       var totalList = $scope.list;
       $scope.selectedlist = [];
       //if selected add all items 
       //if unchecked remove all items from selected list
       angular.forEach(totalList, function(item) {
          item.checked = $scope.checkbox.all;
             if (item.checked) {
                 $scope.selectedlist.push(item.value);
             } else {
                 $scope.selectedlist = [];
             }
      });
    };

So,until now in this link function we wrote two functions which will manipulate our selected list on check & uncheck of check box in all possible scenario’s and whether to display the ‘All’ option or not.

  • Coming to our last step in writing our directive, which is crucial for entire thing is to watch our source list and prepare the list with label ,value and checked values as objects and sort them accordingly to display multiple check boxes  .
//always watch my source list if it has been modified and update back if so..
     $scope.$watch('orginallist', function(value) {           
        //sort accordingly..
        value = $filter('orderBy')(value, $scope.sortBy);
        $scope.list = [];
        if (angular.isArray(value)) {
            angular.forEach(value, function(item) {
                  $scope.list.push({
                       value: item[$scope.value],
                       label: item[$scope.label],
                       checked: item.checked
             });
          });
         }
       }, true);
     //clear 'All' checkbox value if all items are de selected
     $scope.$watch('selectedlist', function(value) {
     if (!angular.isArray(value) || (angular.isArray(value) && value.length <= 0)) {
                    $scope.checkbox.all = false;
       }
    }, true);
   }
 };

We have written two watchers, one for selected list and another for original list.In the case of original list we want to prepare the display list when ever there is any change in my data source so that we will have updated list and whenever check boxes are deselected we want to remove the tick from ‘All’ check box.

Phew ! our custom directive is almost completed,so that leaves us with preparing data source list and calling our directive from HTML.Let’s do that.

 

var mainApp = angular.module("mainApp", []);
//create a controller and set the data source...
mainApp.controller('MainCtrl', ['$scope', '$rootScope', function($scope, $rootScope) {

    $scope.req = {};
    $scope.req.selectedList = [];
    $scope.req.sourceList = [{
        'code': 1,
        'desc': 'A'
    }, {
        'code': 2,
        'desc': 'B'
    }, {
        'code': 6,
        'desc': 'F'
    }, {
        'code': 7,
        'desc': 'G'
    }, {
        'code': 3,
        'desc': 'C'
    }, {
        'code': 4,
        'desc': 'D'
    }, {
        'code': 5,
        'desc': 'E'
    }, ];

}]);

Our html templates  will look like this:

index.html

<body ng-app="mainApp" ng-controller="MainCtrl">
 <h1>Multi Check box</h1>
 <multi-checkbox selectedlist="req.selectedList" orginallist="req.sourceList" value="code" label="desc" all="true" sort-by="desc"></multi-checkbox>
 <pre ng-cloak>{{req.selectedList |json}}</pre>
</body>

multiCheckboxTemplate.html

 multiCheckbox

Okay let’s see what’s happening at index.html part,we have passed our source list , dummy list and declared what to display (label) and what to insert(value) in selected list.By saying “all=’true’ ” means we want to display ‘All’ option,set to false if  you don’t need.For sorting/ordering the list by particular object we have specified as “sort-by=’desc'”,i.e the list will be in the ASC order of descriptions provided in the source list.

Finally ! 🙂 we are done with our multi check box custom directive which is  both generic and dynamic in nature, as it allows me to decide ordering sequence, to select all and no java script logic is required to fetch selected check boxes list as it will be generated automatically.

Here  is the link to plunker Multi Checkbox directive have  fun time playing with it !

Cheers 🙂

Advertisements