var app = angular.module('plunker', ['ui.multiselect']);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.cars = [{id:1, name: 'Audi'}, {id:2, name: 'BMW'}, {id:1, name: 'Honda'}];
$scope.selectedCar = [];
$scope.fruits = [{id: 1, name: 'Apple'}, {id: 2, name: 'Orange'},{id: 3, name: 'Banana'}];
$scope.selectedFruit = null;
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap-css@2.3.2" data-semver="2.3.2" rel="stylesheet" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" />
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.1.x" src="http://code.angularjs.org/1.1.5/angular.min.js" data-semver="1.1.5"></script>
<script src="app.js"></script>
<script src="multiselect.js"></script>
</head>
<body ng-controller="MainCtrl">
<h1>Example</h1>
<multiselect class="input-xlarge" multiple="true"
ng-model="selectedCar"
options="c.name for c in cars"
change="selected()" ></multiselect>
<div class="well well-small">
{{selectedCar}}
</div>
Single select:
<multiselect class="input-xlarge"
ng-model="selectedFruit"
options="c.name for c in fruits"
change="selected()" ></multiselect>
<div class="well well-small">
{{selectedFruit}}
</div>
</body>
</html>
/* Put your css in here */
multiselect {
display:block;
}
multiselect .btn {
width: 100%;
background-color: #FFF;
}
multiselect .btn.error{
border: 1px solid #da4f49 !important;
}
multiselect .dropdown-menu {
max-height: 300px;
overflow-y: auto;
}
multiselect .dropdown-menu {
width: 100%;
box-sizing: border-box;
padding: 2px;
}
multiselect .dropdown-menu > li > a {
padding: 3px 10px;
cursor:pointer;
}
<div class="dropdown">
<button class="btn" ng-click="toggleSelect()" ng-disabled="disabled" ng-class="{'error': !valid()}">
<span class="pull-left">{{header}}</span>
<span class="caret pull-right"></span>
</button>
<ul class="dropdown-menu">
<li>
<input class="input-block-level" type="text" ng-model="searchText.label" autofocus="autofocus" placeholder="Filter" />
</li>
<li ng-show="multiple">
<button class="btn-link btn-small" ng-click="checkAll()"><i class="icon-ok"></i> Check all</button>
<button class="btn-link btn-small" ng-click="uncheckAll()"><i class="icon-remove"></i> Uncheck all</button>
</li>
<li ng-repeat="i in items | filter:searchText">
<a ng-click="select(i); focus()">
<i ng-class="{'icon-ok': i.checked, 'icon-empty': !i.checked}"></i>{{i.label}}</a>
</li>
</ul>
</div>
angular.module('ui.multiselect', [])
//from bootstrap-ui typeahead parser
.factory('optionParser', ['$parse', function ($parse) {
// 00000111000000000000022200000000000000003333333333333330000000000044000
var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
return {
parse: function (input) {
var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
if (!match) {
throw new Error(
"Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
" but got '" + input + "'.");
}
return {
itemName: match[3],
source: $parse(match[4]),
viewMapper: $parse(match[2] || match[1]),
modelMapper: $parse(match[1])
};
}
};
}])
.directive('multiselect', ['$parse', '$document', '$compile', 'optionParser',
function ($parse, $document, $compile, optionParser) {
return {
restrict: 'E',
require: 'ngModel',
link: function (originalScope, element, attrs, modelCtrl) {
var exp = attrs.options,
parsedResult = optionParser.parse(exp),
isMultiple = attrs.multiple ? true : false,
required = false,
scope = originalScope.$new(),
changeHandler = attrs.change || anguler.noop;
scope.items = [];
scope.header = 'Select';
scope.multiple = isMultiple;
scope.disabled = false;
originalScope.$on('$destroy', function () {
scope.$destroy();
});
var popUpEl = angular.element('<multiselect-popup></multiselect-popup>');
//required validator
if (attrs.required || attrs.ngRequired) {
required = true;
}
attrs.$observe('required', function(newVal) {
required = newVal;
});
//watch disabled state
scope.$watch(function () {
return $parse(attrs.disabled)(originalScope);
}, function (newVal) {
scope.disabled = newVal;
});
//watch single/multiple state for dynamically change single to multiple
scope.$watch(function () {
return $parse(attrs.multiple)(originalScope);
}, function (newVal) {
isMultiple = newVal || false;
});
//watch option changes for options that are populated dynamically
scope.$watch(function () {
return parsedResult.source(originalScope);
}, function (newVal) {
if (angular.isDefined(newVal))
parseModel();
});
//watch model change
scope.$watch(function () {
return modelCtrl.$modelValue;
}, function (newVal, oldVal) {
//when directive initialize, newVal usually undefined. Also, if model value already set in the controller
//for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
//model changes. We need to do this only if it is done outside directive scope, from controller, for example.
if (angular.isDefined(newVal)) {
markChecked(newVal);
scope.$eval(changeHandler);
}
getHeaderText();
modelCtrl.$setValidity('required', scope.valid());
}, true);
function parseModel() {
scope.items.length = 0;
var model = parsedResult.source(originalScope);
for (var i = 0; i < model.length; i++) {
var local = {};
local[parsedResult.itemName] = model[i];
scope.items.push({
label: parsedResult.viewMapper(local),
model: model[i],
checked: false
});
}
}
parseModel();
element.append($compile(popUpEl)(scope));
function getHeaderText() {
if (!modelCtrl.$modelValue || !modelCtrl.$modelValue.length) return scope.header = 'Select';
if (isMultiple) {
scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
} else {
var local = {};
local[parsedResult.itemName] = modelCtrl.$modelValue;
scope.header = parsedResult.viewMapper(local);
}
}
scope.valid = function validModel() {
if(!required) return true;
var value = modelCtrl.$modelValue;
return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
};
function selectSingle(item) {
if (item.checked) {
scope.uncheckAll();
} else {
scope.uncheckAll();
item.checked = !item.checked;
}
setModelValue(false);
}
function selectMultiple(item) {
item.checked = !item.checked;
setModelValue(true);
}
function setModelValue(isMultiple) {
var value;
if (isMultiple) {
value = [];
angular.forEach(scope.items, function (item) {
if (item.checked) value.push(item.model);
})
} else {
angular.forEach(scope.items, function (item) {
if (item.checked) {
value = item.model;
return false;
}
})
}
modelCtrl.$setViewValue(value);
}
function markChecked(newVal) {
if (!angular.isArray(newVal)) {
angular.forEach(scope.items, function (item) {
if (angular.equals(item.model, newVal)) {
item.checked = true;
return false;
}
});
} else {
angular.forEach(newVal, function (i) {
angular.forEach(scope.items, function (item) {
if (angular.equals(item.model, i)) {
item.checked = true;
}
});
});
}
}
scope.checkAll = function () {
if (!isMultiple) return;
angular.forEach(scope.items, function (item) {
item.checked = true;
});
setModelValue(true);
};
scope.uncheckAll = function () {
angular.forEach(scope.items, function (item) {
item.checked = false;
});
setModelValue(true);
};
scope.select = function (item) {
if (isMultiple === false) {
selectSingle(item);
scope.toggleSelect();
} else {
selectMultiple(item);
}
}
}
};
}])
.directive('multiselectPopup', ['$document', function ($document) {
return {
restrict: 'E',
scope: false,
replace: true,
templateUrl: 'multiselect.tmpl.html',
link: function (scope, element, attrs) {
scope.isVisible = false;
scope.toggleSelect = function () {
if (element.hasClass('open')) {
element.removeClass('open');
$document.unbind('click', clickHandler);
} else {
element.addClass('open');
scope.focus();
$document.bind('click', clickHandler);
}
};
function clickHandler(event) {
if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
return;
element.removeClass('open');
$document.unbind('click', clickHandler);
scope.$digest();
}
scope.focus = function focus(){
var searchBox = element.find('input')[0];
searchBox.focus();
}
var elementMatchesAnyInArray = function (element, elementArray) {
for (var i = 0; i < elementArray.length; i++)
if (element == elementArray[i])
return true;
return false;
}
}
}
}]);