1angular.module('oi.multiselect', ['template/multiselect/template.html']); 2 3angular.module("template/multiselect/template.html", []).run(["$templateCache", function($templateCache) { 4 $templateCache.put("template/multiselect/template.html", 5 "<div class=\"multiselect-search\" ng-class=\"{open: isOpen, focused: isFocused, loading: showLoader}\" ng-click=\"setFocus($event)\">\n" + 6 " <ul class=\"multiselect-search-list\">\n" + 7 " <li class=\"btn btn-default btn-xs multiselect-search-list-item multiselect-search-list-item_selection\"\n" + 8 " ng-repeat=\"item in output track by $index\"\n" + 9 " ng-class=\"{focused: backspaceFocus && $last}\"\n" + 10 " ng-click=\"removeItem($index)\"\n" + 11 " ng-bind-html=\"getSearchLabel(item)\"></li>\n" + 12 " <li class=\"multiselect-search-list-item multiselect-search-list-item_input\"><input autocomplete=\"off\"\n" + 13 " ng-model=\"query\"\n" + 14 " ng-style=\"{'width': inputWidth + 'px'}\"\n" + 15 " ng-keydown=\"keyParser($event)\"/></li>\n" + 16 " <li class=\"multiselect-search-list-item multiselect-search-list-item_loader\" ng-show=\"showLoader\"></li>\n" + 17 " </ul>\n" + 18 "</div>\n" + 19 "<div class=\"multiselect-dropdown\" ng-show=\"isOpen\" ng-click=\"setFocus($event)\">\n" + 20 " <ul ng-if=\"isOpen\" class=\"multiselect-dropdown-optgroup\" ng-repeat=\"(group, options) in groups\">\n" + 21 " <div class=\"multiselect-dropdown-optgroup-header\"\n" + 22 " ng-if=\"group && options.length\"\n" + 23 " ng-bind=\"group\"></div><!-- we use fast getElementsByTagName('li')-->\n" + 24 " <li class=\"multiselect-dropdown-optgroup-option\"\n" + 25 " ng-repeat=\"option in options\"\n" + 26 " ng-class=\"{'active': selectorPosition === groupPos[group] + $index}\"\n" + 27 " ng-click=\"addItem(option)\"\n" + 28 " ng-mouseenter=\"setSelection(groupPos[group] + $index)\"\n" + 29 " ng-bind-html=\"getDropdownLabel(option)\"></li>\n" + 30 " </ul>\n" + 31 "</div>"); 32}]); 33 34angular.module('oi.multiselect') 35 36.provider('oiMultiselect', function() { 37 return { 38 options: { 39 debounce: 500, 40 searchFilter: 'oiMultiselectCloseIcon', 41 dropdownFilter: 'oiMultiselectHighlight', 42 listFilter: 'oiMultiselectAscSort', 43 saveLastQuery: null, 44 newItem: false, 45 saveTrigger: 'enter, slash' 46 }, 47 $get: function() { 48 return { 49 options: this.options 50 }; 51 } 52 }; 53}) 54 55.factory('oiUtils', ['$document', function($document) { 56 /** 57 * Measures the width of a string within a 58 * parent element (in pixels). 59 * 60 * @param {string} str 61 * @param {object} $parent 62 * @returns {int} 63 */ 64 function measureString(str, $parent) { 65 var $mirror = angular.element('<mirror>').css({ 66 position: 'absolute', 67 width: 'auto', 68 padding: 0, 69 whiteSpace: 'pre', 70 visibility: 'hidden', 71 'z-index': -99999 72 }).text(str || ''); 73 74 transferStyles($parent, $mirror, 'letterSpacing fontSize fontFamily fontWeight textTransform'.split(' ')); 75 76 $document[0].body.appendChild($mirror[0]); 77 78 var width = $mirror[0].offsetWidth; 79 $mirror.remove(); 80 81 return width; 82 } 83 84 /** 85 * Copies CSS properties from one element to another. 86 * 87 * @param {object} $from 88 * @param {object} $to 89 * @param {array} properties 90 */ 91 function transferStyles($from, $to, properties) { 92 var stylesTo = {}, 93 stylesFrom = getComputedStyle($from[0], ''); 94 95 for (var i = 0, n = properties.length; i < n; i++) { 96 stylesTo[properties[i]] = stylesFrom[properties[i]]; 97 } 98 99 $to.css(stylesTo); 100 } 101 102 /** 103 * Sets the selected item in the dropdown menu 104 * of available options. 105 * 106 * @param {object} list 107 * @param {object} item 108 */ 109 function scrollActiveOption(list, item) { 110 var y, height_menu, height_item, scroll, scroll_top, scroll_bottom; 111 112 if (item) { 113 height_menu = list.offsetHeight; 114 height_item = getWidthOrHeight(item, 'height', 'margin'); //outerHeight(true); 115 scroll = list.scrollTop || 0; 116 y = getOffset(item).top - getOffset(list).top + scroll; 117 scroll_top = y; 118 scroll_bottom = y - height_menu + height_item; 119 120 //TODO Make animation 121 if (y + height_item > height_menu + scroll) { 122 list.scrollTop = scroll_bottom; 123 } else if (y < scroll) { 124 list.scrollTop = scroll_top; 125 } 126 } 127 } 128 129 // Used for matching numbers 130 var core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source; 131 var rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ); 132 133 function augmentWidthOrHeight(elem, name, extra, isBorderBox, styles) { 134 var i = extra === (isBorderBox ? 'border' : 'content') ? 135 // If we already have the right measurement, avoid augmentation 136 4 : 137 // Otherwise initialize for horizontal or vertical properties 138 name === 'width' ? 1 : 0, 139 140 val = 0, 141 cssExpand = ['Top','Right','Bottom','Left']; 142 143 //TODO Use angular.element.css instead of getStyleValue after https://github.com/caitp/angular.js/commit/92bbb5e225253ebddd38ef5735d66ffef76b6a14 will be applied 144 function getStyleValue(name) { 145 return parseFloat(styles[name]); 146 } 147 148 for (; i < 4; i += 2) { 149 // both box models exclude margin, so add it if we want it 150 if (extra === 'margin') { 151 val += getStyleValue(extra + cssExpand[i]); 152 } 153 154 if ( isBorderBox ) { 155 // border-box includes padding, so remove it if we want content 156 if (extra === 'content') { 157 val -= getStyleValue('padding' + cssExpand[i]); 158 } 159 160 // at this point, extra isn't border nor margin, so remove border 161 if (extra !== 'margin') { 162 val -= getStyleValue('border' + cssExpand[i] + 'Width'); 163 } 164 } else { 165 val += getStyleValue('padding' + cssExpand[i]); 166 167 // at this point, extra isn't content nor padding, so add border 168 if (extra !== 'padding') { 169 val += getStyleValue('border' + cssExpand[i] + 'Width'); 170 } 171 } 172 } 173 174 return val; 175 } 176 177 function getOffset(elem) { 178 var docElem, win, 179 box = elem.getBoundingClientRect(), 180 doc = elem && elem.ownerDocument; 181 182 if (!doc) { 183 return; 184 } 185 186 docElem = doc.documentElement; 187 win = getWindow(doc); 188 189 return { 190 top: box.top + win.pageYOffset - docElem.clientTop, 191 left: box.left + win.pageXOffset - docElem.clientLeft 192 }; 193 } 194 195 function getWindow(elem) { 196 return elem != null && elem === elem.window ? elem : elem.nodeType === 9 && elem.defaultView; 197 } 198 199 function getWidthOrHeight(elem, name, extra) { 200 201 // Start with offset property, which is equivalent to the border-box value 202 var valueIsBorderBox = true, 203 val = name === 'width' ? elem.offsetWidth : elem.offsetHeight, 204 styles = window.getComputedStyle(elem, null), 205 206 //TODO Make isBorderBox after https://github.com/caitp/angular.js/commit/92bbb5e225253ebddd38ef5735d66ffef76b6a14 will be applied 207 isBorderBox = false; //jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; 208 209 // some non-html elements return undefined for offsetWidth, so check for null/undefined 210 // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 211 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 212 if (val <= 0 || val == null) { 213 // Fall back to computed then uncomputed css if necessary 214 val = styles[name]; 215 216 if (val < 0 || val == null) { 217 val = elem.style[name]; 218 } 219 220 // Computed unit is not pixels. Stop here and return. 221 if (rnumnonpx.test(val)) { 222 return val; 223 } 224 225 // we need the check for style in case a browser which returns unreliable values 226 // for getComputedStyle silently falls back to the reliable elem.style 227 //valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); 228 229 // Normalize "", auto, and prepare for extra 230 val = parseFloat(val) || 0; 231 } 232 233 // use the active box-sizing model to add/subtract irrelevant styles 234 return val + augmentWidthOrHeight(elem, name, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, styles); 235 } 236 237 function copyWidth(srcElement, dstElement) { 238 dstElement.css('width', getWidthOrHeight(srcElement[0], 'width', 'margin') + 'px'); 239 } 240 241 function groupsIsEmpty(groups) { 242 for (var k in groups) { 243 if (groups.hasOwnProperty(k) && groups[k].length) { 244 return false;
245 } 246 } 247 return true; 248 } 249 250 function objToArr(obj) { 251 var arr = []; 252 253 angular.forEach(obj, function(value) { 254 arr.push(value); 255 }); 256 257 return arr; 258 } 259 260 //lodash _.isEqual 261 function isEqual(x, y) { 262 if ( x === y ) return true; 263 if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false; 264 if ( x.constructor !== y.constructor ) return false; 265 266 for ( var p in x ) { 267 if ( ! x.hasOwnProperty( p ) ) continue; 268 if ( ! y.hasOwnProperty( p ) ) return false; 269 if ( x[ p ] === y[ p ] ) continue; 270 if ( typeof( x[ p ] ) !== "object" ) return false; 271 if ( ! objectEquals( x[ p ], y[ p ] ) ) return false; 272 } 273 274 for ( p in y ) { 275 if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false; 276 } 277 return true; 278 } 279 280 //lodash _.intersection + filter + callback + invert 281 function intersection(xArr, yArr, callback, xFilter, yFilter, invert) { 282 var i, j, n, filteredX, filteredY, out = invert ? [].concat(xArr) : []; 283 284 callback = callback || function(xValue, yValue) { 285 return xValue === yValue; 286 }; 287 288 for (i = 0, n = xArr.length; i < xArr.length; i++) { 289 filteredX = xFilter ? xFilter(xArr[i]) : xArr[i]; 290 291 for (j = 0; j < yArr.length; j++) { 292 filteredY = yFilter ? yFilter(yArr[j]) : yArr[j]; 293 294 if (callback(filteredX, filteredY, xArr, yArr, i, j)) { 295 invert ? out.splice(i + out.length - n, 1) : out.push(xArr[i]); 296 break; 297 } 298 } 299 } 300 return out; 301 } 302 303 function getValue(valueName, item, scope, getter) { 304 var locals = {}; 305 306 //'name.subname' -> {name: {subname: list}}' 307 valueName.split('.').reduce(function(previousValue, currentItem, index, arr) { 308 return previousValue[currentItem] = index < arr.length - 1 ? {} : item; 309 }, locals); 310 311 return getter(scope, locals); 312 } 313 314 return { 315 copyWidth: copyWidth, 316 measureString: measureString, 317 scrollActiveOption: scrollActiveOption, 318 groupsIsEmpty: groupsIsEmpty, 319 objToArr: objToArr, 320 getValue: getValue, 321 isEqual: isEqual, 322 intersection: intersection 323 } 324}]); 325angular.module('oi.multiselect') 326 327.directive('oiMultiselect', ['$document', '$q', '$timeout', '$parse', '$interpolate', '$injector', '$filter', 'oiUtils', 'oiMultiselect', function($document, $q, $timeout, $parse, $interpolate, $injector, $filter, oiUtils, oiMultiselect) { 328 var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, 329 VALUES_REGEXP = /([^\(\)\s\|\s]*)\s*(\(.*\))?\s*(\|?\s*.+)?/; 330 331 return { 332 restrict: 'AE', 333 templateUrl: 'template/multiselect/template.html', 334 require: 'ngModel', 335 scope: {}, 336 compile: function (element, attrs) { 337 var optionsExp = attrs.ngOptions, 338 match; 339 340 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 341 throw new Error("Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'"); 342 } 343 344 var selectAsName = / as /.test(match[0]) && match[1], //item.modelValue 345 displayName = match[2] || match[1], //item.label 346 valueName = match[4] || match[6], //item 347 groupByName = match[3] || '', //item.groupName 348 trackByName = match[8] || displayName, //item.id 349 valueMatches = match[7].match(VALUES_REGEXP); //collection 350 351 var valuesName = valueMatches[1], //collection 352 filteredValuesName = valuesName + (valueMatches[3] || ''), //collection | filter 353 valuesFnName = valuesName + (valueMatches[2] || ''); //collection() 354 355 var selectAsFn = selectAsName && $parse(selectAsName), 356 displayFn = $parse(displayName),
357 groupByFn = $parse(groupByName), 358 filteredValuesFn = $parse(filteredValuesName), 359 valuesFn = $parse(valuesFnName), 360 trackByFn = $parse(trackByName); 361 362 var multiple = angular.isDefined(attrs.multiple), 363 multipleLimit = Number(attrs.multipleLimit), 364 placeholderFn = $interpolate(attrs.placeholder || ''), 365 keyUpDownWerePressed = false, 366 matchesWereReset = false, 367 optionsFn = $parse(attrs.oiMultiselectOptions); 368 369 var timeoutPromise, 370 lastQuery; 371 372 return function(scope, element, attrs, ctrl) { 373 var inputElement = element.find('input'), 374 listElement = angular.element(element[0].querySelector('.multiselect-dropdown')), 375 placeholder = placeholderFn(scope), 376 options = angular.extend({}, oiMultiselect.options, optionsFn(scope.$parent)), 377 lastQueryFn = options.saveLastQuery ? $injector.get(options.saveLastQuery) : function() {return ''}; 378 379 options.newItemModelFn = function (query) { 380 return (optionsFn({$query: query}) || {}).newItemModel || query; 381 }; 382 383 if (angular.isDefined(attrs.autofocus)) { 384 $timeout(function() { 385 inputElement[0].focus(); 386 }); 387 } 388 389 if (angular.isDefined(attrs.readonly)) { 390 inputElement.attr('readonly', true) 391 } 392 393 attrs.$observe('disabled', function(value) { 394 inputElement.prop('disabled', value); 395 }); 396 397 scope.$parent.$watch(attrs.ngModel, function(value) { 398 adjustInput(); 399 400 var output = value instanceof Array ? value : value ? [value]: [], 401 promise = $q.when(output); 402 403 if (selectAsFn && value) { 404 promise = getMatches(null, value) 405 .then(function(collection) { 406 return oiUtils.intersection(collection, output, oiUtils.isEqual, selectAs); 407 }); 408 timeoutPromise = null; //`resetMatches` should not cancel the `promise` 409 } 410 411 promise.then(function(collection) { 412 scope.output = collection; 413 414 if (collection.length !== output.length) { 415 scope.removeItem(collection.length); //if newItem was not created 416 } 417 }); 418 }); 419 420 scope.$watch('query', function(inputValue, oldValue) { 421 adjustInput(); 422 423 //We don't get matches if nothing added into matches list 424 if (inputValue !== oldValue && (!scope.oldQuery || inputValue) && !matchesWereReset) { 425 listElement[0].scrollTop = 0; 426 427 if (inputValue) { 428 getMatches(inputValue); 429 scope.oldQuery = null; 430 } else { 431 resetMatches(); 432 matchesWereReset = true; 433 } 434 } 435 matchesWereReset = false; 436 }); 437 438 scope.$watch('groups', function(groups) { 439 if (oiUtils.groupsIsEmpty(groups)) { 440 scope.isOpen = false; 441 442 } else if (!scope.isOpen && !attrs.disabled) { 443 scope.isOpen = true; 444 oiUtils.copyWidth(element, listElement); 445 446 if (!scope.isFocused) { 447 $document.on('click', blurHandler); 448 scope.isFocused = true; 449 } 450 } 451 }); 452 453 scope.setFocus = function(event) { 454 if (attrs.disabled) return; 455 456 if (angular.element(event.target).scope() === this) { //not click on add or remove buttons 457 if (scope.isOpen && !scope.query) { 458 resetMatches() 459 } else { 460 getMatches(scope.query) 461 } 462 } 463 scope.backspaceFocus = false;
464 465 if (event.target.nodeName !== 'INPUT') { 466 inputElement[0].focus(); 467 } 468 }; 469 470 scope.addItem = function addItem(option) { 471 lastQuery = scope.query; 472 473 //duplicate 474 if (oiUtils.intersection(scope.output, [option], null, getLabel, getLabel).length) return; 475 476 //limit is reached 477 if (!isNaN(multipleLimit) && scope.output.length >= multipleLimit) return; 478 479 var optionGroup = scope.groups[getGroupName(option)]; 480 var modelOption = selectAsFn ? selectAs(option) : option; 481 482 optionGroup.splice(optionGroup.indexOf(option), 1); 483 484 if (multiple) { 485 ctrl.$setViewValue(angular.isArray(ctrl.$modelValue) ? ctrl.$modelValue.concat(modelOption) : [modelOption]); 486 updateGroupPos(); 487 } else { 488 ctrl.$setViewValue(modelOption); 489 resetMatches(); 490 } 491 492 if (oiUtils.groupsIsEmpty(scope.groups)) { 493 scope.groups = {}; //it is necessary for groups watcher 494 } 495 496 scope.oldQuery = scope.oldQuery || scope.query; 497 scope.query = ''; 498 scope.backspaceFocus = false; 499 500 adjustInput(); 501 }; 502 503 scope.removeItem = function removeItem(position) { 504 var removedValue; 505 506 if (attrs.disabled) return; 507 508 if (multiple) { 509 removedValue = ctrl.$modelValue[position]; 510 ctrl.$modelValue.splice(position, 1); 511 ctrl.$setViewValue([].concat(ctrl.$modelValue)); 512 513 } else if (!angular.isDefined(attrs.notempty)) { 514 removedValue = ctrl.$modelValue; 515 ctrl.$setViewValue(undefined); 516 } 517 518 scope.query = lastQueryFn(removedValue, lastQuery); 519 520 if (scope.isOpen || scope.oldQuery || !multiple) { 521 getMatches(scope.oldQuery); //stay old list 522 } 523 524 adjustInput(); 525 }; 526 527 scope.setSelection = function(index) { 528 if (!keyUpDownWerePressed && scope.selectorPosition !== index) { 529 setOption(listElement, index); 530 } else { 531 keyUpDownWerePressed = false; 532 } 533 }; 534 535 function saveOn(triggerName) { 536 var isTriggered = (new RegExp(triggerName)).test(options.saveTrigger), 537 isNewItem = options.newItem && scope.query, 538 isSelectedItem = angular.isNumber(scope.selectorPosition), 539 selectedOrder = scope.order[scope.selectorPosition], 540 newItemFn = options.newItemFn || options.newItemModelFn, 541 itemPromise = $q.reject(); 542 543 if (isTriggered && (isNewItem || isSelectedItem && selectedOrder)) { 544 scope.showLoader = true; 545 itemPromise = $q.when(triggerName !== 'blur' && selectedOrder || scope.query && newItemFn(scope.query)); 546 } 547 548 itemPromise 549 .then(scope.addItem) 550 .finally(function() { 551 var bottom = scope.order.length - 1; 552 553 if (scope.selectorPosition === bottom) { 554 setOption(listElement, 0); //TODO optimise when list will be closed 555 } 556 options.newItemFn && !isSelectedItem || $timeout(angular.noop); //TODO $applyAsync work since Angular 1.3 557 resetMatches(); 558 }); 559 } 560 561 scope.keyParser = function keyParser(event) { 562 var top = 0, 563 bottom = scope.order.length - 1; 564 565 switch (event.keyCode) { 566 case 38: /* up */ 567 scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top; 568 setOption(listElement, scope.selectorPosition === top ? bottom : scope.selectorPosition - 1); 569 keyUpDownWerePressed = true; 570 break; 571 572 case 40: /* down */ 573 scope.selectorPosition = angular.isNumber(scope.selectorPosition) ? scope.selectorPosition : top - 1; 574 setOption(listElement, scope.selectorPosition === bottom ? top : scope.selectorPosition + 1); 575 keyUpDownWerePressed = true; 576 if (!scope.query.length && !scope.isOpen) { 577 getMatches(); 578 } 579 break; 580 581 case 37: /* left */ 582 case 39: /* right */ 583 break; 584 585 case 13: /* enter */ 586 saveOn('enter'); 587 break; 588 case 9: /* tab */ 589 saveOn('tab'); 590 break; 591 case 220: /* slash */ 592 saveOn('slash'); 593 event.preventDefault(); //backslash interpreted as a regexp 594 break; 595 596 case 27: /* esc */ 597 resetMatches(); 598 break; 599 600 case 8: /* backspace */ 601 if (!scope.query.length) { 602 if (scope.backspaceFocus && scope.output) { 603 scope.removeItem(scope.output.length - 1); 604 if (!scope.output.length) { 605 getMatches(); 606 break; 607 } 608 } 609 scope.backspaceFocus = !scope.backspaceFocus; 610 break; 611 } 612 default: /* any key */ 613 scope.backspaceFocus = false;
614 return false; //preventDefaults 615 } 616 }; 617 618 scope.getSearchLabel = function(option) { 619 var label = getLabel(option); 620 621 if (options.searchFilter) { 622 label = $filter(options.searchFilter)(label, scope.oldQuery || scope.query, option) 623 } 624 return label; 625 }; 626 627 scope.getDropdownLabel = function(option) { 628 var label = getLabel(option); 629 630 if (options.dropdownFilter) { 631 label = $filter(options.dropdownFilter)(label, scope.oldQuery || scope.query, option) 632 } 633 return label; 634 }; 635 636 if (multiple) { 637 // Override the standard $isEmpty because an empty array means the input is empty. 638 ctrl.$isEmpty = function(value) { 639 return !value || !value.length; 640 }; 641 } 642 643 resetMatches(); 644 645 function blurHandler(event) { 646 if (event.target.ownerDocument.activeElement !== inputElement[0]) { 647 saveOn('blur'); 648 $document.off('click', blurHandler); 649 scope.isFocused = false; 650 scope.$digest(); 651 } 652 } 653 654 function adjustInput() { 655 var currentPlaceholder = ctrl.$modelValue && ctrl.$modelValue.length ? '' : placeholder; 656 inputElement.attr('placeholder', currentPlaceholder); 657 // expand input box width based on content 658 scope.inputWidth = oiUtils.measureString(scope.query || currentPlaceholder, inputElement) + 4; 659 } 660 661 function trackBy(item) { 662 return oiUtils.getValue(valueName, item, scope, trackByFn); 663 } 664 665 function selectAs(item) { 666 return oiUtils.getValue(valueName, item, scope, selectAsFn); 667 } 668 669 function getLabel(item) { 670 return oiUtils.getValue(valueName, item, scope, displayFn); 671 } 672 673 function getGroupName(option) { 674 return oiUtils.getValue(valueName, option, scope, groupByFn) || ''; 675 } 676 677 function filter(list) { 678 return oiUtils.getValue(valuesName, list, scope.$parent, filteredValuesFn); 679 } 680 681 function getMatches(query, querySelectAs) { 682 var values = valuesFn(scope.$parent, {$query: query, $querySelectAs: querySelectAs}), 683 waitTime = 0; 684 685 scope.selectorPosition = options.newItem === 'prompt' ? false : 0; 686 687 if (!query && !querySelectAs) { 688 scope.oldQuery = null; 689 } 690 691 if (timeoutPromise && angular.isFunction(values.then)) { 692 $timeout.cancel(timeoutPromise); //cancel previous timeout 693 waitTime = options.debounce; 694 } 695 696 timeoutPromise = $timeout(function() { 697 scope.showLoader = true; 698 699 return $q.when(values) 700 .then(function(values) { 701 if (!querySelectAs) { 702 var filteredList = $filter(options.listFilter)(toArr(values), query, getLabel); 703 var withoutOverlap = oiUtils.intersection(filteredList, scope.output, oiUtils.isEqual, trackBy, trackBy, true); 704 var filteredOutput = filter(withoutOverlap); 705 706 scope.groups = group(filteredOutput); 707 708 updateGroupPos(); 709 } 710 return values; 711 }) 712 .finally(function(){ 713 scope.showLoader = false;
714 }); 715 }, waitTime); 716 717 return timeoutPromise; 718 } 719 720 function toArr(list) { 721 var input = angular.isArray(list) ? list : oiUtils.objToArr(list); 722 723 return [].concat(input); 724 } 725 726 function updateGroupPos() { 727 var i, key, value, collectionKeys = [], groupCount = 0; 728 729 scope.order = []; 730 scope.groupPos = {}; 731 732 for (key in scope.groups) { 733 if (scope.groups.hasOwnProperty(key) && key.charAt(0) != '$') { 734 collectionKeys.push(key); 735 } 736 } 737 collectionKeys.sort(); 738 739 for (i = 0; i < collectionKeys.length; i++) { 740 key = collectionKeys[i]; 741 value = scope.groups[key]; 742 743 scope.order = scope.order.concat(value); 744 scope.groupPos[key] = groupCount; 745 groupCount += value.length 746 } 747 } 748 749 function resetMatches() { 750 scope.oldQuery = null; 751 scope.backspaceFocus = false; // clears focus on any chosen item for del 752 scope.query = ''; 753 scope.groups = {}; 754 scope.order = []; 755 scope.showLoader = false;
756 scope.isOpen = false; 757 758 if (timeoutPromise) { 759 $timeout.cancel(timeoutPromise);//cancel previous timeout 760 } 761 } 762 763 function setOption(listElement, position) { 764 scope.selectorPosition = position; 765 oiUtils.scrollActiveOption(listElement[0], listElement.find('li')[position]); 766 } 767 768 function group(input) { 769 var optionGroups = {'':[]}, 770 optionGroupName, 771 optionGroup; 772 773 for (var i = 0; i < input.length; i++) { 774 optionGroupName = getGroupName(input[i]); 775 776 if (!(optionGroup = optionGroups[optionGroupName])) { 777 optionGroup = optionGroups[optionGroupName] = []; 778 } 779 optionGroup.push(input[i]); 780 } 781 782 return optionGroups; 783 } 784 } 785 } 786 } 787}]); 788angular.module('oi.multiselect') 789 790.filter('oiMultiselectCloseIcon', ['$sce', function($sce) { 791 return function(label) { 792 var closeIcon = '<span class="close multiselect-search-list-item_selection-remove">Ã</span>'; 793 794 return $sce.trustAsHtml(label + closeIcon); 795 }; 796}]) 797 798.filter('oiMultiselectHighlight', ['$sce', function($sce) { 799 return function(label, query) { 800 801 var html; 802 if (query.length > 0 || angular.isNumber(query)) { 803 label = label.toString(); 804 query = query.toString(); 805 html = label.replace(new RegExp(query, 'gi'), '<strong>$&</strong>'); 806 } else { 807 html = label; 808 } 809 810 return $sce.trustAsHtml(html); 811 }; 812}]) 813 814.filter('oiMultiselectAscSort', function() { 815 function ascSort(input, query, getLabel) { 816 var i, output, output1 = [], output2 = [], output3 = []; 817 818 if (query) { 819 for (i = 0; i < input.length; i++) { 820 if (getLabel(input[i]).match(new RegExp(query, "i"))) { 821 output1.push(input[i]); 822 } 823 } 824 for (i = 0; i < output1.length; i++) { 825 if (getLabel(output1[i]).match(new RegExp('^' + query, "i"))) { 826 output2.push(output1[i]); 827 } else { 828 output3.push(output1[i]); 829 } 830 } 831 output = output2.concat(output3); 832 } else { 833 output = [].concat(input); 834 } 835 836 return output; 837 } 838 839 return ascSort; 840});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.