angular-dropdown-multiselect
Version:
Angular JS directive for dropdown with multi-select feature.
342 lines (268 loc) • 15 kB
JavaScript
/*
* Dropdown-multiselect directive for AngularJS
* By Rajush Shakya
* https://github.com/rajush/angular-dropdown-multiselect
*/
'use strict';
angular.module( 'dropdown-multiselect', [] )
.directive( 'dropdownMultiselect', [ function () {
return {
restrict: 'AE',
replace: true,
transclude: true,
template: '<div class="dropdown" id="dropdownMultiselect">' +
'<button class="btn btn-default dropdown-toggle" ng-class="{\'disabled\':disabled}" type="button" id="dropdownMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" ng-click="dropdownToggle()">' +
'<span class="pull-left" ng-if="!hasText">Select </span>' +
'<span class="pull-left" ng-transclude></span>' +
'<div class="pull-right">' +
'<span class="badge" ng-if="isBadgeVisible"> {{model.length}}</span>' +
'<span class="caret"></span>' +
'</div>' +
'</button>' +
'<ul class="dropdown-menu" aria-labelledby="dropdownMenu">' +
'<li>' +
'<div>' +
'<ul class="dropdown-static">' +
'<li><a ng-click="selectAll()"><i class="glyphicon glyphicon-ok"></i> Select All</a></li>' +
'<li><a ng-click="unSelectAll()"><i class="glyphicon glyphicon-remove"></i> Unselect All</a></li>' +
'<div class="filter-parent" id="search" ng-if="isFilterVisible">' +
'<label for="filter-by"><i class="glyphicon glyphicon-search"></i></label>' +
'<div>' +
'<input placeholder="Search" id="filter-by" class="form-control" tabindex="1" ng-model="$parent.option">' +
'</div>' +
'</div>' +
'</ul>' +
'</div>' +
'</li>' +
'<li>' +
'<ul class="dropdown-scrollable" ng-class="{\'dropdown-height\': defaultHeight, \'hasfilter\': isFilterVisible === true}">' +
'<li ng-repeat="option in options | filter: option">' +
'<a ng-click="setSelectedItem()">' +
'<span class="pull-right" ng-class="isChecked(option[trackByKey], dropdownType)"></span> {{option[leftKey]}} {{divider}} {{option[rightKey]}}' +
'</a>' +
'</li>' +
'</ul>' +
'</li>' +
'</ul>' +
'</div>',
scope: {
config: '=dropdownConfig', // configuration from controller
primaryKey: '@dropdownTrackby', // main key from the option's object, to select or unselect the item
disableDropdown: '=dropdownDisable', // boolean
dropdownType: '=', // view type
model: '=?', // model to bind with view & bind data back to the controller
options: '=dropdownOptions', // object for repeater
notifyParent: '&dropdownSelected' // notifier
},
controller: function ( $scope ) {
var model = [],
badgeVisibility = true, // default badge visibility
filterVisibility = false, // drfault filter visibility
divider = '-', // default divider sign
icon = 'glyphicon glyphicon-ok', // default icon
viewType = $scope.dropdownType,
key = $scope.primaryKey; // default key from dropdown-trackby attribute
// binding with view (used for check icon)
$scope.trackByKey = key;
$scope.isBadgeVisible = badgeVisibility;
$scope.isFilterVisible = filterVisibility;
$scope.divider = divider;
$scope.model = model;
try {
//check if config is defined or not
if ( angular.isDefined( $scope.config ) ) {
// if 'options' property exists, set the dropdown list items
if ( $scope.config.hasOwnProperty( 'options' ) ) {
$scope.options = $scope.config.options;
}
// if 'displayBy' property exists, set the left and right data to be displayed for view
if ( $scope.config.hasOwnProperty( 'displayBy' ) ) {
$scope.leftKey = $scope.config.displayBy[ 0 ];
$scope.rightKey = $scope.config.displayBy[ 1 ];
} else {
// grab the first two property and set it automatically as leftKey and rightKey
var optionsProperties = [];
for ( var prop in $scope.options[ 0 ] ) {
optionsProperties.push( prop );
}
$scope.leftKey = optionsProperties[ 0 ];
$scope.rightKey = optionsProperties[ 1 ];
}
if ( $scope.config.hasOwnProperty( 'divider' ) ) {
$scope.divider = $scope.config.divider;
}
if ( $scope.config.hasOwnProperty( 'icon' ) ) {
icon = $scope.config.icon;
}
if ( $scope.config.hasOwnProperty( 'filter' ) ) {
filterVisibility = $scope.config.filter;
$scope.isFilterVisible = filterVisibility;
}
if ( $scope.config.hasOwnProperty( 'displayBadge' ) ) {
badgeVisibility = ( angular.equals( true, $scope.config.displayBadge ) ) ? badgeVisibility = true : badgeVisibility = false;
$scope.isBadgeVisible = badgeVisibility;
}
// if 'trackBy' property exists, set the main key to track the dropdown list
//(config file overrites the dropdown-trackby attributes)
if ( $scope.config.hasOwnProperty( 'trackBy' ) ) {
key = $scope.config.trackBy;
// binding with view (used for check icon)
$scope.trackByKey = key;
} else {
//run auto assign condition
keyIsUndefined( key );
}
} else {
// check for dropdown-trackby attribute is defined or not
//run auto assign condition
keyIsUndefined( key );
}
var unbindWatcher = $scope.$watch( 'options', function ( newVal, oldVal ) {
// when true
if ( $scope.disableDropdown ) {
$scope.disabled = true;
} else {
$scope.disabled = false;
}
// reset the model if the options is changed
if ( newVal ) {
model = [];
$scope.model = model;
}
} );
$scope.selectAll = function () {
var options = $scope.options;
if ( angular.isUndefined( options ) ) {
return false;
}
if ( options.length === model.length ) {
return false;
} else {
for ( var i = 0; i < options.length; i++ ) {
var id = options[ i ][ $scope.trackByKey ],
chainId = options[ i ].ChainId,
obj = {};
var found = false;
// looking for if each item from the 'options' array already exists in 'model' array
for ( var x = 0; x < model.length; x++ ) {
var current = model[ x ][ $scope.trackByKey ];
// item from the 'options' array already exists in 'model' array
if ( id === current ) {
found = true;
break;
}
}
if ( !found ) {
obj[ $scope.trackByKey ] = id;
model.push( options[ i ] );
}
}
}
// notify on each select or unselect
$scope.notifyParent( {
selected: model
} );
};
$scope.unSelectAll = function () {
model = [];
$scope.model = model;
// notify on each select or unselect
$scope.notifyParent( {
selected: model
} );
};
$scope.setSelectedItem = function () {
var id = this.option[ $scope.trackByKey ],
chainId = this.option.ChainId,
obj = {};
var duplicate = isDuplicate( id, model );
if ( !duplicate ) {
obj[ $scope.trackByKey ] = id;
model.push( this.option );
}
// notify on each select or unselect
$scope.notifyParent( {
selected: model
} );
};
$scope.isChecked = function ( id, type ) {
for ( var i = 0; i < model.length; i++ ) {
// add checkmark if the selected item is already in 'model' array
if ( model[ i ][ $scope.trackByKey ] === id ) {
return icon;
}
}
return false;
};
} catch ( e ) {
console.error( e );
}
function keyIsUndefined( key ) {
// if dropdownTrackby is not defined
if ( angular.isUndefined( key ) ) {
// TODO: might need a better alternative for checking
/****************************************************/
//check if an object in option array has a property named 'Id' or 'id'.
//here, doing a check only on first index assuming that the objects are set with correct property
key = $scope.options[ 0 ].hasOwnProperty( 'Id' ) ? 'Id' : $scope.options[ 0 ].hasOwnProperty( 'id' ) ? 'id' : false;
// set trackByKey to newly auto assigned key for binding with view (used for check icon)
$scope.trackByKey = key;
//if no key throw error
if ( key === false ) {
throw 'ReferenceError: Expecting property "Id" in an object from an array binded to "dropdown-options" attribute. Consider providing "dropdown-trackby" attribute or make sure property "Id" exists in binded array\'s object.';
}
}
}
function isDuplicate( id, array ) {
for ( var i = 0; i < array.length; i++ ) {
var current = array[ i ][ $scope.trackByKey ];
// remove if already exists
if ( id === current ) {
var indexOfId = findIndexByKeyValue( array, $scope.trackByKey, id );
array.splice( indexOfId, 1 );
return true;
}
}
}
function findIndexByKeyValue( array, key, valuetosearch ) {
for ( var i = 0; i < array.length; i++ ) {
if ( array[ i ][ key ] === valuetosearch ) {
return i;
}
}
return null;
}
},
link: function ( scope, element, attr, ctrl ) {
scope.dropdownToggle = function () {
angular.element( document.querySelector( '.dropdown-menu' ) ).toggleClass( 'dropdown-show' );
};
document.addEventListener( 'click', function ( event ) {
var dropdownMultiselect = document.getElementById( 'dropdownMultiselect' ),
//finding parent target element for browser
parentTarget = ( angular.isDefined( event.srcElement ) ) ? event.srcElement.offsetParent /* chrome/safari */ : event.originalTarget.offsetParent /*firefox*/ ;
if ( parentTarget !== null ) {
var eventSrc = parentTarget;
while ( eventSrc !== dropdownMultiselect ) {
eventSrc = eventSrc[ 'offsetParent' ];
if ( eventSrc === null ) {
angular.element( document.querySelector( '.dropdown-menu' ) ).removeClass( 'dropdown-show' );
break;
}
}
} else {
angular.element( document.querySelector( '.dropdown-menu' ) ).removeClass( 'dropdown-show' )
}
}, true );
if ( angular.isDefined( scope.config.height ) ) {
var custom_height = scope.config.height,
dropdownListBox = angular.element( document.querySelector( '.dropdown-scrollable' ) );
dropdownListBox.css( 'max-height', custom_height )
} else {
scope.defaultHeight = true;
}
var transcluded = element.find( 'span' ).contents();
scope.hasText = ( transcluded.length > 0 );
}
};
} ] );