"use strict"; // ------------------------------- // WARNING // ------------------------------- // This file is taken from https://github.com/sebastianha/angular-bootstrap-multiselect // There are local modifications like support for translation // ------------------------------- angular.module("ui.multiselect", ["multiselect.tpl.html"]) //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); 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", "$interpolate", "$translate", "optionParser", function($parse, $document, $compile, $interpolate, $translate, optionParser) { return { restrict: "E", require : "ngModel", link : function(originalScope, element, attrs, modelCtrl) { var exp = attrs.options; var parsedResult = optionParser.parse(exp); var isMultiple = attrs.multiple ? true : false; var compareByKey = attrs.compareBy; var headerKey = attrs.headerKey; var dividerKey = attrs.dividerKey; var scrollAfterRows = attrs.scrollAfterRows; var tabindex = attrs.tabindex; var maxWidth = attrs.maxWidth; var required = false; var scope = originalScope.$new(); scope.filterAfterRows = attrs.filterAfterRows; var changeHandler = attrs.change || angular.noop; scope.items = []; scope.header = "Select"; scope.multiple = isMultiple; scope.disabled = false; scope.ulStyle = {}; if(scrollAfterRows !== undefined && parseInt(scrollAfterRows).toString() === scrollAfterRows) { scope.ulStyle = {"max-height": (scrollAfterRows*26+14)+"px", "overflow-y": "auto", "overflow-x": "hidden"}; } if(tabindex !== undefined && parseInt(tabindex).toString() === tabindex) { scope.tabindex = tabindex; } if(maxWidth !== undefined && parseInt(maxWidth).toString() === maxWidth) { scope.maxWidth = {"max-width": maxWidth + "px"}; } originalScope.$on("$destroy", function() { scope.$destroy(); }); var popUpEl = angular.element(""); //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 parsedResult.source(originalScope); }, function(newVal) { if(angular.isDefined(newVal)) { parseModel(); } }, true); //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()); }); function parseModel() { scope.items.length = 0; var model = parsedResult.source(originalScope); if(!angular.isDefined(model) || model === null) { return; } for(var i = 0; i < model.length; i++) { var local = {}; local[parsedResult.itemName] = model[i]; // calculate checked status of the option // https://github.com/sebastianha/angular-bootstrap-multiselect/pull/4/files var id = model[i]; var checked = false; var modelValue = modelCtrl.$modelValue; if (modelValue) { if (angular.isArray(modelValue)) { for (var j = 0; j < modelValue.length; j++) if (modelValue[j] == id) { checked = true; break; } } else { checked = modelValue == id; } } scope.items.push({ label : parsedResult.viewMapper(local), model : model[i], checked: checked, header : model[i][headerKey], divider : model[i][dividerKey] }); } } parseModel(); element.append($compile(popUpEl)(scope)); function getHeaderText() { if(isEmpty(modelCtrl.$modelValue)) { scope.header = attrs.msHeader || $translate.instant('main.multiselect.select'); return scope.header; } if(isMultiple) { if(attrs.msSelected) { scope.header = $interpolate(attrs.msSelected)(scope); } else { scope.header = $translate.instant('main.multiselect.selected', { n: modelCtrl.$modelValue.length }); } } else { var local = {}; local[parsedResult.itemName] = modelCtrl.$modelValue; scope.header = parsedResult.viewMapper(local); } } function isEmpty(obj) { if(obj === true || obj === false) { return false; } if(!obj) { return true; } if(obj.length && obj.length > 0) { return false; } for(var prop in obj) { if(obj[prop]) { return false; } } if(compareByKey !== undefined && obj[compareByKey] !== undefined) { return false; } return true; } 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(); 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) { item.checked = false; if(compareByKey === undefined && angular.equals(item.model, newVal)) { item.checked = true; } else if(compareByKey !== undefined && newVal !== null && item.model[compareByKey] !== undefined && angular.equals(item.model[compareByKey], newVal[compareByKey])) { item.checked = true; } }); } else { angular.forEach(scope.items, function(item) { item.checked = false; angular.forEach(newVal, function(i) { if(compareByKey === undefined && angular.equals(item.model, i)) { item.checked = true; } else if(compareByKey !== undefined && item.model[compareByKey] !== undefined && angular.equals(item.model[compareByKey], i[compareByKey])) { 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(event, item) { if(isMultiple === false) { selectSingle(item); scope.toggleSelect(); } else { event.stopPropagation(); selectMultiple(item); } }; } }; }]) .directive("multiselectPopup", ["$document", function($document) { return { restrict : "E", scope : false, replace : true, templateUrl: "multiselect.tpl.html", link : function(scope, element, attrs) { scope.isVisible = false; scope.toggleSelect = function() { if(element.hasClass("open")) { scope.filter = ""; element.removeClass("open"); $document.unbind("click", clickHandler); } else { scope.filter = ""; element.addClass("open"); $document.bind("click", clickHandler); } }; // $("ul.dropdown-menu").on("click", "[data-stopPropagation]", function(e) { // e.stopPropagation(); // }); function clickHandler(event) { if(elementMatchesAnyInArray(event.target, element.find(event.target.tagName))) { return; } element.removeClass("open"); $document.unbind("click", clickHandler); scope.$apply(); } var elementMatchesAnyInArray = function(element, elementArray) { for(var i = 0; i < elementArray.length; i++) { if(element === elementArray[i]) { return true; } } return false; }; } }; }]); angular.module("multiselect.tpl.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("multiselect.tpl.html", "
\n" + " \n" + " \n" + "
"); }]);