xref: /OpenGrok/opengrok-web/src/main/webapp/js/searchable-option-list-2.0.15.js (revision 87e827912b828682c1fa2526fb09b09f073c339b)
1*87e82791SAdam Hornacek/*
2*87e82791SAdam Hornacek * SOL - Searchable Option List jQuery plugin
3*87e82791SAdam Hornacek * Version 2.0.2
4*87e82791SAdam Hornacek * https://pbauerochse.github.io/searchable-option-list/
5*87e82791SAdam Hornacek *
6*87e82791SAdam Hornacek * Copyright 2015, Patrick Bauerochse
7*87e82791SAdam Hornacek * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
8*87e82791SAdam Hornacek *
9*87e82791SAdam Hornacek * Licensed under the MIT license:
10*87e82791SAdam Hornacek * http://www.opensource.org/licenses/MIT
11*87e82791SAdam Hornacek *
12*87e82791SAdam Hornacek */
13*87e82791SAdam Hornacek
14*87e82791SAdam Hornacek/*
15*87e82791SAdam Hornacek * Original based on SOL v2.0.2
16*87e82791SAdam Hornacek * Modified by Krystof Tulinger for OpenGrok in 2016.
17*87e82791SAdam Hornacek */
18*87e82791SAdam Hornacek
19*87e82791SAdam Hornacek/*jslint nomen: true */
20*87e82791SAdam Hornacek;
21*87e82791SAdam Hornacek(function ($, window, document) {
22*87e82791SAdam Hornacek    'use strict';
23*87e82791SAdam Hornacek
24*87e82791SAdam Hornacek    // constructor
25*87e82791SAdam Hornacek    var SearchableOptionList = function ($element, options) {
26*87e82791SAdam Hornacek        this.$originalElement = $element;
27*87e82791SAdam Hornacek        this.options = options;
28*87e82791SAdam Hornacek
29*87e82791SAdam Hornacek        // allow setting options as data attribute
30*87e82791SAdam Hornacek        // e.g. <select data-sol-options="{'allowNullSelection':true}">
31*87e82791SAdam Hornacek        this.metadata = this.$originalElement.data('sol-options');
32*87e82791SAdam Hornacek    };
33*87e82791SAdam Hornacek
34*87e82791SAdam Hornacek    // plugin prototype
35*87e82791SAdam Hornacek    SearchableOptionList.prototype = {
36*87e82791SAdam Hornacek
37*87e82791SAdam Hornacek        SOL_OPTION_FORMAT: {
38*87e82791SAdam Hornacek            type:     'option',        // fixed
39*87e82791SAdam Hornacek            value:    undefined,       // value that will be submitted
40*87e82791SAdam Hornacek            selected: false,           // boolean selected state
41*87e82791SAdam Hornacek            disabled: false,           // boolean disabled state
42*87e82791SAdam Hornacek            label:    undefined,       // label string
43*87e82791SAdam Hornacek            tooltip:  undefined,       // tooltip string
44*87e82791SAdam Hornacek            cssClass: ''               // custom css class for container
45*87e82791SAdam Hornacek        },
46*87e82791SAdam Hornacek        SOL_OPTIONGROUP_FORMAT: {
47*87e82791SAdam Hornacek            type:     'optiongroup',    // fixed
48*87e82791SAdam Hornacek            label:    undefined,        // label string
49*87e82791SAdam Hornacek            tooltip:  undefined,        // tooltip string
50*87e82791SAdam Hornacek            disabled: false,            // all children disabled boolean property
51*87e82791SAdam Hornacek            children: undefined         // array of SOL_OPTION_FORMAT objects
52*87e82791SAdam Hornacek        },
53*87e82791SAdam Hornacek
54*87e82791SAdam Hornacek        DATA_KEY: 'sol-element',
55*87e82791SAdam Hornacek        WINDOW_EVENTS_KEY: 'sol-window-events',
56*87e82791SAdam Hornacek
57*87e82791SAdam Hornacek        // default option values
58*87e82791SAdam Hornacek        defaults: {
59*87e82791SAdam Hornacek            data: undefined,
60*87e82791SAdam Hornacek            name: undefined,           // name attribute, can also be set as name="" attribute on original element or data-sol-name=""
61*87e82791SAdam Hornacek
62*87e82791SAdam Hornacek            texts: {
63*87e82791SAdam Hornacek                noItemsAvailable: 'No entries found',
64*87e82791SAdam Hornacek                selectAll: 'Select all',
65*87e82791SAdam Hornacek                selectNone: 'Select none',
66*87e82791SAdam Hornacek                quickDelete: '&times;',
67*87e82791SAdam Hornacek                searchplaceholder: 'Click here to search',
68*87e82791SAdam Hornacek                loadingData: 'Still loading data...',
69*87e82791SAdam Hornacek                /*
70*87e82791SAdam Hornacek                 * Modified for OpenGrok in 2016.
71*87e82791SAdam Hornacek                 */
72*87e82791SAdam Hornacek                itemsSelected: '{$a} more items selected'
73*87e82791SAdam Hornacek            },
74*87e82791SAdam Hornacek
75*87e82791SAdam Hornacek            events: {
76*87e82791SAdam Hornacek                onInitialized: undefined,
77*87e82791SAdam Hornacek                onRendered: undefined,
78*87e82791SAdam Hornacek                onOpen: undefined,
79*87e82791SAdam Hornacek                onClose: undefined,
80*87e82791SAdam Hornacek                onChange: undefined,
81*87e82791SAdam Hornacek                onScroll: function () {
82*87e82791SAdam Hornacek
83*87e82791SAdam Hornacek                    var selectionContainerYPos = this.$input.offset().top - this.config.scrollTarget.scrollTop() + this.$input.outerHeight(false),
84*87e82791SAdam Hornacek                        selectionContainerHeight = this.$selectionContainer.outerHeight(false),
85*87e82791SAdam Hornacek                        selectionContainerBottom = selectionContainerYPos + selectionContainerHeight,
86*87e82791SAdam Hornacek                        displayContainerAboveInput = this.config.displayContainerAboveInput || document.documentElement.clientHeight - this.config.scrollTarget.scrollTop() < selectionContainerBottom,
87*87e82791SAdam Hornacek                        selectionContainerWidth = this.$innerContainer.outerWidth(false) - parseInt(this.$selectionContainer.css('border-left-width'), 10) - parseInt(this.$selectionContainer.css('border-right-width'), 10);
88*87e82791SAdam Hornacek
89*87e82791SAdam Hornacek                    if (displayContainerAboveInput) {
90*87e82791SAdam Hornacek                        // position the popup above the input
91*87e82791SAdam Hornacek                        selectionContainerYPos = this.$input.offset().top - selectionContainerHeight - this.config.scrollTarget.scrollTop() + parseInt(this.$selectionContainer.css('border-bottom-width'), 10);
92*87e82791SAdam Hornacek                        this.$container
93*87e82791SAdam Hornacek                            .removeClass('sol-selection-bottom')
94*87e82791SAdam Hornacek                            .addClass('sol-selection-top');
95*87e82791SAdam Hornacek                    } else {
96*87e82791SAdam Hornacek                        this.$container
97*87e82791SAdam Hornacek                            .removeClass('sol-selection-top')
98*87e82791SAdam Hornacek                            .addClass('sol-selection-bottom');
99*87e82791SAdam Hornacek                    }
100*87e82791SAdam Hornacek
101*87e82791SAdam Hornacek                    if (this.$innerContainer.css('display') !== 'block') {
102*87e82791SAdam Hornacek                        // container has a certain width
103*87e82791SAdam Hornacek                        // make selection container a bit wider
104*87e82791SAdam Hornacek                        selectionContainerWidth = selectionContainerWidth * 1.2;
105*87e82791SAdam Hornacek                    } else {
106*87e82791SAdam Hornacek
107*87e82791SAdam Hornacek                        var borderRadiusSelector = displayContainerAboveInput ? 'border-bottom-right-radius' : 'border-top-right-radius';
108*87e82791SAdam Hornacek
109*87e82791SAdam Hornacek                        // no border radius on top
110*87e82791SAdam Hornacek                        this.$selectionContainer
111*87e82791SAdam Hornacek                            .css(borderRadiusSelector, 'initial');
112*87e82791SAdam Hornacek
113*87e82791SAdam Hornacek                        if (this.$actionButtons) {
114*87e82791SAdam Hornacek                            this.$actionButtons
115*87e82791SAdam Hornacek                                .css(borderRadiusSelector, 'initial');
116*87e82791SAdam Hornacek                        }
117*87e82791SAdam Hornacek                    }
118*87e82791SAdam Hornacek
119*87e82791SAdam Hornacek                    this.$selectionContainer
120*87e82791SAdam Hornacek                        .css('top', Math.floor(selectionContainerYPos))
121*87e82791SAdam Hornacek                        .css('left', Math.floor(this.$container.offset().left))
122*87e82791SAdam Hornacek                        .css('width', selectionContainerWidth);
123*87e82791SAdam Hornacek
124*87e82791SAdam Hornacek                    // remember the position
125*87e82791SAdam Hornacek                    this.config.displayContainerAboveInput = displayContainerAboveInput;
126*87e82791SAdam Hornacek                }
127*87e82791SAdam Hornacek            },
128*87e82791SAdam Hornacek
129*87e82791SAdam Hornacek            selectAllMaxItemsThreshold: 30,
130*87e82791SAdam Hornacek            showSelectAll: function () {
131*87e82791SAdam Hornacek                return this.config.multiple && this.config.selectAllMaxItemsThreshold && this.items && this.items.length <= this.config.selectAllMaxItemsThreshold;
132*87e82791SAdam Hornacek            },
133*87e82791SAdam Hornacek
134*87e82791SAdam Hornacek            useBracketParameters: false,
135*87e82791SAdam Hornacek            multiple: undefined,
136*87e82791SAdam Hornacek            /*
137*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016.
138*87e82791SAdam Hornacek             */
139*87e82791SAdam Hornacek            resultsContainer: undefined,
140*87e82791SAdam Hornacek            closeOnClick: false,
141*87e82791SAdam Hornacek            showSelectionBelowList: false,
142*87e82791SAdam Hornacek            allowNullSelection: false,
143*87e82791SAdam Hornacek            scrollTarget: undefined,
144*87e82791SAdam Hornacek            maxHeight: undefined,
145*87e82791SAdam Hornacek            converter: undefined,
146*87e82791SAdam Hornacek            asyncBatchSize: 300,
147*87e82791SAdam Hornacek            searchTimeout: 300,
148*87e82791SAdam Hornacek            maxShow: 0
149*87e82791SAdam Hornacek        },
150*87e82791SAdam Hornacek
151*87e82791SAdam Hornacek        // initialize the plugin
152*87e82791SAdam Hornacek        init: function () {
153*87e82791SAdam Hornacek            this.numSelected = 0;
154*87e82791SAdam Hornacek            this.valMap = null;
155*87e82791SAdam Hornacek            this.config = $.extend(true, {}, this.defaults, this.options, this.metadata);
156*87e82791SAdam Hornacek
157*87e82791SAdam Hornacek            var originalName = this._getNameAttribute(),
158*87e82791SAdam Hornacek                sol = this;
159*87e82791SAdam Hornacek
160*87e82791SAdam Hornacek            if (!originalName) {
161*87e82791SAdam Hornacek                this._showErrorLabel('name attribute is required');
162*87e82791SAdam Hornacek                return;
163*87e82791SAdam Hornacek            }
164*87e82791SAdam Hornacek
165*87e82791SAdam Hornacek            // old IE does not support trim
166*87e82791SAdam Hornacek            if (typeof String.prototype.trim !== 'function') {
167*87e82791SAdam Hornacek                String.prototype.trim = function () {
168*87e82791SAdam Hornacek                    return this.replace(/^\s+|\s+$/g, '');
169*87e82791SAdam Hornacek                }
170*87e82791SAdam Hornacek            }
171*87e82791SAdam Hornacek
172*87e82791SAdam Hornacek            this.config.multiple = this.config.multiple || this.$originalElement.attr('multiple');
173*87e82791SAdam Hornacek
174*87e82791SAdam Hornacek            if (!this.config.scrollTarget) {
175*87e82791SAdam Hornacek                this.config.scrollTarget = $(window);
176*87e82791SAdam Hornacek            }
177*87e82791SAdam Hornacek
178*87e82791SAdam Hornacek            this._registerWindowEventsIfNecessary();
179*87e82791SAdam Hornacek            this._initializeUiElements();
180*87e82791SAdam Hornacek            this._initializeInputEvents();
181*87e82791SAdam Hornacek
182*87e82791SAdam Hornacek            setTimeout(function () {
183*87e82791SAdam Hornacek                sol._initializeData();
184*87e82791SAdam Hornacek
185*87e82791SAdam Hornacek                // take original form element out of form submission
186*87e82791SAdam Hornacek                // by removing the name attribute
187*87e82791SAdam Hornacek                sol.$originalElement
188*87e82791SAdam Hornacek                    .data(sol.DATA_KEY, sol)
189*87e82791SAdam Hornacek                    .removeAttr('name')
190*87e82791SAdam Hornacek                    .data('sol-name', originalName);
191*87e82791SAdam Hornacek            }, 0);
192*87e82791SAdam Hornacek
193*87e82791SAdam Hornacek            this.$originalElement.hide();
194*87e82791SAdam Hornacek            this.$container
195*87e82791SAdam Hornacek                .css('visibility', 'initial')
196*87e82791SAdam Hornacek                .show();
197*87e82791SAdam Hornacek
198*87e82791SAdam Hornacek            return this;
199*87e82791SAdam Hornacek        },
200*87e82791SAdam Hornacek
201*87e82791SAdam Hornacek        _getNameAttribute: function () {
202*87e82791SAdam Hornacek            return this.config.name || this.$originalElement.data('sol-name') || this.$originalElement.attr('name');
203*87e82791SAdam Hornacek        },
204*87e82791SAdam Hornacek
205*87e82791SAdam Hornacek        // shows an error label
206*87e82791SAdam Hornacek        _showErrorLabel: function (message) {
207*87e82791SAdam Hornacek            var $errorMessage = $('<div style="color: red; font-weight: bold;" />').html(message);
208*87e82791SAdam Hornacek            if (!this.$container) {
209*87e82791SAdam Hornacek                $errorMessage.insertAfter(this.$originalElement);
210*87e82791SAdam Hornacek            } else {
211*87e82791SAdam Hornacek                this.$container.append($errorMessage);
212*87e82791SAdam Hornacek            }
213*87e82791SAdam Hornacek        },
214*87e82791SAdam Hornacek
215*87e82791SAdam Hornacek        // register click handler to determine when to trigger the close event
216*87e82791SAdam Hornacek        _registerWindowEventsIfNecessary: function () {
217*87e82791SAdam Hornacek            if (!window[this.WINDOW_EVENTS_KEY]) {
218*87e82791SAdam Hornacek                $(document).click(function (event) {
219*87e82791SAdam Hornacek                    // if clicked inside a sol element close all others
220*87e82791SAdam Hornacek                    // else close all sol containers
221*87e82791SAdam Hornacek
222*87e82791SAdam Hornacek                    var $clickedElement = $(event.target),
223*87e82791SAdam Hornacek                        $closestSelectionContainer = $clickedElement.closest('.sol-selection-container'),
224*87e82791SAdam Hornacek                        $closestInnerContainer = $clickedElement.closest('.sol-inner-container'),
225*87e82791SAdam Hornacek                        $clickedWithinThisSolContainer;
226*87e82791SAdam Hornacek
227*87e82791SAdam Hornacek                    if ($closestInnerContainer.length) {
228*87e82791SAdam Hornacek                        $clickedWithinThisSolContainer = $closestInnerContainer.first().parent('.sol-container');
229*87e82791SAdam Hornacek                    } else if ($closestSelectionContainer.length) {
230*87e82791SAdam Hornacek                        $clickedWithinThisSolContainer = $closestSelectionContainer.first().parent('.sol-container');
231*87e82791SAdam Hornacek                    }
232*87e82791SAdam Hornacek
233*87e82791SAdam Hornacek                    $('.sol-active')
234*87e82791SAdam Hornacek                        .not($clickedWithinThisSolContainer)
235*87e82791SAdam Hornacek                        .each(function (index, item) {
236*87e82791SAdam Hornacek                            $(item)
237*87e82791SAdam Hornacek                                .data(SearchableOptionList.prototype.DATA_KEY)
238*87e82791SAdam Hornacek                                .close();
239*87e82791SAdam Hornacek                        });
240*87e82791SAdam Hornacek                });
241*87e82791SAdam Hornacek
242*87e82791SAdam Hornacek                // remember we already registered the global events
243*87e82791SAdam Hornacek                window[this.WINDOW_EVENTS_KEY] = true;
244*87e82791SAdam Hornacek            }
245*87e82791SAdam Hornacek        },
246*87e82791SAdam Hornacek
247*87e82791SAdam Hornacek        // add sol ui elements
248*87e82791SAdam Hornacek        _initializeUiElements: function () {
249*87e82791SAdam Hornacek            var self = this;
250*87e82791SAdam Hornacek
251*87e82791SAdam Hornacek            this.internalScrollWrapper = function () {
252*87e82791SAdam Hornacek                if ($.isFunction(self.config.events.onScroll)) {
253*87e82791SAdam Hornacek                    self.config.events.onScroll.call(self);
254*87e82791SAdam Hornacek                }
255*87e82791SAdam Hornacek            };
256*87e82791SAdam Hornacek
257*87e82791SAdam Hornacek            this.$input = $('<input type="text"/>')
258*87e82791SAdam Hornacek                .attr('placeholder', this.config.texts.searchplaceholder);
259*87e82791SAdam Hornacek
260*87e82791SAdam Hornacek            this.$noResultsItem = $('<div class="sol-no-results"/>').html(this.config.texts.noItemsAvailable).hide();
261*87e82791SAdam Hornacek            this.$loadingData = $('<div class="sol-loading-data"/>').html(this.config.texts.loadingData);
262*87e82791SAdam Hornacek            this.$xItemsSelected = $('<div class="sol-results-count"/>');
263*87e82791SAdam Hornacek
264*87e82791SAdam Hornacek            this.$caret = $('<div class="sol-caret-container"><b class="sol-caret"/></div>').click(function (e) {
265*87e82791SAdam Hornacek                self.toggle();
266*87e82791SAdam Hornacek                e.preventDefault();
267*87e82791SAdam Hornacek                return false;
268*87e82791SAdam Hornacek            });
269*87e82791SAdam Hornacek
270*87e82791SAdam Hornacek            var $inputContainer = $('<div class="sol-input-container"/>').append(this.$input);
271*87e82791SAdam Hornacek            this.$innerContainer = $('<div class="sol-inner-container"/>').append($inputContainer).append(this.$caret);
272*87e82791SAdam Hornacek            this.$selection = $('<div class="sol-selection"/>');
273*87e82791SAdam Hornacek            this.$selectionContainer = $('<div class="sol-selection-container"/>')
274*87e82791SAdam Hornacek                .append(this.$noResultsItem)
275*87e82791SAdam Hornacek                .append(this.$loadingData)
276*87e82791SAdam Hornacek                .append(this.$selection);
277*87e82791SAdam Hornacek
278*87e82791SAdam Hornacek            this.$container = $('<div class="sol-container"/>')
279*87e82791SAdam Hornacek                .hide()
280*87e82791SAdam Hornacek                /*
281*87e82791SAdam Hornacek                 * Modified for OpenGrok in 2016.
282*87e82791SAdam Hornacek                 */
283*87e82791SAdam Hornacek                .keydown(function (e) {
284*87e82791SAdam Hornacek                    if (e.keyCode == 13) {
285*87e82791SAdam Hornacek                        var concat = '';
286*87e82791SAdam Hornacek                        $("#sbox #qtbl input[type='text']").each(function () {
287*87e82791SAdam Hornacek                            concat += $.trim($(this).val());
288*87e82791SAdam Hornacek                        });
289*87e82791SAdam Hornacek                        if (e.keyCode == 13 && concat === '') {
290*87e82791SAdam Hornacek                            // follow the project user's typed (may not exist)
291*87e82791SAdam Hornacek                            if(self.$input.val() !== '') {
292*87e82791SAdam Hornacek                                window.location = document.xrefPath + '/' + self.$input.val();
293*87e82791SAdam Hornacek                                return false;
294*87e82791SAdam Hornacek                            }
295*87e82791SAdam Hornacek                            var $el = $(".keyboard-selection").first().find(".sol-checkbox")
296*87e82791SAdam Hornacek                            // follow the actual project
297*87e82791SAdam Hornacek                            if($el.length && $el.data('sol-item') &&
298*87e82791SAdam Hornacek                                    $el.data('sol-item').label) {
299*87e82791SAdam Hornacek                                window.location = document.xrefPath +
300*87e82791SAdam Hornacek                                                    '/' +
301*87e82791SAdam Hornacek                                                    $el.data('sol-item').label;
302*87e82791SAdam Hornacek                                return false;
303*87e82791SAdam Hornacek                            }
304*87e82791SAdam Hornacek                            // follow first selected project
305*87e82791SAdam Hornacek                            $el = $(".sol-selected-display-item").first()
306*87e82791SAdam Hornacek                            if($el.length && $el.data('label')) {
307*87e82791SAdam Hornacek                               window.location = document.xrefPath + '/' + $el.data('label');
308*87e82791SAdam Hornacek                                return false;
309*87e82791SAdam Hornacek                            }
310*87e82791SAdam Hornacek                            return false;
311*87e82791SAdam Hornacek                        }
312*87e82791SAdam Hornacek                        return true;
313*87e82791SAdam Hornacek                    }
314*87e82791SAdam Hornacek                })
315*87e82791SAdam Hornacek                .data(this.DATA_KEY, this)
316*87e82791SAdam Hornacek                .append(this.$selectionContainer)
317*87e82791SAdam Hornacek                .append(this.$innerContainer)
318*87e82791SAdam Hornacek                .insertBefore(this.$originalElement);
319*87e82791SAdam Hornacek
320*87e82791SAdam Hornacek            // add selected items display container
321*87e82791SAdam Hornacek            this.$showSelectionContainer = $('<div class="sol-current-selection"/>');
322*87e82791SAdam Hornacek
323*87e82791SAdam Hornacek            /*
324*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016.
325*87e82791SAdam Hornacek             */
326*87e82791SAdam Hornacek            var $el = this.config.resultsContainer || this.$innerContainer
327*87e82791SAdam Hornacek            if (this.config.resultsContainer) {
328*87e82791SAdam Hornacek                this.$showSelectionContainer.appendTo($el);
329*87e82791SAdam Hornacek            } else {
330*87e82791SAdam Hornacek                if (this.config.showSelectionBelowList) {
331*87e82791SAdam Hornacek                    this.$showSelectionContainer.insertAfter($el);
332*87e82791SAdam Hornacek                } else {
333*87e82791SAdam Hornacek                    this.$showSelectionContainer.insertBefore($el);
334*87e82791SAdam Hornacek                }
335*87e82791SAdam Hornacek            }
336*87e82791SAdam Hornacek
337*87e82791SAdam Hornacek            // dimensions
338*87e82791SAdam Hornacek            if (this.config.maxHeight) {
339*87e82791SAdam Hornacek                this.$selection.css('max-height', this.config.maxHeight);
340*87e82791SAdam Hornacek            }
341*87e82791SAdam Hornacek
342*87e82791SAdam Hornacek            // detect inline css classes and styles
343*87e82791SAdam Hornacek            var cssClassesAsString = this.$originalElement.attr('class'),
344*87e82791SAdam Hornacek                cssStylesAsString = this.$originalElement.attr('style'),
345*87e82791SAdam Hornacek                cssClassList = [],
346*87e82791SAdam Hornacek                stylesList = [];
347*87e82791SAdam Hornacek
348*87e82791SAdam Hornacek            if (cssClassesAsString && cssClassesAsString.length > 0) {
349*87e82791SAdam Hornacek                cssClassList = cssClassesAsString.split(/\s+/);
350*87e82791SAdam Hornacek
351*87e82791SAdam Hornacek                // apply css classes to $container
352*87e82791SAdam Hornacek                for (var i = 0; i < cssClassList.length; i++) {
353*87e82791SAdam Hornacek                    this.$container.addClass(cssClassList[i]);
354*87e82791SAdam Hornacek                }
355*87e82791SAdam Hornacek            }
356*87e82791SAdam Hornacek
357*87e82791SAdam Hornacek            if (cssStylesAsString && cssStylesAsString.length > 0) {
358*87e82791SAdam Hornacek                stylesList = cssStylesAsString.split(/\;/);
359*87e82791SAdam Hornacek
360*87e82791SAdam Hornacek                // apply css inline styles to $container
361*87e82791SAdam Hornacek                for (var i = 0; i < stylesList.length; i++) {
362*87e82791SAdam Hornacek                    var splitted = stylesList[i].split(/\s*\:\s*/g);
363*87e82791SAdam Hornacek
364*87e82791SAdam Hornacek                    if (splitted.length === 2) {
365*87e82791SAdam Hornacek
366*87e82791SAdam Hornacek                        if (splitted[0].toLowerCase().indexOf('height') >= 0) {
367*87e82791SAdam Hornacek                            // height property, apply to innerContainer instead of outer
368*87e82791SAdam Hornacek                            this.$innerContainer.css(splitted[0].trim(), splitted[1].trim());
369*87e82791SAdam Hornacek                        } else {
370*87e82791SAdam Hornacek                            this.$container.css(splitted[0].trim(), splitted[1].trim());
371*87e82791SAdam Hornacek                        }
372*87e82791SAdam Hornacek                    }
373*87e82791SAdam Hornacek                }
374*87e82791SAdam Hornacek            }
375*87e82791SAdam Hornacek
376*87e82791SAdam Hornacek            if (this.$originalElement.css('display') !== 'block') {
377*87e82791SAdam Hornacek                this.$container.css('width', this._getActualCssPropertyValue(this.$originalElement, 'width'));
378*87e82791SAdam Hornacek            }
379*87e82791SAdam Hornacek
380*87e82791SAdam Hornacek            if ($.isFunction(this.config.events.onRendered)) {
381*87e82791SAdam Hornacek                this.config.events.onRendered.call(this, this);
382*87e82791SAdam Hornacek            }
383*87e82791SAdam Hornacek        },
384*87e82791SAdam Hornacek
385*87e82791SAdam Hornacek        _getActualCssPropertyValue: function ($element, property) {
386*87e82791SAdam Hornacek
387*87e82791SAdam Hornacek            var domElement = $element.get(0),
388*87e82791SAdam Hornacek                originalDisplayProperty = $element.css('display');
389*87e82791SAdam Hornacek
390*87e82791SAdam Hornacek            // set invisible to get original width setting instead of translated to px
391*87e82791SAdam Hornacek            // see https://bugzilla.mozilla.org/show_bug.cgi?id=707691#c7
392*87e82791SAdam Hornacek            $element.css('display', 'none');
393*87e82791SAdam Hornacek
394*87e82791SAdam Hornacek            if (domElement.currentStyle) {
395*87e82791SAdam Hornacek                return domElement.currentStyle[property];
396*87e82791SAdam Hornacek            } else if (window.getComputedStyle) {
397*87e82791SAdam Hornacek                return document.defaultView.getComputedStyle(domElement, null).getPropertyValue(property);
398*87e82791SAdam Hornacek            }
399*87e82791SAdam Hornacek
400*87e82791SAdam Hornacek            $element.css('display', originalDisplayProperty);
401*87e82791SAdam Hornacek
402*87e82791SAdam Hornacek            return $element.css(property);
403*87e82791SAdam Hornacek        },
404*87e82791SAdam Hornacek
405*87e82791SAdam Hornacek        _initializeInputEvents: function () {
406*87e82791SAdam Hornacek            // form event
407*87e82791SAdam Hornacek            var self = this,
408*87e82791SAdam Hornacek                $form = this.$input.parents('form').first();
409*87e82791SAdam Hornacek
410*87e82791SAdam Hornacek            if ($form && $form.length === 1 && !$form.data(this.WINDOW_EVENTS_KEY)) {
411*87e82791SAdam Hornacek                var resetFunction = function () {
412*87e82791SAdam Hornacek                    var $changedItems = [];
413*87e82791SAdam Hornacek
414*87e82791SAdam Hornacek                    $form.find('.sol-option input').each(function (index, item) {
415*87e82791SAdam Hornacek                        var $item = $(item),
416*87e82791SAdam Hornacek                            initialState = $item.data('sol-item').selected;
417*87e82791SAdam Hornacek
418*87e82791SAdam Hornacek                        if ($item.prop('checked') !== initialState) {
419*87e82791SAdam Hornacek                            $item
420*87e82791SAdam Hornacek                                .prop('checked', initialState)
421*87e82791SAdam Hornacek                                .trigger('sol-change', true);
422*87e82791SAdam Hornacek                            $changedItems.push($item);
423*87e82791SAdam Hornacek                        }
424*87e82791SAdam Hornacek                    });
425*87e82791SAdam Hornacek
426*87e82791SAdam Hornacek                    if ($changedItems.length > 0 && $.isFunction(self.config.events.onChange)) {
427*87e82791SAdam Hornacek                        self.config.events.onChange.call(self, self, $changedItems);
428*87e82791SAdam Hornacek                    }
429*87e82791SAdam Hornacek                };
430*87e82791SAdam Hornacek
431*87e82791SAdam Hornacek                $form.on('reset', function (event) {
432*87e82791SAdam Hornacek                    // unfortunately the reset event gets fired _before_
433*87e82791SAdam Hornacek                    // the inputs are actually reset. The only possibility
434*87e82791SAdam Hornacek                    // to overcome this is to set an interval to execute
435*87e82791SAdam Hornacek                    // own scripts some time after the actual reset event
436*87e82791SAdam Hornacek
437*87e82791SAdam Hornacek                    // before fields are actually reset by the browser
438*87e82791SAdam Hornacek                    // needed to reset newly checked fields
439*87e82791SAdam Hornacek                    resetFunction.call(self);
440*87e82791SAdam Hornacek
441*87e82791SAdam Hornacek                    // timeout for selection after form reset
442*87e82791SAdam Hornacek                    // needed to reset previously checked fields
443*87e82791SAdam Hornacek                    setTimeout(function () {
444*87e82791SAdam Hornacek                        resetFunction.call(self);
445*87e82791SAdam Hornacek                    }, 100);
446*87e82791SAdam Hornacek                });
447*87e82791SAdam Hornacek
448*87e82791SAdam Hornacek                $form.data(this.WINDOW_EVENTS_KEY, true);
449*87e82791SAdam Hornacek            }
450*87e82791SAdam Hornacek
451*87e82791SAdam Hornacek            // text input events
452*87e82791SAdam Hornacek            this.$input
453*87e82791SAdam Hornacek                .focus(function () {
454*87e82791SAdam Hornacek                    self.open();
455*87e82791SAdam Hornacek                })
456*87e82791SAdam Hornacek                .on('propertychange input', function (e) {
457*87e82791SAdam Hornacek                    var valueChanged = true;
458*87e82791SAdam Hornacek                    if (e.type=='propertychange') {
459*87e82791SAdam Hornacek                        valueChanged = e.originalEvent.propertyName.toLowerCase()=='value';
460*87e82791SAdam Hornacek                    }
461*87e82791SAdam Hornacek                    if (valueChanged) {
462*87e82791SAdam Hornacek                        if ($(this).data('timeout')) {
463*87e82791SAdam Hornacek                            clearTimeout($(this).data('timeout'));
464*87e82791SAdam Hornacek                        }
465*87e82791SAdam Hornacek                        $(this).data('timeout', setTimeout(function () {
466*87e82791SAdam Hornacek                            self._applySearchTermFilter();
467*87e82791SAdam Hornacek                        }, self.config.searchTimeout))
468*87e82791SAdam Hornacek
469*87e82791SAdam Hornacek                    }
470*87e82791SAdam Hornacek                });
471*87e82791SAdam Hornacek
472*87e82791SAdam Hornacek            // keyboard navigation
473*87e82791SAdam Hornacek            this.$container
474*87e82791SAdam Hornacek                .on('keydown', function (e) {
475*87e82791SAdam Hornacek                    var keyCode = e.keyCode;
476*87e82791SAdam Hornacek
477*87e82791SAdam Hornacek                    // event handling for keyboard navigation
478*87e82791SAdam Hornacek                    // only when there are results to be shown
479*87e82791SAdam Hornacek                    if (!self.$noResultsItem.is(':visible')) {
480*87e82791SAdam Hornacek
481*87e82791SAdam Hornacek                        var $currentHighlightedOption,
482*87e82791SAdam Hornacek                            $nextHighlightedOption,
483*87e82791SAdam Hornacek                            directionValue,
484*87e82791SAdam Hornacek                            preventDefault = false,
485*87e82791SAdam Hornacek                            $allVisibleOptions = self.$selection.find('.sol-option:visible');
486*87e82791SAdam Hornacek
487*87e82791SAdam Hornacek                        if (keyCode === 40 || keyCode === 38) {
488*87e82791SAdam Hornacek                            // arrow up or down to select an item
489*87e82791SAdam Hornacek                            self._setKeyBoardNavigationMode(true);
490*87e82791SAdam Hornacek                            /*
491*87e82791SAdam Hornacek                             * Modified for OpenGrok in 2016.
492*87e82791SAdam Hornacek                             */
493*87e82791SAdam Hornacek                            $currentHighlightedOption = self.$selection.find('.sol-option.keyboard-selection')
494*87e82791SAdam Hornacek                            $currentHighlightedOption.find("input[type='checkbox']").blur();
495*87e82791SAdam Hornacek                            directionValue = (keyCode === 38) ? -1 : 1;   // negative for up, positive for down
496*87e82791SAdam Hornacek
497*87e82791SAdam Hornacek                            var indexOfNextHighlightedOption = $allVisibleOptions.index($currentHighlightedOption) + directionValue;
498*87e82791SAdam Hornacek                            if (indexOfNextHighlightedOption < 0) {
499*87e82791SAdam Hornacek                                indexOfNextHighlightedOption = $allVisibleOptions.length - 1;
500*87e82791SAdam Hornacek                            } else if (indexOfNextHighlightedOption >= $allVisibleOptions.length) {
501*87e82791SAdam Hornacek                                indexOfNextHighlightedOption = 0;
502*87e82791SAdam Hornacek                            }
503*87e82791SAdam Hornacek
504*87e82791SAdam Hornacek                            $currentHighlightedOption.removeClass('keyboard-selection');
505*87e82791SAdam Hornacek                            $nextHighlightedOption = $($allVisibleOptions[indexOfNextHighlightedOption])
506*87e82791SAdam Hornacek                                .addClass('keyboard-selection');
507*87e82791SAdam Hornacek                            /*
508*87e82791SAdam Hornacek                             * Modified for OpenGrok in 2016.
509*87e82791SAdam Hornacek                             */
510*87e82791SAdam Hornacek                            $nextHighlightedOption.find("input[type='checkbox']").focus()
511*87e82791SAdam Hornacek
512*87e82791SAdam Hornacek                            /*
513*87e82791SAdam Hornacek                             * Modified for OpenGrok in 2016.
514*87e82791SAdam Hornacek                             */
515*87e82791SAdam Hornacek                            //self.$selection.scrollTop(self.$selection.scrollTop() + $nextHighlightedOption.position().top);
516*87e82791SAdam Hornacek
517*87e82791SAdam Hornacek                            preventDefault = true;
518*87e82791SAdam Hornacek                        } else if (self.keyboardNavigationMode === true && keyCode === 32) {
519*87e82791SAdam Hornacek                            // toggle current selected item with space bar
520*87e82791SAdam Hornacek                            $currentHighlightedOption = self.$selection.find('.sol-option.keyboard-selection input');
521*87e82791SAdam Hornacek                            $currentHighlightedOption
522*87e82791SAdam Hornacek                                .prop('checked', !$currentHighlightedOption.is(':checked'))
523*87e82791SAdam Hornacek                                .trigger('change');
524*87e82791SAdam Hornacek
525*87e82791SAdam Hornacek                            preventDefault = true;
526*87e82791SAdam Hornacek                        }
527*87e82791SAdam Hornacek
528*87e82791SAdam Hornacek                        if (preventDefault) {
529*87e82791SAdam Hornacek                            // dont trigger any events in the input
530*87e82791SAdam Hornacek                            e.preventDefault();
531*87e82791SAdam Hornacek                            return false;
532*87e82791SAdam Hornacek                        }
533*87e82791SAdam Hornacek                    }
534*87e82791SAdam Hornacek                })
535*87e82791SAdam Hornacek                .on('keyup', function (e) {
536*87e82791SAdam Hornacek                    var keyCode = e.keyCode;
537*87e82791SAdam Hornacek
538*87e82791SAdam Hornacek                    if (keyCode === 27) {
539*87e82791SAdam Hornacek                        // escape key
540*87e82791SAdam Hornacek                        if (self.keyboardNavigationMode === true) {
541*87e82791SAdam Hornacek                            self._setKeyBoardNavigationMode(false);
542*87e82791SAdam Hornacek                        } else if (self.$input.val() === '') {
543*87e82791SAdam Hornacek                            // trigger closing of container
544*87e82791SAdam Hornacek                            self.$caret.trigger('click');
545*87e82791SAdam Hornacek                            self.$input.trigger('blur');
546*87e82791SAdam Hornacek                        } else {
547*87e82791SAdam Hornacek                            // reset input and result filter
548*87e82791SAdam Hornacek                            self.$input.val('').trigger('input');
549*87e82791SAdam Hornacek                        }
550*87e82791SAdam Hornacek                    } else if (keyCode === 16 || keyCode === 17 || keyCode === 18 || keyCode === 20) {
551*87e82791SAdam Hornacek                        // special events like shift and control
552*87e82791SAdam Hornacek                        return;
553*87e82791SAdam Hornacek                    }
554*87e82791SAdam Hornacek                });
555*87e82791SAdam Hornacek        },
556*87e82791SAdam Hornacek
557*87e82791SAdam Hornacek        _setKeyBoardNavigationMode: function (keyboardNavigationOn) {
558*87e82791SAdam Hornacek
559*87e82791SAdam Hornacek            if (keyboardNavigationOn) {
560*87e82791SAdam Hornacek                // on
561*87e82791SAdam Hornacek                this.keyboardNavigationMode = true;
562*87e82791SAdam Hornacek                this.$selection.addClass('sol-keyboard-navigation');
563*87e82791SAdam Hornacek            } else {
564*87e82791SAdam Hornacek                // off
565*87e82791SAdam Hornacek                this.keyboardNavigationMode = false;
566*87e82791SAdam Hornacek                this.$selection.find('.sol-option.keyboard-selection')
567*87e82791SAdam Hornacek                this.$selection.removeClass('sol-keyboard-navigation');
568*87e82791SAdam Hornacek                this.$selectionContainer.find('.sol-option.keyboard-selection').removeClass('keyboard-selection');
569*87e82791SAdam Hornacek                this.$selection.scrollTop(0);
570*87e82791SAdam Hornacek            }
571*87e82791SAdam Hornacek        },
572*87e82791SAdam Hornacek
573*87e82791SAdam Hornacek        _applySearchTermFilter: function () {
574*87e82791SAdam Hornacek            if (!this.items || this.items.length === 0) {
575*87e82791SAdam Hornacek                return;
576*87e82791SAdam Hornacek            }
577*87e82791SAdam Hornacek
578*87e82791SAdam Hornacek            var searchTerm = this.$input.val(),
579*87e82791SAdam Hornacek                lowerCased = (searchTerm || '').toLowerCase();
580*87e82791SAdam Hornacek
581*87e82791SAdam Hornacek            // show previously filtered elements again
582*87e82791SAdam Hornacek            this.$selectionContainer.find('.sol-filtered-search').removeClass('sol-filtered-search');
583*87e82791SAdam Hornacek            this._setNoResultsItemVisible(false);
584*87e82791SAdam Hornacek
585*87e82791SAdam Hornacek            if (lowerCased.trim().length > 0) {
586*87e82791SAdam Hornacek                this._findTerms(this.items, lowerCased);
587*87e82791SAdam Hornacek            }
588*87e82791SAdam Hornacek
589*87e82791SAdam Hornacek            // call onScroll to position the popup again
590*87e82791SAdam Hornacek            // important if showing popup above list
591*87e82791SAdam Hornacek            if ($.isFunction(this.config.events.onScroll)) {
592*87e82791SAdam Hornacek                this.config.events.onScroll.call(this);
593*87e82791SAdam Hornacek            }
594*87e82791SAdam Hornacek        },
595*87e82791SAdam Hornacek
596*87e82791SAdam Hornacek        _findTerms: function (dataArray, searchTerm) {
597*87e82791SAdam Hornacek            if (!dataArray || !$.isArray(dataArray) || dataArray.length === 0) {
598*87e82791SAdam Hornacek                return;
599*87e82791SAdam Hornacek            }
600*87e82791SAdam Hornacek
601*87e82791SAdam Hornacek            var self = this,
602*87e82791SAdam Hornacek                    amountOfUnfilteredItems = dataArray.length
603*87e82791SAdam Hornacek
604*87e82791SAdam Hornacek            // reset keyboard navigation mode when applying new filter
605*87e82791SAdam Hornacek            this._setKeyBoardNavigationMode(false);
606*87e82791SAdam Hornacek
607*87e82791SAdam Hornacek            /*
608*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016.
609*87e82791SAdam Hornacek             * recursion was very slow (however good lookin')
610*87e82791SAdam Hornacek             */
611*87e82791SAdam Hornacek            for (var itemIndex = 0; itemIndex < dataArray.length; itemIndex++) {
612*87e82791SAdam Hornacek                var item = dataArray[itemIndex];
613*87e82791SAdam Hornacek                if (item.type === 'option') {
614*87e82791SAdam Hornacek                    var $element = item.displayElement,
615*87e82791SAdam Hornacek                            elementSearchableTerms = (item.label + ' ' + item.tooltip).trim().toLowerCase();
616*87e82791SAdam Hornacek
617*87e82791SAdam Hornacek                    if (elementSearchableTerms.indexOf(searchTerm) === -1) {
618*87e82791SAdam Hornacek                        $element.addClass('sol-filtered-search');
619*87e82791SAdam Hornacek                        amountOfUnfilteredItems--;
620*87e82791SAdam Hornacek                    }
621*87e82791SAdam Hornacek                } else {
622*87e82791SAdam Hornacek                    var amountOfUnfilteredChildren = item.children.length
623*87e82791SAdam Hornacek                    for (var childrenIndex = 0; childrenIndex < item.children.length; childrenIndex++) {
624*87e82791SAdam Hornacek                        var child = item.children[childrenIndex];
625*87e82791SAdam Hornacek                        if (child.type === 'option') {
626*87e82791SAdam Hornacek                            var $element = child.displayElement,
627*87e82791SAdam Hornacek                                    elementSearchableTerms = (child.label + ' ' + child.tooltip).trim().toLowerCase();
628*87e82791SAdam Hornacek
629*87e82791SAdam Hornacek                            if (elementSearchableTerms.indexOf(searchTerm) === -1) {
630*87e82791SAdam Hornacek                                $element.addClass('sol-filtered-search');
631*87e82791SAdam Hornacek                                amountOfUnfilteredChildren--;
632*87e82791SAdam Hornacek                            }
633*87e82791SAdam Hornacek                        }
634*87e82791SAdam Hornacek                    }
635*87e82791SAdam Hornacek
636*87e82791SAdam Hornacek                    if (amountOfUnfilteredChildren === 0) {
637*87e82791SAdam Hornacek                        item.displayElement.addClass('sol-filtered-search');
638*87e82791SAdam Hornacek                        amountOfUnfilteredItems--;
639*87e82791SAdam Hornacek                    }
640*87e82791SAdam Hornacek                }
641*87e82791SAdam Hornacek            }
642*87e82791SAdam Hornacek
643*87e82791SAdam Hornacek            this._setNoResultsItemVisible(amountOfUnfilteredItems === 0);
644*87e82791SAdam Hornacek        },
645*87e82791SAdam Hornacek
646*87e82791SAdam Hornacek        _initializeData: function () {
647*87e82791SAdam Hornacek            if (!this.config.data) {
648*87e82791SAdam Hornacek                this.items = this._detectDataFromOriginalElement();
649*87e82791SAdam Hornacek            } else if ($.isFunction(this.config.data)) {
650*87e82791SAdam Hornacek                this.items = this._fetchDataFromFunction(this.config.data);
651*87e82791SAdam Hornacek            } else if ($.isArray(this.config.data)) {
652*87e82791SAdam Hornacek                this.items = this._fetchDataFromArray(this.config.data);
653*87e82791SAdam Hornacek            } else if (typeof this.config.data === (typeof 'a string')) {
654*87e82791SAdam Hornacek                this._loadItemsFromUrl(this.config.data);
655*87e82791SAdam Hornacek            } else {
656*87e82791SAdam Hornacek                this._showErrorLabel('Unknown data type');
657*87e82791SAdam Hornacek            }
658*87e82791SAdam Hornacek
659*87e82791SAdam Hornacek            if (this.items) {
660*87e82791SAdam Hornacek                // done right away -> invoke postprocessing
661*87e82791SAdam Hornacek                this._processDataItems(this.items);
662*87e82791SAdam Hornacek            }
663*87e82791SAdam Hornacek        },
664*87e82791SAdam Hornacek
665*87e82791SAdam Hornacek        _detectDataFromOriginalElement: function () {
666*87e82791SAdam Hornacek            if (this.$originalElement.prop('tagName').toLowerCase() === 'select') {
667*87e82791SAdam Hornacek                var self = this,
668*87e82791SAdam Hornacek                    solData = [];
669*87e82791SAdam Hornacek
670*87e82791SAdam Hornacek                $.each(this.$originalElement.children(), function (index, item) {
671*87e82791SAdam Hornacek                    var $item = $(item),
672*87e82791SAdam Hornacek                        itemTagName = $item.prop('tagName').toLowerCase(),
673*87e82791SAdam Hornacek                        solDataItem;
674*87e82791SAdam Hornacek
675*87e82791SAdam Hornacek                    if (itemTagName === 'option') {
676*87e82791SAdam Hornacek                        solDataItem = self._processSelectOption($item);
677*87e82791SAdam Hornacek                        if (solDataItem) {
678*87e82791SAdam Hornacek                            solData.push(solDataItem);
679*87e82791SAdam Hornacek                        }
680*87e82791SAdam Hornacek                    } else if (itemTagName === 'optgroup') {
681*87e82791SAdam Hornacek                        solDataItem = self._processSelectOptgroup($item);
682*87e82791SAdam Hornacek                        if (solDataItem) {
683*87e82791SAdam Hornacek                            solData.push(solDataItem);
684*87e82791SAdam Hornacek                        }
685*87e82791SAdam Hornacek                    } else {
686*87e82791SAdam Hornacek                        self._showErrorLabel('Invalid element found in select: ' + itemTagName + '. Only option and optgroup are allowed');
687*87e82791SAdam Hornacek                    }
688*87e82791SAdam Hornacek                });
689*87e82791SAdam Hornacek                return this._invokeConverterIfNecessary(solData);
690*87e82791SAdam Hornacek            } else if (this.$originalElement.data('sol-data')) {
691*87e82791SAdam Hornacek                var solDataAttributeValue = this.$originalElement.data('sol-data');
692*87e82791SAdam Hornacek                return this._invokeConverterIfNecessary(solDataAttributeValue);
693*87e82791SAdam Hornacek            } else {
694*87e82791SAdam Hornacek                this._showErrorLabel('Could not determine data from original element. Must be a select or data must be provided as data-sol-data="" attribute');
695*87e82791SAdam Hornacek            }
696*87e82791SAdam Hornacek        },
697*87e82791SAdam Hornacek
698*87e82791SAdam Hornacek        _processSelectOption: function ($option) {
699*87e82791SAdam Hornacek            return $.extend({}, this.SOL_OPTION_FORMAT, {
700*87e82791SAdam Hornacek                value: $option.val(),
701*87e82791SAdam Hornacek                selected: $option.prop('selected'),
702*87e82791SAdam Hornacek                disabled: $option.prop('disabled'),
703*87e82791SAdam Hornacek                cssClass: $option.attr('class'),
704*87e82791SAdam Hornacek                label: $option.html(),
705*87e82791SAdam Hornacek                tooltip: $option.attr('title'),
706*87e82791SAdam Hornacek                element: $option
707*87e82791SAdam Hornacek            });
708*87e82791SAdam Hornacek        },
709*87e82791SAdam Hornacek
710*87e82791SAdam Hornacek        _processSelectOptgroup: function ($optgroup) {
711*87e82791SAdam Hornacek            var self = this,
712*87e82791SAdam Hornacek                solOptiongroup = $.extend({}, this.SOL_OPTIONGROUP_FORMAT, {
713*87e82791SAdam Hornacek                    label: $optgroup.attr('label'),
714*87e82791SAdam Hornacek                    tooltip: $optgroup.attr('title'),
715*87e82791SAdam Hornacek                    disabled: $optgroup.prop('disabled'),
716*87e82791SAdam Hornacek                    children: []
717*87e82791SAdam Hornacek                }),
718*87e82791SAdam Hornacek                optgroupChildren = $optgroup.children('option');
719*87e82791SAdam Hornacek
720*87e82791SAdam Hornacek            $.each(optgroupChildren, function (index, item) {
721*87e82791SAdam Hornacek                var $child = $(item),
722*87e82791SAdam Hornacek                    solOption = self._processSelectOption($child);
723*87e82791SAdam Hornacek
724*87e82791SAdam Hornacek                // explicitly disable children when optgroup is disabled
725*87e82791SAdam Hornacek                if (solOptiongroup.disabled) {
726*87e82791SAdam Hornacek                    solOption.disabled = true;
727*87e82791SAdam Hornacek                }
728*87e82791SAdam Hornacek
729*87e82791SAdam Hornacek                solOptiongroup.children.push(solOption);
730*87e82791SAdam Hornacek            });
731*87e82791SAdam Hornacek
732*87e82791SAdam Hornacek            return solOptiongroup;
733*87e82791SAdam Hornacek        },
734*87e82791SAdam Hornacek
735*87e82791SAdam Hornacek        _fetchDataFromFunction: function (dataFunction) {
736*87e82791SAdam Hornacek            return this._invokeConverterIfNecessary(dataFunction(this));
737*87e82791SAdam Hornacek        },
738*87e82791SAdam Hornacek
739*87e82791SAdam Hornacek        _fetchDataFromArray: function (dataArray) {
740*87e82791SAdam Hornacek            return this._invokeConverterIfNecessary(dataArray);
741*87e82791SAdam Hornacek        },
742*87e82791SAdam Hornacek
743*87e82791SAdam Hornacek        _loadItemsFromUrl: function (url) {
744*87e82791SAdam Hornacek            var self = this;
745*87e82791SAdam Hornacek            $.ajax(url, {
746*87e82791SAdam Hornacek                success: function (actualData) {
747*87e82791SAdam Hornacek                    self.items = self._invokeConverterIfNecessary(actualData);
748*87e82791SAdam Hornacek                    if (self.items) {
749*87e82791SAdam Hornacek                        self._processDataItems(self.items);
750*87e82791SAdam Hornacek                    }
751*87e82791SAdam Hornacek                },
752*87e82791SAdam Hornacek                error: function (xhr, status, message) {
753*87e82791SAdam Hornacek                    self._showErrorLabel('Error loading from url ' + url + ': ' + message);
754*87e82791SAdam Hornacek                },
755*87e82791SAdam Hornacek                dataType: 'json'
756*87e82791SAdam Hornacek            });
757*87e82791SAdam Hornacek        },
758*87e82791SAdam Hornacek
759*87e82791SAdam Hornacek        _invokeConverterIfNecessary: function (dataItems) {
760*87e82791SAdam Hornacek            if ($.isFunction(this.config.converter)) {
761*87e82791SAdam Hornacek                return this.config.converter.call(this, this, dataItems);
762*87e82791SAdam Hornacek            }
763*87e82791SAdam Hornacek            return dataItems;
764*87e82791SAdam Hornacek        },
765*87e82791SAdam Hornacek
766*87e82791SAdam Hornacek        _processDataItems: function (solItems) {
767*87e82791SAdam Hornacek            if (!solItems) {
768*87e82791SAdam Hornacek                this._showErrorLabel('Data items not present. Maybe the converter did not return any values');
769*87e82791SAdam Hornacek                return;
770*87e82791SAdam Hornacek            }
771*87e82791SAdam Hornacek
772*87e82791SAdam Hornacek            if (solItems.length === 0) {
773*87e82791SAdam Hornacek                this._setNoResultsItemVisible(true);
774*87e82791SAdam Hornacek                this.$loadingData.remove();
775*87e82791SAdam Hornacek                return;
776*87e82791SAdam Hornacek            }
777*87e82791SAdam Hornacek
778*87e82791SAdam Hornacek            var self = this,
779*87e82791SAdam Hornacek                nextIndex = 0,
780*87e82791SAdam Hornacek                dataProcessedFunction = function () {
781*87e82791SAdam Hornacek                    // hide "loading data"
782*87e82791SAdam Hornacek                    this.$loadingData.remove();
783*87e82791SAdam Hornacek                    this._initializeSelectAll();
784*87e82791SAdam Hornacek
785*87e82791SAdam Hornacek                    if ($.isFunction(this.config.events.onInitialized)) {
786*87e82791SAdam Hornacek                        this.config.events.onInitialized.call(this, this, solItems);
787*87e82791SAdam Hornacek                    }
788*87e82791SAdam Hornacek                },
789*87e82791SAdam Hornacek                loopFunction = function () {
790*87e82791SAdam Hornacek
791*87e82791SAdam Hornacek                    var currentBatch = 0,
792*87e82791SAdam Hornacek                        item;
793*87e82791SAdam Hornacek
794*87e82791SAdam Hornacek                    while (currentBatch++ < self.config.asyncBatchSize && nextIndex < solItems.length) {
795*87e82791SAdam Hornacek                        item = solItems[nextIndex++];
796*87e82791SAdam Hornacek                        if (item.type === self.SOL_OPTION_FORMAT.type) {
797*87e82791SAdam Hornacek                            self._renderOption(item);
798*87e82791SAdam Hornacek                        } else if (item.type === self.SOL_OPTIONGROUP_FORMAT.type) {
799*87e82791SAdam Hornacek                            self._renderOptiongroup(item);
800*87e82791SAdam Hornacek                        } else {
801*87e82791SAdam Hornacek                            self._showErrorLabel('Invalid item type found ' + item.type);
802*87e82791SAdam Hornacek                            return;
803*87e82791SAdam Hornacek                        }
804*87e82791SAdam Hornacek                    }
805*87e82791SAdam Hornacek
806*87e82791SAdam Hornacek                    if (nextIndex >= solItems.length) {
807*87e82791SAdam Hornacek                        dataProcessedFunction.call(self);
808*87e82791SAdam Hornacek                    } else {
809*87e82791SAdam Hornacek                        setTimeout(loopFunction, 0);
810*87e82791SAdam Hornacek                    }
811*87e82791SAdam Hornacek                };
812*87e82791SAdam Hornacek
813*87e82791SAdam Hornacek            // start async rendering of html elements
814*87e82791SAdam Hornacek            loopFunction.call(this);
815*87e82791SAdam Hornacek        },
816*87e82791SAdam Hornacek
817*87e82791SAdam Hornacek        _renderOption: function (solOption, $optionalTargetContainer) {
818*87e82791SAdam Hornacek            var self = this,
819*87e82791SAdam Hornacek                $actualTargetContainer = $optionalTargetContainer || this.$selection,
820*87e82791SAdam Hornacek                $inputElement,
821*87e82791SAdam Hornacek                /*
822*87e82791SAdam Hornacek                * Modified for OpenGrok in 2016.
823*87e82791SAdam Hornacek                */
824*87e82791SAdam Hornacek                $labelText = $('<div class="sol-label-text"/>')
825*87e82791SAdam Hornacek                        .html(solOption.label.trim().length === 0 ? '&nbsp;' : solOption.label)
826*87e82791SAdam Hornacek                    .addClass(solOption.cssClass),
827*87e82791SAdam Hornacek                $label,
828*87e82791SAdam Hornacek                $displayElement,
829*87e82791SAdam Hornacek                inputName = this._getNameAttribute();
830*87e82791SAdam Hornacek            /*
831*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016, 2019.
832*87e82791SAdam Hornacek             */
833*87e82791SAdam Hornacek            var data = $(solOption.element).data('messages');
834*87e82791SAdam Hornacek            var messagesLevel = $(solOption.element).data('messages-level');
835*87e82791SAdam Hornacek            var messagesAvailable = data && data.length;
836*87e82791SAdam Hornacek            if (messagesAvailable && messagesLevel) {
837*87e82791SAdam Hornacek                var cssString = 'pull-right ';
838*87e82791SAdam Hornacek                cssString += 'note-' + messagesLevel;
839*87e82791SAdam Hornacek                cssString += ' important-note important-note-rounded';
840*87e82791SAdam Hornacek
841*87e82791SAdam Hornacek                $labelText.append(
842*87e82791SAdam Hornacek                        $('<span>')
843*87e82791SAdam Hornacek                        .addClass(cssString)
844*87e82791SAdam Hornacek                        .data("messages", data)
845*87e82791SAdam Hornacek                        .attr('data-messages', '')
846*87e82791SAdam Hornacek                        .text('!')
847*87e82791SAdam Hornacek                        );
848*87e82791SAdam Hornacek            }
849*87e82791SAdam Hornacek
850*87e82791SAdam Hornacek            if (this.config.multiple) {
851*87e82791SAdam Hornacek                // use checkboxes
852*87e82791SAdam Hornacek                $inputElement = $('<input type="checkbox" class="sol-checkbox"/>');
853*87e82791SAdam Hornacek
854*87e82791SAdam Hornacek                if (this.config.useBracketParameters) {
855*87e82791SAdam Hornacek                    inputName += '[]';
856*87e82791SAdam Hornacek                }
857*87e82791SAdam Hornacek            } else {
858*87e82791SAdam Hornacek                // use radio buttons
859*87e82791SAdam Hornacek                $inputElement = $('<input type="radio" class="sol-radio"/>')
860*87e82791SAdam Hornacek                    .on('change', function () {
861*87e82791SAdam Hornacek                        // when selected notify all others of being deselected
862*87e82791SAdam Hornacek                        self.$selectionContainer.find('input[type="radio"][name="' + inputName + '"]').not($(this)).trigger('sol-deselect');
863*87e82791SAdam Hornacek                    })
864*87e82791SAdam Hornacek                    .on('sol-deselect', function () {
865*87e82791SAdam Hornacek                        // remove display selection item
866*87e82791SAdam Hornacek                        // TODO also better show it inline instead of above or below to save space
867*87e82791SAdam Hornacek                        self._removeSelectionDisplayItem($(this));
868*87e82791SAdam Hornacek                    });
869*87e82791SAdam Hornacek            }
870*87e82791SAdam Hornacek
871*87e82791SAdam Hornacek            $inputElement
872*87e82791SAdam Hornacek                .on('change', function (event, skipCallback) {
873*87e82791SAdam Hornacek                    $(this).trigger('sol-change', skipCallback);
874*87e82791SAdam Hornacek                })
875*87e82791SAdam Hornacek                .on('sol-change', function (event, skipCallback) {
876*87e82791SAdam Hornacek                    /*
877*87e82791SAdam Hornacek                     * Modified for OpenGrok in 2016.
878*87e82791SAdam Hornacek                     */
879*87e82791SAdam Hornacek                    var $closestOption = $(this).closest('.sol-option')
880*87e82791SAdam Hornacek                    self._setKeyBoardNavigationMode(true)
881*87e82791SAdam Hornacek                    self.$selection
882*87e82791SAdam Hornacek                            .find('.sol-option.keyboard-selection')
883*87e82791SAdam Hornacek                            .removeClass("keyboard-selection")
884*87e82791SAdam Hornacek
885*87e82791SAdam Hornacek                    $closestOption.addClass('keyboard-selection')
886*87e82791SAdam Hornacek                    //self.$selection.scrollTop(self.$selection.scrollTop() + $closestOption.position().top)
887*87e82791SAdam Hornacek
888*87e82791SAdam Hornacek                    self._selectionChange($(this), skipCallback);
889*87e82791SAdam Hornacek                })
890*87e82791SAdam Hornacek                .data('sol-item', solOption)
891*87e82791SAdam Hornacek                .prop('checked', solOption.selected)
892*87e82791SAdam Hornacek                .prop('disabled', solOption.disabled)
893*87e82791SAdam Hornacek                .attr('name', inputName)
894*87e82791SAdam Hornacek                .val(solOption.value);
895*87e82791SAdam Hornacek
896*87e82791SAdam Hornacek            $label = $('<label class="sol-label"/>')
897*87e82791SAdam Hornacek                .attr('title', solOption.tooltip)
898*87e82791SAdam Hornacek                .append($inputElement)
899*87e82791SAdam Hornacek                .append($labelText);
900*87e82791SAdam Hornacek            /*
901*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016.
902*87e82791SAdam Hornacek             */
903*87e82791SAdam Hornacek            $displayElement = $('<div class="sol-option"/>').dblclick(function (e) {
904*87e82791SAdam Hornacek                var $el = $(this).find('.sol-checkbox');
905*87e82791SAdam Hornacek                if ($el.length && $el.data('sol-item') && $el.data('sol-item').label) {
906*87e82791SAdam Hornacek                    // go first project
907*87e82791SAdam Hornacek                    window.location = document.xrefPath + '/' + $(this).find('.sol-checkbox').data('sol-item').label;
908*87e82791SAdam Hornacek                }
909*87e82791SAdam Hornacek            }).append($label);
910*87e82791SAdam Hornacek            /*
911*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016, 2019.
912*87e82791SAdam Hornacek             */
913*87e82791SAdam Hornacek            $inputElement.data('messages-available', messagesAvailable);
914*87e82791SAdam Hornacek            if (messagesLevel) {
915*87e82791SAdam Hornacek                $inputElement.data('messages-level', messagesLevel);
916*87e82791SAdam Hornacek            }
917*87e82791SAdam Hornacek
918*87e82791SAdam Hornacek            solOption.displayElement = $displayElement;
919*87e82791SAdam Hornacek
920*87e82791SAdam Hornacek            $actualTargetContainer.append($displayElement);
921*87e82791SAdam Hornacek
922*87e82791SAdam Hornacek            if (solOption.selected) {
923*87e82791SAdam Hornacek                this._addSelectionDisplayItem($inputElement);
924*87e82791SAdam Hornacek            }
925*87e82791SAdam Hornacek        },
926*87e82791SAdam Hornacek
927*87e82791SAdam Hornacek        _renderOptiongroup: function (solOptiongroup) {
928*87e82791SAdam Hornacek            var self = this,
929*87e82791SAdam Hornacek                $groupCaption = $('<div class="sol-optiongroup-label"/>')
930*87e82791SAdam Hornacek                    .attr('title', solOptiongroup.tooltip)
931*87e82791SAdam Hornacek                    .html(solOptiongroup.label),
932*87e82791SAdam Hornacek                $groupCheckbox = $('<input class="sol-checkbox" style="display: none" type="checkbox" name="group" value="' + solOptiongroup.label+ '"/>'),
933*87e82791SAdam Hornacek                $groupItem = $('<div class="sol-optiongroup"/>').append($groupCaption).append($groupCheckbox);
934*87e82791SAdam Hornacek
935*87e82791SAdam Hornacek            if (solOptiongroup.disabled) {
936*87e82791SAdam Hornacek                $groupItem.addClass('disabled');
937*87e82791SAdam Hornacek            }
938*87e82791SAdam Hornacek            /*
939*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016, 2017.
940*87e82791SAdam Hornacek             */
941*87e82791SAdam Hornacek            $groupCaption.click(function (e) {
942*87e82791SAdam Hornacek                // select all group
943*87e82791SAdam Hornacek                if (self.config.multiple) {
944*87e82791SAdam Hornacek                    if (!e.ctrlKey) {
945*87e82791SAdam Hornacek                        self.deselectAll();
946*87e82791SAdam Hornacek                    }
947*87e82791SAdam Hornacek                    self.selectAll($(this).text())
948*87e82791SAdam Hornacek                    self.$selection.scrollTop(self.$selection.scrollTop() + $(this).position().top)
949*87e82791SAdam Hornacek                }
950*87e82791SAdam Hornacek            });
951*87e82791SAdam Hornacek
952*87e82791SAdam Hornacek            /*
953*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016.
954*87e82791SAdam Hornacek             */
955*87e82791SAdam Hornacek            this.$selection.append($groupItem);
956*87e82791SAdam Hornacek
957*87e82791SAdam Hornacek            if ($.isArray(solOptiongroup.children)) {
958*87e82791SAdam Hornacek                $.each(solOptiongroup.children, function (index, item) {
959*87e82791SAdam Hornacek                    self._renderOption(item, $groupItem);
960*87e82791SAdam Hornacek                });
961*87e82791SAdam Hornacek            }
962*87e82791SAdam Hornacek
963*87e82791SAdam Hornacek            solOptiongroup.displayElement = $groupItem;
964*87e82791SAdam Hornacek        },
965*87e82791SAdam Hornacek
966*87e82791SAdam Hornacek        _initializeSelectAll: function () {
967*87e82791SAdam Hornacek            // multiple values selectable
968*87e82791SAdam Hornacek            if (this.config.showSelectAll === true || ($.isFunction(this.config.showSelectAll) && this.config.showSelectAll.call(this))) {
969*87e82791SAdam Hornacek                // buttons for (de-)select all
970*87e82791SAdam Hornacek                var self = this,
971*87e82791SAdam Hornacek                    $deselectAllButton = $('<a href="#" class="sol-deselect-all"/>').html(this.config.texts.selectNone).click(function (e) {
972*87e82791SAdam Hornacek                        self.deselectAll();
973*87e82791SAdam Hornacek                        e.preventDefault();
974*87e82791SAdam Hornacek                        return false;
975*87e82791SAdam Hornacek                    }),
976*87e82791SAdam Hornacek                    $selectAllButton = $('<a href="#" class="sol-select-all"/>').html(this.config.texts.selectAll).click(function (e) {
977*87e82791SAdam Hornacek                        self.selectAll();
978*87e82791SAdam Hornacek                        e.preventDefault();
979*87e82791SAdam Hornacek                        return false;
980*87e82791SAdam Hornacek                    });
981*87e82791SAdam Hornacek
982*87e82791SAdam Hornacek                this.$actionButtons = $('<div class="sol-action-buttons"/>').append($selectAllButton).append($deselectAllButton).append('<div class="sol-clearfix"/>');
983*87e82791SAdam Hornacek                this.$selectionContainer.prepend(this.$actionButtons);
984*87e82791SAdam Hornacek            }
985*87e82791SAdam Hornacek        },
986*87e82791SAdam Hornacek
987*87e82791SAdam Hornacek        _selectionChange: function ($changeItem, skipCallback) {
988*87e82791SAdam Hornacek
989*87e82791SAdam Hornacek            // apply state to original select if necessary
990*87e82791SAdam Hornacek            // helps to keep old legacy code running which depends
991*87e82791SAdam Hornacek            // on retrieving the value via jQuery option selectors
992*87e82791SAdam Hornacek            // e.g. $('#myPreviousSelectWhichNowIsSol').val()
993*87e82791SAdam Hornacek            if (this.$originalElement && this.$originalElement.prop('tagName').toLowerCase() === 'select') {
994*87e82791SAdam Hornacek                var self = this;
995*87e82791SAdam Hornacek                if (this.valMap == null) {
996*87e82791SAdam Hornacek                    this.$originalElement.find('option').each(function (index, item) {
997*87e82791SAdam Hornacek                        var $currentOriginalOption = $(item);
998*87e82791SAdam Hornacek                        if ($currentOriginalOption.val() === $changeItem.val()) {
999*87e82791SAdam Hornacek                            $currentOriginalOption.prop('selected', $changeItem.prop('checked'));
1000*87e82791SAdam Hornacek                            self.$originalElement.trigger('change');
1001*87e82791SAdam Hornacek                            return false; // stop the loop
1002*87e82791SAdam Hornacek                        }
1003*87e82791SAdam Hornacek                    });
1004*87e82791SAdam Hornacek                } else {
1005*87e82791SAdam Hornacek                    var mappedVal = this.valMap.get($changeItem.val());
1006*87e82791SAdam Hornacek                    if (mappedVal) {
1007*87e82791SAdam Hornacek                        mappedVal.prop('selected', $changeItem.prop('checked'));
1008*87e82791SAdam Hornacek                        self.$originalElement.trigger('change');
1009*87e82791SAdam Hornacek                    }
1010*87e82791SAdam Hornacek                }
1011*87e82791SAdam Hornacek            }
1012*87e82791SAdam Hornacek
1013*87e82791SAdam Hornacek            if ($changeItem.prop('checked')) {
1014*87e82791SAdam Hornacek                this._addSelectionDisplayItem($changeItem);
1015*87e82791SAdam Hornacek            } else {
1016*87e82791SAdam Hornacek                this._removeSelectionDisplayItem($changeItem);
1017*87e82791SAdam Hornacek            }
1018*87e82791SAdam Hornacek
1019*87e82791SAdam Hornacek            if (this.config.multiple) {
1020*87e82791SAdam Hornacek                // update position of selection container
1021*87e82791SAdam Hornacek                // to allow selecting more entries
1022*87e82791SAdam Hornacek                this.config.scrollTarget.trigger('scroll');
1023*87e82791SAdam Hornacek            } else {
1024*87e82791SAdam Hornacek                // only one option selectable
1025*87e82791SAdam Hornacek                // close selection container
1026*87e82791SAdam Hornacek                this.close();
1027*87e82791SAdam Hornacek            }
1028*87e82791SAdam Hornacek
1029*87e82791SAdam Hornacek            if (!skipCallback && $.isFunction(this.config.events.onChange)) {
1030*87e82791SAdam Hornacek                this.config.events.onChange.call(this, this, $changeItem);
1031*87e82791SAdam Hornacek            }
1032*87e82791SAdam Hornacek        },
1033*87e82791SAdam Hornacek
1034*87e82791SAdam Hornacek        _setXItemsSelected: function() {
1035*87e82791SAdam Hornacek            if (this.config.maxShow !== 0 && this.numSelected > this.config.maxShow) {
1036*87e82791SAdam Hornacek                var xItemsText = this.config.texts.itemsSelected.replace('{$a}',
1037*87e82791SAdam Hornacek                    this.numSelected - this.config.maxShow);
1038*87e82791SAdam Hornacek                this.$xItemsSelected.html('<div class="sol-selected-display-item-text">' +
1039*87e82791SAdam Hornacek                    xItemsText + '<div>');
1040*87e82791SAdam Hornacek                this.$showSelectionContainer.append(this.$xItemsSelected);
1041*87e82791SAdam Hornacek                this.$xItemsSelected.show();
1042*87e82791SAdam Hornacek            } else {
1043*87e82791SAdam Hornacek                this.$xItemsSelected.hide();
1044*87e82791SAdam Hornacek            }
1045*87e82791SAdam Hornacek        },
1046*87e82791SAdam Hornacek
1047*87e82791SAdam Hornacek        _addSelectionDisplayItem: function ($changedItem) {
1048*87e82791SAdam Hornacek            this.numSelected = 1 + this.numSelected;
1049*87e82791SAdam Hornacek            if (this.config.numSelectedItem) {
1050*87e82791SAdam Hornacek                this.config.numSelectedItem.val(this.numSelected);
1051*87e82791SAdam Hornacek            }
1052*87e82791SAdam Hornacek
1053*87e82791SAdam Hornacek            if (this.config.maxShow !== 0 && this.numSelected > this.config.maxShow) {
1054*87e82791SAdam Hornacek                if (this.valMap == null) {
1055*87e82791SAdam Hornacek                    this._setXItemsSelected();
1056*87e82791SAdam Hornacek                }
1057*87e82791SAdam Hornacek            } else {
1058*87e82791SAdam Hornacek                this._buildSelectionDisplayItem($changedItem);
1059*87e82791SAdam Hornacek            }
1060*87e82791SAdam Hornacek        },
1061*87e82791SAdam Hornacek
1062*87e82791SAdam Hornacek        _buildSelectionDisplayItem: function ($changedItem) {
1063*87e82791SAdam Hornacek            var solOptionItem = $changedItem.data('sol-item'),
1064*87e82791SAdam Hornacek                self = this,
1065*87e82791SAdam Hornacek                $existingDisplayItem,
1066*87e82791SAdam Hornacek                $displayItemText;
1067*87e82791SAdam Hornacek
1068*87e82791SAdam Hornacek            /*
1069*87e82791SAdam Hornacek             * Modified for OpenGrok in 2016, 2019.
1070*87e82791SAdam Hornacek             */
1071*87e82791SAdam Hornacek            var label = solOptionItem.label;
1072*87e82791SAdam Hornacek            if ($changedItem.data('messages-available')) {
1073*87e82791SAdam Hornacek                label += ' <span class="';
1074*87e82791SAdam Hornacek                label += 'note-' + $changedItem.data('messages-level');
1075*87e82791SAdam Hornacek                label += ' important-note important-note-rounded" ';
1076*87e82791SAdam Hornacek                label += 'title="Some message is present for this project.';
1077*87e82791SAdam Hornacek                label += ' Find more info in the project list.">!</span>'
1078*87e82791SAdam Hornacek            }
1079*87e82791SAdam Hornacek
1080*87e82791SAdam Hornacek            $displayItemText = $('<span class="sol-selected-display-item-text" />').html(label);
1081*87e82791SAdam Hornacek            $existingDisplayItem = $('<div class="sol-selected-display-item"/>')
1082*87e82791SAdam Hornacek                .append($displayItemText)
1083*87e82791SAdam Hornacek                .attr('title', solOptionItem.tooltip)
1084*87e82791SAdam Hornacek                .data('label', solOptionItem.label)
1085*87e82791SAdam Hornacek                .appendTo(this.$showSelectionContainer)
1086*87e82791SAdam Hornacek                .dblclick(function () { // Modified for OpenGrok in 2017.
1087*87e82791SAdam Hornacek                    $changedItem.dblclick();
1088*87e82791SAdam Hornacek                });
1089*87e82791SAdam Hornacek
1090*87e82791SAdam Hornacek            // show remove button on display items if not disabled and null selection allowed
1091*87e82791SAdam Hornacek            if ((this.config.multiple || this.config.allowNullSelection) && !$changedItem.prop('disabled')) {
1092*87e82791SAdam Hornacek                $('<span class="sol-quick-delete"/>')
1093*87e82791SAdam Hornacek                    .html(this.config.texts.quickDelete)
1094*87e82791SAdam Hornacek                    .click(function () { // deselect the project and refresh the search
1095*87e82791SAdam Hornacek                        $changedItem
1096*87e82791SAdam Hornacek                            .prop('checked', false)
1097*87e82791SAdam Hornacek                            .trigger('change');
1098*87e82791SAdam Hornacek                        /*
1099*87e82791SAdam Hornacek                         * Modified for OpenGrok in 2017.
1100*87e82791SAdam Hornacek                         */
1101*87e82791SAdam Hornacek                        if (self.config.quickDeleteForm) {
1102*87e82791SAdam Hornacek                            if (self.config.quickDeletePermit) {
1103*87e82791SAdam Hornacek                                if (self.config.quickDeletePermit()) {
1104*87e82791SAdam Hornacek                                    self.config.quickDeleteForm.submit();
1105*87e82791SAdam Hornacek                                }
1106*87e82791SAdam Hornacek                            } else {
1107*87e82791SAdam Hornacek                                self.config.quickDeleteForm.submit();
1108*87e82791SAdam Hornacek                            }
1109*87e82791SAdam Hornacek                        }
1110*87e82791SAdam Hornacek                    })
1111*87e82791SAdam Hornacek                    .prependTo($existingDisplayItem);
1112*87e82791SAdam Hornacek            }
1113*87e82791SAdam Hornacek
1114*87e82791SAdam Hornacek            solOptionItem.displaySelectionItem = $existingDisplayItem;
1115*87e82791SAdam Hornacek        },
1116*87e82791SAdam Hornacek
1117*87e82791SAdam Hornacek        _removeSelectionDisplayItem: function ($changedItem) {
1118*87e82791SAdam Hornacek            var solOptionItem = $changedItem.data('sol-item'),
1119*87e82791SAdam Hornacek                $myDisplayItem = solOptionItem.displaySelectionItem;
1120*87e82791SAdam Hornacek
1121*87e82791SAdam Hornacek            var wasExceeding = this.config.maxShow !== 0 && this.numSelected > this.config.maxShow;
1122*87e82791SAdam Hornacek            this.numSelected = this.numSelected - 1;
1123*87e82791SAdam Hornacek            if (this.config.numSelectedItem) {
1124*87e82791SAdam Hornacek                this.config.numSelectedItem.val(this.numSelected);
1125*87e82791SAdam Hornacek            }
1126*87e82791SAdam Hornacek
1127*87e82791SAdam Hornacek            if ($myDisplayItem) {
1128*87e82791SAdam Hornacek                $myDisplayItem.remove();
1129*87e82791SAdam Hornacek                solOptionItem.displaySelectionItem = undefined;
1130*87e82791SAdam Hornacek
1131*87e82791SAdam Hornacek                /*
1132*87e82791SAdam Hornacek                 * N.b. for bulk mode, wasExceeding handling is off since only
1133*87e82791SAdam Hornacek                 * Clear or Invert-Selection would cause this function to be
1134*87e82791SAdam Hornacek                 * called. For Clear, there won't be any selected items at the
1135*87e82791SAdam Hornacek                 * end, so wasExceeding is irrelevant. For Invert-Selection,
1136*87e82791SAdam Hornacek                 * checked options are unchecked first -- i.e. we go to zero
1137*87e82791SAdam Hornacek                 * this.numSelected first -- so normal _addSelectionDisplayItem
1138*87e82791SAdam Hornacek                 * takes care of things.
1139*87e82791SAdam Hornacek                 */
1140*87e82791SAdam Hornacek
1141*87e82791SAdam Hornacek                if (wasExceeding && this.valMap == null) {
1142*87e82791SAdam Hornacek                    var self = this;
1143*87e82791SAdam Hornacek                    this.$selectionContainer
1144*87e82791SAdam Hornacek                        .find('.sol-option input[type="checkbox"]:not([disabled]):checked')
1145*87e82791SAdam Hornacek                        .each(function (index, item) {
1146*87e82791SAdam Hornacek                            var $currentOptionItem = $(item);
1147*87e82791SAdam Hornacek                            if ($currentOptionItem.data('sol-item').displaySelectionItem == null) {
1148*87e82791SAdam Hornacek                                self._buildSelectionDisplayItem($currentOptionItem);
1149*87e82791SAdam Hornacek                                return false;
1150*87e82791SAdam Hornacek                            }
1151*87e82791SAdam Hornacek                        });
1152*87e82791SAdam Hornacek                }
1153*87e82791SAdam Hornacek            }
1154*87e82791SAdam Hornacek            if (this.valMap == null) {
1155*87e82791SAdam Hornacek                this._setXItemsSelected();
1156*87e82791SAdam Hornacek            }
1157*87e82791SAdam Hornacek        },
1158*87e82791SAdam Hornacek
1159*87e82791SAdam Hornacek        _setNoResultsItemVisible: function (visible) {
1160*87e82791SAdam Hornacek            if (visible) {
1161*87e82791SAdam Hornacek                this.$noResultsItem.show();
1162*87e82791SAdam Hornacek                this.$selection.hide();
1163*87e82791SAdam Hornacek
1164*87e82791SAdam Hornacek                if (this.$actionButtons) {
1165*87e82791SAdam Hornacek                    this.$actionButtons.hide();
1166*87e82791SAdam Hornacek                }
1167*87e82791SAdam Hornacek            } else {
1168*87e82791SAdam Hornacek                this.$noResultsItem.hide();
1169*87e82791SAdam Hornacek                this.$selection.show();
1170*87e82791SAdam Hornacek
1171*87e82791SAdam Hornacek                if (this.$actionButtons) {
1172*87e82791SAdam Hornacek                    this.$actionButtons.show();
1173*87e82791SAdam Hornacek                }
1174*87e82791SAdam Hornacek            }
1175*87e82791SAdam Hornacek        },
1176*87e82791SAdam Hornacek
1177*87e82791SAdam Hornacek        _buildValMap: function () {
1178*87e82791SAdam Hornacek            if (this.$originalElement && this.$originalElement.prop('tagName').toLowerCase() === 'select') {
1179*87e82791SAdam Hornacek                var self = this;
1180*87e82791SAdam Hornacek                this.valMap = new Map();
1181*87e82791SAdam Hornacek                this.$originalElement.find('option').each(function (index, item) {
1182*87e82791SAdam Hornacek                    var $currentOriginalOption = $(item);
1183*87e82791SAdam Hornacek                    self.valMap.set($currentOriginalOption.val(), $currentOriginalOption);
1184*87e82791SAdam Hornacek                });
1185*87e82791SAdam Hornacek            }
1186*87e82791SAdam Hornacek        },
1187*87e82791SAdam Hornacek
1188*87e82791SAdam Hornacek        isOpen: function () {
1189*87e82791SAdam Hornacek            return this.$container.hasClass('sol-active');
1190*87e82791SAdam Hornacek        },
1191*87e82791SAdam Hornacek
1192*87e82791SAdam Hornacek        isClosed: function () {
1193*87e82791SAdam Hornacek            return !this.isOpen();
1194*87e82791SAdam Hornacek        },
1195*87e82791SAdam Hornacek
1196*87e82791SAdam Hornacek        toggle: function () {
1197*87e82791SAdam Hornacek            if (this.isOpen()) {
1198*87e82791SAdam Hornacek                this.close();
1199*87e82791SAdam Hornacek            } else {
1200*87e82791SAdam Hornacek                this.open();
1201*87e82791SAdam Hornacek            }
1202*87e82791SAdam Hornacek        },
1203*87e82791SAdam Hornacek
1204*87e82791SAdam Hornacek        open: function () {
1205*87e82791SAdam Hornacek            if (this.isClosed()) {
1206*87e82791SAdam Hornacek                this.$container.addClass('sol-active');
1207*87e82791SAdam Hornacek                this.config.scrollTarget.bind('scroll', this.internalScrollWrapper).trigger('scroll');
1208*87e82791SAdam Hornacek                $(window).on('resize', this.internalScrollWrapper);
1209*87e82791SAdam Hornacek
1210*87e82791SAdam Hornacek                if ($.isFunction(this.config.events.onOpen)) {
1211*87e82791SAdam Hornacek                    this.config.events.onOpen.call(this, this);
1212*87e82791SAdam Hornacek                }
1213*87e82791SAdam Hornacek            }
1214*87e82791SAdam Hornacek        },
1215*87e82791SAdam Hornacek
1216*87e82791SAdam Hornacek        close: function () {
1217*87e82791SAdam Hornacek            if (this.isOpen()) {
1218*87e82791SAdam Hornacek                this._setKeyBoardNavigationMode(false);
1219*87e82791SAdam Hornacek
1220*87e82791SAdam Hornacek
1221*87e82791SAdam Hornacek                this.$container.removeClass('sol-active');
1222*87e82791SAdam Hornacek                this.config.scrollTarget.unbind('scroll', this.internalScrollWrapper);
1223*87e82791SAdam Hornacek                $(window).off('resize');
1224*87e82791SAdam Hornacek
1225*87e82791SAdam Hornacek                // reset search on close
1226*87e82791SAdam Hornacek                this.$input.val('');
1227*87e82791SAdam Hornacek                this._applySearchTermFilter();
1228*87e82791SAdam Hornacek
1229*87e82791SAdam Hornacek                // clear to recalculate position again the next time sol is opened
1230*87e82791SAdam Hornacek                this.config.displayContainerAboveInput = undefined;
1231*87e82791SAdam Hornacek
1232*87e82791SAdam Hornacek                if ($.isFunction(this.config.events.onClose)) {
1233*87e82791SAdam Hornacek                    this.config.events.onClose.call(this, this);
1234*87e82791SAdam Hornacek                }
1235*87e82791SAdam Hornacek            }
1236*87e82791SAdam Hornacek        },
1237*87e82791SAdam Hornacek        /*
1238*87e82791SAdam Hornacek         * Modified for OpenGrok in 2016.
1239*87e82791SAdam Hornacek         */
1240*87e82791SAdam Hornacek        selectAll: function (/* string or undefined */optgroup) {
1241*87e82791SAdam Hornacek            if (this.config.multiple) {
1242*87e82791SAdam Hornacek                this._buildValMap();
1243*87e82791SAdam Hornacek
1244*87e82791SAdam Hornacek                var $changedInputs = !optgroup ? this.$selectionContainer
1245*87e82791SAdam Hornacek                        : this.$selectionContainer
1246*87e82791SAdam Hornacek                        .find(".sol-optiongroup-label")
1247*87e82791SAdam Hornacek                        .filter(function () {
1248*87e82791SAdam Hornacek                            return $(this).text() === optgroup;
1249*87e82791SAdam Hornacek                        }).closest('.sol-optiongroup')
1250*87e82791SAdam Hornacek
1251*87e82791SAdam Hornacek                $changedInputs = $changedInputs.find('input[type="checkbox"]:not([disabled], :checked)')
1252*87e82791SAdam Hornacek                            .prop('checked', true)
1253*87e82791SAdam Hornacek                            .trigger('change', true);
1254*87e82791SAdam Hornacek
1255*87e82791SAdam Hornacek                this.config.closeOnClick && this.close();
1256*87e82791SAdam Hornacek
1257*87e82791SAdam Hornacek                if ($.isFunction(this.config.events.onChange)) {
1258*87e82791SAdam Hornacek                    this.config.events.onChange.call(this, this, $changedInputs);
1259*87e82791SAdam Hornacek                }
1260*87e82791SAdam Hornacek
1261*87e82791SAdam Hornacek                this.valMap = null;
1262*87e82791SAdam Hornacek                this._setXItemsSelected();
1263*87e82791SAdam Hornacek            }
1264*87e82791SAdam Hornacek        },
1265*87e82791SAdam Hornacek        /*
1266*87e82791SAdam Hornacek         * Modified for OpenGrok in 2016, 2019.
1267*87e82791SAdam Hornacek         */
1268*87e82791SAdam Hornacek        invert: function () {
1269*87e82791SAdam Hornacek            if (this.config.multiple) {
1270*87e82791SAdam Hornacek                this._buildValMap();
1271*87e82791SAdam Hornacek
1272*87e82791SAdam Hornacek                var $closedInputs = this.$selectionContainer
1273*87e82791SAdam Hornacek                    .find('input[type="checkbox"][name=project]:not([disabled], :checked)')
1274*87e82791SAdam Hornacek                var $openedInputs = this.$selectionContainer
1275*87e82791SAdam Hornacek                    .find('input[type="checkbox"][name=project]').filter('[disabled], :checked')
1276*87e82791SAdam Hornacek
1277*87e82791SAdam Hornacek                $openedInputs.prop('checked', false)
1278*87e82791SAdam Hornacek                             .trigger('change', true);
1279*87e82791SAdam Hornacek                $closedInputs.prop('checked', true)
1280*87e82791SAdam Hornacek                             .trigger('change', true)
1281*87e82791SAdam Hornacek
1282*87e82791SAdam Hornacek                this.config.closeOnClick && this.close();
1283*87e82791SAdam Hornacek
1284*87e82791SAdam Hornacek                if ($.isFunction(this.config.events.onChange)) {
1285*87e82791SAdam Hornacek                    this.config.events.onChange.call(this, this, $openedInputs.add($closedInputs));
1286*87e82791SAdam Hornacek                }
1287*87e82791SAdam Hornacek
1288*87e82791SAdam Hornacek                this.valMap = null;
1289*87e82791SAdam Hornacek                this._setXItemsSelected();
1290*87e82791SAdam Hornacek            }
1291*87e82791SAdam Hornacek        },
1292*87e82791SAdam Hornacek        /*
1293*87e82791SAdam Hornacek         * Modified for OpenGrok in 2016.
1294*87e82791SAdam Hornacek         */
1295*87e82791SAdam Hornacek        deselectAll: function ( /* string or undefined */ optgroup) {
1296*87e82791SAdam Hornacek            if (this.config.multiple) {
1297*87e82791SAdam Hornacek                this._buildValMap();
1298*87e82791SAdam Hornacek
1299*87e82791SAdam Hornacek                var $changedInputs = !optgroup ? this.$selectionContainer
1300*87e82791SAdam Hornacek                        : this.$selectionContainer
1301*87e82791SAdam Hornacek                        .find(".sol-optiongroup-label")
1302*87e82791SAdam Hornacek                        .filter(function () {
1303*87e82791SAdam Hornacek                            return $(this).text() === optgroup;
1304*87e82791SAdam Hornacek                        }).closest('.sol-optiongroup')
1305*87e82791SAdam Hornacek
1306*87e82791SAdam Hornacek                $changedInputs = $changedInputs.find('.sol-option input[type="checkbox"]:not([disabled]):checked')
1307*87e82791SAdam Hornacek                            .prop('checked', false)
1308*87e82791SAdam Hornacek                            .trigger('change', true);
1309*87e82791SAdam Hornacek
1310*87e82791SAdam Hornacek                this.config.closeOnClick && this.close();
1311*87e82791SAdam Hornacek
1312*87e82791SAdam Hornacek                if ($.isFunction(this.config.events.onChange)) {
1313*87e82791SAdam Hornacek                    this.config.events.onChange.call(this, this, $changedInputs);
1314*87e82791SAdam Hornacek                }
1315*87e82791SAdam Hornacek
1316*87e82791SAdam Hornacek                this.valMap = null;
1317*87e82791SAdam Hornacek                this._setXItemsSelected();
1318*87e82791SAdam Hornacek            }
1319*87e82791SAdam Hornacek        },
1320*87e82791SAdam Hornacek
1321*87e82791SAdam Hornacek        selectRadio: function(val) {
1322*87e82791SAdam Hornacek            this.$selectionContainer.find('input[type="radio"]')
1323*87e82791SAdam Hornacek                .each(function (index, item) {
1324*87e82791SAdam Hornacek                    var $currentOptionItem = $(item);
1325*87e82791SAdam Hornacek                    if ($currentOptionItem.val() === val) {
1326*87e82791SAdam Hornacek                        if (!$currentOptionItem.is(':checked')) {
1327*87e82791SAdam Hornacek                            $currentOptionItem.prop("checked", true).trigger('change', true);
1328*87e82791SAdam Hornacek                        }
1329*87e82791SAdam Hornacek                        return false;
1330*87e82791SAdam Hornacek                    }
1331*87e82791SAdam Hornacek                });
1332*87e82791SAdam Hornacek        },
1333*87e82791SAdam Hornacek
1334*87e82791SAdam Hornacek        getSelection: function () {
1335*87e82791SAdam Hornacek            return this.$selection.find('input:checked');
1336*87e82791SAdam Hornacek        }
1337*87e82791SAdam Hornacek    };
1338*87e82791SAdam Hornacek
1339*87e82791SAdam Hornacek    // jquery plugin boiler plate code
1340*87e82791SAdam Hornacek    SearchableOptionList.defaults = SearchableOptionList.prototype.defaults;
1341*87e82791SAdam Hornacek    window.SearchableOptionList = SearchableOptionList;
1342*87e82791SAdam Hornacek
1343*87e82791SAdam Hornacek    $.fn.searchableOptionList = function (options) {
1344*87e82791SAdam Hornacek        var result = [];
1345*87e82791SAdam Hornacek        this.each(function () {
1346*87e82791SAdam Hornacek            var $this = $(this),
1347*87e82791SAdam Hornacek                $alreadyInitializedSol = $this.data(SearchableOptionList.prototype.DATA_KEY);
1348*87e82791SAdam Hornacek
1349*87e82791SAdam Hornacek            if ($alreadyInitializedSol) {
1350*87e82791SAdam Hornacek                result.push($alreadyInitializedSol);
1351*87e82791SAdam Hornacek            } else {
1352*87e82791SAdam Hornacek                var newSol = new SearchableOptionList($this, options);
1353*87e82791SAdam Hornacek                result.push(newSol);
1354*87e82791SAdam Hornacek
1355*87e82791SAdam Hornacek                setTimeout(function() {
1356*87e82791SAdam Hornacek                    newSol.init();
1357*87e82791SAdam Hornacek                }, 0);
1358*87e82791SAdam Hornacek            }
1359*87e82791SAdam Hornacek        });
1360*87e82791SAdam Hornacek
1361*87e82791SAdam Hornacek        if (result.length === 1) {
1362*87e82791SAdam Hornacek            return result[0];
1363*87e82791SAdam Hornacek        }
1364*87e82791SAdam Hornacek
1365*87e82791SAdam Hornacek        return result;
1366*87e82791SAdam Hornacek    };
1367*87e82791SAdam Hornacek
1368*87e82791SAdam Hornacek}(jQuery, window, document));
1369