263 lines
7.9 KiB
JavaScript
263 lines
7.9 KiB
JavaScript
angular.module('ui.multiselect', [])
|
|
|
|
//from bootstrap-ui typeahead parser
|
|
.factory('optionParser', ['$parse', function ($parse) {
|
|
|
|
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() {
|
|
var edit = "edit";
|
|
if (!modelCtrl.$modelValue || !modelCtrl.$modelValue.length) return scope.header = 'Can ' + edit;
|
|
if (isMultiple) {
|
|
edit = '';
|
|
var accessLevelArray = [];
|
|
modelCtrl.$modelValue.forEach(function(level) {
|
|
accessLevelArray.push(level.name);
|
|
});
|
|
scope.header = "Can " + accessLevelArray.join(', ');
|
|
} 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: 'templates/widgets/share-access-multiselect.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;
|
|
};
|
|
}
|
|
};
|
|
}]);
|