<html>
<head>
<title>Angular Multiselect Checkbox Dropdown</title>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-animate.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-loader.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-sanitize.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-cookies.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-touch.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular-resource.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui-ieshiv.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="style.css">
<script type='text/javascript' src="script.js"></script>
<script type='text/javascript' src="multiselect.js"></script>
</head>
<body ng-app="myApp">
<div ng-include="'menu.html'"></div>
<div ui-view="main"></div>
<script type="text/ng-template" id="menu.html">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<span class="navbar-brand">Our Menu</span>
</div>
</div>
</nav>
</script>
<script type="text/ng-template" id="state1.html">
<div class="row container-fluid">
<div class="buttons col-lg-2">
<div class="buttons">
<button ng-click="vm.clear()">Clear Selections</button>
<button ng-click="vm.randomSelect()">Randomly Select</button>
</div>
<br/>
</div>
<div class="col-lg-2">
<form>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option1" options="p.key as p.value for p in vm.options1" select-limit="5" header="Select Stuff1" selected-header="options selected" multiple="true" required enable-filter="true" filter-placeholder="Filter stuff.."
ng-change="vm.change('option1')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option2" options="p.key as p.value for p in vm.options2" select-limit="5" header="Select Stuff2 Simple" selected-header="options selected" multiple="true" required enable-filter="true" filter-placeholder="Filter stuff.."
ng-change="vm.change('option2')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option3" options="p.key as p.value for p in vm.options2" select-limit="5" header="Select Stuff3 Complex" selected-header="options selected" multiple="true" complex-models="true" required enable-filter="true"
filter-placeholder="Filter stuff.." ng-change="vm.change('option3')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option4" options="p.key as p.value for p in vm.options1" select-limit="5" header="Single Select Stuff4" selected-header="options selected" multiple="false" required complex-models="true" enable-filter="true"
filter-placeholder="Filter stuff.." ng-change="vm.change('option4')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option5" options="p.key as p.value for p in vm.options1" select-limit="5" header="Select Stuff5 No Filter" selected-header="options selected" multiple="true" enable-filter="false" filter-placeholder="Filter stuff.."
ng-change="vm.change('option5')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option6" options="p.key as p.value for p in vm.options1" select-limit="5" header="Select Stuff6 Prepopulated" selected-header="options selected" multiple="true" enable-filter="true" filter-placeholder="Filter stuff.."
ng-change="vm.change('option6')">
</multiselect>
</div>
<div class="form-group">
<multiselect class="input-xlarge multiselect" ng-model="vm.option7" options="p.key as p.value for p in vm.options2" select-limit="5" header="Select Stuff7 Prepopulated" selected-header="options selected" multiple="true" enable-filter="true" filter-placeholder="Filter stuff.."
ng-change="vm.change('option7')">
</multiselect>
</div>
</form>
</div>
<div class="row container-fluid">
<div class="col-md-6">
Change log
<pre ng-bind="vm.changes | json"></pre>
</div>
<div class="col-md-6">
<div>
Option1
<pre ng-bind="vm.option1 | json"></pre>
</div>
<div>
Option2 Simple
<pre ng-bind="vm.option2 | json"></pre>
</div>
<div>
Option3 Complex
<pre ng-bind="vm.option3 | json"></pre>
</div>
<div>
Option4 Single
<pre ng-bind="vm.option4 | json"></pre>
</div>
<div>
Option5 No Filter
<pre ng-bind="vm.option5 | json"></pre>
</div>
<div>
Option6 Pre-populated with one
<pre ng-bind="vm.option6 | json"></pre>
</div>
<div>
Option7 Pre-populated with multiple
<pre ng-bind="vm.option7 | json"></pre>
</div>
</div>
</div>
</div>
</script>
</body>
</html>
(function () {
angular.module('myApp.controllers', []);
var myApp = angular.module('myApp', [
'myApp.controllers',
'long2know',
'ngSanitize',
'ui.bootstrap',
'ui.router',
'ui']);
var state1Ctrl = function ($log) {
var vm = this,
getRandomInt = function(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
};
vm.changes = [];
vm.options1 = [];
for (var i = 0; i < 10; i++) {
vm.options1.push({ key: i + 1, value: 'Prop' + (i + 1).toString() });
}
vm.options2 = [];
for (var i = 0; i < 100; i++) {
vm.options2.push({ key: i + 1, value: 'Prop' + (i + 1).toString() });
}
vm.option6 = 3;
vm.option7 = [4, 11, 23];
vm.clear = function() {
vm.option1 = [];
vm.option2 = [];
vm.option3 = [];
vm.option4 = [];
vm.option5 = [];
vm.option6 = [];
vm.option7 = [];
vm.changes = [];
};
vm.change = function (input) {
var message = 'Change triggered: ' + input;
vm.changes.push(message);
$log.log(message);
}
vm.randomSelect = function() {
vm.clear();
var arrSelected = [ vm.option1, vm.option2, vm.option3, vm.option4, vm.option5, vm.option6, vm.option7];
var arrOptions = [ vm.options1, vm.options2, vm.options2, vm.options1, vm.options1, vm.options1, vm.options2 ];
var arrIsSingle = [ false, false, false, true, false, false, false ];
var arrIsSimple = [ true, true, false, false, true, true, true ];
for (var i = 0; i < arrSelected.length; i++) {
var selected = arrSelected[i];
var options = arrOptions[i];
var isSingle = arrIsSingle[i];
var isSimple = arrIsSimple[i];
var min = 0;
var max = options.length - 1;
if (isSingle) {
var randIndex = getRandomInt(min, max);
if (isSimple) {
selected.push(options[randIndex].key);
} else {
selected.push(options[randIndex]);
}
}
else
{
var toSelectIndexes = [];
var numItems = getRandomInt(0, options.length) + 1;
for (var j = 0; j < getRandomInt(1, numItems); j++)
{
var randIndex = getRandomInt(min, max);
var arrIndex = toSelectIndexes.indexOf(randIndex);
if (arrIndex == -1) {
toSelectIndexes.push(randIndex);
if (isSimple) {
selected.push(options[randIndex].key);
} else {
selected.push(options[randIndex]);
}
}
}
}
}
}
};
state1Ctrl.$inject = ['$log'];
angular.module('myApp.controllers')
.controller('state1Ctrl', state1Ctrl);
myApp.config(['$locationProvider', '$stateProvider', '$urlRouterProvider',
function ($locationProvider, $stateProvider, $urlRouterProvider) {
$locationProvider.html5Mode(false);
$urlRouterProvider.when('/', '/state1')
.otherwise("/state1");
$stateProvider.state('app', {
abstract: true,
url: '/',
views: {
'main': {
template: '<div ui-view>/div>'
}
}
})
.state('app.state1', {
url: 'state1',
templateUrl: 'state1.html',
controller: 'state1Ctrl',
controllerAs: 'vm',
reloadOnSearch: false
})
}]);
myApp.run(['$log', function ($log) {
$log.log("Start.");
}]);
})()
multiselect {
display: block;
}
multiselect > .btn-group {
min-width: 180px;
}
multiselect .btn {
width: 100%;
background-color: #FFF;
}
multiselect .btn.has-error {
border: 1px solid #a94442 !important;
color: #db524b;
}
multiselect .dropdown-menu {
max-height: 300px;
min-width: 200px;
overflow-y: auto;
}
multiselect .dropdown-menu .filter > input {
width: 99%;
}
multiselect .dropdown-menu .filter .glyphicon {
cursor: pointer;
pointer-events: all;
}
multiselect .dropdown-menu {
width: 100%;
box-sizing: border-box;
padding: 2px;
}
multiselect > .btn-group > button {
padding-right: 20px;
}
multiselect > .btn-group > button > .caret {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid black;
right: 5px;
top: 45%;
position: absolute;
}
multiselect .dropdown-menu > li > a {
padding: 3px 10px;
cursor: pointer;
}
multiselect .dropdown-menu > li > a i {
margin-right: 4px;
}
.glyphicon-none:before {
content: "\e013";
color: transparent !important;
}
# Angular-UI Multiselect Checkbox Dropdown
This is an Angular-based dropdown that displays checkboxes for multiselection.
This a test to illustrate ng-change triggering after any selection.
### Links
- [Blog](https://long2know.com/2015/07/angular-multiselect-dropdown/)
- [Github](https://github.com/long2know/angular-directives-general)
(function() {
var long2know;
try {
long2know = angular.module("long2know")
} catch (err) {
long2know = null;
}
if (!long2know) {
angular.module('long2know.services', ['ngResource', 'ngAnimate']);
angular.module('long2know.controllers', []);
angular.module('long2know.directives', []);
angular.module('long2know.constants', []);
angular.module('long2know', [
'long2know.services',
'long2know.controllers',
'long2know.directives',
'long2know.constants'
]);
}
var multiselectParser = function($parse) {
// 00000111000000000000022200000000000000003333333333333330000000000044000
var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
return {
parse: function(input) {
var match = input.match(TYPEAHEAD_REGEXP);
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])
};
}
};
};
var multiselect = function($parse, $timeout, $filter, $document, $compile, $window, $position, optionParser) {
return {
restrict: 'EA',
require: ['ngModel', '?^form'],
link: function(originalScope, element, attrs, ctrls) {
var modelCtrl = ctrls[0];
var formCtrl = (ctrls.length > 1 && typeof(ctrls[1]) !== 'undefined') ? ctrls[1] : null;
//model setter executed upon match selection
var $setModelValue = $parse(attrs.ngModel).assign;
var
modelValue = {},
parserResult = optionParser.parse(attrs.options),
isMultiple = attrs.multiple ? originalScope.$eval(attrs.multiple) : false,
isAutoFocus = attrs.autoFocus ? originalScope.$eval(attrs.autoFocus) : false,
isComplex = attrs.complexModels ? originalScope.$eval(attrs.complexModels) : false,
enableFilter = attrs.enableFilter ? originalScope.$eval(attrs.enableFilter) : true,
enableCheckAll = attrs.enableCheckAll ? originalScope.$eval(attrs.enableCheckAll) : true,
enableUncheckAll = attrs.enableUncheckAll ? originalScope.$eval(attrs.enableUncheckAll) : true,
header = attrs.header ? attrs.header : "Select",
selectedHeader = attrs.selectedHeader ? attrs.selectedHeader : 'selected',
selectLimit = attrs.selectLimit ? originalScope.$eval(attrs.selectLimit) : 0,
useFiltered = attrs.selectLimitUseFiltered ? originalScope.$eval(attrs.selectLimitUseFiltered) : true,
filterPlaceholder = attrs.filterPlaceholder ? attrs.filterPlaceholder : "Filter ..",
checkAllLabel = attrs.checkAllLabel ? attrs.checkAllLabel : "Check all",
uncheckAllLabel = attrs.uncheckAllLabel ? attrs.uncheckAllLabel : "Uncheck all",
appendToBody = attrs.appendToBody ? originalScope.$eval(attrs.appendToBody) : false,
required = false,
lastSelectedLabel = '',
scope = originalScope.$new(true),
changeHandler = attrs.change || angular.noop,
popUpEl = angular.element('<multiselect-popup></multiselect-popup>'),
popupId = 'multiselect-' + scope.$id + '-' + Math.floor(Math.random() * 10000),
timeoutEventPromise,
eventDebounceTime = 200,
isChecked = function(i) {
return i.checked === true;
},
getFilteredItems = function() {
var filteredItems = $filter("filter")(scope.items, scope.searchText);
return filteredItems;
},
getFirstSelectedLabel = function() {
for (var i = 0; i < scope.items.length; i++) {
if (scope.items[i].checked) {
return scope.items[i].label;
}
}
return header;
},
canCheck = function() {
var belowLimit = false;
var atLimit = false;
var aboveLimit = false;
if (selectLimit === 0 || !isMultiple) {
belowLimit = true;
atLimit = false;
} else {
var checkedItems = scope.items.filter(isChecked);
atLimit = checkedItems.length === selectLimit;
aboveLimit = checkedItems.length > selectLimit;
belowLimit = checkedItems.length < selectLimit;
}
scope.maxSelected = atLimit || aboveLimit;
return atLimit || belowLimit;
},
getHeaderText = function() {
var localHeader = header;
var compareModel = modelValue;
// var compareModel = modelCtrl.$modelValue
if (isEmpty(compareModel)) return scope.header = localHeader;
if (isMultiple) {
var isArray = compareModel instanceof Array;
if (isArray && compareModel.length > 1) {
localHeader = compareModel.length + ' ' + selectedHeader;
} else {
localHeader = getFirstSelectedLabel();
}
} else {
localHeader = getFirstSelectedLabel();
}
scope.header = localHeader;
},
isEmpty = function(obj) {
if (!obj) return true;
if (!isComplex && obj) return false;
if (obj.length && obj.length > 0) return false;
for (var prop in obj)
if (obj[prop]) return false;
return true;
},
updateModel = function() {
modelCtrl.$setViewValue(modelValue);
},
parseModel = function() {
scope.items.length = 0;
var model = parserResult.source(originalScope);
if (!angular.isDefined(model)) return;
var compareModel = modelValue;
// var compareModel = modelCtrl.$modelValue
isArray = compareModel instanceof Array;
for (var i = 0; i < model.length; i++) {
var local = {};
local[parserResult.itemName] = model[i];
var value = parserResult.modelMapper(local)
var isChecked = isArray ?
(compareModel.indexOf(value.toString()) != -1 || compareModel.indexOf(value) != -1) :
(!isEmpty(compareModel) && compareModel == value);
var item = {
label: parserResult.viewMapper(local),
model: model[i],
checked: isChecked
};
scope.items.push(item);
}
getHeaderText();
},
selectSingle = function(item) {
if (item.checked) {
scope.uncheckAll();
} else {
scope.uncheckAll();
item.checked = true;
}
setModelValue(false);
updateModel();
},
selectMultiple = function(item) {
var isChanged = false;
if (item.checked) {
isChanged = true;
item.checked = false;
canCheck();
} else if (!scope.maxSelected) {
isChanged = true;
item.checked = canCheck();
}
setModelValue(true);
if (isChanged) {
updateModel();
}
},
getModelValue = function(item) {
if (isComplex) {
value = item.model;
} else {
var local = {};
local[parserResult.itemName] = item.model;
value = parserResult.modelMapper(local);
}
return value;
},
setModelValue = function(isMultiple) {
var value;
if (isMultiple) {
value = [];
angular.forEach(scope.items, function(item) {
// If map simple values
if (item.checked) {
if (isComplex) {
value.push(item.model);
} else {
var local = {};
local[parserResult.itemName] = item.model;
value.push(parserResult.modelMapper(local));
}
}
})
} else {
angular.forEach(scope.items, function(item) {
if (item.checked) {
if (isComplex) {
value = item.model;
return false;
} else {
var local = {};
local[parserResult.itemName] = item.model;
value = parserResult.modelMapper(local);
return false;
}
}
})
}
scope.triggered = true;
modelValue = value;
},
markChecked = function(newVal) {
if (!angular.isArray(newVal)) {
angular.forEach(scope.items, function(item) {
var value = getModelValue(item);
if (angular.equals(value, newVal)) {
item.checked = true;
return false;
}
});
} else {
var
itemsToCheck = [],
itemsToUncheck = [],
itemValues = [],
i = 0,
j = 0;
for (j = 0; j < scope.items.length; j++) {
itemValues.push(getModelValue(scope.items[j]));
itemsToUncheck.push(j);
}
for (i = 0; i < newVal.length; i++) {
for (j = 0; j < itemValues.length; j++) {
if (angular.equals(itemValues[j], newVal[i])) {
itemsToCheck.push(scope.items[j]);
var index = itemsToUncheck.indexOf(j);
itemsToUncheck.splice(index, 1);
break;
}
}
}
for (i = 0; i < itemsToCheck.length; i++) {
itemsToCheck[i].checked = true;
}
for (i = 0; i < itemsToUncheck.length; i++) {
scope.items[itemsToUncheck[i]].checked = false;
}
}
},
// recalculate actual position and set new values to scope
// after digest loop is popup in right position
recalculatePosition = function() {
scope.position = appendToBody ? $position.offset($popup) : $position.position(element);
scope.position.top += $popup.prop('offsetHeight');
},
fireRecalculating = function() {
if (!scope.moveInProgress) {
scope.moveInProgress = true;
scope.$digest();
}
// Cancel previous timeout
if (timeoutEventPromise) {
$timeout.cancel(timeoutEventPromise);
}
// Debounced executing recalculate after events fired
timeoutEventPromise = $timeout(function() {
// if popup is visible
if (scope.isOpen) {
recalculatePosition();
}
scope.moveInProgress = false;
scope.$digest();
}, eventDebounceTime);
};
scope.items = [];
scope.header = header;
scope.multiple = isMultiple;
scope.disabled = false;
scope.filterPlaceholder = filterPlaceholder;
scope.checkAllLabel = checkAllLabel;
scope.uncheckAllLabel = uncheckAllLabel;
scope.selectLimit = selectLimit;
scope.enableFilter = enableFilter;
scope.enableCheckAll = enableCheckAll;
scope.enableUncheckAll = enableUncheckAll;
scope.searchText = {
label: ''
};
scope.isAutoFocus = isAutoFocus;
scope.appendToBody = appendToBody;
scope.moveInProgress = false;
scope.popupId = popupId;
scope.recalculatePosition = recalculatePosition;
scope.isModelValueSet = false;
originalScope.$on('$destroy', function() {
scope.$destroy();
$document.unbind('click', scope.clickHandler);
if (appendToBody) {
$('#' + popupId).remove();
}
});
// bind events only if appendToBody params exist - performance feature
if (appendToBody) {
angular.element($window).bind('resize', fireRecalculating);
$document.find('body').bind('scroll', fireRecalculating);
}
// 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.ngDisabled)(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 parserResult.source(originalScope);
}, function(newVal) {
if (angular.isDefined(newVal)) {
parseModel();
setModelValue(isMultiple);
}
}, true);
////watch model change --> This has an issue in that it seems that all models are updated to the same value
scope.$watch(function() {
return modelCtrl.$modelValue;
}, function(newVal, oldVal) {
//when directive initializes, newVal is usually undefined. Also, if model value is 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 the
//model changes. We need to do this only if it is done outside directive scope, from controller, for example.
if (!scope.triggered) {
if (angular.isDefined(newVal)) {
var isArray = newVal instanceof Array;
if ((isArray && newVal.length == 0) || !isArray) {
scope.uncheckAll();
}
markChecked(newVal);
setModelValue(isMultiple);
scope.isModelValueSet = true;
// Technically, defining ngChange will already have a watcher triggering its handler
// So, triggering it manually should be redundant
//scope.$eval(changeHandler);
} else if (scope.isModelValueSet) {
// If the model value is cleared externally, and we previously had some things checked,
// we need to uncheck them.
scope.uncheckAll();
scope.isModelValueSet = false;
}
}
getHeaderText();
canCheck();
modelCtrl.$setValidity('required', scope.valid());
scope.triggered = false;
}, true);
parseModel();
var $popup = $compile(popUpEl)(scope);
element.append($popup);
$timeout(function() {
recalculatePosition();
}, 100);
scope.valid = function validModel() {
if (!required) return true;
var value = modelCtrl.$modelValue;
return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
};
scope.checkAll = function(setNgModel) {
if (!isMultiple) return;
var items = scope.items;
var anyChecked = false;
var totalChecked = 0;
if (useFiltered) {
items = getFilteredItems();
angular.forEach(items, function(item) {
item.checked = false;
});
totalChecked = scope.items.filter(isChecked).length;
}
if (selectLimit <= 0 || (items.length < selectLimit - totalChecked)) {
angular.forEach(items, function(item) {
item.checked = true;
});
} else {
angular.forEach(items, function(item) {
item.checked = false;
});
for (var i = 0; i < (selectLimit - totalChecked); i++) {
items[i].checked = true;
}
scope.maxSelected = true;
}
setModelValue(true);
if (setNgModel) {
updateModel();
}
};
scope.uncheckAll = function(setNgModel) {
var items = useFiltered ? getFilteredItems() : scope.items;
var anyChecked = false;
angular.forEach(items, function(item) {
if (item.checked) { anyChecked = true; }
item.checked = false;
});
canCheck();
if (isMultiple) {
setModelValue(true);
}
if (anyChecked && setNgModel) {
updateModel();
}
};
scope.select = function(item) {
if (isMultiple === false) {
selectSingle(item);
scope.toggleSelect();
} else {
selectMultiple(item);
}
};
scope.clearFilter = function() {
scope.searchText.label = '';
};
}
};
};
var multiselectPopup = function($document) {
return {
restrict: 'E',
replace: true,
require: ['^ngModel', '?^form'],
templateUrl: 'template/multiselect/multiselectPopup.html',
link: function(scope, element, attrs, ctrls) {
var $dropdown = element.find(".dropdown-menu");
$dropdown.attr("id", scope.popupId);
if (scope.appendToBody) {
$document.find('body').append($dropdown);
}
var
clickHandler = function(event) {
if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
return;
if (scope.appendToBody) {
if (elementMatchesAnyInArray(event.target, $dropdown.find(event.target.tagName)))
return;
}
element.removeClass('open');
scope.isOpen = false;
$document.unbind('click', clickHandler);
scope.$apply();
},
elementMatchesAnyInArray = function(element, elementArray) {
for (var i = 0; i < elementArray.length; i++)
if (element == elementArray[i])
return true;
return false;
};
scope.clickHandler = clickHandler;
scope.isVisible = false;
scope.isHeightChanged = true;
var
dropdownHeight,
dropdownWidth;
scope.toggleSelect = function() {
if (element.hasClass('open') || scope.isOpen) {
element.removeClass('open');
scope.isOpen = false;
$document.unbind('click', clickHandler);
} else {
element.addClass('open');
scope.isOpen = true;
$document.bind('click', clickHandler);
if (scope.isAutoFocus) {
scope.focus();
}
scope.recalculatePosition();
}
// Figure out if dropup
var parent = element.parent();
var windowScrollTop = $(window).scrollTop();
var windowHeight = $(window).height();
var windowWidth = $(window).width();
var ulElement = element.find("ul:first");
if (scope.isHeightChanged) {
dropdownHeight = ulElement.height();
dropdownWidth = ulElement.width();
scope.isHeightChanged = false;
}
// If we have no height/width, the element isn't visisble - we can clone it and show it off screen to get
// its visibile dimensions. Alternatively, we could just make the element visible and then adjust,
// but this might result in some screen flicker... who knows?
if (dropdownHeight <= 0 && dropdownWidth <= 0) {
var clonedElement = $(ulElement)
.clone()
.css('position', 'fixed')
.css('top', '0')
.css('left', '-10000px')
.appendTo(parent)
.removeClass('ng-hide')
.show();
dropdownHeight = clonedElement.height();
dropdownWidth = clonedElement.width();
// Memory clean up - also, if you don't remove the clone from the DOM, IE11 increases the height of the HTML DOM element (buggy piece of junk!)
clonedElement.remove();
clonedElement = null;
}
// Determine if outside of visible range when dropping down
var elementTop = element.offset().top + element.height() - windowScrollTop;
var elementBottom = windowHeight - element.height() - element.offset().top + windowScrollTop;
if ((elementBottom < dropdownHeight) && (elementTop > dropdownHeight)) {
// Alert should drop up!
scope.dropup = true;
} else {
scope.dropup = false;
}
// Figure out if we need left adjust
if (element.offset().left + dropdownWidth >= windowWidth) {
scope.isOffRight = true;
var adjust = ((element.offset().left + dropdownWidth - windowWidth) + 10) * -1.0;
ulElement.css("left", adjust.toString() + "px");
} else {
scope.isOffRight = false;
ulElement.css("left", "0");
}
};
scope.focus = function focus() {
if (scope.enableFilter) {
var searchBox = element.find('input')[0];
searchBox.focus();
}
}
}
}
};
// IE11 doesn't enable the filter box when parent changes is using disabled attribute - so, use ng-disabled in your own HTML!
angular.module("long2know").run(["$templateCache", function($templateCache) {
$templateCache.put("template/multiselect/multiselectPopup.html",
"<div class=\"btn-group\" ng-class=\"{ dropup: dropup, single: !multiple }\">" +
"<button type=\"button\" class=\"btn btn-default dropdown-toggle\" ng-click=\"toggleSelect()\" ng-disabled=\"disabled\" ng-class=\"{'has-error': !valid()}\">" +
"<span class=\"pull-left\" ng-bind=\"header\"></span>" +
"<span class=\"caret pull-right\"></span>" +
"</button>" +
"<ul class=\"dropdown-menu multi-select-popup\" ng-show=\"isOpen && !moveInProgress\" ng-style=\"{ true: {top: position.top +'px', left: position.left +'px'}, false: {}}[appendToBody]\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen}}\">" +
"<li ng-if=\"enableFilter\" class=\"filter-container\">" +
"<div class=\"form-group has-feedback filter\">" +
"<input class=\"form-control\" type=\"text\" ng-model=\"searchText.label\" placeholder=\"{{ filterPlaceholder }}\" />" +
"<span class=\"glyphicon glyphicon-remove-circle form-control-feedback\" ng-click=\"clearFilter()\"></span>" +
"</div>" +
"</li>" +
"<li ng-show=\"multiple && (enableCheckAll || enableUncheckAll)\">" +
"<button ng-if=\"enableCheckAll\" type=\"button\" class=\"btn-link btn-small\" ng-click=\"checkAll(true)\"><i class=\"icon-ok\"></i> {{ checkAllLabel }}</button>" +
"<button ng-if=\"enableUncheckAll\" type=\"button\" class=\"btn-link btn-small\" ng-click=\"uncheckAll(true)\"><i class=\"icon-remove\"></i> {{ uncheckAllLabel }}</button>" +
"</li>" +
"<li ng-show=\"maxSelected\">" +
"<small>Selected maximum of </small><small ng-bind=\"selectLimit\"></small>" +
"</li>" +
"<li ng-repeat=\"i in items | filter:searchText\">" +
//"<a ng-click=\"select(i); focus()\">" +
"<a ng-click=\"select(i);\">" +
"<i class=\"glyphicon\" ng-class=\"{'glyphicon-ok': i.checked, 'glyphicon-none': !i.checked}\"></i>" +
"<span ng-bind=\"i.label\"></span>" +
"</a>" +
"</li>" +
"</ul>" +
"</div>");
}]);
multiselectParser.$inject = ['$parse'];
multiselect.$inject = ['$parse', '$timeout', '$filter', '$document', '$compile', '$window', '$uibPosition', 'multiselectParser'];
multiselectPopup.$inject = ['$document'];
angular
.module("long2know.services")
.factory('multiselectParser', multiselectParser);
angular
.module('long2know.directives')
.directive('multiselectPopup', multiselectPopup);
angular
.module('long2know.directives')
.directive('multiselect', multiselect);
})()