/* * This file is part of the Kimai time-tracking app. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. *//*! * [KIMAI] KimaiFormSelect: enhanced functionality for HTML select's */import KimaiPlugin from "../KimaiPlugin";import jQuery from "jquery";export default class KimaiFormSelect extends KimaiPlugin {    constructor(selector) {        super();        this.selector = selector;    }    getId() {        return 'form-select';    }    activateSelectPicker(selector, container) {        const elementSelector = this.selector;        let options = {};        if (container !== undefined) {            options = {                dropdownParent: $(container),            };        }        // Function to match the name of the parent and not only the names of the children        // Based on the original matcher function of Select2: https://github.com/select2/select2/blob/5765090318c4d382ae56463cfa25ba8ca7bdd495/src/js/select2/defaults.js#L272        // More information: https://select2.org/searching | https://github.com/select2/docs/blob/develop/pages/11.searching/docs.md        function matcher(params, data) {            // Always return the object if there is nothing to compare            if (jQuery.trim(params.term) === '') {                return data;            }            // Check whether options has children            let hasChildren = data.children && data.children.length > 0;            // Split search param by space to search for all terms and convert all to uppercase            let terms = params.term.toUpperCase().split(' ');            let original = data.text.toUpperCase();            // Always return the parent option including its children, when the name matches one of the params            // Check if the text contains all or at least one of the terms            let foundAll = true;            let foundOne = false;            let missingTerms = [];            terms.forEach(function(item, index) {                if (original.indexOf(item) > -1) {                    foundOne = true;                } else {                    foundAll = false;                    missingTerms.push(item);                }            });            // If the option element contains all terms, return it            if (foundAll) {                return data;            }            // Do a recursive check for options with children            if (hasChildren) {                // If the parent already contains one or more search terms, proceed only with the missing ones                // First: Clone the original params object...                let newParams = jQuery.extend(true, {}, params);                if (foundOne) {                    newParams.term = missingTerms.join(' ');                } else {                    newParams.term = params.term;                }                // Clone the data object if there are children                // This is required as we modify the object to remove any non-matches                let match = jQuery.extend(true, {}, data);                // Check each child of the option                for (let c = data.children.length - 1; c >= 0; c--) {                    let child = data.children[c];                    let matches = matcher(newParams, child);                    // If there wasn't a match, remove the object in the array                    if (matches === null) {                        match.children.splice(c, 1);                    }                }                // If any children matched, return the new object                if (match.children.length > 0) {                    return match;                }            }            // If the option or its children do not contain the term, don't return anything            return null;        }        options = {...options, ...{            language: this.getConfiguration('locale').replace('_', '-'),            theme: "bootstrap",            matcher: matcher        }};        const templateResultFunc = function (state) {            return jQuery('<span><span style="background-color:'+state.id+'; width: 20px; height: 20px; display: inline-block; margin-right: 10px;">&nbsp;</span>' + state.text + '</span>');        };        let optionsColor = {...options, ...{            templateSelection: templateResultFunc,            templateResult: templateResultFunc        }};        jQuery(selector + ' ' + elementSelector + ':not([data-renderer=color])').select2(options);        jQuery(selector + ' ' + elementSelector + '[data-renderer=color]').select2(optionsColor);        jQuery('body').on('reset', 'form', function(event){            setTimeout(function() {                jQuery(event.target).find(elementSelector).trigger('change');            }, 10);        });    }    destroySelectPicker(selector) {        jQuery(selector + ' ' + this.selector).select2('destroy');    }    updateOptions(selectIdentifier, data) {        let select = jQuery(selectIdentifier);        let emptyOption = jQuery(selectIdentifier + ' option[value=""]');        const selectedValue = select.val();        select.find('option').remove().end().find('optgroup').remove().end();        if (emptyOption.length !== 0) {            select.append(this._createOption(emptyOption.text(), ''));        }        let emptyOpts = [];        let options = [];        for (const [key, value] of Object.entries(data)) {            if (key === '__empty__') {                for (const entity of value) {                    emptyOpts.push(this._createOption(entity.name, entity.id));                }                continue;            }            let optGroup = this._createOptgroup(key);            for (const entity of value) {                optGroup.appendChild(this._createOption(entity.name, entity.id));            }            options.push(optGroup);        }        select.append(options);        select.append(emptyOpts);        // if available, re-select the previous selected option (mostly usable for global activities)        select.val(selectedValue);        // if we don't trigger the change, the other selects won't reset        select.trigger('change');        // if select2 is active, this will tell the select to refresh        if (select.hasClass('selectpicker')) {            select.trigger('change.select2');        }    }    /**     * @param {string} label     * @param {string} value     * @returns {HTMLElement}     * @private     */    _createOption(label, value) {        let option = document.createElement('option');        option.innerText = label;        option.value = value;        return option;    }    /**     * @param {string} label     * @returns {HTMLElement}     * @private     */    _createOptgroup(label) {        let optGroup = document.createElement('optgroup');        optGroup.label = label;        return optGroup;    }}