<!DOCTYPE html>
<html ng-app="myApp">
  <head>
    <!--<script data-require="angular.js@1.2.15" data-semver="1.2.15" src="https://code.angularjs.org/1.2.15/angular.js"></script>-->
      <!--<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular-animate.min.js"></script>-->
      <!--<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js"></script>-->
      <script src="https://code.angularjs.org/1.3.15/angular.js"></script>
    <!--<link rel="stylesheet" href="style.css" />-->
    <script src="isteven-multi-select-4.0.0.js"></script>
    <link href="isteven-multi-select-4.0.0.css" rel="stylesheet">
    <script src="script.js"></script>
  
  </head>

  <body>
    <div>
      <div ng-controller="myController">
        <div ng-repeat="item in myArray">
          <span ng-bind="item.name"></span>
        </div>
        <span>Two dropdowns but one output model works</span>
        <div    isteven-multi-select
                input-model="myArray"
                output-model="newArray"
                button-label="icon name"
                item-label="icon name maker"
                tick-property="ticked"
        >
        </div>
        <div    isteven-multi-select
                input-model="myArray2"
                output-model="newArray2"
                button-label="icon name"
                item-label="icon name maker"
                tick-property="ticked"
        >  
        </div>
        <pre ng-bind="newArray |json"></pre>
        <pre ng-bind="newArray2 |json"></pre>
        <pre ng-bind="newArray3 |json"></pre>
        
        <span>two dropdowns but using an ng-repeat does not work</span>
        <div ng-repeat="group in groupArray">
          <div  isteven-multi-select
                input-model="group.suites"
                output-model="newArray3.blah"
                button-label="icon name"
                item-label="icon name maker"
                tick-property="ticked"
          >
          </div>
        </div>
      </div>
    </div>
  </body>

</html>
var app = angular.module('myApp', ['isteven-multi-select']);

app.controller('myController', function($scope){
    $scope.myArray = [
      {name:'item1'},
      {name:'item2'}
    ];
    
    $scope.myArray2 = [
      {name:'item1'},
      {name:'item2'}
    ];
    
    $scope.newArray3 = {};
    $scope.newArray3.blah = [];

    $scope.newArray = [];
    
    $scope.groupArray = [
      {
        groupName:"blah1",
        suites:[{
          name:"suite1"
        },{
          name:"suite2"
        }]
      },{
        groupName:"blah2",
        suites:[{
          name:"suite1blah2"
        },{
          name:"suite2blah2"
        }]
      }
    ]

    //$scope.groupArray = [
    //    {
    //        groupName:"blah1",
    //        suites:[{
    //            name:"suite1"
    //        },{
    //            name:"suite2"
    //        }]
    //    }
    //]
});
/* Styles go here */

/* 
 * Angular JS Multi Select
 * Creates a dropdown-like button with checkboxes. 
 *
 * Project started on: Tue, 14 Jan 2014 - 5:18:02 PM
 * Current version: 3.0.0
 * 
 * Released under the MIT License
 * --------------------------------------------------------------------------------
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions: 
 *
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 * --------------------------------------------------------------------------------
 */

'use strict'

angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' , [ '$sce', '$timeout', '$templateCache', function ( $sce, $timeout, $templateCache ) {
    return {
        restrict: 
            'AE',

        scope: 
        {   
            // models
            inputModel      : '=',
            outputModel     : '=',

            // settings based on attribute
            buttonLabel     : '@',
            directiveId     : '@',
            helperElements  : '@',            
            isDisabled      : '=',
            itemLabel       : '@',
            maxLabels       : '@',
            orientation     : '@',
            selectionMode   : '@',    
            minSearchLength : '@',  // 3.0.0 - OK                 
                                                         
            // settings based on input model property 
            tickProperty    : '@',
            disableProperty : '@',
            groupProperty   : '@',
            searchProperty  : '@',  // 3.0.0 - OK
            maxHeight       : '@',            

            // callbacks
            onClear         : '&',  // 3.0.0 - OK
            onClose         : '&',
            onSearchChange  : '&',  // 3.0.0 - OK
            onItemClick     : '&',            
            onOpen          : '&', 
            onReset         : '&',  // 3.0.0 - OK
            onSelectAll     : '&',  // 3.0.0 - OK
            onSelectNone    : '&',  // 3.0.0 - OK                     

            // i18n
            translation     : '='   // 3.0.0 - OK
        },

        template: 
            '<span class="multiSelect inlineBlock" id={{directiveId}}>' +
                '<button type="button"' +
                    'ng-click="toggleCheckboxes( $event ); refreshSelectedItems(); refreshButton(); prepareGrouping; prepareIndex();"' +
                    'ng-bind-html="varButtonLabel">' +
                '</button>' +
                '<div class="checkboxLayer">' +

                    '<div class="helperContainer" ng-if="displayHelper( \'filter\' ) || displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
                        '<div class="line" ng-if="displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +

                            '<button type="button" class="helperButton"' +
                                'ng-if="!isDisabled && displayHelper( \'all\' )"' +
                                'ng-click="select( \'all\', $event );"' +
                                'ng-bind-html="lang.selectAll">' +
                            '</button>'+

                            '<button type="button" class="helperButton"' +
                                'ng-if="!isDisabled && displayHelper( \'none\' )"' +
                                'ng-click="select( \'none\', $event );"' +
                                'ng-bind-html="lang.selectNone">' +
                            '</button>'+

                            '<button type="button" class="helperButton reset"' +
                                'ng-if="!isDisabled && displayHelper( \'reset\' )"' +
                                'ng-click="select( \'reset\', $event );"' +
                                'ng-bind-html="lang.reset">'+
                            '</button>' +
                        '</div>' +

                        '<div class="line" style="position:relative" ng-if="displayHelper( \'filter\' )">'+
                                                    
                            '<input placeholder="{{lang.search}}" type="text"' +
                                'ng-click="select( \'filter\', $event )" '+
                                'ng-model="inputLabel.labelFilter" '+
                                'ng-change="searchChanged()" class="inputFilter"'+
                                '/>'+
            
                            '<button type="button" class="clearButton" ng-click="clearClicked( $event )" >×</button> '+
                        '</div> '+
                    '</div> '+
            
                    '<div class="checkBoxContainer">'+
                        '<div '+
                            'ng-repeat="item in filteredModel | filter:removeGroupEndMarker" class="multiSelectItem"'+
                            'ng-class="{selected: item[ tickProperty ], horizontal: orientationH, vertical: orientationV, multiSelectGroup:item[ groupProperty ], disabled:itemIsDisabled( item )}"'+
                            'ng-click="syncItems( item, $event, $index );" '+
                            'ng-mouseleave="removeFocusStyle( tabIndex );"> '+
            
                            '<div class="acol" ng-if="item[ spacingProperty ] > 0" ng-repeat="i in numberToArray( item[ spacingProperty ] ) track by $index">'+
                            
                        '</div>  '+
            
                        '<div class="acol">'+

                            '<label>'+                                
                                '<input class="checkbox focusable" type="checkbox" '+
                                    'ng-disabled="itemIsDisabled( item )" '+
                                    'ng-checked="item[ tickProperty ]" '+
                                    'ng-click="syncItems( item, $event, $index )" />'+

                                '<span '+
                                    'ng-class="{disabled:itemIsDisabled( item )}" '+
                                    'ng-bind-html="writeLabel( item, \'itemLabel\' )">'+
                                '</span>'+
                            '</label>'+
                        '</div>'+
            
                        '<span class="tickMark" ng-if="item[ groupProperty ] !== true && item[ tickProperty ] === true">✔</span>'+
                    '</div>'+
                '</div>'+
            '</div>'+
        '</span>',                    

        link: function ( $scope, element, attrs ) {                       

            $scope.backUp           = [];
            $scope.varButtonLabel   = '';               
            $scope.spacingProperty  = '';
            $scope.indexProperty    = '';                        
            $scope.orientationH     = false;
            $scope.orientationV     = true;
            $scope.filteredModel    = [];
            $scope.inputLabel       = { labelFilter: '' };                        
            $scope.tabIndex         = 0;            
            $scope.lang             = {};
            $scope.localModel       = [];

            var 
                prevTabIndex        = 0,
                helperItems         = [],
                helperItemsLength   = 0,
                checkBoxLayer       = '',
                scrolled            = false,
                selectedItems       = [],
                formElements        = [],
                vMinSearchLength    = 0,
                clickedItem         = null;

            // v3.0.0
            // clear button clicked
            $scope.clearClicked = function( e ) {                
                $scope.inputLabel.labelFilter = '';
                $scope.updateFilter();
                $scope.select( 'clear', e );                
            }

            // A little hack so that AngularJS ng-repeat can loop using start and end index like a normal loop
            // http://stackoverflow.com/questions/16824853/way-to-ng-repeat-defined-number-of-times-instead-of-repeating-over-array
            $scope.numberToArray = function( num ) {
                return new Array( num );   
            }

            // Call this function when user type on the filter field
            $scope.searchChanged = function() {                                                
                if ( $scope.inputLabel.labelFilter.length < vMinSearchLength && $scope.inputLabel.labelFilter.length > 0 ) {
                    return false;
                }                
                $scope.updateFilter();
            }

            $scope.updateFilter = function()
            {      
                // we check by looping from end of input-model
                $scope.filteredModel = [];
                var i = 0;

                if ( typeof $scope.localModel === 'undefined' ) {
                    return false;                   
                }

                for( i = $scope.localModel.length - 1; i >= 0; i-- ) {

                    // if it's group end, we push it to filteredModel[];
                    if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.localModel[ i ][ $scope.groupProperty ] === false ) {
                        $scope.filteredModel.push( $scope.localModel[ i ] );
                    }
                    
                    // if it's data 
                    var gotData = false;
                    if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] === 'undefined' ) {                        
                        
                        // If we set the search-key attribute, we use this loop. 
                        if ( typeof attrs.searchProperty !== 'undefined' && $scope.searchProperty !== '' ) {

                            for (var key in $scope.localModel[ i ]  ) {
                                if ( 
                                    typeof $scope.localModel[ i ][ key ] !== 'boolean'
                                    && String( $scope.localModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0                                     
                                    && $scope.searchProperty.indexOf( key ) > -1
                                ) {
                                    gotData = true;
                                    break;
                                }
                            }                        
                        }
                        // if there's no search-key attribute, we use this one. Much better on performance.
                        else {
                            for ( var key in $scope.localModel[ i ]  ) {
                                if ( 
                                    typeof $scope.localModel[ i ][ key ] !== 'boolean'
                                    && String( $scope.localModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0                                     
                                ) {
                                    gotData = true;
                                    break;
                                }
                            }                        
                        }

                        if ( gotData === true ) {    
                            // push
                            $scope.filteredModel.push( $scope.localModel[ i ] );
                        }
                    }

                    // if it's group start
                    if ( typeof $scope.localModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.localModel[ i ][ $scope.groupProperty ] === true ) {

                        if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] !== 'undefined' 
                                && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] === false ) {
                            $scope.filteredModel.pop();
                        }
                        else {
                            $scope.filteredModel.push( $scope.localModel[ i ] );
                        }
                    }
                }                

                $scope.filteredModel.reverse();  
                
                $timeout( function() {                    

                    $scope.getFormElements();               
                    
                    // Callback: on filter change                      
                    if ( $scope.inputLabel.labelFilter.length > vMinSearchLength ) {

                        var filterObj = [];

                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value !== 'undefined' ) {                   
                                if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {                                                                    
                                    var tempObj = angular.copy( value );
                                    var index = filterObj.push( tempObj );                                
                                    delete filterObj[ index - 1 ][ $scope.indexProperty ];
                                    delete filterObj[ index - 1 ][ $scope.spacingProperty ];      
                                }
                            }
                        });

                        $scope.onSearchChange({ 
                            data: 
                            {
                                keyword: $scope.inputLabel.labelFilter, 
                                result: filterObj 
                            } 
                        });
                    }
                },0);
            };

            // List all the input elements.
            // This function will be called everytime the filter is updated. Not good for performance, but oh well..
            $scope.getFormElements = function() {                                     
                formElements = [];
                // Get helper - select & reset buttons
                var selectButtons = element.children().children().next().children().children()[ 0 ].getElementsByTagName( 'button' );
                // Get helper - search
                var inputField = element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'input' );
                // Get helper - clear button
                var clearButton = element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'button' );
                // Get checkboxes
                var checkboxes = element.children().children().next().children().next()[ 0 ].getElementsByTagName( 'input' );
                // Push them into global array formElements[] 
                for ( var i = 0; i < selectButtons.length ; i++ )   { formElements.push( selectButtons[ i ] ); }
                for ( var i = 0; i < inputField.length ; i++ )      { formElements.push( inputField[ i ] ); }
                for ( var i = 0; i < clearButton.length ; i++ )     { formElements.push( clearButton[ i ] ); }
                for ( var i = 0; i < checkboxes.length ; i++ )      { formElements.push( checkboxes[ i ] ); }
                
            }            

            // check if an item has $scope.groupProperty (be it true or false)
            $scope.isGroupMarker = function( item , type ) {
                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === type ) return true; 
                return false;
            }

            $scope.removeGroupEndMarker = function( item ) {
                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) return false; 
                return true;
            }
            

            // Show or hide a helper element 
            $scope.displayHelper = function( elementString ) {

                if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {

                    switch( elementString.toUpperCase() ) {
                        case 'ALL':                                                        
                            return false;                    
                            break;
                        case 'NONE':                            
                            return false;
                            break;
                        case 'RESET':
                            if ( typeof attrs.helperElements === 'undefined' ) {
                                return true;                    
                            }
                            else if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'RESET' ) >= 0 ) {
                                return true;
                            }                            
                            break;
                        case 'FILTER':
                            if ( typeof attrs.helperElements === 'undefined' ) {
                                return true;                    
                            }                            
                            if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'FILTER' ) >= 0 ) {
                                return true;
                            }
                            break;                    
                        default:                                         
                            break;
                    }                    

                    return false;
                }

                else {
                    if ( typeof attrs.helperElements === 'undefined' ) {
                        return true;                    
                    }
                    if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( elementString.toUpperCase() ) >= 0 ) {
                        return true;
                    }
                    return false;
                }                
            }                

            // call this function when an item is clicked
            $scope.syncItems = function( item, e, ng_repeat_index ) {                                      

                e.preventDefault();
                e.stopPropagation();

                // if the directive is globaly disabled, do nothing
                if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) {                                        
                    return false;
                }

                // if item is disabled, do nothing
                if ( typeof attrs.isDisabled !== 'undefined' && $scope.isDisabled === true ) {                        
                    return false;
                }                                

                // if end group marker is clicked, do nothing
                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) {
                    return false;
                }                

                var index = $scope.filteredModel.indexOf( item );       

                // if the start of group marker is clicked ( only for multiple selection! )
                //      how it works:
                //      - if, in a group, there are items which are not selected, then they all will be selected
                //      - if, in a group, all items are selected, then they all will be de-selected                
                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === true ) {                                  

                    // this is only for multiple selection, so if selection mode is single, do nothing
                    if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
                        return false;
                    }
                    
                    var i,j,k;
                    var startIndex = 0;
                    var endIndex = $scope.filteredModel.length - 1;
                    var tempArr = [];

                    // nest level is to mark the depth of the group.
                    // when you get into a group (start group marker), nestLevel++
                    // when you exit a group (end group marker), nextLevel--
                    var nestLevel = 0;                    

                    // we loop throughout the filtered model (not whole model)
                    for( i = index ; i < $scope.filteredModel.length ; i++) {  

                        // this break will be executed when we're done processing each group
                        if ( nestLevel === 0 && i > index ) 
                        {
                            break;
                        }
                    
                        if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === true ) {
                            
                            // To cater multi level grouping
                            if ( tempArr.length === 0 ) {
                                startIndex = i + 1; 
                            }                            
                            nestLevel = nestLevel + 1;
                        }                                                

                        // if group end
                        else if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === false ) {

                            nestLevel = nestLevel - 1;                            

                            // cek if all are ticked or not                            
                            if ( tempArr.length > 0 && nestLevel === 0 ) {                                

                                var allTicked = true;       

                                endIndex = i;

                                for ( j = 0; j < tempArr.length ; j++ ) {                                
                                    if ( typeof tempArr[ j ][ $scope.tickProperty ] !== 'undefined' &&  tempArr[ j ][ $scope.tickProperty ] === false ) {
                                        allTicked = false;
                                        break;
                                    }
                                }                                                                                    

                                if ( allTicked === true ) {
                                    for ( j = startIndex; j <= endIndex ; j++ ) {
                                        if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {
                                            if ( typeof attrs.disableProperty === 'undefined' ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = false;
                                            }
                                            else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = false;
                                            }
                                        }
                                    }                                
                                }

                                else {
                                    for ( j = startIndex; j <= endIndex ; j++ ) {
                                        if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {
                                            if ( typeof attrs.disableProperty === 'undefined' ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;                                                
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = true;

                                            }                                            
                                            else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = true;
                                            }
                                        }
                                    }                                
                                }                                                                                    
                            }
                        }
            
                        // if data
                        else {                            
                            tempArr.push( $scope.filteredModel[ i ] );                                                                                    
                        }
                    }                                 
                }

                // if an item (not group marker) is clicked
                else {

                    // If it's single selection mode
                    if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
                        
                        // first, set everything to false
                        for( i=0 ; i < $scope.filteredModel.length ; i++) {                            
                            $scope.filteredModel[ i ][ $scope.tickProperty ] = false;                            
                        }        
                        for( i=0 ; i < $scope.localModel.length ; i++) {                            
                            $scope.localModel[ i ][ $scope.tickProperty ] = false;                            
                        }        
                        
                        // then set the clicked item to true
                        $scope.filteredModel[ index ][ $scope.tickProperty ] = true;

                        // we then hide the checkbox layer
                        $scope.toggleCheckboxes( e );                                                
                    }   

                    // Multiple
                    else {
                        $scope.filteredModel[ index ][ $scope.tickProperty ]   = !$scope.filteredModel[ index ][ $scope.tickProperty ];
                    }

                    // we refresh input model as well
                    var inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ];                                        
                    $scope.localModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ];                    
                }                                  

                // we execute the callback function here
                clickedItem = angular.copy( item );                                                    
                if ( clickedItem !== null ) {                        
                    $timeout( function() {
                        delete clickedItem[ $scope.indexProperty ];
                        delete clickedItem[ $scope.spacingProperty ];      
                        $scope.onItemClick( { data: clickedItem } );
                        clickedItem = null;                    
                    }, 0 );                                                 
                }                                    
                
                $scope.refreshOutputModel();
                $scope.refreshButton();                              

                // We update the index here
                prevTabIndex = $scope.tabIndex;
                $scope.tabIndex = ng_repeat_index + helperItemsLength;
                                
                // Set focus on the hidden checkbox 
                e.target.focus();

                // set & remove CSS style
                $scope.removeFocusStyle( prevTabIndex );
                $scope.setFocusStyle( $scope.tabIndex );
            }     

            // update $scope.outputModel
            $scope.refreshOutputModel = function() {            
                
                $scope.outputModel = [];

                angular.forEach( $scope.localModel, function( value, key ) {
                    if ( typeof value !== 'undefined' ) {                   
                        if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
                            if ( value[ $scope.tickProperty ] === true ) {
                                // selectedItems.push( value );                               
                                var temp = angular.copy( value );
                                var index = $scope.outputModel.push( temp );                                
                                delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ];
                                delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ];      
                            }
                        }
                    }
                });                                
            }

            // refresh button label
            $scope.refreshButton = function() {

                $scope.varButtonLabel   = '';                
                var ctr                 = 0;                  

                // refresh button label...
                if ( $scope.outputModel.length === 0 ) {
                    // https://github.com/isteven/angular-multi-select/pull/19                    
                    $scope.varButtonLabel = $scope.lang.nothingSelected;
                }
                else {                
                    var tempMaxLabels = $scope.outputModel.length;
                    if ( typeof $scope.maxLabels !== 'undefined' && $scope.maxLabels !== '' ) {
                        tempMaxLabels = $scope.maxLabels;
                    }

                    // if max amount of labels displayed..
                    if ( $scope.outputModel.length > tempMaxLabels ) {
                        $scope.more = true;
                    }
                    else {
                        $scope.more = false;
                    }                
                
                    angular.forEach( $scope.outputModel, function( value, key ) {
                        if ( typeof value !== 'undefined' ) {                        
                            if ( ctr < tempMaxLabels ) {                            
                                $scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? '</div>, <div class="buttonLabel">' : '<div class="buttonLabel">') + $scope.writeLabel( value, 'buttonLabel' );
                            }
                            ctr++;
                        }
                    });                

                    if ( $scope.more === true ) {
                        // https://github.com/isteven/angular-multi-select/pull/16
                        if (tempMaxLabels > 0) {
                            $scope.varButtonLabel += ', ... ';
                        }
                        $scope.varButtonLabel += '(' + $scope.outputModel.length + ')';                        
                    }
                }
                $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '<span class="caret"></span>' );                
            }

            // Check if a checkbox is disabled or enabled. It will check the granular control (disableProperty) and global control (isDisabled)
            // Take note that the granular control has higher priority.
            $scope.itemIsDisabled = function( item ) {
                
                if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) {                                        
                    return true;
                }
                else {             
                    if ( $scope.isDisabled === true ) {                        
                        return true;
                    }
                    else {
                        return false;
                    }
                }
                
            }

            // A simple function to parse the item label settings. Used on the buttons and checkbox labels.
            $scope.writeLabel = function( item, type ) {
                                
                // type is either 'itemLabel' or 'buttonLabel'
                var temp    = $scope[ type ].split( ' ' );                    
                var label   = '';

                angular.forEach( temp, function( value, key ) {                    
                    item[ value ] && ( label += '&nbsp;' + value.split( '.' ).reduce( function( prev, current ) {
                        return prev[ current ]; 
                    }, item ));        
                });
                if ( type.toUpperCase() === 'BUTTONLABEL' ) {
                    return label;
                }
                return $sce.trustAsHtml( label );
            }                                

            // UI operations to show/hide checkboxes based on click event..
            $scope.toggleCheckboxes = function( e ) {                                    
                
                // We grab the button
                var clickedEl = element.children()[0];

                // Just to make sure.. had a bug where key events were recorded twice
                angular.element( document ).off( 'click', $scope.externalClickListener );
                angular.element( document ).off( 'keydown', $scope.keyboardListener );                                    

                // clear filter
                $scope.inputLabel.labelFilter = '';                
                $scope.updateFilter();                                

                // The idea below was taken from another multi-select directive - https://github.com/amitava82/angular-multiselect 
                // His version is awesome if you need a more simple multi-select approach.                                

                // close
                if ( angular.element( checkBoxLayer ).hasClass( 'show' )) {                         

                    angular.element( checkBoxLayer ).removeClass( 'show' );                    
                    angular.element( clickedEl ).removeClass( 'buttonClicked' );                    
                    angular.element( document ).off( 'click', $scope.externalClickListener );
                    angular.element( document ).off( 'keydown', $scope.keyboardListener );                                    

                    // clear the focused element;
                    $scope.removeFocusStyle( $scope.tabIndex );

                    // close callback
                    $timeout( function() {
                        $scope.onClose();
                    }, 0 );

                    // set focus on button again
                    element.children().children()[ 0 ].focus();
                } 
                // open
                else                 
                {           
                    helperItems = [];
                    helperItemsLength = 0;

                    angular.element( checkBoxLayer ).addClass( 'show' );
                    angular.element( clickedEl ).addClass( 'buttonClicked' );       

                    // Attach change event listener on the input filter. 
                    // We need this because ng-change is apparently not an event listener.                    
                    angular.element( document ).on( 'click', $scope.externalClickListener );
                    angular.element( document ).on( 'keydown', $scope.keyboardListener );  

                    // to get the initial tab index, depending on how many helper elements we have. 
                    // priority is to always focus it on the input filter 
                    $scope.getFormElements();
                    $scope.tabIndex = 0;

                    var helperContainer = angular.element( element[ 0 ].querySelector( '.helperContainer' ) )[0];                
                    
                    if ( typeof helperContainer !== 'undefined' ) {
                        for ( var i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {
                            helperItems[ i ] = helperContainer.getElementsByTagName( 'BUTTON' )[ i ];
                        }
                        helperItemsLength = helperItems.length + helperContainer.getElementsByTagName( 'INPUT' ).length;
                    }
                    
                    // focus on the filter element on open. 
                    if ( element[ 0 ].querySelector( '.inputFilter' ) ) {                        
                        element[ 0 ].querySelector( '.inputFilter' ).focus();    
                        $scope.tabIndex = $scope.tabIndex + helperItemsLength - 2;
                    }
                    // if there's no filter then just focus on the first checkbox item
                    else {                                                
                        formElements[ $scope.tabIndex ].focus();                                                
                    }                       

                    // open callback
                    $scope.onOpen();
                }                            
            }
            
            // handle clicks outside the button / multi select layer
            $scope.externalClickListener = function( e ) {                   

                var targetsArr = element.find( e.target.tagName );
                for (var i = 0; i < targetsArr.length; i++) {                                        
                    if ( e.target == targetsArr[i] ) {
                        return;
                    }
                }

                angular.element( checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' );                    
                angular.element( checkBoxLayer ).removeClass( 'show' );
                angular.element( document ).off( 'click', $scope.externalClickListener ); 
                angular.element( document ).off( 'keydown', $scope.keyboardListener );                
                
                // close callback                
                $timeout( function() {
                    $scope.onClose();
                }, 0 );

                // set focus on button again
                element.children().children()[ 0 ].focus();
            }
   
            // select All / select None / reset buttons
            $scope.select = function( type, e ) {

                var helperIndex = helperItems.indexOf( e.target );
                $scope.tabIndex = helperIndex;

                switch( type.toUpperCase() ) {
                    case 'ALL':
                        angular.forEach( $scope.filteredModel, function( value, key ) {                            
                            if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {                                
                                if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {                                
                                    value[ $scope.tickProperty ] = true;
                                }
                            }
                        });                            
                        $scope.refreshOutputModel();                                    
                        $scope.refreshButton();                                                  
                        $scope.onSelectAll();                                                
                        break;
                    case 'NONE':
                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {                        
                                if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {                                
                                    value[ $scope.tickProperty ] = false;
                                }
                            }
                        });               
                        $scope.refreshOutputModel();                                    
                        $scope.refreshButton();                                                                          
                        $scope.onSelectNone();                        
                        break;
                    case 'RESET':            
                        angular.forEach( $scope.filteredModel, function( value, key ) {                            
                            if ( typeof value[ $scope.groupProperty ] === 'undefined' && typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {                        
                                var temp = value[ $scope.indexProperty ];                                
                                value[ $scope.tickProperty ] = $scope.backUp[ temp ][ $scope.tickProperty ];
                            }
                        });               
                        $scope.refreshOutputModel();                                    
                        $scope.refreshButton();                                                                          
                        $scope.onReset();                        
                        break;
                    case 'CLEAR':
                        $scope.tabIndex = $scope.tabIndex + 1;
                        $scope.onClear();    
                        break;
                    case 'FILTER':                        
                        $scope.tabIndex = helperItems.length - 1;
                        break;
                    default:                        
                }                                                                                 
            }            

            // just to create a random variable name                
            function genRandomString( length ) {                
                var possible    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
                var temp        = '';
                for( var i=0; i < length; i++ ) {
                     temp += possible.charAt( Math.floor( Math.random() * possible.length ));
                }
                return temp;
            }

            // count leading spaces
            $scope.prepareGrouping = function() {
                var spacing     = 0;                                                
                angular.forEach( $scope.filteredModel, function( value, key ) {
                    value[ $scope.spacingProperty ] = spacing;                    
                    if ( value[ $scope.groupProperty ] === true ) {
                        spacing+=2;
                    }                    
                    else if ( value[ $scope.groupProperty ] === false ) {
                        spacing-=2;
                    }                 
                });
            }

            // prepare original index
            $scope.prepareIndex = function() {
                var ctr = 0;
                angular.forEach( $scope.filteredModel, function( value, key ) {
                    value[ $scope.indexProperty ] = ctr;
                    ctr++;
                });
            }

            // navigate using up and down arrow
            $scope.keyboardListener = function( e ) { 
                
                var key = e.keyCode ? e.keyCode : e.which;      
                var isNavigationKey = false;                                                

                // ESC key (close)
                if ( key === 27 ) {
                    e.preventDefault();                   
                    $scope.toggleCheckboxes( e );
                }                    
                
                // next element ( tab, down & right key )                    
                else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) {                    
                    
                    isNavigationKey = true;
                    prevTabIndex = $scope.tabIndex; 
                    $scope.tabIndex++;                         
                    if ( $scope.tabIndex > formElements.length - 1 ) {
                        $scope.tabIndex = 0;
                        prevTabIndex = formElements.length - 1; 
                    }                                                            
                    while ( formElements[ $scope.tabIndex ].disabled === true ) {                                                                        
                        $scope.tabIndex++;
                        if ( $scope.tabIndex > formElements.length - 1 ) {
                            $scope.tabIndex = 0;                            
                        }                                                                                    
                    }                                
                }
                
                // prev element ( shift+tab, up & left key )
                else if ( key === 38 || key === 37 || ( e.shiftKey && key == 9 ) ) { 
                    isNavigationKey = true;
                    prevTabIndex = $scope.tabIndex; 
                    $scope.tabIndex--;                              
                    if ( $scope.tabIndex < 0 ) {
                        $scope.tabIndex = formElements.length - 1;
                        prevTabIndex = 0;
                    }                                         
                    while ( formElements[ $scope.tabIndex ].disabled === true ) {
                        $scope.tabIndex--;
                        if ( $scope.tabIndex < 0 ) {
                            $scope.tabIndex = formElements.length - 1;
                        }                                                                 
                    }                                 
                }                    

                if ( isNavigationKey === true ) {                                         

                    
                    e.preventDefault();

                    // set focus on the checkbox
                    formElements[ $scope.tabIndex ].focus();    
                    var actEl = document.activeElement;  
                    
                    if ( actEl.type.toUpperCase() === 'CHECKBOX' ) {                                                   
                        $scope.setFocusStyle( $scope.tabIndex );
                        $scope.removeFocusStyle( prevTabIndex );
                    }                    
                    else {
                        $scope.removeFocusStyle( prevTabIndex );
                        $scope.removeFocusStyle( helperItemsLength );
                        $scope.removeFocusStyle( formElements.length - 1 );
                    } 
                }

                isNavigationKey = false;
            }

            // set (add) CSS style on selected row
            $scope.setFocusStyle = function( tabIndex ) {                                
                angular.element( formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' );                        
            }

            // remove CSS style on selected row
            $scope.removeFocusStyle = function( tabIndex ) {
                angular.element( formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );
            }

            /*****************************************************
             *
             * Initializations
             *
             *****************************************************/
            
            // Unfortunately I need to add these grouping properties
            var tempStr = genRandomString( 5 );
            $scope.indexProperty = 'idx_' + tempStr;
            $scope.spacingProperty = 'spc_' + tempStr;         

            // set orientation css            
            if ( typeof attrs.orientation !== 'undefined' ) {

                if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) {                    
                    $scope.orientationH = true;
                    $scope.orientationV = false;
                }
                else 
                {
                    $scope.orientationH = false;
                    $scope.orientationV = true;
                }
            }            

            // get elements required for DOM operation
            checkBoxLayer = element.children().children().next()[0];

            // set max-height property if provided
            if ( typeof attrs.maxHeight !== 'undefined' ) {                
                var layer = element.children().children().children()[0];
                angular.element( layer ).attr( "style", "height:" + $scope.maxHeight + "; overflow-y:scroll;" );                                
            }

            // icons.. I guess you can use <img> tag here if you want to. 
            var icon        = {};
            icon.selectAll  = '&#10003;'    // a tick icon
            icon.selectNone = '&times;'     // x icon
            icon.reset      = '&#8630;'     // undo icon

            // configurable button labels                       
            if ( typeof attrs.translation !== 'undefined' ) {
                $scope.lang.selectAll       = $sce.trustAsHtml( icon.selectAll  + '&nbsp;&nbsp;' + $scope.translation.selectAll );
                $scope.lang.selectNone      = $sce.trustAsHtml( icon.selectNone + '&nbsp;&nbsp;' + $scope.translation.selectNone );
                $scope.lang.reset           = $sce.trustAsHtml( icon.reset      + '&nbsp;&nbsp;' + $scope.translation.reset );
                $scope.lang.search          = $scope.translation.search;                
                $scope.lang.nothingSelected = $sce.trustAsHtml( $scope.translation.nothingSelected );                
            }
            else {
                $scope.lang.selectAll       = $sce.trustAsHtml( icon.selectAll  + '&nbsp;&nbsp;Select All' );                
                $scope.lang.selectNone      = $sce.trustAsHtml( icon.selectNone + '&nbsp;&nbsp;Select None' );
                $scope.lang.reset           = $sce.trustAsHtml( icon.reset      + '&nbsp;&nbsp;Reset' );
                $scope.lang.search          = 'Search...';
                $scope.lang.nothingSelected = 'None Selected';                
            }
                
            // min length of keyword to trigger the filter function
            if ( typeof attrs.MinSearchLength !== 'undefined' && parseInt( attrs.MinSearchLength ) > 0 ) {
                vMinSearchLength = Math.floor( parseInt( attrs.MinSearchLength ) );
            }

            /****************************************************
             *
             * Logic starts here, initiated by watch 1 & watch 2
             *
             ****************************************************/
            
            // watch1, for changes in input model property
            // updates multi-select when user select/deselect a single checkbox programatically
            // https://github.com/isteven/angular-multi-select/issues/8
            $scope.$watch( 'inputModel' , function( newVal ) {                                 
                if ( newVal ) {                    
                    $scope.localModel = angular.copy( $scope.inputModel );                   
                    $scope.refreshOutputModel();                                    
                    $scope.refreshButton();                                                  
                }
            }, true );

            // watch2 for changes in input model as a whole
            // this on updates the multi-select when a user load a whole new input-model. We also update the $scope.backUp variable
            $scope.$watch( 'localModel' , function( newVal ) {  
                if ( newVal ) {
                    $scope.backUp = angular.copy( $scope.localModel );    
                    $scope.updateFilter();
                    $scope.prepareGrouping();
                    $scope.prepareIndex();                                                              
                    $scope.refreshOutputModel();                
                    $scope.refreshButton();                                                                                                                 
                }
            });            

            // watch for changes in directive state (disabled or enabled)
            $scope.$watch( 'isDisabled' , function( newVal ) {         
                $scope.isDisabled = newVal;                               
            });            

            // this is for touch enabled devices. We don't want to hide checkboxes on scroll. 
            angular.element( document ).on( 'touchstart', function( e ) { 
                $scope.$apply( function() {
                    scrolled = false;
                }); 
            });
            
            // also for touch enabled devices
            angular.element( document ).on( 'touchmove', function( e ) { 
                $scope.$apply( function() {
                    scrolled = true;                
                });
            });                                   
        }
    }
}]);

/* 
 * Angular JS Multi Select
 * Creates a dropdown-like button with checkboxes. 
 *
 * Project started on: Tue, 14 Jan 2014 - 5:18:02 PM
 * Current version: 4.0.0
 * 
 * Released under the MIT License
 * --------------------------------------------------------------------------------
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions: 
 *
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 * --------------------------------------------------------------------------------
 */

'use strict'

angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' , [ '$sce', '$timeout', '$templateCache', function ( $sce, $timeout, $templateCache ) {
    return {
        restrict:
            'AE',

        scope:
        {
            // models
            inputModel      : '=',
            outputModel     : '=',

            // settings based on attribute
            isDisabled      : '=',

            // callbacks
            onClear         : '&',
            onClose         : '&',
            onSearchChange  : '&',
            onItemClick     : '&',
            onOpen          : '&',
            onReset         : '&',
            onSelectAll     : '&',
            onSelectNone    : '&',

            // i18n
            translation     : '='
        },

        /* 
         * The rest are attributes. They don't need to be parsed / binded, so we can safely access them by value.
         * - buttonLabel, directiveId, helperElements, itemLabel, maxLabels, orientation, selectionMode, minSearchLength,
         *   tickProperty, disableProperty, groupProperty, searchProperty, maxHeight, outputProperties
         */

        templateUrl:
            'isteven-multi-select.htm',

        link: function ( $scope, element, attrs ) {

            $scope.backUp           = [];
            $scope.varButtonLabel   = '';
            $scope.spacingProperty  = '';
            $scope.indexProperty    = '';
            $scope.orientationH     = false;
            $scope.orientationV     = true;
            $scope.filteredModel    = [];
            $scope.inputLabel       = { labelFilter: '' };
            $scope.tabIndex         = 0;
            $scope.lang             = {};
            $scope.helperStatus     = {
                all     : true,
                none    : true,
                reset   : true,
                filter  : true
            };

            var
                prevTabIndex        = 0,
                helperItems         = [],
                helperItemsLength   = 0,
                checkBoxLayer       = '',
                scrolled            = false,
                selectedItems       = [],
                formElements        = [],
                vMinSearchLength    = 0,
                clickedItem         = null

            // v3.0.0
            // clear button clicked
            $scope.clearClicked = function( e ) {
                $scope.inputLabel.labelFilter = '';
                $scope.updateFilter();
                $scope.select( 'clear', e );
            }

            // A little hack so that AngularJS ng-repeat can loop using start and end index like a normal loop
            // http://stackoverflow.com/questions/16824853/way-to-ng-repeat-defined-number-of-times-instead-of-repeating-over-array
            $scope.numberToArray = function( num ) {
                return new Array( num );
            }

            // Call this function when user type on the filter field
            $scope.searchChanged = function() {
                if ( $scope.inputLabel.labelFilter.length < vMinSearchLength && $scope.inputLabel.labelFilter.length > 0 ) {
                    return false;
                }
                $scope.updateFilter();
            }

            $scope.updateFilter = function()
            {
                // we check by looping from end of input-model
                $scope.filteredModel = [];
                var i = 0;

                if ( typeof $scope.inputModel === 'undefined' ) {
                    return false;
                }

                for( i = $scope.inputModel.length - 1; i >= 0; i-- ) {

                    // if it's group end, we push it to filteredModel[];
                    if ( typeof $scope.inputModel[ i ][ attrs.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ attrs.groupProperty ] === false ) {
                        $scope.filteredModel.push( $scope.inputModel[ i ] );
                    }

                    // if it's data 
                    var gotData = false;
                    if ( typeof $scope.inputModel[ i ][ attrs.groupProperty ] === 'undefined' ) {

                        // If we set the search-key attribute, we use this loop. 
                        if ( typeof attrs.searchProperty !== 'undefined' && attrs.searchProperty !== '' ) {

                            for (var key in $scope.inputModel[ i ]  ) {
                                if (
                                    typeof $scope.inputModel[ i ][ key ] !== 'boolean'
                                    && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0
                                    && attrs.searchProperty.indexOf( key ) > -1
                                ) {
                                    gotData = true;
                                    break;
                                }
                            }
                        }
                        // if there's no search-key attribute, we use this one. Much better on performance.
                        else {
                            for ( var key in $scope.inputModel[ i ]  ) {
                                if (
                                    typeof $scope.inputModel[ i ][ key ] !== 'boolean'
                                    && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0
                                ) {
                                    gotData = true;
                                    break;
                                }
                            }
                        }

                        if ( gotData === true ) {
                            // push
                            $scope.filteredModel.push( $scope.inputModel[ i ] );
                        }
                    }

                    // if it's group start
                    if ( typeof $scope.inputModel[ i ][ attrs.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ attrs.groupProperty ] === true ) {

                        if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ attrs.groupProperty ] !== 'undefined'
                            && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ attrs.groupProperty ] === false ) {
                            $scope.filteredModel.pop();
                        }
                        else {
                            $scope.filteredModel.push( $scope.inputModel[ i ] );
                        }
                    }
                }

                $scope.filteredModel.reverse();

                $timeout( function() {

                    $scope.getFormElements();

                    // Callback: on filter change                      
                    if ( $scope.inputLabel.labelFilter.length > vMinSearchLength ) {

                        var filterObj = [];

                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value !== 'undefined' ) {
                                if ( typeof value[ attrs.groupProperty ] === 'undefined' ) {
                                    var tempObj = angular.copy( value );
                                    var index = filterObj.push( tempObj );
                                    delete filterObj[ index - 1 ][ $scope.indexProperty ];
                                    delete filterObj[ index - 1 ][ $scope.spacingProperty ];
                                }
                            }
                        });

                        $scope.onSearchChange({
                            data:
                            {
                                keyword: $scope.inputLabel.labelFilter,
                                result: filterObj
                            }
                        });
                    }
                },0);
            };

            // List all the input elements. We need this for our keyboard navigation.
            // This function will be called everytime the filter is updated. 
            // Depending on the size of filtered mode, might not good for performance, but oh well..
            $scope.getFormElements = function() {
                formElements = [];

                var
                    selectButtons   = [],
                    inputField      = [],
                    checkboxes      = [],
                    clearButton     = [];

                // If available, then get select all, select none, and reset buttons
                if ( $scope.helperStatus.all || $scope.helperStatus.none || $scope.helperStatus.reset ) {
                    selectButtons = element.children().children().next().children().children()[ 0 ].getElementsByTagName( 'button' );
                    // If available, then get the search box and the clear button
                    if ( $scope.helperStatus.filter ) {
                        // Get helper - search and clear button. 
                        inputField =    element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'input' );
                        clearButton =   element.children().children().next().children().children().next()[ 0 ].getElementsByTagName( 'button' );
                    }
                }
                else {
                    if ( $scope.helperStatus.filter ) {
                        // Get helper - search and clear button. 
                        inputField =    element.children().children().next().children().children()[ 0 ].getElementsByTagName( 'input' );
                        clearButton =   element.children().children().next().children().children()[ 0 ].getElementsByTagName( 'button' );
                    }
                }

                // Get checkboxes
                if ( !$scope.helperStatus.all && !$scope.helperStatus.none && !$scope.helperStatus.reset && !$scope.helperStatus.filter ) {
                    checkboxes = element.children().children().next()[ 0 ].getElementsByTagName( 'input' );
                }
                else {
                    checkboxes = element.children().children().next().children().next()[ 0 ].getElementsByTagName( 'input' );
                }

                // Push them into global array formElements[] 
                for ( var i = 0; i < selectButtons.length ; i++ )   { formElements.push( selectButtons[ i ] );  }
                for ( var i = 0; i < inputField.length ; i++ )      { formElements.push( inputField[ i ] );     }
                for ( var i = 0; i < clearButton.length ; i++ )     { formElements.push( clearButton[ i ] );    }
                for ( var i = 0; i < checkboxes.length ; i++ )      { formElements.push( checkboxes[ i ] );     }
            }

            // check if an item has attrs.groupProperty (be it true or false)
            $scope.isGroupMarker = function( item , type ) {
                if ( typeof item[ attrs.groupProperty ] !== 'undefined' && item[ attrs.groupProperty ] === type ) return true;
                return false;
            }

            $scope.removeGroupEndMarker = function( item ) {
                if ( typeof item[ attrs.groupProperty ] !== 'undefined' && item[ attrs.groupProperty ] === false ) return false;
                return true;
            }

            // call this function when an item is clicked
            $scope.syncItems = function( item, e, ng_repeat_index ) {

                e.preventDefault();
                e.stopPropagation();

                // if the directive is globaly disabled, do nothing
                if ( typeof attrs.disableProperty !== 'undefined' && item[ attrs.disableProperty ] === true ) {
                    return false;
                }

                // if item is disabled, do nothing
                if ( typeof attrs.isDisabled !== 'undefined' && $scope.isDisabled === true ) {
                    return false;
                }

                // if end group marker is clicked, do nothing
                if ( typeof item[ attrs.groupProperty ] !== 'undefined' && item[ attrs.groupProperty ] === false ) {
                    return false;
                }

                var index = $scope.filteredModel.indexOf( item );

                // if the start of group marker is clicked ( only for multiple selection! )
                // how it works:
                // - if, in a group, there are items which are not selected, then they all will be selected
                // - if, in a group, all items are selected, then they all will be de-selected                
                if ( typeof item[ attrs.groupProperty ] !== 'undefined' && item[ attrs.groupProperty ] === true ) {

                    // this is only for multiple selection, so if selection mode is single, do nothing
                    if ( typeof attrs.selectionMode !== 'undefined' && attrs.selectionMode.toUpperCase() === 'SINGLE' ) {
                        return false;
                    }

                    var i,j,k;
                    var startIndex = 0;
                    var endIndex = $scope.filteredModel.length - 1;
                    var tempArr = [];

                    // nest level is to mark the depth of the group.
                    // when you get into a group (start group marker), nestLevel++
                    // when you exit a group (end group marker), nextLevel--
                    var nestLevel = 0;

                    // we loop throughout the filtered model (not whole model)
                    for( i = index ; i < $scope.filteredModel.length ; i++) {

                        // this break will be executed when we're done processing each group
                        if ( nestLevel === 0 && i > index )
                        {
                            break;
                        }

                        if ( typeof $scope.filteredModel[ i ][ attrs.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ attrs.groupProperty ] === true ) {

                            // To cater multi level grouping
                            if ( tempArr.length === 0 ) {
                                startIndex = i + 1;
                            }
                            nestLevel = nestLevel + 1;
                        }

                        // if group end
                        else if ( typeof $scope.filteredModel[ i ][ attrs.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ attrs.groupProperty ] === false ) {

                            nestLevel = nestLevel - 1;

                            // cek if all are ticked or not                            
                            if ( tempArr.length > 0 && nestLevel === 0 ) {

                                var allTicked = true;

                                endIndex = i;

                                for ( j = 0; j < tempArr.length ; j++ ) {
                                    if ( typeof tempArr[ j ][ $scope.tickProperty ] !== 'undefined' &&  tempArr[ j ][ $scope.tickProperty ] === false ) {
                                        allTicked = false;
                                        break;
                                    }
                                }

                                if ( allTicked === true ) {
                                    for ( j = startIndex; j <= endIndex ; j++ ) {
                                        if ( typeof $scope.filteredModel[ j ][ attrs.groupProperty ] === 'undefined' ) {
                                            if ( typeof attrs.disableProperty === 'undefined' ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
                                            }
                                            else if ( $scope.filteredModel[ j ][ attrs.disableProperty ] !== true ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
                                            }
                                        }
                                    }
                                }

                                else {
                                    for ( j = startIndex; j <= endIndex ; j++ ) {
                                        if ( typeof $scope.filteredModel[ j ][ attrs.groupProperty ] === 'undefined' ) {
                                            if ( typeof attrs.disableProperty === 'undefined' ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;

                                            }
                                            else if ( $scope.filteredModel[ j ][ attrs.disableProperty ] !== true ) {
                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;
                                                // we refresh input model as well
                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        // if data
                        else {
                            tempArr.push( $scope.filteredModel[ i ] );
                        }
                    }
                }

                // if an item (not group marker) is clicked
                else {

                    // If it's single selection mode
                    if ( typeof attrs.selectionMode !== 'undefined' && attrs.selectionMode.toUpperCase() === 'SINGLE' ) {

                        // first, set everything to false
                        for( i=0 ; i < $scope.filteredModel.length ; i++) {
                            $scope.filteredModel[ i ][ $scope.tickProperty ] = false;
                        }
                        for( i=0 ; i < $scope.inputModel.length ; i++) {
                            $scope.inputModel[ i ][ $scope.tickProperty ] = false;
                        }

                        // then set the clicked item to true
                        $scope.filteredModel[ index ][ $scope.tickProperty ] = true;
                    }

                    // Multiple
                    else {
                        $scope.filteredModel[ index ][ $scope.tickProperty ]   = !$scope.filteredModel[ index ][ $scope.tickProperty ];
                    }

                    // we refresh input model as well
                    var inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ];
                    $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ];
                }

                // we execute the callback function here
                clickedItem = angular.copy( item );
                if ( clickedItem !== null ) {
                    $timeout( function() {
                        delete clickedItem[ $scope.indexProperty ];
                        delete clickedItem[ $scope.spacingProperty ];
                        $scope.onItemClick( { data: clickedItem } );
                        clickedItem = null;
                    }, 0 );
                }

                $scope.refreshOutputModel();
                $scope.refreshButton();

                // We update the index here
                prevTabIndex = $scope.tabIndex;
                $scope.tabIndex = ng_repeat_index + helperItemsLength;

                // Set focus on the hidden checkbox 
                e.target.focus();

                // set & remove CSS style
                $scope.removeFocusStyle( prevTabIndex );
                $scope.setFocusStyle( $scope.tabIndex );

                if ( typeof attrs.selectionMode !== 'undefined' && attrs.selectionMode.toUpperCase() === 'SINGLE' ) {
                    // on single selection mode, we then hide the checkbox layer
                    $scope.toggleCheckboxes( e );
                }
            }

            // update $scope.outputModel
            $scope.refreshOutputModel = function() {

                $scope.outputModel  = [];
                var
                    outputProps     = [],
                    tempObj         = {};

                // v4.0.0
                if ( typeof attrs.outputProperties !== 'undefined' ) {
                    outputProps = attrs.outputProperties.split(' ');
                    angular.forEach( $scope.inputModel, function( value, key ) {
                        if (
                            typeof value !== 'undefined'
                            && typeof value[ attrs.groupProperty ] === 'undefined'
                            && value[ $scope.tickProperty ] === true
                        ) {
                            tempObj         = {};
                            angular.forEach( value, function( value1, key1 ) {
                                if ( outputProps.indexOf( key1 ) > -1 ) {
                                    tempObj[ key1 ] = value1;
                                }
                            });
                            var index = $scope.outputModel.push( tempObj );
                            delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ];
                            delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ];
                        }
                    });
                }
                else {
                    angular.forEach( $scope.inputModel, function( value, key ) {
                        if (
                            typeof value !== 'undefined'
                            && typeof value[ attrs.groupProperty ] === 'undefined'
                            && value[ $scope.tickProperty ] === true
                        ) {
                            var temp = angular.copy( value );
                            var index = $scope.outputModel.push( temp );
                            delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ];
                            delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ];
                        }
                    });
                }
            }

            // refresh button label
            $scope.refreshButton = function() {

                $scope.varButtonLabel   = '';
                var ctr                 = 0;

                // refresh button label...
                if ( $scope.outputModel.length === 0 ) {
                    // https://github.com/isteven/angular-multi-select/pull/19                    
                    $scope.varButtonLabel = $scope.lang.nothingSelected;
                }
                else {
                    var tempMaxLabels = $scope.outputModel.length;
                    if ( typeof attrs.maxLabels !== 'undefined' && attrs.maxLabels !== '' ) {
                        tempMaxLabels = attrs.maxLabels;
                    }

                    // if max amount of labels displayed..
                    if ( $scope.outputModel.length > tempMaxLabels ) {
                        $scope.more = true;
                    }
                    else {
                        $scope.more = false;
                    }

                    angular.forEach( $scope.inputModel, function( value, key ) {
                        if ( typeof value !== 'undefined' && value[ attrs.tickProperty ] === true ) {
                            if ( ctr < tempMaxLabels ) {
                                $scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? '</div>, <div class="buttonLabel">' : '<div class="buttonLabel">') + $scope.writeLabel( value, 'buttonLabel' );
                            }
                            ctr++;
                        }
                    });

                    if ( $scope.more === true ) {
                        // https://github.com/isteven/angular-multi-select/pull/16
                        if (tempMaxLabels > 0) {
                            $scope.varButtonLabel += ', ... ';
                        }
                        $scope.varButtonLabel += '(' + $scope.outputModel.length + ')';
                    }
                }
                $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '<span class="caret"></span>' );
            }

            // Check if a checkbox is disabled or enabled. It will check the granular control (disableProperty) and global control (isDisabled)
            // Take note that the granular control has higher priority.
            $scope.itemIsDisabled = function( item ) {

                if ( typeof attrs.disableProperty !== 'undefined' && item[ attrs.disableProperty ] === true ) {
                    return true;
                }
                else {
                    if ( $scope.isDisabled === true ) {
                        return true;
                    }
                    else {
                        return false;
                    }
                }

            }

            // A simple function to parse the item label settings. Used on the buttons and checkbox labels.
            $scope.writeLabel = function( item, type ) {

                // type is either 'itemLabel' or 'buttonLabel'
                var temp    = attrs[ type ].split( ' ' );
                var label   = '';

                angular.forEach( temp, function( value, key ) {
                    item[ value ] && ( label += '&nbsp;' + value.split( '.' ).reduce( function( prev, current ) {
                        return prev[ current ];
                    }, item ));
                });

                if ( type.toUpperCase() === 'BUTTONLABEL' ) {
                    return label;
                }
                return $sce.trustAsHtml( label );
            }

            // UI operations to show/hide checkboxes based on click event..
            $scope.toggleCheckboxes = function( e ) {

                // We grab the button
                var clickedEl = element.children()[0];

                // Just to make sure.. had a bug where key events were recorded twice
                angular.element( document ).off( 'click', $scope.externalClickListener );
                angular.element( document ).off( 'keydown', $scope.keyboardListener );

                // The idea below was taken from another multi-select directive - https://github.com/amitava82/angular-multiselect 
                // His version is awesome if you need a more simple multi-select approach.                                

                // close
                if ( angular.element( checkBoxLayer ).hasClass( 'show' )) {

                    angular.element( checkBoxLayer ).removeClass( 'show' );
                    angular.element( clickedEl ).removeClass( 'buttonClicked' );
                    angular.element( document ).off( 'click', $scope.externalClickListener );
                    angular.element( document ).off( 'keydown', $scope.keyboardListener );

                    // clear the focused element;
                    $scope.removeFocusStyle( $scope.tabIndex );
                    if ( typeof formElements[ $scope.tabIndex ] !== 'undefined' ) {
                        formElements[ $scope.tabIndex ].blur();
                    }

                    // close callback
                    $timeout( function() {
                        $scope.onClose();
                    }, 0 );

                    // set focus on button again
                    element.children().children()[ 0 ].focus();
                }
                // open
                else
                {
                    // clear filter
                    $scope.inputLabel.labelFilter = '';
                    $scope.updateFilter();

                    helperItems = [];
                    helperItemsLength = 0;

                    angular.element( checkBoxLayer ).addClass( 'show' );
                    angular.element( clickedEl ).addClass( 'buttonClicked' );

                    // Attach change event listener on the input filter. 
                    // We need this because ng-change is apparently not an event listener.                    
                    angular.element( document ).on( 'click', $scope.externalClickListener );
                    angular.element( document ).on( 'keydown', $scope.keyboardListener );

                    // to get the initial tab index, depending on how many helper elements we have. 
                    // priority is to always focus it on the input filter                                                                
                    $scope.getFormElements();
                    $scope.tabIndex = 0;

                    var helperContainer = angular.element( element[ 0 ].querySelector( '.helperContainer' ) )[0];

                    if ( typeof helperContainer !== 'undefined' ) {
                        for ( var i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {
                            helperItems[ i ] = helperContainer.getElementsByTagName( 'BUTTON' )[ i ];
                        }
                        helperItemsLength = helperItems.length + helperContainer.getElementsByTagName( 'INPUT' ).length;
                    }

                    // focus on the filter element on open. 
                    if ( element[ 0 ].querySelector( '.inputFilter' ) ) {
                        element[ 0 ].querySelector( '.inputFilter' ).focus();
                        $scope.tabIndex = $scope.tabIndex + helperItemsLength - 2;
                        // blur button in vain
                        angular.element( element ).children()[ 0 ].blur();
                    }
                    // if there's no filter then just focus on the first checkbox item
                    else {
                        if ( !$scope.isDisabled ) {
                            $scope.tabIndex = $scope.tabIndex + helperItemsLength;
                            if ( $scope.inputModel.length > 0 ) {
                                formElements[ $scope.tabIndex ].focus();
                                $scope.setFocusStyle( $scope.tabIndex );
                                // blur button in vain
                                angular.element( element ).children()[ 0 ].blur();
                            }
                        }
                    }

                    // open callback
                    $scope.onOpen();
                }
            }

            // handle clicks outside the button / multi select layer
            $scope.externalClickListener = function( e ) {

                var targetsArr = element.find( e.target.tagName );
                for (var i = 0; i < targetsArr.length; i++) {
                    if ( e.target == targetsArr[i] ) {
                        return;
                    }
                }

                angular.element( checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' );
                angular.element( checkBoxLayer ).removeClass( 'show' );
                angular.element( document ).off( 'click', $scope.externalClickListener );
                angular.element( document ).off( 'keydown', $scope.keyboardListener );

                // close callback                
                $timeout( function() {
                    $scope.onClose();
                }, 0 );

                // set focus on button again
                element.children().children()[ 0 ].focus();
            }

            // select All / select None / reset buttons
            $scope.select = function( type, e ) {

                var helperIndex = helperItems.indexOf( e.target );
                $scope.tabIndex = helperIndex;

                switch( type.toUpperCase() ) {
                    case 'ALL':
                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value !== 'undefined' && value[ attrs.disableProperty ] !== true ) {
                                if ( typeof value[ attrs.groupProperty ] === 'undefined' ) {
                                    value[ $scope.tickProperty ] = true;
                                }
                            }
                        });
                        $scope.refreshOutputModel();
                        $scope.refreshButton();
                        $scope.onSelectAll();
                        break;
                    case 'NONE':
                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value !== 'undefined' && value[ attrs.disableProperty ] !== true ) {
                                if ( typeof value[ attrs.groupProperty ] === 'undefined' ) {
                                    value[ $scope.tickProperty ] = false;
                                }
                            }
                        });
                        $scope.refreshOutputModel();
                        $scope.refreshButton();
                        $scope.onSelectNone();
                        break;
                    case 'RESET':
                        angular.forEach( $scope.filteredModel, function( value, key ) {
                            if ( typeof value[ attrs.groupProperty ] === 'undefined' && typeof value !== 'undefined' && value[ attrs.disableProperty ] !== true ) {
                                var temp = value[ $scope.indexProperty ];
                                value[ $scope.tickProperty ] = $scope.backUp[ temp ][ $scope.tickProperty ];
                            }
                        });
                        $scope.refreshOutputModel();
                        $scope.refreshButton();
                        $scope.onReset();
                        break;
                    case 'CLEAR':
                        $scope.tabIndex = $scope.tabIndex + 1;
                        $scope.onClear();
                        break;
                    case 'FILTER':
                        $scope.tabIndex = helperItems.length - 1;
                        break;
                    default:
                }
            }

            // just to create a random variable name                
            function genRandomString( length ) {
                var possible    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
                var temp        = '';
                for( var i=0; i < length; i++ ) {
                    temp += possible.charAt( Math.floor( Math.random() * possible.length ));
                }
                return temp;
            }

            // count leading spaces
            $scope.prepareGrouping = function() {
                var spacing     = 0;
                angular.forEach( $scope.filteredModel, function( value, key ) {
                    value[ $scope.spacingProperty ] = spacing;
                    if ( value[ attrs.groupProperty ] === true ) {
                        spacing+=2;
                    }
                    else if ( value[ attrs.groupProperty ] === false ) {
                        spacing-=2;
                    }
                });
            }

            // prepare original index
            $scope.prepareIndex = function() {
                var ctr = 0;
                angular.forEach( $scope.filteredModel, function( value, key ) {
                    value[ $scope.indexProperty ] = ctr;
                    ctr++;
                });
            }

            // navigate using up and down arrow
            $scope.keyboardListener = function( e ) {

                var key = e.keyCode ? e.keyCode : e.which;
                var isNavigationKey = false;

                // ESC key (close)
                if ( key === 27 ) {
                    e.preventDefault();
                    e.stopPropagation();
                    $scope.toggleCheckboxes( e );
                }


                // next element ( tab, down & right key )                    
                else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) {

                    isNavigationKey = true;
                    prevTabIndex = $scope.tabIndex;
                    $scope.tabIndex++;
                    if ( $scope.tabIndex > formElements.length - 1 ) {
                        $scope.tabIndex = 0;
                        prevTabIndex = formElements.length - 1;
                    }
                    while ( formElements[ $scope.tabIndex ].disabled === true ) {
                        $scope.tabIndex++;
                        if ( $scope.tabIndex > formElements.length - 1 ) {
                            $scope.tabIndex = 0;
                        }
                        if ( $scope.tabIndex === prevTabIndex ) {
                            break;
                        }
                    }
                }

                // prev element ( shift+tab, up & left key )
                else if ( key === 38 || key === 37 || ( e.shiftKey && key == 9 ) ) {
                    isNavigationKey = true;
                    prevTabIndex = $scope.tabIndex;
                    $scope.tabIndex--;
                    if ( $scope.tabIndex < 0 ) {
                        $scope.tabIndex = formElements.length - 1;
                        prevTabIndex = 0;
                    }
                    while ( formElements[ $scope.tabIndex ].disabled === true ) {
                        $scope.tabIndex--;
                        if ( $scope.tabIndex === prevTabIndex ) {
                            break;
                        }
                        if ( $scope.tabIndex < 0 ) {
                            $scope.tabIndex = formElements.length - 1;
                        }
                    }
                }

                if ( isNavigationKey === true ) {

                    e.preventDefault();

                    // set focus on the checkbox                    
                    formElements[ $scope.tabIndex ].focus();
                    var actEl = document.activeElement;

                    if ( actEl.type.toUpperCase() === 'CHECKBOX' ) {
                        $scope.setFocusStyle( $scope.tabIndex );
                        $scope.removeFocusStyle( prevTabIndex );
                    }
                    else {
                        $scope.removeFocusStyle( prevTabIndex );
                        $scope.removeFocusStyle( helperItemsLength );
                        $scope.removeFocusStyle( formElements.length - 1 );
                    }
                }

                isNavigationKey = false;
            }

            // set (add) CSS style on selected row
            $scope.setFocusStyle = function( tabIndex ) {
                angular.element( formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' );
            }

            // remove CSS style on selected row
            $scope.removeFocusStyle = function( tabIndex ) {
                angular.element( formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );
            }

            /*********************
             *********************
             *
             * 1) Initializations
             *
             *********************
             *********************/

                // attrs to $scope - attrs-$scope - attrs - $scope
                // Copy some properties that will be used on the template. They need to be in the $scope.
            $scope.groupProperty    = attrs.groupProperty;
            $scope.tickProperty     = attrs.tickProperty;
            $scope.directiveId      = attrs.directiveId;

            // Unfortunately I need to add these grouping properties into the input model
            var tempStr = genRandomString( 5 );
            $scope.indexProperty = 'idx_' + tempStr;
            $scope.spacingProperty = 'spc_' + tempStr;

            // set orientation css            
            if ( typeof attrs.orientation !== 'undefined' ) {

                if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) {
                    $scope.orientationH = true;
                    $scope.orientationV = false;
                }
                else
                {
                    $scope.orientationH = false;
                    $scope.orientationV = true;
                }
            }

            // get elements required for DOM operation
            checkBoxLayer = element.children().children().next()[0];

            // set max-height property if provided
            if ( typeof attrs.maxHeight !== 'undefined' ) {
                var layer = element.children().children().children()[0];
                angular.element( layer ).attr( "style", "height:" + attrs.maxHeight + "; overflow-y:scroll;" );
            }

            // some flags for easier checking            
            for ( var property in $scope.helperStatus ) {
                if ( $scope.helperStatus.hasOwnProperty( property )) {
                    if (
                        typeof attrs.helperElements !== 'undefined'
                        && attrs.helperElements.toUpperCase().indexOf( property.toUpperCase() ) === -1
                    ) {
                        $scope.helperStatus[ property ] = false;
                    }
                }
            }
            if ( typeof attrs.selectionMode !== 'undefined' && attrs.selectionMode.toUpperCase() === 'SINGLE' )  {
                $scope.helperStatus[ 'all' ] = false;
                $scope.helperStatus[ 'none' ] = false;
            }

            // helper button icons.. I guess you can use html tag here if you want to. 
            $scope.icon        = {};
            $scope.icon.selectAll  = '&#10003;';    // a tick icon
            $scope.icon.selectNone = '&times;';     // x icon
            $scope.icon.reset      = '&#8630;';     // undo icon            
            // this one is for the selected items
            $scope.icon.tickMark   = '&#10003;';    // a tick icon 

            // configurable button labels                       
            if ( typeof attrs.translation !== 'undefined' ) {
                $scope.lang.selectAll       = $sce.trustAsHtml( $scope.icon.selectAll  + '&nbsp;&nbsp;' + $scope.translation.selectAll );
                $scope.lang.selectNone      = $sce.trustAsHtml( $scope.icon.selectNone + '&nbsp;&nbsp;' + $scope.translation.selectNone );
                $scope.lang.reset           = $sce.trustAsHtml( $scope.icon.reset      + '&nbsp;&nbsp;' + $scope.translation.reset );
                $scope.lang.search          = $scope.translation.search;
                $scope.lang.nothingSelected = $sce.trustAsHtml( $scope.translation.nothingSelected );
            }
            else {
                $scope.lang.selectAll       = $sce.trustAsHtml( $scope.icon.selectAll  + '&nbsp;&nbsp;Select All' );
                $scope.lang.selectNone      = $sce.trustAsHtml( $scope.icon.selectNone + '&nbsp;&nbsp;Select None' );
                $scope.lang.reset           = $sce.trustAsHtml( $scope.icon.reset      + '&nbsp;&nbsp;Reset' );
                $scope.lang.search          = 'Search...';
                $scope.lang.nothingSelected = 'None Selected';
            }
            $scope.icon.tickMark = $sce.trustAsHtml( $scope.icon.tickMark );

            // min length of keyword to trigger the filter function
            if ( typeof attrs.MinSearchLength !== 'undefined' && parseInt( attrs.MinSearchLength ) > 0 ) {
                vMinSearchLength = Math.floor( parseInt( attrs.MinSearchLength ) );
            }

            /*******************************************************
             *******************************************************
             *
             * 2) Logic starts here, initiated by watch 1 & watch 2
             *
             *******************************************************
             *******************************************************/

                // watch1, for changes in input model property
                // updates multi-select when user select/deselect a single checkbox programatically
                // https://github.com/isteven/angular-multi-select/issues/8
            $scope.$watch( 'inputModel' , function( newVal ) {
                if ( newVal ) {
                    $scope.refreshOutputModel();
                    $scope.refreshButton();
                }
            }, true );

            // watch2 for changes in input model as a whole
            // this on updates the multi-select when a user load a whole new input-model. We also update the $scope.backUp variable
            $scope.$watch( 'inputModel' , function( newVal ) {
                if ( newVal ) {
                    $scope.backUp = angular.copy( $scope.inputModel );
                    $scope.updateFilter();
                    $scope.prepareGrouping();
                    $scope.prepareIndex();
                    $scope.refreshOutputModel();
                    $scope.refreshButton();
                }
            });

            // watch for changes in directive state (disabled or enabled)
            $scope.$watch( 'isDisabled' , function( newVal ) {
                $scope.isDisabled = newVal;
            });

            // this is for touch enabled devices. We don't want to hide checkboxes on scroll. 
            var onTouchStart = function( e ) {
                $scope.$apply( function() {
                    $scope.scrolled = false;
                });
            };
            angular.element( document ).bind( 'touchstart', onTouchStart);
            var onTouchMove = function( e ) {
                $scope.$apply( function() {
                    $scope.scrolled = true;
                });
            };
            angular.element( document ).bind( 'touchmove', onTouchMove);

            // unbind document events to prevent memory leaks
            $scope.$on( '$destroy', function () {
                angular.element( document ).unbind( 'touchstart', onTouchStart);
                angular.element( document ).unbind( 'touchmove', onTouchMove);
            });
        }
    }
}]).run( [ '$templateCache' , function( $templateCache ) {
    var template =
        '<span class="multiSelect inlineBlock">' +
            // main button
        '<button id="{{directiveId}}" type="button"' +
        'ng-click="toggleCheckboxes( $event ); refreshSelectedItems(); refreshButton(); prepareGrouping; prepareIndex();"' +
        'ng-bind-html="varButtonLabel"' +
        'ng-disabled="disable-button"' +
        '>' +
        '</button>' +
            // overlay layer
        '<div class="checkboxLayer">' +
            // container of the helper elements
        '<div class="helperContainer" ng-if="helperStatus.filter || helperStatus.all || helperStatus.none || helperStatus.reset ">' +
            // container of the first 3 buttons, select all, none and reset
        '<div class="line" ng-if="helperStatus.all || helperStatus.none || helperStatus.reset ">' +
            // select all
        '<button type="button" class="helperButton"' +
        'ng-disabled="isDisabled"' +
        'ng-if="helperStatus.all"' +
        'ng-click="select( \'all\', $event );"' +
        'ng-bind-html="lang.selectAll">' +
        '</button>'+
            // select none
        '<button type="button" class="helperButton"' +
        'ng-disabled="isDisabled"' +
        'ng-if="helperStatus.none"' +
        'ng-click="select( \'none\', $event );"' +
        'ng-bind-html="lang.selectNone">' +
        '</button>'+
            // reset
        '<button type="button" class="helperButton reset"' +
        'ng-disabled="isDisabled"' +
        'ng-if="helperStatus.reset"' +
        'ng-click="select( \'reset\', $event );"' +
        'ng-bind-html="lang.reset">'+
        '</button>' +
        '</div>' +
            // the search box
        '<div class="line" style="position:relative" ng-if="helperStatus.filter">'+
            // textfield
        '<input placeholder="{{lang.search}}" type="text"' +
        'ng-click="select( \'filter\', $event )" '+
        'ng-model="inputLabel.labelFilter" '+
        'ng-change="searchChanged()" class="inputFilter"'+
        '/>'+
            // clear button
        '<button type="button" class="clearButton" ng-click="clearClicked( $event )" >×</button> '+
        '</div> '+
        '</div> '+
            // selection items
        '<div class="checkBoxContainer">'+
        '<div '+
        'ng-repeat="item in filteredModel | filter:removeGroupEndMarker" class="multiSelectItem"'+
        'ng-class="{selected: item[ tickProperty ], horizontal: orientationH, vertical: orientationV, multiSelectGroup:item[ groupProperty ], disabled:itemIsDisabled( item )}"'+
        'ng-click="syncItems( item, $event, $index );" '+
        'ng-mouseleave="removeFocusStyle( tabIndex );"> '+
            // this is the spacing for grouped items
        '<div class="acol" ng-if="item[ spacingProperty ] > 0" ng-repeat="i in numberToArray( item[ spacingProperty ] ) track by $index">'+
        '</div>  '+
        '<div class="acol">'+
        '<label>'+
            // input, so that it can accept focus on keyboard click
        '<input class="checkbox focusable" type="checkbox" '+
        'ng-disabled="itemIsDisabled( item )" '+
        'ng-checked="item[ tickProperty ]" '+
        'ng-click="syncItems( item, $event, $index )" />'+
            // item label using ng-bind-hteml
        '<span '+
        'ng-class="{disabled:itemIsDisabled( item )}" '+
        'ng-bind-html="writeLabel( item, \'itemLabel\' )">'+
        '</span>'+
        '</label>'+
        '</div>'+
            // the tick/check mark
        '<span class="tickMark" ng-if="item[ groupProperty ] !== true && item[ tickProperty ] === true" ng-bind-html="icon.tickMark"></span>'+
        '</div>'+
        '</div>'+
        '</div>'+
        '</span>';
    $templateCache.put( 'isteven-multi-select.htm' , template );
}]); 
/*
 * Don't modify things marked with ! - unless you know what you're doing
 */

/* ! vertical layout */
.multiSelect .vertical {
    float: none;
}

/* ! horizontal layout */
.multiSelect .horizontal:not(.multiSelectGroup) {
    float: left;
}

/* ! create a "row" */
.multiSelect .line {
    padding:  2px 0px 4px 0px;
    max-height: 30px;
    overflow: hidden;
    box-sizing: content-box;
}

/* ! create a "column" */
.multiSelect .acol {
    display: inline-block;
    min-width: 12px;
}

/* ! */
.multiSelect .inlineBlock {
    display: inline-block;
}

/* the multiselect button */
.multiSelect > button {
    display: inline-block;
    position: relative;
    text-align: center;
    cursor: pointer;
    border: 1px solid #c6c6c6;
    padding: 1px 8px 1px 8px;
    font-size: 14px;
    min-height : 38px !important;
    border-radius: 4px;
    color: #555;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
    white-space:normal;
    background-color: #fff;
    background-image: linear-gradient(#fff, #f7f7f7);
}

/* button: hover */
.multiSelect > button:hover {
    background-image: linear-gradient(#fff, #e9e9e9);
}

/* button: disabled */
.multiSelect > button:disabled {
    background-image: linear-gradient(#fff, #fff);
    border: 1px solid #ddd;
    color: #999;
}

/* button: clicked */
.multiSelect .buttonClicked {
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
}

/* labels on the button */
.multiSelect .buttonLabel {
    display: inline-block;
    padding: 5px 0px 5px 0px;
}

/* downward pointing arrow */
.multiSelect .caret {
    display: inline-block;
    width: 0;
    height: 0;
    margin: 0px 0px 1px 12px  !important;
    vertical-align: middle;
    border-top: 4px solid #333;
    border-right: 4px solid transparent;
    border-left: 4px solid transparent;
    border-bottom: 0 dotted;
}

/* the main checkboxes and helper layer */
.multiSelect .checkboxLayer {
    background-color: #fff;
    position: absolute;
    z-index: 999;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
    min-width:278px;
    display: none !important;
}

/* container of helper elements */
.multiSelect .helperContainer {
    border-bottom: 1px solid #ddd;
    padding: 8px 8px 0px 8px;
}

/* helper buttons (select all, none, reset); */
.multiSelect .helperButton {
    display: inline;
    text-align: center;
    cursor: pointer;
    border: 1px solid #ccc;
    height: 26px;
    font-size: 13px;
    border-radius: 2px;
    color: #666;
    background-color: #f1f1f1;
    line-height: 1.6;
    margin: 0px 0px 8px 0px;
}

.multiSelect .helperButton.reset{
    float: right;
}

.multiSelect .helperButton:not( .reset ) {
    margin-right: 4px;
}

/* clear button */
.multiSelect .clearButton {
    position: absolute;
    display: inline;
    text-align: center;
    cursor: pointer;
    border: 1px solid #ccc;
    height: 22px;
    width: 22px;
    font-size: 13px;
    border-radius: 2px;
    color: #666;
    background-color: #f1f1f1;
    line-height: 1.4;
    right : 2px;
    top: 4px;
}

/* filter */
.multiSelect .inputFilter {
    border-radius: 2px;
    border: 1px solid #ccc;
    height: 26px;
    font-size: 14px;
    width:100%;
    padding-left:7px;
    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
    box-sizing: border-box;         /* Opera/IE 8+ */
    color: #888;
    margin: 0px 0px 8px 0px;
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}

/* helper elements on hover & focus */
.multiSelect .clearButton:hover,
.multiSelect .helperButton:hover {
    border: 1px solid #ccc;
    color: #999;
    background-color: #f4f4f4;
}
.multiSelect .helperButton:disabled {
    color: #ccc;
    border: 1px solid #ddd;
}

.multiSelect .clearButton:focus,
.multiSelect .helperButton:focus,
.multiSelect .inputFilter:focus {
    border: 1px solid #66AFE9 !important;
    outline: 0;
    -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
    box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
}

/* container of multi select items */
.multiSelect .checkBoxContainer {
    display: block;
    padding: 8px;
    overflow: hidden;
}

/* ! to show / hide the checkbox layer above */
.multiSelect .show {
    display: block !important;
}

/* item labels */
.multiSelect .multiSelectItem {
    display: block;
    padding: 3px;
    color: #444;
    white-space: nowrap;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
    border: 1px solid transparent;
    position: relative;
    min-width:278px;
    min-height: 32px;
}

/* Styling on selected items */
.multiSelect .multiSelectItem:not(.multiSelectGroup).selected
{
    background-image: linear-gradient( #e9e9e9, #f1f1f1 );
    color: #555;
    cursor: pointer;
    border-top: 1px solid #e4e4e4;
    border-left: 1px solid #e4e4e4;
    border-right: 1px solid #d9d9d9;
}

.multiSelect .multiSelectItem .acol label {
    display: inline-block;
    padding-right: 30px;
    margin: 0px;
    font-weight: normal;
    line-height: normal;
}

/* item labels focus on mouse hover */
.multiSelect .multiSelectItem:hover,
.multiSelect .multiSelectGroup:hover {
    background-image: linear-gradient( #c1c1c1, #999 ) !important;
    color: #fff !important;
    cursor: pointer;
    border: 1px solid #ccc !important;
}

/* item labels focus using keyboard */
.multiSelect .multiSelectFocus {
    background-image: linear-gradient( #c1c1c1, #999 ) !important;
    color: #fff !important;
    cursor: pointer;
    border: 1px solid #ccc !important;
}

/* change mouse pointer into the pointing finger */
.multiSelect .multiSelectItem span:hover,
.multiSelect .multiSelectGroup span:hover
{
    cursor: pointer;
}

/* ! group labels */
.multiSelect .multiSelectGroup {
    display: block;
    clear: both;
}

/* right-align the tick mark (&#10004;) */
.multiSelect .tickMark {
    display:inline-block;
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 10px;
}

/* hide the original HTML checkbox away */
.multiSelect .checkbox {
    color: #ddd !important;
    position: absolute;
    left: -9999px;
    cursor: pointer;
}

/* checkboxes currently disabled */
.multiSelect .disabled,
.multiSelect .disabled:hover,
.multiSelect .disabled label input:hover ~ span {
    color: #c4c4c4 !important;
    cursor: not-allowed !important;
}

/* If you use images in button / checkbox label, you might want to change the image style here. */
.multiSelect img {
    vertical-align: middle;
    margin-bottom:0px;
    max-height: 22px;
    max-width:22px;
}
/* 
 * Don't modify things marked with ! - unless you know what you're doing
 */

/* ! vertical layout */
.multiSelect .vertical {
    float: none;        
}

/* ! horizontal layout */
.multiSelect .horizontal:not(.multiSelectGroup) {
    float: left;
}

/* ! create a "row" */
.multiSelect .line {
    padding:  2px 0px 4px 0px;
    max-height: 30px;   
    overflow: hidden;
    box-sizing: content-box;
}

/* ! create a "column" */
.multiSelect .acol {
    display: inline-block;    
    min-width: 12px;
}

/* ! */
.multiSelect .inlineBlock {
    display: inline-block;
}

/* the multiselect button */
.multiSelect > button {
    display: inline-block;
    position: relative;
    text-align: center;    
    cursor: pointer;
    border: 1px solid #c6c6c6;    
    padding: 1px 8px 1px 8px;    
    font-size: 14px;
    min-height : 38px !important;
    border-radius: 4px;
    color: #555;   
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none; 
    white-space:normal;
    background-color: #fff;
    background-image: linear-gradient(#fff, #f7f7f7);      
}

/* button: hover */
.multiSelect > button:hover {    
    background-image: linear-gradient(#fff, #e9e9e9);    
}

/* button: clicked */
.multiSelect .buttonClicked {
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
}

/* labels on the button */
.multiSelect .buttonLabel {
    display: inline-block;
    padding: 5px 0px 5px 0px; 
}

/* downward pointing arrow */
.multiSelect .caret {
    display: inline-block;
    width: 0;
    height: 0;
    margin: 0px 0px 1px 12px  !important;    
    vertical-align: middle;
    border-top: 4px solid #333;
    border-right: 4px solid transparent;
    border-left: 4px solid transparent;
    border-bottom: 0 dotted; 
}

/* the main checkboxes and helper layer */
.multiSelect .checkboxLayer {
    background-color: #fff;
    position: absolute;
    z-index: 999;
    border: 1px solid rgba(0, 0, 0, 0.15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);    
    min-width:278px;
    display: none !important;    
}

/* container of helper elements */
.multiSelect .helperContainer {
    border-bottom: 1px solid #ddd;
    padding: 8px 8px 0px 8px;    
}

/* helper buttons (select all, none, reset); */
.multiSelect .helperButton {
    display: inline;
    text-align: center;
    cursor: pointer;
    border: 1px solid #ccc;
    height: 26px;
    font-size: 13px;
    border-radius: 2px;
    color: #666;    
    background-color: #f1f1f1;
    line-height: 1.6;
    margin: 0px 0px 8px 0px;
}

.multiSelect .helperButton.reset{
    float: right;
}

.multiSelect .helperButton:not( .reset ) {
    margin-right: 4px;    
}

/* clear button */
.multiSelect .clearButton {
    position: absolute;
    display: inline;
    text-align: center;
    cursor: pointer;
    border: 1px solid #ccc;
    height: 22px;
    width: 22px;
    font-size: 13px;
    border-radius: 2px;
    color: #666;    
    background-color: #f1f1f1;
    line-height: 1.4;
    right : 2px;
    top: 4px;
}

/* filter */
.multiSelect .inputFilter {
    border-radius: 2px; 
    border: 1px solid #ccc;
    height: 26px;
    font-size: 14px;
    width:100%;    
    padding-left:7px;
    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
    box-sizing: border-box;         /* Opera/IE 8+ */    
    color: #888;
    margin: 0px 0px 8px 0px;
    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}

/* helper elements on hover & focus */
.multiSelect .clearButton:hover,
.multiSelect .helperButton:hover {
    border: 1px solid #ccc;
    color: #999;    
    background-color: #f4f4f4;    
}

.multiSelect .clearButton:focus,
.multiSelect .helperButton:focus,
.multiSelect .inputFilter:focus {
    border: 1px solid #66AFE9 !important;
    outline: 0;
    -webkit-box-shadow: box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
            box-shadow: inset 0 0 1px rgba(0,0,0,.065), 0 0 5px rgba(102, 175, 233, .6) !important;
}

/* container of multi select items */
.multiSelect .checkBoxContainer {
    display: block;
    padding: 8px;   
    overflow: hidden;
}

/* ! to show / hide the checkbox layer above */
.multiSelect .show {
    display: block !important;
}

/* item labels */
.multiSelect .multiSelectItem {
    display: block;
    padding: 3px;
    color: #444;       
    white-space: nowrap;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;      
    border: 1px solid transparent;
    position: relative;
    min-width:278px;    
    min-height: 32px;    
}

/* Styling on selected items */
.multiSelect .multiSelectItem:not(.multiSelectGroup).selected
{
    background-image: linear-gradient( #e9e9e9, #f1f1f1 );    
    color: #555;        
    cursor: pointer;        
    border-top: 1px solid #e4e4e4;
    border-left: 1px solid #e4e4e4;    
    border-right: 1px solid #d9d9d9;            
}

.multiSelect .multiSelectItem .acol label {
    display: inline-block;
    padding-right: 30px;    
    margin: 0px;
    font-weight: normal;    
    line-height: normal;
}

/* item labels focus on mouse hover */
.multiSelect .multiSelectItem:hover, 
.multiSelect .multiSelectGroup:hover {
    background-image: linear-gradient( #c1c1c1, #999 ) !important;    
    color: #fff !important;    
    cursor: pointer;        
    border: 1px solid #ccc !important;
}

/* item labels focus using keyboard */
.multiSelect .multiSelectFocus { 
    background-image: linear-gradient( #c1c1c1, #999 ) !important;    
    color: #fff !important;    
    cursor: pointer;        
    border: 1px solid #ccc !important;
}

/* change mouse pointer into the pointing finger */
.multiSelect .multiSelectItem span:hover, 
.multiSelect .multiSelectGroup span:hover
{    
    cursor: pointer;  
}

/* ! group labels */
.multiSelect .multiSelectGroup {    
    display: block;
    clear: both;
}

/* right-align the tick mark (&#10004;) */
.multiSelect .tickMark {
    display:inline-block; 
    position: absolute; 
    right: 10px; 
    top: 7px;
    font-size: 10px;          
}

/* hide the original HTML checkbox away */
.multiSelect .checkbox {    
    color: #ddd !important;    
    position: absolute;
    left: -9999px;
    cursor: pointer;    
}

/* checkboxes currently disabled */
.multiSelect .disabled, 
.multiSelect .disabled:hover,
.multiSelect .disabled label input:hover ~ span {
    color: #c4c4c4 !important;
    cursor: not-allowed !important;
}

/* If you use images in button / checkbox label, you might want to change the image style here. */
.multiSelect img {
    vertical-align: middle;
    margin-bottom:0px;
    max-height: 22px;
    max-width:22px;
}