Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"search_combobox.min.js","sources":["../../src/comboboxsearch/search_combobox.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\nimport $ from 'jquery';\nimport {debounce} from 'core/utils';\nimport Pending from 'core/pending';\n\n/**\n * The class that manages the state of the search within a combobox.\n *\n * @module    core/comboboxsearch/search_combobox\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default class {\n    // Define our standard lookups.\n    selectors = {\n        component: this.componentSelector(),\n        toggle: '[data-toggle=\"dropdown\"]',\n        instance: '[data-region=\"instance\"]',\n        input: '[data-action=\"search\"]',\n        clearSearch: '[data-action=\"clearsearch\"]',\n        dropdown: this.dropdownSelector(),\n        resultitems: '[role=\"option\"]',\n        viewall: '#select-all',\n        combobox: '[role=\"combobox\"]',\n    };\n\n    // The results from the called filter function.\n    matchedResults = [];\n\n    // What did the user search for?\n    searchTerm = '';\n\n    // What the user searched for as a lowercase.\n    preppedSearchTerm = null;\n\n    // The DOM nodes after the dropdown render.\n    resultNodes = [];\n\n    // Where does the user currently have focus?\n    currentNode = null;\n\n    // The current node for the view all link.\n    currentViewAll = null;\n\n    dataset = null;\n\n    datasetSize = 0;\n\n    // DOM nodes that persist.\n    component = document.querySelector(this.selectors.component);\n    instance = this.component.dataset.instance;\n    toggle = this.component.querySelector(this.selectors.toggle);\n    searchInput = this.component.querySelector(this.selectors.input);\n    searchDropdown = this.component.querySelector(this.selectors.dropdown);\n    clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n    combobox = this.component.querySelector(this.selectors.combobox);\n    $component = $(this.component);\n\n    constructor() {\n        // If we have a search input, try to get the value otherwise fallback.\n        this.setSearchTerms(this.searchInput?.value ?? '');\n        // Begin handling the base search component.\n        this.registerClickHandlers();\n\n        // Conditionally set up the input handler since we don't know exactly how we were called.\n        // If the combobox is rendered later, then you'll need to call this.registerInputHandlers() manually.\n        // An example of this is the collapse columns in the gradebook.\n        if (this.searchInput !== null) {\n            this.registerInputHandlers();\n            this.registerChangeHandlers();\n        }\n\n        // If we have a search term, show the clear button.\n        if (this.getSearchTerm() !== '') {\n            this.clearSearchButton.classList.remove('d-none');\n        }\n    }\n\n    /**\n     * Stub out a required function.\n     */\n    fetchDataset() {\n        throw new Error(`fetchDataset() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     * @param {Array} dataset\n     */\n    filterDataset(dataset) {\n        throw new Error(`filterDataset(${dataset}) must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     */\n    filterMatchDataset() {\n        throw new Error(`filterMatchDataset() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     */\n    renderDropdown() {\n        throw new Error(`renderDropdown() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     */\n    componentSelector() {\n        throw new Error(`componentSelector() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     */\n    dropdownSelector() {\n        throw new Error(`dropdownSelector() must be implemented in ${this.constructor.name}`);\n    }\n\n    /**\n     * Stub out a required function.\n     * @deprecated since Moodle 4.4\n     */\n    triggerSelector() {\n        window.console.warning('triggerSelector() is deprecated. Consider using this.selectors.toggle');\n    }\n\n    /**\n     * Return the dataset that we will be searching upon.\n     *\n     * @returns {Promise<null>}\n     */\n    async getDataset() {\n        if (!this.dataset) {\n            this.dataset = await this.fetchDataset();\n        }\n        this.datasetSize = this.dataset.length;\n        return this.dataset;\n    }\n\n    /**\n     * Return the size of the dataset.\n     *\n     * @returns {number}\n     */\n    getDatasetSize() {\n        return this.datasetSize;\n    }\n\n    /**\n     * Return the results of the filter upon the dataset.\n     *\n     * @returns {Array}\n     */\n    getMatchedResults() {\n        return this.matchedResults;\n    }\n\n    /**\n     * Given a filter has been run across the dataset, store the matched results.\n     *\n     * @param {Array} result\n     */\n    setMatchedResults(result) {\n        this.matchedResults = result;\n    }\n\n    /**\n     * Get the value that the user entered.\n     *\n     * @returns {string}\n     */\n    getSearchTerm() {\n        return this.searchTerm;\n    }\n\n    /**\n     * Get the transformed search value.\n     *\n     * @returns {string}\n     */\n    getPreppedSearchTerm() {\n        return this.preppedSearchTerm;\n    }\n\n    /**\n     * When a user searches for something, set our variable to manage it.\n     *\n     * @param {string} result\n     */\n    setSearchTerms(result) {\n        this.searchTerm = result;\n        this.preppedSearchTerm = result.toLowerCase();\n    }\n\n    /**\n     * Return an object containing a handfull of dom nodes that we sometimes need the value of.\n     *\n     * @returns {object}\n     */\n    getHTMLElements() {\n        this.updateNodes();\n        return {\n            searchDropdown: this.searchDropdown,\n            currentViewAll: this.currentViewAll,\n            searchInput: this.searchInput,\n            clearSearchButton: this.clearSearchButton,\n            trigger: this.component.querySelector(this.selectors.trigger),\n        };\n    }\n\n    /**\n     * When called, close the dropdown and reset the input field attributes.\n     *\n     * @param {Boolean} clear Conditionality clear the input box.\n     */\n    closeSearch(clear = false) {\n        this.toggleDropdown();\n        if (clear) {\n            // Hide the \"clear\" search button search bar.\n            this.clearSearchButton.classList.add('d-none');\n            // Clear the entered search query in the search bar and hide the search results container.\n            this.setSearchTerms('');\n            this.searchInput.value = \"\";\n        }\n    }\n\n    /**\n     * Check whether search results are currently visible.\n     *\n     * @returns {Boolean}\n     */\n    searchResultsVisible() {\n        const {searchDropdown} = this.getHTMLElements();\n        // If a Node is not visible, then the offsetParent is null.\n        return searchDropdown.offsetParent !== null;\n    }\n\n    /**\n     * When called, update the dropdown fields.\n     *\n     * @param {Boolean} on Flag to toggle hiding or showing values.\n     */\n    toggleDropdown(on = false) {\n        if (on) {\n            $(this.toggle).dropdown('show');\n        } else {\n            $(this.toggle).dropdown('hide');\n        }\n    }\n\n    /**\n     * These class members change when a new result set is rendered. So update for fresh data.\n     */\n    updateNodes() {\n        this.resultNodes = [...this.component.querySelectorAll(this.selectors.resultitems)];\n        this.currentNode = this.resultNodes.find(r => r.id === document.activeElement.id);\n        this.currentViewAll = this.component.querySelector(this.selectors.viewall);\n        this.clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n        this.searchInput = this.component.querySelector(this.selectors.input);\n        this.searchDropdown = this.component.querySelector(this.selectors.dropdown);\n    }\n\n    /**\n     * Register clickable event listeners.\n     */\n    registerClickHandlers() {\n        // Register click events within the component.\n        this.component.addEventListener('click', this.clickHandler.bind(this));\n    }\n\n    /**\n     * Register change event listeners.\n     */\n    registerChangeHandlers() {\n        const valueElement = this.component.querySelector(`#${this.combobox.dataset.inputElement}`);\n        valueElement.addEventListener('change', this.changeHandler.bind(this));\n    }\n\n    /**\n     * Register input event listener for the text input area.\n     */\n    registerInputHandlers() {\n        // Register & handle the text input.\n        this.searchInput.addEventListener('input', debounce(async() => {\n            if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n                window.console.warn(`Search term matches input value - skipping`);\n                // The debounce canhappen multiple times quickly. GRrargh\n                return;\n            }\n            this.setSearchTerms(this.searchInput.value);\n\n            const pendingPromise = new Pending();\n            if (this.getSearchTerm() === '') {\n                this.toggleDropdown();\n                this.clearSearchButton.classList.add('d-none');\n                await this.filterrenderpipe();\n            } else {\n                this.clearSearchButton.classList.remove('d-none');\n                await this.renderAndShow();\n            }\n            pendingPromise.resolve();\n        }, 300, {pending: true}));\n    }\n\n    /**\n     * Update any changeable nodes, filter and then render the result.\n     *\n     * @returns {Promise<void>}\n     */\n    async filterrenderpipe() {\n        this.updateNodes();\n        this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n        this.filterMatchDataset();\n        await this.renderDropdown();\n    }\n\n    /**\n     * A combo method to take the matching fields and render out the results.\n     *\n     * @returns {Promise<void>}\n     */\n    async renderAndShow() {\n        // User has given something for us to filter against.\n        this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n        await this.filterMatchDataset();\n        // Replace the dropdown node contents and show the results.\n        await this.renderDropdown();\n        // Set the dropdown to open.\n        this.toggleDropdown(true);\n    }\n\n    /**\n     * The handler for when a user interacts with the component.\n     *\n     * @param {MouseEvent} e The triggering event that we are working with.\n     */\n    async clickHandler(e) {\n        this.updateNodes();\n        // The \"clear search\" button is triggered.\n        if (e.target.closest(this.selectors.clearSearch)) {\n            this.closeSearch(true);\n            this.searchInput.focus();\n            // Remove aria-activedescendant when the available options change.\n            this.searchInput.removeAttribute('aria-activedescendant');\n        }\n        // User may have accidentally clicked off the dropdown and wants to reopen it.\n        if (\n            this.getSearchTerm() !== ''\n            && !this.getHTMLElements().searchDropdown.classList.contains('show')\n            && e.target.closest(this.selectors.input)\n        ) {\n            await this.renderAndShow();\n        }\n    }\n\n    /**\n     * The handler for when a user changes the value of the component (selects an option from the dropdown).\n     *\n     * @param {Event} e The change event.\n     */\n    // eslint-disable-next-line no-unused-vars\n    changeHandler(e) {\n        // Components may override this method to do something.\n    }\n}\n"],"names":["constructor","component","this","componentSelector","toggle","instance","input","clearSearch","dropdown","dropdownSelector","resultitems","viewall","combobox","document","querySelector","selectors","dataset","setSearchTerms","searchInput","_this$searchInput","value","registerClickHandlers","registerInputHandlers","registerChangeHandlers","getSearchTerm","clearSearchButton","classList","remove","fetchDataset","Error","name","filterDataset","filterMatchDataset","renderDropdown","triggerSelector","window","console","warning","datasetSize","length","getDatasetSize","getMatchedResults","matchedResults","setMatchedResults","result","searchTerm","getPreppedSearchTerm","preppedSearchTerm","toLowerCase","getHTMLElements","updateNodes","searchDropdown","currentViewAll","trigger","closeSearch","clear","toggleDropdown","add","searchResultsVisible","offsetParent","resultNodes","querySelectorAll","currentNode","find","r","id","activeElement","addEventListener","clickHandler","bind","inputElement","changeHandler","async","warn","pendingPromise","Pending","filterrenderpipe","renderAndShow","resolve","pending","getDataset","e","target","closest","focus","removeAttribute","contains"],"mappings":"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"}