question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js

// 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/>.

/**
 * Filter managing display of subcategories questions.
 *
 * @module     qbank_managecategories/datafilter/filtertypes/categories
 * @author     Mark Johnson <mark.johnson@catalyst-eu.net>
 * @copyright  2023 Catalyst IT Europe Ltd.
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

import GenericFilter from 'core/datafilter/filtertype';
import Templates from 'core/templates';
import {getUserPreference, setUserPreference} from 'core_user/repository';
import Notification from 'core/notification';
import {get_strings as getStrings} from 'core/str';

export default class extends GenericFilter {

    SELECTORS = {
        includeSubcategories: 'input[name=category-subcategories]',
        selectInput: 'select#filter-value-category',
        selectInputOption: 'select#filter-value-category option',
        validationInput: 'div[data-filter-type="category"] div.form-autocomplete-input input',
    };

    /**
     * Construct a new categoires filter
     *
     * @param {String} filterType The type of filter that this relates to (categories)
     * @param {HTMLElement} rootNode The root node for the participants filterset
     * @param {Array} initialValues The currently selected category IDs.
     * @param {Object} filterOptions An object containing the additional options for the filter, currently "includesubcategories"
     *     is supported, which if true will display the "Also show questions from subcategories" checkbox as checked.
     */
    constructor(filterType, rootNode, initialValues, filterOptions = {includesubcategories: false}) {
        super(filterType, rootNode, initialValues);
        this.addSubcategoryCheckbox(filterOptions.includesubcategories);
    }

    async addSubcategoryCheckbox(checked = null) {
        const filterValueNode = this.getFilterValueNode();
        if (checked === null || checked === undefined) {
            checked = await getUserPreference('qbank_managecategories_includesubcategories_filter_default');
        } else {
            setUserPreference('qbank_managecategories_includesubcategories_filter_default', checked);
        }
        const {html} = await Templates.renderForPromise('qbank_managecategories/include_subcategories_checkbox', {
            checked: checked && checked !== '0',
        });
        filterValueNode.insertAdjacentHTML('afterend', html);
    }

    get filterOptions() {
        return [
            {name: 'includesubcategories', value: this.filterRoot.querySelector(this.SELECTORS.includeSubcategories).checked}
        ];
    }

    get filterValue() {
        return {
            name: this.name,
            jointype: this.jointype,
            values: this.values,
            filteroptions: this.filterOptions,
        };
    }

    validate() {

        // Get the possible option values and filter out empty ones.
        const nodelist = this.filterRoot.querySelectorAll(this.SELECTORS.selectInputOption);

        // This gets all the values from the autocomplete's hidden select menu and filters out
        // any which are not positive integers.
        const validoptions = Array.from(nodelist).map(
            option => (parseInt(option.value) > 0) ? parseInt(option.value) : false
        ).filter(Boolean);

        // Get the value supplied.
        const value = parseInt(this.filterRoot.querySelector(this.SELECTORS.selectInput).value);

        // Reset validation.
        const node = this.filterRoot.querySelector(this.SELECTORS.validationInput);
        node.setCustomValidity('');

        // Make sure the value supplied is valid.
        if (!validoptions.includes(value)) {
            getStrings([
                {
                    key: 'error:category',
                    component: 'qbank_managecategories',
                },
            ]).then((strings) => {
                node.setCustomValidity(strings[0]);
                node.reportValidity();
                return strings;
            }).catch(Notification.exception);

            return false;
        }

        return true;

    }

}