PageSourceSearch

https://rekrutacja.pedagogium.pl/pdg-irk-app/content/script/oi.multiselect/multiselect-tpls.js

js pedagogium.pl collected 2026-10-04 13:33:27 UTC 33,977 bytes, 840 lines download raw bytes

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.