Proyectos de Subversion Moodle

Rev

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

{"version":3,"file":"datafilter.min.js","sources":["../src/datafilter.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\n/**\n * Data filter management.\n *\n * @module     core/datafilter\n * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CourseFilter from 'core/datafilter/filtertypes/courseid';\nimport GenericFilter from 'core/datafilter/filtertype';\nimport {getStrings} from 'core/str';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport CustomEvents from 'core/custom_interaction_events';\nimport jQuery from 'jquery';\n\nexport default class {\n\n    /**\n     * Initialise the filter on the element with the given filterSet and callback.\n     *\n     * @param {HTMLElement} filterSet The filter element.\n     * @param {Function} applyCallback Callback function when updateTableFromFilter\n     */\n    constructor(filterSet, applyCallback) {\n\n        this.filterSet = filterSet;\n        this.applyCallback = applyCallback;\n        // Keep a reference to all of the active filters.\n        this.activeFilters = {\n            courseid: new CourseFilter('courseid', filterSet),\n        };\n    }\n\n    /**\n     * Initialise event listeners to the filter.\n     */\n    init() {\n        // Add listeners for the main actions.\n        this.filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {\n            if (e.target.closest(Selectors.filterset.actions.addRow)) {\n                e.preventDefault();\n\n                this.addFilterRow();\n            }\n\n            if (e.target.closest(Selectors.filterset.actions.applyFilters)) {\n                e.preventDefault();\n\n                this.updateTableFromFilter();\n            }\n\n            if (e.target.closest(Selectors.filterset.actions.resetFilters)) {\n                e.preventDefault();\n\n                this.removeAllFilters();\n            }\n        });\n\n        // Add the listener to remove a single filter.\n        this.filterSet.querySelector(Selectors.filterset.regions.filterlist).addEventListener('click', e => {\n            if (e.target.closest(Selectors.filter.actions.remove)) {\n                e.preventDefault();\n\n                this.removeOrReplaceFilterRow(e.target.closest(Selectors.filter.region), true);\n            }\n        });\n\n        // Add listeners for the filter type selection.\n        let filterRegion = jQuery(this.getFilterRegion());\n        CustomEvents.define(filterRegion, [CustomEvents.events.accessibleChange]);\n        filterRegion.on(CustomEvents.events.accessibleChange, e => {\n            const typeField = e.target.closest(Selectors.filter.fields.type);\n            if (typeField && typeField.value) {\n                const filter = e.target.closest(Selectors.filter.region);\n\n                this.addFilter(filter, typeField.value);\n            }\n        });\n\n        this.filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {\n            this.filterSet.dataset.filterverb = e.target.value;\n        });\n    }\n\n    /**\n     * Get the filter list region.\n     *\n     * @return {HTMLElement}\n     */\n    getFilterRegion() {\n        return this.filterSet.querySelector(Selectors.filterset.regions.filterlist);\n    }\n\n    /**\n     * Add a filter row.\n     *\n     * @param {Object} filterdata Optional, data for adding for row with an existing filter.\n     * @return {Promise}\n     */\n    addFilterRow(filterdata = {}) {\n        const pendingPromise = new Pending('core/datafilter:addFilterRow');\n        const rownum = filterdata.rownum ?? 1 + this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n        return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rownum})\n            .then(({html, js}) => {\n                const newContentNodes = Templates.appendNodeContents(this.getFilterRegion(), html, js);\n\n                return newContentNodes;\n            })\n            .then(filterRow => {\n                // Note: This is a nasty hack.\n                // We should try to find a better way of doing this.\n                // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n                // it in place.\n                const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n                filterRow.forEach(contentNode => {\n                    const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n                    if (contentTypeList) {\n                        contentTypeList.innerHTML = typeList.innerHTML;\n                    }\n                });\n\n                return filterRow;\n            })\n            .then(filterRow => {\n                this.updateFiltersOptions();\n\n                return filterRow;\n            })\n            .then(result => {\n                pendingPromise.resolve();\n\n                // If an existing filter is passed in, add it. Otherwise, leave the row empty.\n                if (filterdata.filtertype) {\n                    result.forEach(filter => {\n                        this.addFilter(filter, filterdata.filtertype, filterdata.values,\n                            filterdata.jointype, filterdata.filteroptions);\n                    });\n                }\n                return result;\n            })\n            .catch(Notification.exception);\n    }\n\n    /**\n     * Get the filter data source node fro the specified filter type.\n     *\n     * @param {String} filterType\n     * @return {HTMLElement}\n     */\n    getFilterDataSource(filterType) {\n        const filterDataNode = this.filterSet.querySelector(Selectors.filterset.regions.datasource);\n\n        return filterDataNode.querySelector(Selectors.data.fields.byName(filterType));\n    }\n\n    /**\n     * Add a filter to the list of active filters, performing any necessary setup.\n     *\n     * @param {HTMLElement} filterRow\n     * @param {String} filterType\n     * @param {Array} initialFilterValues The initially selected values for the filter\n     * @param {String} filterJoin\n     * @param {Object} filterOptions\n     * @returns {Filter}\n     */\n    async addFilter(filterRow, filterType, initialFilterValues, filterJoin, filterOptions) {\n        // Name the filter on the filter row.\n        filterRow.dataset.filterType = filterType;\n\n        const filterDataNode = this.getFilterDataSource(filterType);\n\n        // Instantiate the Filter class.\n        let Filter = GenericFilter;\n        if (filterDataNode.dataset.filterTypeClass) {\n            Filter = await import(filterDataNode.dataset.filterTypeClass);\n        }\n        this.activeFilters[filterType] = new Filter(filterType, this.filterSet, initialFilterValues, filterOptions);\n\n        // Disable the select.\n        const typeField = filterRow.querySelector(Selectors.filter.fields.type);\n        typeField.value = filterType;\n        typeField.disabled = 'disabled';\n        // Update the join list.\n        this.updateJoinList(JSON.parse(filterDataNode.dataset.joinList), filterRow);\n        const joinField = filterRow.querySelector(Selectors.filter.fields.join);\n        if (!isNaN(filterJoin)) {\n            joinField.value = filterJoin;\n        }\n        // Update the list of available filter types.\n        this.updateFiltersOptions();\n\n        return this.activeFilters[filterType];\n    }\n\n    /**\n     * Get the registered filter class for the named filter.\n     *\n     * @param {String} name\n     * @return {Object} See the Filter class.\n     */\n    getFilterObject(name) {\n        return this.activeFilters[name];\n    }\n\n    /**\n     * Remove or replace the specified filter row and associated class, ensuring that if there is only one filter row,\n     * that it is replaced instead of being removed.\n     *\n     * @param {HTMLElement} filterRow\n     * @param {Bool} refreshContent Whether to refresh the table content when removing\n     */\n    removeOrReplaceFilterRow(filterRow, refreshContent) {\n        const filterCount = this.getFilterRegion().querySelectorAll(Selectors.filter.region).length;\n        if (filterCount === 1) {\n            this.replaceFilterRow(filterRow, refreshContent);\n        } else {\n            this.removeFilterRow(filterRow, refreshContent);\n        }\n    }\n\n    /**\n     * Remove the specified filter row and associated class.\n     *\n     * @param {HTMLElement} filterRow\n     * @param {Bool} refreshContent Whether to refresh the table content when removing\n     */\n    async removeFilterRow(filterRow, refreshContent = true) {\n        if (filterRow.querySelector(Selectors.data.required)) {\n            return;\n        }\n        const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n        const hasFilterValue = !!filterType.value;\n\n        // Remove the filter object.\n        this.removeFilterObject(filterRow.dataset.filterType);\n\n        // Remove the actual filter HTML.\n        filterRow.remove();\n\n        // Update the list of available filter types.\n        this.updateFiltersOptions();\n\n        if (hasFilterValue && refreshContent) {\n            // Refresh the table if there was any content in this row.\n            this.updateTableFromFilter();\n        }\n\n        // Update filter fieldset legends.\n        const filterLegends = await this.getAvailableFilterLegends();\n\n        this.getFilterRegion().querySelectorAll(Selectors.filter.region).forEach((filterRow, index) => {\n            filterRow.querySelector('legend').innerText = filterLegends[index];\n        });\n\n    }\n\n    /**\n     * Replace the specified filter row with a new one.\n     *\n     * @param {HTMLElement} filterRow\n     * @param {Bool} refreshContent Whether to refresh the table content when removing\n     * @param {Number} rowNum The number used to label the filter fieldset legend (eg Row 1). Defaults to 1 (the first filter).\n     * @return {Promise}\n     */\n    replaceFilterRow(filterRow, refreshContent = true, rowNum = 1) {\n        if (filterRow.querySelector(Selectors.data.required)) {\n            return;\n        }\n        // Remove the filter object.\n        this.removeFilterObject(filterRow.dataset.filterType);\n\n        return Templates.renderForPromise('core/datafilter/filter_row', {\"rownumber\": rowNum})\n            .then(({html, js}) => {\n                const newContentNodes = Templates.replaceNode(filterRow, html, js);\n\n                return newContentNodes;\n            })\n            .then(filterRow => {\n                // Note: This is a nasty hack.\n                // We should try to find a better way of doing this.\n                // We do not have the list of types in a readily consumable format, so we take the pre-rendered one and copy\n                // it in place.\n                const typeList = this.filterSet.querySelector(Selectors.data.typeList);\n\n                filterRow.forEach(contentNode => {\n                    const contentTypeList = contentNode.querySelector(Selectors.filter.fields.type);\n\n                    if (contentTypeList) {\n                        contentTypeList.innerHTML = typeList.innerHTML;\n                    }\n                });\n\n                return filterRow;\n            })\n            .then(filterRow => {\n                this.updateFiltersOptions();\n\n                return filterRow;\n            })\n            .then(filterRow => {\n                // Refresh the table.\n                if (refreshContent) {\n                    return this.updateTableFromFilter();\n                } else {\n                    return filterRow;\n                }\n            })\n            .catch(Notification.exception);\n    }\n\n    /**\n     * Remove the Filter Object from the register.\n     *\n     * @param {string} filterName The name of the filter to be removed\n     */\n    removeFilterObject(filterName) {\n        if (filterName) {\n            const filter = this.getFilterObject(filterName);\n            if (filter) {\n                filter.tearDown();\n\n                // Remove from the list of active filters.\n                delete this.activeFilters[filterName];\n            }\n        }\n    }\n\n    /**\n     * Remove all filters.\n     *\n     * @returns {Promise}\n     */\n    removeAllFilters() {\n        const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n        filters.forEach(filterRow => this.removeOrReplaceFilterRow(filterRow, false));\n\n        // Refresh the table.\n        return this.updateTableFromFilter();\n    }\n\n    /**\n     * Remove any empty filters.\n     */\n    removeEmptyFilters() {\n        const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n        filters.forEach(filterRow => {\n            const filterType = filterRow.querySelector(Selectors.filter.fields.type);\n            if (!filterType.value) {\n                this.removeOrReplaceFilterRow(filterRow, false);\n            }\n        });\n    }\n\n    /**\n     * Update the list of filter types to filter out those already selected.\n     */\n    updateFiltersOptions() {\n        const filters = this.getFilterRegion().querySelectorAll(Selectors.filter.region);\n        filters.forEach(filterRow => {\n            const options = filterRow.querySelectorAll(Selectors.filter.fields.type + ' option');\n            options.forEach(option => {\n                if (option.value === filterRow.dataset.filterType) {\n                    option.classList.remove('hidden');\n                    option.disabled = false;\n                } else if (this.activeFilters[option.value]) {\n                    option.classList.add('hidden');\n                    option.disabled = true;\n                } else {\n                    option.classList.remove('hidden');\n                    option.disabled = false;\n                }\n            });\n        });\n\n        // Configure the state of the \"Add row\" button.\n        // This button is disabled when there is a filter row available for each condition.\n        const addRowButton = this.filterSet.querySelector(Selectors.filterset.actions.addRow);\n        const filterDataNode = this.filterSet.querySelectorAll(Selectors.data.fields.all);\n        if (filterDataNode.length <= filters.length) {\n            addRowButton.setAttribute('disabled', 'disabled');\n        } else {\n            addRowButton.removeAttribute('disabled');\n        }\n\n        if (filters.length === 1) {\n            this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.add('hidden');\n            this.filterSet.querySelector(Selectors.filterset.fields.join).value = 2;\n            this.filterSet.dataset.filterverb = 2;\n        } else {\n            this.filterSet.querySelector(Selectors.filterset.regions.filtermatch).classList.remove('hidden');\n        }\n    }\n\n    /**\n     * Update the Dynamic table based upon the current filter.\n     */\n    updateTableFromFilter() {\n        const pendingPromise = new Pending('core/datafilter:updateTableFromFilter');\n\n        const filters = {};\n        Object.values(this.activeFilters).forEach(filter => {\n            filters[filter.filterValue.name] = filter.filterValue;\n        });\n\n        if (this.applyCallback) {\n            this.applyCallback(filters, pendingPromise);\n        }\n    }\n\n    /**\n     * Fetch the strings used to populate the fieldset legends for the maximum number of filters possible.\n     *\n     * @return {array}\n     */\n    async getAvailableFilterLegends() {\n        const maxFilters = document.querySelector(Selectors.data.typeListSelect).length - 1;\n        let requests = [];\n\n        [...Array(maxFilters)].forEach((_, rowIndex) => {\n            requests.push({\n                \"key\": \"filterrowlegend\",\n                \"component\": \"core\",\n                // Add 1 since rows begin at 1 (index begins at zero).\n                \"param\": rowIndex + 1\n            });\n        });\n\n        const legendStrings = await getStrings(requests)\n            .then(fetchedStrings => {\n                return fetchedStrings;\n            })\n            .catch(Notification.exception);\n\n        return legendStrings;\n    }\n\n    /**\n     * Update the list of join types for a filter.\n     *\n     * This will update the list of join types based on the allowed types defined for a filter.\n     * If only one type is allowed, the list will be hidden.\n     *\n     * @param {Array} filterJoinList Array of join types, a subset of the regularJoinList array in this function.\n     * @param {Element} filterRow The row being updated.\n     */\n    updateJoinList(filterJoinList, filterRow) {\n        const regularJoinList = [0, 1, 2];\n        // If a join list was specified for this filter, find the default join list and disable the options that are not allowed\n        // for this filter.\n        if (filterJoinList.length !== 0) {\n            const joinField = filterRow.querySelector(Selectors.filter.fields.join);\n            // Check each option from the default list, and disable the option in this filter row if it is not allowed\n            // for this filter.\n            regularJoinList.forEach((join) => {\n                if (!filterJoinList.includes(join)) {\n                    joinField.options[join].classList.add('hidden');\n                    joinField.options[join].disabled = true;\n                }\n            });\n            // Now remove the disabled options, and hide the select list of there is only one option left.\n            joinField.options.forEach((element, index) => {\n                if (element.disabled) {\n                    joinField.options[index] = null;\n                }\n            });\n            if (joinField.options.length === 1) {\n                joinField.hidden = true;\n            }\n        }\n    }\n}\n"],"names":["constructor","filterSet","applyCallback","activeFilters","courseid","CourseFilter","init","querySelector","Selectors","filterset","region","addEventListener","e","target","closest","actions","addRow","preventDefault","addFilterRow","applyFilters","updateTableFromFilter","resetFilters","removeAllFilters","regions","filterlist","filter","remove","removeOrReplaceFilterRow","filterRegion","this","getFilterRegion","define","CustomEvents","events","accessibleChange","on","typeField","fields","type","value","addFilter","join","dataset","filterverb","filterdata","pendingPromise","Pending","rownum","querySelectorAll","length","Templates","renderForPromise","then","_ref","html","js","appendNodeContents","filterRow","typeList","data","forEach","contentNode","contentTypeList","innerHTML","updateFiltersOptions","result","resolve","filtertype","values","jointype","filteroptions","catch","Notification","exception","getFilterDataSource","filterType","datasource","byName","initialFilterValues","filterJoin","filterOptions","filterDataNode","Filter","GenericFilter","filterTypeClass","disabled","updateJoinList","JSON","parse","joinList","joinField","isNaN","getFilterObject","name","refreshContent","replaceFilterRow","removeFilterRow","required","hasFilterValue","removeFilterObject","filterLegends","getAvailableFilterLegends","index","innerText","rowNum","_ref2","replaceNode","filterName","tearDown","removeEmptyFilters","filters","option","classList","add","addRowButton","all","setAttribute","removeAttribute","filtermatch","Object","filterValue","maxFilters","document","typeListSelect","requests","Array","_","rowIndex","push","fetchedStrings","filterJoinList","regularJoinList","includes","options","element","hidden"],"mappings":"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"}