/*
* Angular JS Multi Select
* Creates a dropdown-like button with checkboxes.
*/
'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:
'' +
'' +
'' +
'
' +
'
' +
''+
''+
'' +
'
' +
'
'+
''+
' '+
'
'+
'
'+
'
'+
'
'+
'
'+
'
'+
'
'+
''+
'
'+
'
✔'+
'
'+
'
'+
'
'+
'',
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 ];
if(inputModelIndex==undefined){
$scope.filteredModel[ index ][ $scope.indexProperty ]=index;
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 ? ',