diff --git a/isteven-multi-select.js b/isteven-multi-select.js index b5d3465..72bc4a0 100644 --- a/isteven-multi-select.js +++ b/isteven-multi-select.js @@ -1,32 +1,32 @@ -/* +/* * Angular JS Multi Select - * Creates a dropdown-like button with checkboxes. + * 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: + * 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 + * 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 + * + * 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. * -------------------------------------------------------------------------------- */ @@ -35,11 +35,11 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' , [ '$sce', '$timeout', '$templateCache', function ( $sce, $timeout, $templateCache ) { return { - restrict: + restrict: 'AE', - scope: - { + scope: + { // models inputModel : '=', outputModel : '=', @@ -48,39 +48,39 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' isDisabled : '=', // callbacks - onClear : '&', + onClear : '&', onClose : '&', - onSearchChange : '&', - onItemClick : '&', - onOpen : '&', - onReset : '&', - onSelectAll : '&', - onSelectNone : '&', + onSearchChange : '&', + onItemClick : '&', + onOpen : '&', + onReset : '&', + onSelectAll : '&', + onSelectNone : '&', // i18n - translation : '=' + 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 ) { + templateUrl: + 'isteven-multi-select.htm', + + link: function ( $scope, element, attrs ) { $scope.backUp = []; - $scope.varButtonLabel = ''; + $scope.varButtonLabel = ''; $scope.spacingProperty = ''; - $scope.indexProperty = ''; + $scope.indexProperty = ''; $scope.orientationH = false; $scope.orientationV = true; $scope.filteredModel = []; - $scope.inputLabel = { labelFilter: '' }; - $scope.tabIndex = 0; + $scope.inputLabel = { labelFilter: '' }; + $scope.tabIndex = 0; $scope.lang = {}; $scope.helperStatus = { all : true, @@ -89,7 +89,7 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' filter : true }; - var + var prevTabIndex = 0, helperItems = [], helperItemsLength = 0, @@ -98,38 +98,38 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' selectedItems = [], formElements = [], vMinSearchLength = 0, - clickedItem = null + clickedItem = null // v3.0.0 // clear button clicked - $scope.clearClicked = function( e ) { + $scope.clearClicked = function( e ) { $scope.inputLabel.labelFilter = ''; $scope.updateFilter(); - $scope.select( 'clear', e ); + $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 ); + return new Array( num ); } // Call this function when user type on the filter field - $scope.searchChanged = function() { + $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; + return false; } for( i = $scope.inputModel.length - 1; i >= 0; i-- ) { @@ -138,39 +138,39 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' if ( typeof $scope.inputModel[ i ][ attrs.groupProperty ] !== 'undefined' && $scope.inputModel[ i ][ attrs.groupProperty ] === false ) { $scope.filteredModel.push( $scope.inputModel[ i ] ); } - - // if it's data + + // 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 $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 ( + if ( typeof $scope.inputModel[ i ][ key ] !== 'boolean' - && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 + && 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 ( + if ( typeof $scope.inputModel[ i ][ key ] !== 'boolean' - && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 + && String( $scope.inputModel[ i ][ key ] ).toUpperCase().indexOf( $scope.inputLabel.labelFilter.toUpperCase() ) >= 0 ) { gotData = true; break; } - } + } } - if ( gotData === true ) { + if ( gotData === true ) { // push $scope.filteredModel.push( $scope.inputModel[ i ] ); } @@ -179,7 +179,7 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // 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' + if ( typeof $scope.filteredModel[ $scope.filteredModel.length - 1 ][ attrs.groupProperty ] !== 'undefined' && $scope.filteredModel[ $scope.filteredModel.length - 1 ][ attrs.groupProperty ] === false ) { $scope.filteredModel.pop(); } @@ -187,71 +187,71 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' $scope.filteredModel.push( $scope.inputModel[ i ] ); } } - } + } + + $scope.filteredModel.reverse(); - $scope.filteredModel.reverse(); - - $timeout( function() { + $timeout( function() { + + $scope.getFormElements(); - $scope.getFormElements(); - - // Callback: on filter change + // 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' ) { + if ( typeof value !== 'undefined' ) { + if ( typeof value[ attrs.groupProperty ] === 'undefined' ) { var tempObj = angular.copy( value ); - var index = filterObj.push( tempObj ); + var index = filterObj.push( tempObj ); delete filterObj[ index - 1 ][ $scope.indexProperty ]; - delete filterObj[ index - 1 ][ $scope.spacingProperty ]; + delete filterObj[ index - 1 ][ $scope.spacingProperty ]; } } }); - $scope.onSearchChange({ - data: + $scope.onSearchChange({ + data: { - keyword: $scope.inputLabel.labelFilter, - result: filterObj - } + 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. + // 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() { + $scope.getFormElements = function() { formElements = []; - var + 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 ( $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' ); + 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' ); + 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' ); @@ -260,58 +260,58 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' checkboxes = element.children().children().next().children().next()[ 0 ].getElementsByTagName( 'input' ); } - // Push them into global array formElements[] + // 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 ] ); } - } + 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; + 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; + 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 ) { + $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 ) { + 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 ) { + 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 ); + 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 ) { + // - 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; @@ -320,44 +320,44 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // 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; + var nestLevel = 0; // we loop throughout the filtered model (not whole model) - for( i = index ; i < $scope.filteredModel.length ; i++) { + 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 ) + 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; - } + 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; + nestLevel = nestLevel - 1; - // cek if all are ticked or not - if ( tempArr.length > 0 && nestLevel === 0 ) { + // cek if all are ticked or not + if ( tempArr.length > 0 && nestLevel === 0 ) { - var allTicked = true; + var allTicked = true; endIndex = i; - for ( j = 0; j < tempArr.length ; j++ ) { + 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++ ) { @@ -375,19 +375,19 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' $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; + $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 @@ -395,16 +395,16 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = true; } } - } - } + } + } } } - + // if data - else { - tempArr.push( $scope.filteredModel[ i ] ); + else { + tempArr.push( $scope.filteredModel[ i ] ); } - } + } } // if an item (not group marker) is clicked @@ -412,18 +412,18 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // 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; - } - + 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; - } + $scope.filteredModel[ index ][ $scope.tickProperty ] = true; + } // Multiple else { @@ -431,29 +431,29 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' } // we refresh input model as well - var inputModelIndex = $scope.filteredModel[ index ][ $scope.indexProperty ]; - $scope.inputModel[ inputModelIndex ][ $scope.tickProperty ] = $scope.filteredModel[ index ][ $scope.tickProperty ]; - } + 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 ) { + clickedItem = angular.copy( item ); + if ( clickedItem !== null ) { $timeout( function() { delete clickedItem[ $scope.indexProperty ]; - delete clickedItem[ $scope.spacingProperty ]; + delete clickedItem[ $scope.spacingProperty ]; $scope.onItemClick( { data: clickedItem } ); - clickedItem = null; - }, 0 ); - } - + clickedItem = null; + }, 0 ); + } + $scope.refreshOutputModel(); - $scope.refreshButton(); + $scope.refreshButton(); // We update the index here prevTabIndex = $scope.tabIndex; $scope.tabIndex = ng_repeat_index + helperItemsLength; - - // Set focus on the hidden checkbox + + // Set focus on the hidden checkbox e.target.focus(); // set & remove CSS style @@ -462,67 +462,67 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' if ( typeof attrs.selectionMode !== 'undefined' && attrs.selectionMode.toUpperCase() === 'SINGLE' ) { // on single selection mode, we then hide the checkbox layer - $scope.toggleCheckboxes( e ); + $scope.toggleCheckboxes( e ); } - } + } // update $scope.outputModel - $scope.refreshOutputModel = function() { - + $scope.refreshOutputModel = function() { + $scope.outputModel = []; - var + 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 + 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; + angular.forEach( value, function( value1, key1 ) { + if ( outputProps.indexOf( key1 ) > -1 ) { + tempObj[ key1 ] = value1; } }); - var index = $scope.outputModel.push( tempObj ); + var index = $scope.outputModel.push( tempObj ); delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ]; - delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ]; + 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 + 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 ); + var index = $scope.outputModel.push( temp ); delete $scope.outputModel[ index - 1 ][ $scope.indexProperty ]; - delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ]; + delete $scope.outputModel[ index - 1 ][ $scope.spacingProperty ]; } - }); + }); } } // refresh button label $scope.refreshButton = function() { - $scope.varButtonLabel = ''; - var ctr = 0; + $scope.varButtonLabel = ''; + var ctr = 0; // refresh button label... if ( $scope.outputModel.length === 0 ) { - // https://github.com/isteven/angular-multi-select/pull/19 + // https://github.com/isteven/angular-multi-select/pull/19 $scope.varButtonLabel = $scope.lang.nothingSelected; } - else { + else { var tempMaxLabels = $scope.outputModel.length; if ( typeof attrs.maxLabels !== 'undefined' && attrs.maxLabels !== '' ) { tempMaxLabels = attrs.maxLabels; @@ -534,85 +534,85 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' } 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 ? ',
' : '
') + $scope.writeLabel( value, 'buttonLabel' ); + if ( typeof value !== 'undefined' && value[ attrs.tickProperty ] === true ) { + if ( ctr < tempMaxLabels ) { + $scope.varButtonLabel += ( $scope.varButtonLabel.length > 0 ? ',
' : '
') + $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 += '(' + $scope.outputModel.length + ')'; } } - $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '' ); + $scope.varButtonLabel = $sce.trustAsHtml( $scope.varButtonLabel + '' ); } // 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 ) { + + if ( typeof attrs.disableProperty !== 'undefined' && item[ attrs.disableProperty ] === true ) { return true; } - else { - if ( $scope.isDisabled === 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 = ''; + var temp = attrs[ type ].split( ' ' ); + var label = ''; - angular.forEach( temp, function( value, key ) { + angular.forEach( temp, function( value, key ) { item[ value ] && ( label += ' ' + value.split( '.' ).reduce( function( prev, current ) { - return prev[ current ]; - }, item )); + return prev[ current ]; + }, item )); }); - - if ( type.toUpperCase() === 'BUTTONLABEL' ) { + + if ( type.toUpperCase() === 'BUTTONLABEL' ) { return label; } return $sce.trustAsHtml( label ); - } + } // UI operations to show/hide checkboxes based on click event.. - $scope.toggleCheckboxes = function( e ) { - + $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 ); + angular.element( document ).off( 'click touchstart', $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. + // 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' )) { + 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 ); + angular.element( checkBoxLayer ).removeClass( 'show' ); + angular.element( clickedEl ).removeClass( 'buttonClicked' ); + angular.element( document ).off( 'click touchstart', $scope.externalClickListener ); + angular.element( document ).off( 'keydown', $scope.keyboardListener ); // clear the focused element; $scope.removeFocusStyle( $scope.tabIndex ); @@ -627,80 +627,80 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // set focus on button again element.children().children()[ 0 ].focus(); - } + } // open - else - { + else + { // clear filter - $scope.inputLabel.labelFilter = ''; - $scope.updateFilter(); + $scope.inputLabel.labelFilter = ''; + $scope.updateFilter(); helperItems = []; helperItemsLength = 0; angular.element( checkBoxLayer ).addClass( 'show' ); - angular.element( clickedEl ).addClass( 'buttonClicked' ); + 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 ); + // 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 touchstart', $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 + // 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]; - + 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(); + + // 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 ) { + 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 ) { + $scope.externalClickListener = function( e ) { var targetsArr = element.find( e.target.tagName ); - for (var i = 0; i < targetsArr.length; i++) { + for (var i = 0; i < targetsArr.length; i++) { if ( e.target == targetsArr[i] ) { return; } } - angular.element( checkBoxLayer.previousSibling ).removeClass( 'buttonClicked' ); + 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 + angular.element( document ).off( 'click touchstart', $scope.externalClickListener ); + angular.element( document ).off( 'keydown', $scope.keyboardListener ); + + // close callback $timeout( function() { $scope.onClose(); }, 0 ); @@ -708,7 +708,7 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // set focus on button again element.children().children()[ 0 ].focus(); } - + // select All / select None / reset buttons $scope.select = function( type, e ) { @@ -717,53 +717,53 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' 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' ) { + 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(); + }); + $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' ) { + if ( typeof value !== 'undefined' && value[ attrs.disableProperty ] !== true ) { + if ( typeof value[ attrs.groupProperty ] === 'undefined' ) { value[ $scope.tickProperty ] = false; } } - }); - $scope.refreshOutputModel(); - $scope.refreshButton(); - $scope.onSelectNone(); + }); + $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 ]; + 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(); + }); + $scope.refreshOutputModel(); + $scope.refreshButton(); + $scope.onReset(); break; case 'CLEAR': $scope.tabIndex = $scope.tabIndex + 1; - $scope.onClear(); + $scope.onClear(); break; - case 'FILTER': + case 'FILTER': $scope.tabIndex = helperItems.length - 1; break; - default: - } - } + default: + } + } - // just to create a random variable name - function genRandomString( length ) { + // just to create a random variable name + function genRandomString( length ) { var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; var temp = ''; for( var i=0; i < length; i++ ) { @@ -774,15 +774,15 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // count leading spaces $scope.prepareGrouping = function() { - var spacing = 0; + var spacing = 0; angular.forEach( $scope.filteredModel, function( value, key ) { - value[ $scope.spacingProperty ] = spacing; + value[ $scope.spacingProperty ] = spacing; if ( value[ attrs.groupProperty ] === true ) { spacing+=2; - } + } else if ( value[ attrs.groupProperty ] === false ) { spacing-=2; - } + } }); } @@ -796,94 +796,94 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' } // navigate using up and down arrow - $scope.keyboardListener = function( e ) { - - var key = e.keyCode ? e.keyCode : e.which; - var isNavigationKey = false; + $scope.keyboardListener = function( e ) { + + var key = e.keyCode ? e.keyCode : e.which; + var isNavigationKey = false; // ESC key (close) if ( key === 27 ) { - e.preventDefault(); + e.preventDefault(); e.stopPropagation(); $scope.toggleCheckboxes( e ); - } - - - // next element ( tab, down & right key ) - else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) { - + } + + + // next element ( tab, down & right key ) + else if ( key === 40 || key === 39 || ( !e.shiftKey && key == 9 ) ) { + isNavigationKey = true; - prevTabIndex = $scope.tabIndex; - $scope.tabIndex++; + prevTabIndex = $scope.tabIndex; + $scope.tabIndex++; if ( $scope.tabIndex > formElements.length - 1 ) { $scope.tabIndex = 0; - prevTabIndex = formElements.length - 1; - } + prevTabIndex = formElements.length - 1; + } while ( formElements[ $scope.tabIndex ].disabled === true ) { $scope.tabIndex++; if ( $scope.tabIndex > formElements.length - 1 ) { - $scope.tabIndex = 0; - } + $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 ) ) { + else if ( key === 38 || key === 37 || ( e.shiftKey && key == 9 ) ) { isNavigationKey = true; - prevTabIndex = $scope.tabIndex; - $scope.tabIndex--; + prevTabIndex = $scope.tabIndex; + $scope.tabIndex--; if ( $scope.tabIndex < 0 ) { $scope.tabIndex = formElements.length - 1; prevTabIndex = 0; - } - while ( formElements[ $scope.tabIndex ].disabled === true ) { + } + 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 ) { - if ( isNavigationKey === true ) { - e.preventDefault(); - // set focus on the checkbox - formElements[ $scope.tabIndex ].focus(); - var actEl = document.activeElement; - - if ( actEl.type.toUpperCase() === 'CHECKBOX' ) { + // 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' ); + $scope.setFocusStyle = function( tabIndex ) { + angular.element( formElements[ tabIndex ] ).parent().parent().parent().addClass( 'multiSelectFocus' ); } // remove CSS style on selected row - $scope.removeFocusStyle = function( tabIndex ) { + $scope.removeFocusStyle = function( tabIndex ) { angular.element( formElements[ tabIndex ] ).parent().parent().parent().removeClass( 'multiSelectFocus' ); } /********************* - ********************* + ********************* * * 1) Initializations * @@ -892,44 +892,44 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' // 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.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; + $scope.spacingProperty = 'spc_' + tempStr; - // set orientation css + // set orientation css if ( typeof attrs.orientation !== 'undefined' ) { - if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) { + if ( attrs.orientation.toUpperCase() === 'HORIZONTAL' ) { $scope.orientationH = true; $scope.orientationV = false; } - else + 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' ) { + if ( typeof attrs.maxHeight !== 'undefined' ) { var layer = element.children().children().children()[0]; - angular.element( layer ).attr( "style", "height:" + attrs.maxHeight + "; overflow-y:scroll;" ); + angular.element( layer ).attr( "style", "height:" + attrs.maxHeight + "; overflow-y:scroll;" ); } - // some flags for easier checking + // 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 + if ( $scope.helperStatus.hasOwnProperty( property )) { + if ( + typeof attrs.helperElements !== 'undefined' + && attrs.helperElements.toUpperCase().indexOf( property.toUpperCase() ) === -1 ) { $scope.helperStatus[ property ] = false; } @@ -940,31 +940,31 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' $scope.helperStatus[ 'none' ] = false; } - // helper button icons.. I guess you can use html tag here if you want to. - $scope.icon = {}; + // helper button icons.. I guess you can use html tag here if you want to. + $scope.icon = {}; $scope.icon.selectAll = '✓'; // a tick icon $scope.icon.selectNone = '×'; // x icon - $scope.icon.reset = '↶'; // undo icon + $scope.icon.reset = '↶'; // undo icon // this one is for the selected items - $scope.icon.tickMark = '✓'; // a tick icon + $scope.icon.tickMark = '✓'; // a tick icon - // configurable button labels + // configurable button labels if ( typeof attrs.translation !== 'undefined' ) { $scope.lang.selectAll = $sce.trustAsHtml( $scope.icon.selectAll + '  ' + $scope.translation.selectAll ); $scope.lang.selectNone = $sce.trustAsHtml( $scope.icon.selectNone + '  ' + $scope.translation.selectNone ); $scope.lang.reset = $sce.trustAsHtml( $scope.icon.reset + '  ' + $scope.translation.reset ); - $scope.lang.search = $scope.translation.search; - $scope.lang.nothingSelected = $sce.trustAsHtml( $scope.translation.nothingSelected ); + $scope.lang.search = $scope.translation.search; + $scope.lang.nothingSelected = $sce.trustAsHtml( $scope.translation.nothingSelected ); } else { - $scope.lang.selectAll = $sce.trustAsHtml( $scope.icon.selectAll + '  Select All' ); + $scope.lang.selectAll = $sce.trustAsHtml( $scope.icon.selectAll + '  Select All' ); $scope.lang.selectNone = $sce.trustAsHtml( $scope.icon.selectNone + '  Select None' ); $scope.lang.reset = $sce.trustAsHtml( $scope.icon.reset + '  Reset' ); $scope.lang.search = 'Search...'; - $scope.lang.nothingSelected = 'None Selected'; + $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 ) ); @@ -977,48 +977,48 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' * ******************************************************* *******************************************************/ - + // 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(); + // 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 ) { + $scope.$watch( 'inputModel' , function( newVal ) { if ( newVal ) { - $scope.backUp = angular.copy( $scope.inputModel ); + $scope.backUp = angular.copy( $scope.inputModel ); $scope.updateFilter(); $scope.prepareGrouping(); - $scope.prepareIndex(); - $scope.refreshOutputModel(); - $scope.refreshButton(); + $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.$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 ) { + var onTouchMove = function( e ) { $scope.$apply( function() { - $scope.scrolled = true; + $scope.scrolled = true; }); }; - angular.element( document ).bind( 'touchmove', onTouchMove); + angular.element( document ).bind( 'touchmove', onTouchMove); // unbind document events to prevent memory leaks $scope.$on( '$destroy', function () { @@ -1028,10 +1028,10 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' } } }]).run( [ '$templateCache' , function( $templateCache ) { - var template = + var template = '' + // main button - ''+ // select none ''+ // reset '
' + // the search box '
'+ - // textfield + // textfield ''+ // clear button - ' '+ + ' '+ '
'+ '
'+ // selection items @@ -1085,10 +1085,10 @@ angular.module( 'isteven-multi-select', ['ng'] ).directive( 'istevenMultiSelect' 'ng-click="syncItems( item, $event, $index );" '+ 'ng-mouseleave="removeFocusStyle( tabIndex );"> '+ // this is the spacing for grouped items - '
'+ - '
'+ + '
'+ + '
'+ '
'+ - '