Skip to content

Instantly share code, notes, and snippets.

@g-alonso
Last active January 2, 2016 02:49
Show Gist options
  • Save g-alonso/8240155 to your computer and use it in GitHub Desktop.
Save g-alonso/8240155 to your computer and use it in GitHub Desktop.
Angular Select Multiple Directive
/*
Original Source: http://blog.boxelderweb.com/2013/08/22/angularjs-multi-select-widget/
Modif:
-03/12/2013
Gabriel Alonso (gbr.alonso@gmail.com)
Example:
<multi-select
add-callback="addCallback(params)"
ng-model="productsSelected"
available="productsAvailables"
selected-label="Productos adicionales asociados"
available-label="Productos adicionales disponibles"
display-attr="NAME">
</multi-select>
*/
angular.module('dataFields', [])
.directive('multiSelect', function($q) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
selectedLabel: "@",
availableLabel: "@",
displayAttr: "@",
available: "=",
model: "=ngModel",
addCallback: "&addCallback",
addAllCallback: "&addAllCallback",
removeCallback: "&removeCallback",
removeAllCallback: "&removeAllCallback"
},
template: '<div class="row-fluid">'+
'<div class="span5">'+
'<label for="multiSelectSelected">{{ selectedLabel }} ' +
'({{ model.length }})</label>' +
'<select id="currentRoles" ng-model="selected.current" multiple ' +
'class="input-block-level" ng-options="e as e[displayAttr] for e in model">' +
'</select>' +
'</div>'+
'<div class="span2" style="text-align:center;">'+
'<div class="row-fluid" style="margin:40px 0 0 0">'+
'<button class="btn btn-warning btn-mini" ng-click="add()" title="Add selected" ' +
'ng-disabled="selected.available.length == 0">' +
'&nbsp;&lt;&nbsp;' +
'</button>&nbsp;' +
'<button class="btn btn-warning btn-mini" ng-click="remove()" title="Remove selected" ' +
'ng-disabled="selected.current.length == 0">' +
'&nbsp;&gt;&nbsp;' +
'</button>' +
'</div>'+
'<div class="row-fluid" style="margin:5px 0 0 0;">'+
'<button class="btn btn-warning btn-mini"'+
'ng-click="addAll()" ng-disabled="available.length == 0">&lt;&lt;</button>&nbsp;'+
'<button class="btn btn-warning btn-mini"'+
'ng-click="removeAll()" ng-disabled="model.length == 0">&gt;&gt;</button>'+
'</div>'+
'</div>'+
'<div class="span5">'+
'<label for="multiSelectAvailable">{{ availableLabel }} ' +
'({{ available.length }})</label>' +
'<select id="multiSelectAvailable" ng-model="selected.available" multiple ' +
'class="input-block-level" ng-options="e as e[displayAttr] for e in available"></select>' +
'</div>'+
'</div>',
link: function(scope, elm, attrs) {
scope.selected = {
available: [],
current: []
};
/* Handles cases where scope data hasn't been initialized yet */
var dataLoading = function(scopeAttr) {
var loading = $q.defer();
if(scope[scopeAttr]) {
loading.resolve(scope[scopeAttr]);
} else {
scope.$watch(scopeAttr, function(newValue, oldValue) {
if(newValue !== undefined)
loading.resolve(newValue);
});
}
return loading.promise;
};
/* Filters out items in original that are also in toFilter. Compares by reference. */
var filterOut = function(original, toFilter) {
var filtered = [];
angular.forEach(original, function(entity) {
var match = false;
for(var i = 0; i < toFilter.length; i++) {
if(toFilter[i][attrs.displayAttr] == entity[attrs.displayAttr]) {
match = true;
break;
}
}
if(!match) {
filtered.push(entity);
}
});
return filtered;
};
scope.refreshAvailable = function() {
scope.available = filterOut(scope.available, scope.model);
scope.selected.available = [];
scope.selected.current = [];
};
//Add
scope.add = function() {
if(typeof(scope.addCallback) == "function")
scope.addCallback({params: scope.selected.available});
scope.model = scope.model.concat(scope.selected.available);
scope.refreshAvailable();
};
//Remove
scope.remove = function() {
if(typeof(scope.removeCallback) == "function")
scope.removeCallback({params: scope.selected.current});
scope.available = scope.available.concat(scope.selected.current);
scope.model = filterOut(scope.model, scope.selected.current);
scope.refreshAvailable();
};
//Add All
scope.addAll = function() {
if(typeof(scope.addAllCallback) == "function")
scope.addAllCallback({params: scope.available});
scope.model = scope.model.concat(scope.available);
scope.refreshAvailable();
};
//Remove All
scope.removeAll = function() {
if(typeof(scope.removeAllCallback) == "function")
scope.removeAllCallback({params: scope.model});
scope.available = scope.available.concat(scope.model);
scope.model = filterOut(scope.model, scope.available);
};
$q.all([dataLoading("model"), dataLoading("available")]).then(function(results) {
scope.refreshAvailable();
});
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment