PageSourceSearch

https://utilisocial.io/lib/angular-multi-select.js?v=utilisocial-25.8.0.3.2

js utilisocial.io collected 2026-10-08 19:26:54 UTC 42,299 bytes, 899 lines download raw bytes

1/*
2 * Angular JS Multi Select
3 * Creates a dropdown-like button with checkboxes.
4 *
5 * Project started on: Tue, 14 Jan 2014 - 5:18:02 PM
6 * Current version: 2.0.2
7 *
8 * Released under the MIT License
9 * --------------------------------------------------------------------------------
10 * The MIT License (MIT)
11 *
12 * Copyright (c) 2014 Ignatius Steven (https://github.com/isteven)
13 *
14 * Permission is hereby granted, free of charge, to any person obtaining a copy
15 * of this software and associated documentation files (the "Software"), to deal
16 * in the Software without restriction, including without limitation the rights
17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 * copies of the Software, and to permit persons to whom the Software is
19 * furnished to do so, subject to the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be included in all
22 * copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 * SOFTWARE.
31 * --------------------------------------------------------------------------------
32 */
33
34angular.module( 'multi-select', ['ng'] ).directive( 'multiSelect' , [ '$sce', '$timeout', function ( $sce, $timeout ) {
35    return {
36        restrict:
37            'AE',
38
39        replace:
40            true,
41
42        scope:
43        {
44            // models
45            inputModel      : '=',
46            outputModel     : '=',
47
48            // settings based on attribute
49            buttonLabel     : '@',
50            defaultLabel    : '@',
51            directiveId     : '@',
52            helperElements  : '@',
53            isDisabled      : '=',
54            itemLabel       : '@',
55            maxLabels       : '@',
56            orientation     : '@',
57            selectionMode   : '@',
58
59            // settings based on input model property
60            tickProperty    : '@',
61            disableProperty : '@',
62            groupProperty   : '@',
63            maxHeight       : '@',
64
65            // callbacks
66            onClose         : '&',
67            onItemClick     : '&',
68            onOpen          : '&'
69        },
70
71        template:
72            '<span class="multiSelect inlineBlock">' +
73            '<button type="button" class="button multiSelectButton" ng-class="{disabled: isDisabled}" ng-click="toggleCheckboxes( $event ); refreshSelectedItems(); refreshButton();" ng-bind-html="varButtonLabel">' +
74            '</button>' +
75            '<div class="checkboxLayer">' +
76            '<form>' +
77            '<div class="helperContainer" ng-if="displayHelper( \'filter\' ) || displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
78            '<div class="line" ng-if="displayHelper( \'all\' ) || displayHelper( \'none\' ) || displayHelper( \'reset\' )">' +
79            '<button type="button" ng-click="select( \'all\',   $event );"    class="helperButton" ng-if="!isDisabled && displayHelper( \'all\' )">   &#10003;&nbsp; Select All</button> ' +
80            '<button type="button" ng-click="select( \'none\',  $event );"   class="helperButton" ng-if="!isDisabled && displayHelper( \'none\' )">  &times;&nbsp; Select None</button>' +
81            '<button type="button" ng-click="select( \'reset\', $event );"  class="helperButton" ng-if="!isDisabled && displayHelper( \'reset\' )" style="float:right">&#8630;&nbsp; Reset</button>' +
82            '</div>' +
83            '<div class="line" style="position:relative" ng-if="displayHelper( \'filter\' )">' +
84            '<input placeholder="Search..." type="text" ng-click="select( \'filter\', $event )" ng-model="inputLabel.labelFilter" ng-change="updateFilter();$scope.getFormElements();" class="inputFilter" />' +
85            '<button type="button" class="clearButton" ng-click="inputLabel.labelFilter=\'\';updateFilter();prepareGrouping();prepareIndex();select( \'clear\', $event )">&times;</button> ' +
86            '</div>' +
87            '</div>' +
88            '<div class="checkBoxContainer" style="{{setHeight();}}">' +
89            '<div ng-repeat="item in filteredModel | filter:removeGroupEndMarker" class="multiSelectItem"' +
90            'ng-class="{selected: item[ tickProperty ], horizontal: orientationH, vertical: orientationV, multiSelectGroup:item[ groupProperty ], disabled:itemIsDisabled( item )}"' +
91            'ng-click="syncItems( item, $event, $index );"' +
92            'ng-mouseleave="removeFocusStyle( tabIndex );">' +
93            '<div class="acol" ng-if="item[ spacingProperty ] > 0" ng-repeat="i in numberToArray( item[ spacingProperty ] ) track by $index">&nbsp;</div>' +
94            '<div class="acol">' +
95            '<label>' +
96            '<input class="checkbox focusable" type="checkbox" ng-disabled="itemIsDisabled( item )" ng-checked="item[ tickProperty ]" ng-click="syncItems( item, $event, $index )" />' +
97            '<span ng-class="{disabled:itemIsDisabled( item )}" ng-bind-html="writeLabel( item, \'itemLabel\' )"></span>' +
98            '</label>' +
99            '</div>' +
100            '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
101            '<span class="tickMark" ng-if="item[ groupProperty ] !== true && item[ tickProperty ] === true">&#10004;</span>' +
102            '</div>' +
103            '</div>' +
104            '</form>' +
105            '</div>' +
106            '</span>',
107
108        link: function ( $scope, element, attrs ) {
109
110            $scope.backUp           = [];
111            $scope.varButtonLabel   = '';
112            $scope.scrolled         = false;
113            $scope.spacingProperty  = '';
114            $scope.indexProperty    = '';
115            $scope.checkBoxLayer    = '';
116            $scope.orientationH     = false;
117            $scope.orientationV     = true;
118            $scope.filteredModel    = [];
119            $scope.inputLabel       = { labelFilter: '' };
120            $scope.selectedItems    = [];
121            $scope.formElements     = [];
122            $scope.tabIndex         = 0;
123            $scope.clickedItem      = null;
124            prevTabIndex            = 0;
125            helperItems             = [];
126            helperItemsLength       = 0;
127
128            // If user specify a height, call this function
129            $scope.setHeight = function() {
130                if ( typeof $scope.maxHeight !== 'undefined' ) {
131                    return 'max-height: ' + $scope.maxHeight + '; overflow-y:scroll';
132                }
133            }
134
135            // A little hack so that AngularJS ng-repeat can loop using start and end index like a normal loop
136            // http://stackoverflow.com/questions/16824853/way-to-ng-repeat-defined-number-of-times-instead-of-repeating-over-array
137            $scope.numberToArray = function( num ) {
138                return new Array( num );
139            }
140
141            $scope.updateFilter = function()
142            {
143                // we check by looping from end of array
144                $scope.filteredModel   = [];
145                var i = 0;
146
147                if ( typeof $scope.inputModel === 'undefined' ) {
148                    return [];
149                }
150
151                for( i = $scope.inputModel.length - 1; i >= 0; i-- ) {
152
153                    // if it's group end
154                    if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === false ) {
155                        $scope.filteredModel.push( $scope.inputModel[ i ] );
156                    }
157
158                    // if it's data
159                    var gotData = false;
160                    if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] === 'undefined' ) {
161
162                        for (var key in $scope.inputModel[ i ] ) {
163                            // if filter string is in one of object property
164                            if ( typeof $scope.inputModel[ i ][ key ] !== 'boolean'  && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 ) {
165                                gotData = true;
166                                break;
167                            }
168                        }
169                        if ( gotData === true ) {
170                            // push
171                            $scope.filteredModel.push( $scope.inputModel[ i ] );
172                        }
173                    }
174
175                    // if it's group start
176                    if ( typeof $scope.inputModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ $scope.groupProperty ] === true ) {
177
178                        if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ $scope.groupProperty ] === false ) {
179                            $scope.filteredModel.pop();
180                        }
181                        else {
182                            $scope.filteredModel.push( $scope.inputModel[ i ] );
183                        }
184                    }
185                }
186
187                $scope.filteredModel.reverse();
188                $timeout( function() {
189                    $scope.getFormElements();
190                },0);
191            };
192
193            // List all the input elements.
194            // This function will be called everytime the filter is updated. Not good for performance, but oh well..
195            $scope.getFormElements = function() {
196                $scope.formElements = [];
197                for ( var i = 0; i < element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements.length ; i++ ) {
198                    $scope.formElements.push( element[ 0 ].getElementsByTagName( 'FORM' )[ 0 ].elements[ i ] );
199                }
200            }
201
202            // check if an item has $scope.groupProperty (be it true or false)
203            $scope.isGroupMarker = function( item , type ) {
204                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === type ) return true;
205                return false;
206            }
207
208            $scope.removeGroupEndMarker = function( item ) {
209                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) return false;
210                return true;
211            }
212
213
214            // Show or hide a helper element
215            $scope.displayHelper = function( elementString ) {
216
217                if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
218
219                    switch( elementString.toUpperCase() ) {
220                        case 'ALL':
221                            return false;
222                            break;
223                        case 'NONE':
224                            return false;
225                            break;
226                        case 'RESET':
227                            if ( typeof attrs.helperElements === 'undefined' ) {
228                                return true;
229                            }
230                            else if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'RESET' ) >= 0 ) {
231                                return true;
232                            }
233                            break;
234                        case 'FILTER':
235                            if ( typeof attrs.helperElements === 'undefined' ) {
236                                return true;
237                            }
238                            if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( 'FILTER' ) >= 0 ) {
239                                return true;
240                            }
241                            break;
242                        default:
243                            break;
244                    }
245
246                    return false;
247                }
248
249                else {
250                    if ( typeof attrs.helperElements === 'undefined' ) {
251                        return true;
252                    }
253                    if ( attrs.helperElements && $scope.helperElements.toUpperCase().indexOf( elementString.toUpperCase() ) >= 0 ) {
254                        return true;
255                    }
256                    return false;
257                }
258            }
259
260            // call this function when an item is clicked
261            $scope.syncItems = function( item, e, ng_repeat_index ) {
262
263                e.preventDefault();
264                e.stopPropagation();
265
266                // if it's globaly disabled, then don't do anything
267                if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) {
268                    return false;
269                }
270
271                // don't change disabled items
272                if ( typeof attrs.isDisabled !== 'undefined' && $scope.isDisabled === true ) {
273                    return false;
274                }
275
276                // we don't care about end of group markers
277                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === false ) {
278                    return false;
279                }
280
281                index = $scope.filteredModel.indexOf( item );
282
283                // process items if the start of group marker is clicked ( only for multiple selection! )
284                // if, in a group, there are items which are not selected, then they all will be selected
285                // if, in a group, all items are selected, then they all will be de-selected
286                if ( typeof item[ $scope.groupProperty ] !== 'undefined' && item[ $scope.groupProperty ] === true ) {
287
288                    if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
289                        return false;
290                    }
291
292                    var i,j,k;
293                    var startIndex = 0;
294                    var endIndex = $scope.filteredModel.length - 1;
295                    var tempArr = [];
296                    var nestLevel = 0;
297
298                    for( i = index ; i < $scope.filteredModel.length ; i++) {
299
300                        if ( nestLevel === 0 && i > index )
301                        {
302                            break;
303                        }
304
305                        // if group start
306                        if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === true ) {
307
308                            // To cater multi level grouping
309                            if ( tempArr.length === 0 ) {
310                                startIndex = i + 1;
311                            }
312                            nestLevel = nestLevel + 1;
313                        }
314
315                        // if group end
316                        else if ( typeof $scope.filteredModel[ i ][ $scope.groupProperty ] !== 'undefined' && $scope.filteredModel[ i ][ $scope.groupProperty ] === false ) {
317
318                            nestLevel = nestLevel - 1;
319
320                            // cek if all are ticked or not
321                            if ( tempArr.length > 0 && nestLevel === 0 ) {
322
323                                var allTicked = true;
324
325                                endIndex = i;
326
327                                for ( j = 0; j < tempArr.length ; j++ ) {
328                                    if ( typeof tempArr[ j ][ $scope.tickProperty ] !== 'undefined' &&  tempArr[ j ][ $scope.tickProperty ] === false ) {
329                                        allTicked = false;
330                                        break;
331                                    }
332                                }
333
334                                if ( allTicked === true ) {
335                                    for ( j = startIndex; j <= endIndex ; j++ ) {
336                                        if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {
337                                            if ( typeof attrs.disableProperty === 'undefined' ) {
338                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
339                                                // we refresh input model as well
340                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
341                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
342                                            }
343                                            else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
344                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = false;
345                                                // we refresh input model as well
346                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
347                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = false;
348                                            }
349                                        }
350                                    }
351                                }
352
353                                else {
354                                    for ( j = startIndex; j <= endIndex ; j++ ) {
355                                        if ( typeof $scope.filteredModel[ j ][ $scope.groupProperty ] === 'undefined' ) {
356                                            if ( typeof attrs.disableProperty === 'undefined' ) {
357                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;
358                                                // we refresh input model as well
359                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
360                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;
361
362                                            }
363                                            else if ( $scope.filteredModel[ j ][ $scope.disableProperty ] !== true ) {
364                                                $scope.filteredModel[ j ][ $scope.tickProperty ] = true;
365                                                // we refresh input model as well
366                                                inputModelIndex = $scope.filteredModel[ j ][ $scope.indexProperty ];
367                                                $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true;
368                                            }
369                                        }
370                                    }
371                                }
372                            }
373                        }
374
375                        // if data
376                        else {
377                            tempArr.push( $scope.filteredModel[ i ] );
378                        }
379                    }
380                }
381
382                // single item click
383                else {
384
385                    // If it's single selection mode
386                    if ( attrs.selectionMode && $scope.selectionMode.toUpperCase() === 'SINGLE' ) {
387
388                        // first, set everything to false
389                        for( i=0 ; i < $scope.filteredModel.length ; i++) {
390                            $scope.filteredModel[ i ][ $scope.tickProperty ] = false;
391                        }
392                        for( i=0 ; i < $scope.inputModel.length ; i++) {
393                            $scope.inputModel[ i ][ $scope.tickProperty ] = false;
394                        }
395
396                        // then set the clicked item to true
397                        $scope.filteredModel[ index ][ $scope.tickProperty ] = true;
398
399                        $scope.toggleCheckboxes( e );
400                    }
401
402                    // Multiple
403                    else {
404                        $scope.filteredModel[ index ][ $scope.tickProperty ]   = !$scope.filteredModel[ index ][ $scope.tickProperty ];
405                    }
406
407                    // we refresh input model as well
408                    inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ];
409                    $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ];
410                }
411
412                $scope.clickedItem = angular.copy( item );
413
414                // We update the index here
415                prevTabIndex = $scope.tabIndex;
416                $scope.tabIndex = ng_repeat_index + helperItemsLength;
417
418                // Set focus on the hidden checkbox
419                e.target.focus();
420
421                // set & remove CSS style
422                $scope.removeFocusStyle( prevTabIndex );
423                $scope.setFocusStyle( $scope.tabIndex );
424            }
425
426            // update $scope.selectedItems
427            // this variable is used in $scope.outputModel and to refresh the button label
428            $scope.refreshSelectedItems = function() {
429                $scope.selectedItems    = [];
430                angular.forEach( $scope.inputModel, function( value, key ) {
431                    if ( typeof value !== 'undefined' ) {
432                        if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
433                            if ( value[ $scope.tickProperty ] === true ) {
434                                $scope.selectedItems.push( value );
435                            }
436                        }
437                    }
438                });
439            }
440
441            // refresh output model as well
442            $scope.refreshOutputModel = function() {
443                if ( typeof attrs.outputModel !== 'undefined' ) {
444                    $scope.outputModel = angular.copy( $scope.selectedItems );
445                    angular.forEach( $scope.outputModel, function( value, key ) {
446                        // remove the index number and spacing number from output model
447                        delete value[ $scope.indexProperty ];
448                        delete value[ $scope.spacingProperty ];
449                    });
450                }
451            }
452
453            // refresh button label
454            $scope.refreshButton = function() {
455
456                $scope.varButtonLabel   = '';
457                ctr                     = 0;
458
459                // refresh button label...
460                if ( $scope.selectedItems.length === 0 ) {
461                    // https://github.com/isteven/angular-multi-select/pull/19
462                    $scope.varButtonLabel = ( typeof $scope.defaultLabel !== 'undefined' ) ? $scope.defaultLabel : 'None selected';
463                }
464                else {
465                    var tempMaxLabels = $scope.selectedItems.length;
466                    if ( typeof $scope.maxLabels !== 'undefined' && $scope.maxLabels !== '' ) {
467                        tempMaxLabels = $scope.maxLabels;
468                    }
469
470                    // if max amount of labels displayed..
471                    if ( $scope.selectedItems.length > tempMaxLabels ) {
472                        $scope.more = true;
473                    }
474                    else {
475                        $scope.more = false;
476                    }
477
478                    angular.forEach( $scope.selectedItems, function( value, key ) {
479                        if ( typeof value !== 'undefined' ) {
480                            if ( ctr < tempMaxLabels ) {
481                                $scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? '</div>, <div class="buttonLabel">' : '<div class="buttonLabel">') + $scope.writeLabel( value, 'buttonLabel' );
482                            }
483                            ctr++;
484                        }
485                    });
486
487                    if ( $scope.more === true ) {
488                        // https://github.com/isteven/angular-multi-select/pull/16
489                        if (tempMaxLabels > 0) {
490                            $scope.varButtonLabel += ', ... ';
491                        }
492                        $scope.varButtonLabel += '(Total: ' + $scope.selectedItems.length + ')';
493                    }
494                }
495                $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '<span class="caret"></span>' );
496            }
497
498            // Check if a checkbox is disabled or enabled. It will check the granular control (disableProperty) and global control (isDisabled)
499            // Take note that the granular control has higher priority.
500            $scope.itemIsDisabled = function( item ) {
501
502                if ( typeof attrs.disableProperty !== 'undefined' && item[ $scope.disableProperty ] === true ) {
503                    return true;
504                }
505                else {
506                    if ( $scope.isDisabled === true ) {
507                        return true;
508                    }
509                    else {
510                        return false;
511                    }
512                }
513
514            }
515
516            // A simple function to parse the item label settings
517            $scope.writeLabel = function( item, type ) {
518                var label = '';
519                var temp = $scope[ type ].split( ' ' );
520                angular.forEach( temp, function( value2, key2 ) {
521                    if ( typeof value2 !== 'undefined' ) {
522                        angular.forEach( item, function( value1, key1 ) {
523                            if ( key1 == value2 ) {
524                                label += '&nbsp;' + value1;
525                            }
526                        });
527                    }
528                });
529                if ( type.toUpperCase() === 'BUTTONLABEL' ) {
530                    return label;
531                }
532                return $sce.trustAsHtml( label );
533            }
534
535            // UI operations to show/hide checkboxes based on click event..
536            $scope.toggleCheckboxes = function( e ) {
537
538                // We grab the checkboxLayer
539                $scope.checkBoxLayer = element.children()[1];
540
541                // We grab the button
542                clickedEl = element.children()[0];
543
544                // Just to make sure.. had a bug where key events were recorded twice
545                angular.element( document ).unbind( 'click', $scope.externalClickListener );
546                angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
547
548                // clear filter
549                $scope.inputLabel.labelFilter = '';
550                $scope.updateFilter();
551
552                // close if ESC key is pressed.
553                if ( e.keyCode === 27 ) {
554                    angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
555                    angular.element( clickedEl ).removeClass( 'buttonClicked' );
556                    angular.element( document ).unbind( 'click', $scope.externalClickListener );
557                    angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
558
559                    // clear the focused element;
560                    $scope.removeFocusStyle( $scope.tabIndex );
561
562                    // close callback
563                    $scope.onClose( { data: element } );
564                    return true;
565                }
566
567                // The idea below was taken from another multi-select directive - https://github.com/amitava82/angular-multiselect
568                // His version is awesome if you need a more simple multi-select approach.
569
570                // close
571                if ( angular.element( $scope.checkBoxLayer ).hasClass( 'show' )) {
572                    angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
573                    angular.element( clickedEl ).removeClass( 'buttonClicked' );
574                    angular.element( document ).unbind( 'click', $scope.externalClickListener );
575                    angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
576
577                    // clear the focused element;
578                    $scope.removeFocusStyle( $scope.tabIndex );
579
580                    // close callback
581                    $scope.onClose( { data: element } );
582                }
583                // open
584                else
585                {
586                    helperItems = [];
587                    helperItemsLength = 0;
588
589                    angular.element( $scope.checkBoxLayer ).addClass( 'show' );
590                    angular.element( clickedEl ).addClass( 'buttonClicked' );
591                    angular.element( document ).bind( 'click', $scope.externalClickListener );
592                    angular.element( document ).bind( 'keydown', $scope.keyboardListener );
593
594                    // to get the initial tab index, depending on how many helper elements we have.
595                    // priority is to always focus it on the input filter
596                    $scope.getFormElements();
597                    $scope.tabIndex = 0;
598
599                    var helperContainer = angular.element( element[ 0 ].querySelector( '.helperContainer' ) )[0];
600
601                    if ( typeof helperContainer !== 'undefined' ) {
602                        for ( i = 0; i < helperContainer.getElementsByTagName( 'BUTTON' ).length ; i++ ) {
603                            helperItems[ i ] = helperContainer.getElementsByTagName( 'BUTTON' )[ i ];
604                        }
605                        helperItemsLength = helperItems.length + helperContainer.getElementsByTagName( 'INPUT' ).length;
606                    }
607
608                    // focus on the filter element on open.
609                    if ( element[ 0 ].querySelector( '.inputFilter' ) ) {
610                        element[ 0 ].querySelector( '.inputFilter' ).focus();
611                        $scope.tabIndex = $scope.tabIndex + helperItemsLength - 2;
612                    }
613                    // if there's no filter then just focus on the first checkbox item
614                    else {
615                        $scope.formElements[ $scope.tabIndex ].focus();
616                    }
617
618                    // open callback
619                    $scope.onOpen( { data: element } );
620                }
621            }
622
623            // handle clicks outside the button / multi select layer
624            $scope.externalClickListener = function( e ) {
625                targetsArr = element.find( e.target.tagName );
626                for (var i = 0; i < targetsArr.length; i++) {
627                    if ( e.target == targetsArr[i] ) {
628                        return;
629                    }
630                }
631
632                angular.element( $scope.checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' );
633                angular.element( $scope.checkBoxLayer ).removeClass( 'show' );
634                angular.element( document ).unbind( 'click', $scope.externalClickListener );
635                angular.element( document ).unbind( 'keydown', $scope.keyboardListener );
636
637                // close callback
638                $timeout( function() {
639                    $scope.onClose( { data: element } );
640                }, 0 );
641            }
642
643            // traverse up to find the button tag
644            // http://stackoverflow.com/questions/7332179/how-to-recursively-search-all-parentnodes
645            $scope.findUpTag = function ( el, tag, className ) {
646                while ( el.parentNode ) {
647                    el = el.parentNode;
648                    if ( typeof el.tagName !== 'undefined' ) {
649                        if ( el.tagName.toUpperCase() === tag.toUpperCase() && el.className.indexOf( className ) > -1 ) {
650                            return el;
651                        }
652                    }
653                }
654                return null;
655            }
656
657            // select All / select None / reset buttons
658            $scope.select = function( type, e ) {
659
660                helperIndex = helperItems.indexOf( e.target );
661                $scope.tabIndex = helperIndex;
662
663                switch( type.toUpperCase() ) {
664                    case 'ALL':
665                        angular.forEach( $scope.filteredModel, function( value, key ) {
666                            if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {
667                                if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
668                                    value[ $scope.tickProperty ] = true;
669                                }
670                            }
671                        });
672                        break;
673                    case 'NONE':
674                        angular.forEach( $scope.filteredModel, function( value, key ) {
675                            if ( typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {
676                                if ( typeof value[ $scope.groupProperty ] === 'undefined' ) {
677                                    value[ $scope.tickProperty ] = false;
678                                }
679                            }
680                        });
681                        break;
682                    case 'RESET':
683                        angular.forEach( $scope.filteredModel, function( value, key ) {
684                            if ( typeof value[ $scope.groupProperty ] === 'undefined' && typeof value !== 'undefined' && value[ $scope.disableProperty ] !== true ) {
685                                temp = value[ $scope.indexProperty ];
686                                value[ $scope.tickProperty ] = $scope.backUp[ temp ][ $scope.tickProperty ];
687                            }
688                        });
689                        break;
690                    case 'CLEAR':
691                        $scope.tabIndex = $scope.tabIndex + 1;
692                        break;
693                    case 'FILTER':
694                        $scope.tabIndex = helperItems.length - 1;
695                        break;
696                    default:
697                }
698            }
699
700            // just to create a random variable name
701            genRandomString = function( length ) {
702                var possible    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
703                var temp        = '';
704                for( var i=0; i < length; i++ ) {
705                    temp += possible.charAt( Math.floor( Math.random() * possible.length ));
706                }
707                return temp;
708            }
709
710            // count leading spaces
711            $scope.prepareGrouping = function() {
712                var spacing     = 0;
713                angular.forEach( $scope.filteredModel, function( value, key ) {
714                    value[ $scope.spacingProperty ] = spacing;
715                    if ( value[ $scope.groupProperty ] === true ) {
716                        spacing+=2;
717                    }
718                    else if ( value[ $scope.groupProperty ] === false ) {
719                        spacing-=2;
720                    }
721                });
722            }
723
724            // prepare original index
725            $scope.prepareIndex = function() {
726                ctr = 0;
727                angular.forEach( $scope.filteredModel, function( value, key ) {
728                    value[ $scope.indexProperty ] = ctr;
729                    ctr++;
730                });
731            }
732
733            // navigate using up and down arrow
734            $scope.keyboardListener = function( e ) {
735
736                var key = e.keyCode ? e.keyCode : e.which;
737                var isNavigationKey = false;
738
739                // ESC key (close)
740                if ( key === 27 ) {
741                    $scope.toggleCheckboxes( e );
742                }
743
744                // next element ( tab, down & right key )
745                else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) {
746                    isNavigationKey = true;
747                    prevTabIndex = $scope.tabIndex;
748                    $scope.tabIndex++;
749                    if ( $scope.tabIndex > $scope.formElements.length - 1 ) {
750                        $scope.tabIndex = 0;
751                        prevTabIndex = $scope.formElements.length - 1;
752                    }
753                    while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) {
754                        $scope.tabIndex++;
755                        if ( $scope.tabIndex > $scope.formElements.length - 1 ) {
756                            $scope.tabIndex = 0;
757                        }
758                    }
759                }
760
761                // prev element ( shift+tab, up & left key )
762                else if ( key === 38 || key === 37 || ( e.shiftKey && key == 9 ) ) {
763                    isNavigationKey = true;
764                    prevTabIndex = $scope.tabIndex;
765                    $scope.tabIndex--;
766                    if ( $scope.tabIndex < 0 ) {
767                        $scope.tabIndex = $scope.formElements.length - 1;
768                        prevTabIndex = 0;
769                    }
770                    while ( $scope.formElements[ $scope.tabIndex ].disabled === true ) {
771                        $scope.tabIndex--;
772                        if ( $scope.tabIndex < 0 ) {
773                            $scope.tabIndex = $scope.formElements.length - 1;
774                        }
775                    }
776                }
777
778                if ( isNavigationKey === true ) {
779
780                    e.preventDefault();
781                    e.stopPropagation();
782
783                    // set focus on the checkbox
784                    $scope.formElements[ $scope.tabIndex ].focus();
785
786                    // css styling
787                    var actEl = document.activeElement;
788
789                    if ( actEl.type.toUpperCase() === 'CHECKBOX' ) {
790                        $scope.setFocusStyle( $scope.tabIndex );
791                        $scope.removeFocusStyle( prevTabIndex );
792                    }
793                    else {
794                        $scope.removeFocusStyle( prevTabIndex );
795                        $scope.removeFocusStyle( helperItemsLength );
796                        $scope.removeFocusStyle( $scope.formElements.length - 1 );
797                    }
798                }
799
800                isNavigationKey = false;
801            }
802
803            // set (add) CSS style on selected row
804            $scope.setFocusStyle = function( tabIndex ) {
805                angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' );
806            }
807
808            // remove CSS style on selected row
809            $scope.removeFocusStyle = function( tabIndex ) {
810                angular.element( $scope.formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' );
811            }
812
813            ///////////////////////////////////////////////////////
814            //
815            // Logic starts here, initiated by watch 1 & watch 2.
816            //
817            ///////////////////////////////////////////////////////
818
819            var tempStr = genRandomString( 5 );
820            $scope.indexProperty = 'idx_' + tempStr;
821            $scope.spacingProperty = 'spc_' + tempStr;
822
823            // set orientation css
824            if ( typeof attrs.orientation !== 'undefined' ) {
825                if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) {
826                    $scope.orientationH = true;
827                    $scope.orientationV = false;
828                }
829                else {
830                    $scope.orientationH = false;
831                    $scope.orientationV = true;
832                }
833            }
834
835            // watch1, for changes in input model property
836            // updates multi-select when user select/deselect a single checkbox programatically
837            // https://github.com/isteven/angular-multi-select/issues/8
838            $scope.$watch( 'inputModel' , function( newVal ) {
839                if ( newVal ) {
840                    $scope.refreshSelectedItems();
841                    $scope.refreshOutputModel();
842                    $scope.refreshButton();
843                    if ( $scope.clickedItem !== null ) {
844                        $timeout( function() {
845                            $scope.onItemClick( { data: $scope.clickedItem } );
846                            $scope.clickedItem = null;
847                        }, 0 );
848                    }
849                }
850            }, true);
851
852            // watch2 for changes in input model as a whole
853            // this on updates the multi-select when a user load a whole new input-model. We also update the $scope.backUp variable
854            $scope.$watch( 'inputModel' , function( newVal ) {
855                if ( newVal ) {
856                    $scope.backUp = angular.copy( $scope.inputModel );
857                    $scope.updateFilter();
858                    $scope.prepareGrouping();
859                    $scope.prepareIndex();
860                    $scope.refreshSelectedItems();
861                    $scope.refreshOutputModel();
862                    $scope.refreshButton();
863                }
864            });
865
866            // watch for changes in directive state (disabled or enabled)
867            $scope.$watch( 'isDisabled' , function( newVal ) {
868                $scope.isDisabled = newVal;
869            });
870
871            // this is for touch enabled devices. We don't want to hide checkboxes on scroll.
872            angular.element( document ).bind( 'touchstart', function( e ) {
873                $scope.$apply( function() {
874                    $scope.scrolled = false;
875                });
876            });
877
878            // also for touch enabled devices
879            angular.element( document ).bind( 'touchmove', function( e ) {
880                $scope.$apply( function() {
881                    $scope.scrolled = true;
882                });
883            });
884
885            // for IE8, perhaps. Not sure if this is really executed.
886            if ( !Array.prototype.indexOf ) {
887                Array.prototype.indexOf = function(what, i) {
888                    i = i || 0;
889                    var L = this.length;
890                    while (i < L) {
891                        if(this[i] === what) return i;
892                        ++i;
893                    }
894                    return -1;
895                };
896            }
897        }
898    }
899}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.