<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
<link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="ng-multiselect.css">
<script data-require="jquery@2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script data-require="bootstrap@*" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script data-require="angular.js@1.3.8" data-semver="1.3.8" src="https://code.angularjs.org/1.3.8/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-sanitize.js"></script>
<script src="ng-multiselect.js"></script>
<script src="app.js"></script>
<style>
.user{
color: #0074D9;
}
</style>
</head>
<body ng-app="myApp">
<div class="container" ng-controller="DemoController">
<h3>Dynamic Data Demo</h3>
<multiselect placeholder="Select users..."
ms-model="multiselect.selected"
ms-config="multiselect.config"
ms-options="multiselect.options">
</multiselect>
<hr>
<small class="text-muted">
<p><b>Selected users:</b> {{displayUsers()}}</p>
<p class="text-center">
<a href="https://github.com/aurbano/ng-multiselect">ng-multiselect</a> •
Demo by <a href="http://urbanoalvarez.es">Alejandro U. Alvarez</a> •
<em>Test data from jsonplaceholder.typicode.com</em>
</p>
</small>
</div>
</body>
</html>
# [Angular Multiselect](https://github.com/aurbano/ng-multiselect)
Angular directive for multiselect elements with a nice UX.
## Getting started
1. Download [ng-multiselect.js](https://github.com/aurbano/ng-multiselect/blob/master/dist/ng-multiselect.js) and [ng-multiselect.css](https://github.com/aurbano/ng-multiselect/blob/master/dist/ng-multiselect.css) and add to your project. Or get automatically:
```bash
bower install ng-multiselect
```
2. Add the module to the app:
```js
angular.module('myApp', ['aurbano.multiselect'])
```
3. Profit!
## Usage
Add the element in the view:
```html
<multiselect placeholder="Select users..."
ms-model="multiselect.selected"
ms-config="multiselect.config"
ms-options="multiselect.options">
</multiselect>
```
The parameters are:
* `ms-model`: The model where the selection will be stored.
* `ms-config`: Configuration object to customize the behavior (see below).
* `ms-options`: Array with options to offer.
### Customization
Using the `ms-config` you can pass an object with the following configuration options:
* **`hideOnBlur`**: Only show the dropdown when the input is focused
* **`showSelected`**: Show the elements that have already been selected
* **`itemTemplate`**: function that takes each element from ms-options and returns what should be displayed on the list. It defaults to the element itself. Use sce.trustAsHtml if you want to add custom HTML here.
* **`labelTemplate`**: function that takes each selected element, and displays it on the label list. This goes inside the `<span class="label"></span>`. Use `$sce.trustAsHtml()` if you want to add custom HTML here.
## Demo
### [Custom Formatting](http://embed.plnkr.co/s2GFxXBXf1vderC0XCvP/)
### [Dynamic data](http://embed.plnkr.co/TrwUBp1odAYmBPRarK5k/)
-----------
*By [Alejandro U. Alvarez](http://urbanoalvarez.es) - MIT License*
[![Analytics](https://ga-beacon.appspot.com/UA-3181088-16/ng-multiselect/readme)](https://github.com/aurbano)
angular.module('myApp', ['aurbano.multiselect'])
.controller('DemoController', ['$scope', '$http', '$sce',
function($scope, $http, $sce) {
$scope.multiselect = {
selected: [],
options: [],
config: {
hideOnBlur: false,
showSelected: false,
itemTemplate: function(item){
return $sce.trustAsHtml(item.name+' ('+item.email+')');
},
labelTemplate: function(item){
return $sce.trustAsHtml(item.name);
}
}
};
$scope.displayUsers = function(){
return $scope.multiselect.selected.map(function(each){
return each.name;
}).join(', ');
};
$http.get('http://jsonplaceholder.typicode.com/users')
.success(function(data){
// Prepare the fake data
$scope.multiselect.options = data.map(function(item){
return {
name: item.name,
email: item.email,
id: item.id
};
});
}).error(function(err){
console.error(err);
});
}]);
/**
* Angular Multiselect
* Directive for multiselect fields, with labels for the selected items
*
* Usage:
* <multiselect placeholder="Placeholder for the input"
* ms-model="model"
* ms-options="select options"
* ms-config="configObject"
* ms-change="changeFunction(newVal, oldVal)"/>
*
* ms-config takes an object with the following optional options (all booleans are false by default):
* - hideOnBlur: Only show the dropdown when the input is focused
* - showSelected: Show the elements that have already been selected
* - itemTemplate: function that takes each element from ms-options and returns what should be displayed on the list.
* defaults to the element itself. Use sce.trustAsHtml if you want to add custom HTML here.
* - labelTemplate: function that takes each selected element, and displays it on the label list. This goes inside the
* <span class="label"></span>. Use sce.trustAsHtml if you want to add custom HTML here.
*
* @author Alejandro U. Alvarez
* @homepage https://github.com/aurbano/ng-multiselect
*/
angular.module('aurbano.multiselect', []).directive('multiselect', function() {
return {
restrict: 'E',
scope: {
msModel: '=',
msOptions: '=',
msConfig: '=',
msChange: '&'
},
replace: true,
controller: ['$scope',
function($scope){
/* -------------------------------- *
* Configuration defaults *
* -------------------------------- */
$scope.config = $scope.msConfig;
if(typeof($scope.config) === 'undefined'){
$scope.config = {};
}
if(typeof($scope.config.itemTemplate) === 'undefined'){
$scope.config.itemTemplate = function(elem){
return elem;
};
}
if(typeof($scope.config.labelTemplate) === 'undefined'){
$scope.config.labelTemplate = function(elem){
return elem;
};
}
if(typeof($scope.config.modelFormat) === 'undefined'){
$scope.config.modelFormat = function(elem){
return elem;
};
}
$scope.$watch('msOptions', function(){
_filterOptions();
});
/* -------------------------------- *
* Multiselect setup *
* -------------------------------- */
if($scope.msModel === null || typeof($scope.msModel) === 'undefined'){
$scope.msModel = [];
}
if($scope.msOptions === null || typeof($scope.msOptions) === 'undefined'){
$scope.msOptions = [];
}
$scope.multiselect = {
filter: '',
filtered: [],
options: $scope.msOptions,
displayDropdown: true,
deleteSelected: function(index){
var oldVal = angular.copy($scope.msModel);
$scope.msModel.splice(index, 1);
$scope.msChange({
newVal: $scope.msModel,
oldVal: oldVal
});
_filterOptions();
},
selectElement: function(index){
var oldVal = angular.copy($scope.msModel);
$scope.msModel.push($scope.multiselect.filtered[index]);
$scope.msChange({
newVal: $scope.msModel,
oldVal: oldVal
});
$scope.multiselect.filter = '';
_filterOptions();
},
focusFilter: function(){
$scope.multiselect.currentElement = 0;
if($scope.config.hideOnBlur){
$scope.multiselect.displayDropdown = true;
}
},
blurFilter: function(){
$scope.multiselect.currentElement = null;
if($scope.config.hideOnBlur){
$scope.multiselect.displayDropdown = false;
}
},
currentElement: null
};
if($scope.config.hideOnBlur){
$scope.multiselect.displayDropdown = false;
}
/* -------------------------------- *
* Helper methods *
* -------------------------------- */
var _filterOptions = function(){
if($scope.msModel === null){
$scope.multiselect.options = $scope.msOptions;
return;
}
$scope.multiselect.options = $scope.msOptions.filter(function(each){
return $scope.msModel.indexOf(each) < 0;
});
};
}
],
link: function(scope, element, attrs){
element.on('keydown', function(e){
var code = e.keyCode || e.which;
switch(code){
case 40: // Down arrow
scope.$apply(function(){
scope.multiselect.currentElement++;
if(scope.multiselect.currentElement >= scope.multiselect.filtered.length){
scope.multiselect.currentElement = 0;
}
});
break;
case 38: // Up arrow
scope.$apply(function(){
scope.multiselect.currentElement--;
if(scope.multiselect.currentElement < 0){
scope.multiselect.currentElement = scope.multiselect.filtered.length - 1;
}
});
break;
case 13: // Enter
scope.$apply(function(){
scope.multiselect.selectElement(scope.multiselect.currentElement);
});
break;
default:
scope.$apply(function(){
scope.multiselect.currentElement = 0;
});
}
});
},
template: function(elem, attrs){
//var template = $(elem).find('template');
return '<div class="multiselect">' +
' <div class="multiselect-labels">' +
' <span class="label label-default multiselect-labels-lg" ng-repeat="element in msModel">' +
' <span ng-bind-html="config.labelTemplate(element)"></span>' +
' <a href="" ng-click="$event.preventDefault(); multiselect.deleteSelected($index)" title="Remove element">' +
' <i class="fa fa-times"></i>' +
' </a>' +
' </span>'+
' </div>' +
' <input ng-show="multiselect.options.length > 0" placeholder="'+attrs.placeholder+'" type="text" class="form-control" ng-model="multiselect.filter" ng-focus="multiselect.focusFilter()" ng-blur="multiselect.blurFilter()" />' +
' <div ng-show="msModel.length < multiselect.options.length && multiselect.options.length === 0 && !multiselect.options.$resolved"><em>Loading...</em></div>'+
' <ul class="dropdown-menu" role="menu" ng-show="multiselect.displayDropdown && multiselect.options.length > 0">' +
' <li ng-repeat="element in multiselect.filtered = (multiselect.options | filter:multiselect.filter) track by $index" role="presentation" ng-class="{active: $index == multiselect.currentElement}">' +
' <a href="" role="menuitem" ng-click="$event.preventDefault(); multiselect.selectElement($index)" ng-bind-html="config.itemTemplate(element)"></a>' +
' </li>' +
' </ul>' +
'</div>';
}
};
});
.multiselect .dropdown-menu{
position: static;
box-shadow: none;
display: block;
float:none;
max-height:300px;
overflow:auto;
}
.multiselect-labels a .fa{
color: #ccc;
}
.multiselect-labels a .fa:hover{
color: #fff;
}
.multiselect-labels .label{
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
padding: 4px 6px;
}
.multiselect .dropdown-menu > li > a{
white-space: normal;
}