Last active
January 2, 2016 02:49
-
-
Save g-alonso/8240155 to your computer and use it in GitHub Desktop.
Angular Select Multiple Directive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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">' + | |
' < ' + | |
'</button> ' + | |
'<button class="btn btn-warning btn-mini" ng-click="remove()" title="Remove selected" ' + | |
'ng-disabled="selected.current.length == 0">' + | |
' > ' + | |
'</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"><<</button> '+ | |
'<button class="btn btn-warning btn-mini"'+ | |
'ng-click="removeAll()" ng-disabled="model.length == 0">>></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