mod/quiz/amd/src/modal_add_random_question.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/>.

/**
 * Contain the logic for the add random question modal.
 *
 * @module     mod_quiz/modal_add_random_question
 * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
define([
    'jquery',
    'core/notification',
    'core/modal',
    'core/modal_events',
    'core/modal_registry',
    'core/fragment',
    'core/templates',
    'core_form/changechecker',
],
function(
    $,
    Notification,
    Modal,
    ModalEvents,
    ModalRegistry,
    Fragment,
    Templates,
    FormChangeChecker,
) {

    var registered = false;
    var SELECTORS = {
        EXISTING_CATEGORY_CONTAINER: '[data-region="existing-category-container"]',
        EXISTING_CATEGORY_FORM_ELEMENT: '#id_existingcategoryheader',
        NEW_CATEGORY_CONTAINER: '[data-region="new-category-container"]',
        NEW_CATEGORY_FORM_ELEMENT: '#id_newcategoryheader',
        TAB_CONTENT: '[data-region="tab-content"]',
        ADD_ON_PAGE_FORM_ELEMENT: '[name="addonpage"]',
        SUBMIT_BUTTON_ELEMENT: 'input[type="submit"]',
        CANCEL_BUTTON_ELEMENT: 'input[type="submit"][name="cancel"]',
        FORM_HEADER: 'legend',
        BUTTON_CONTAINER: '.fitem'
    };

    /**
     * Constructor for the Modal.
     *
     * @param {object} root The root jQuery element for the modal
     */
    var ModalAddRandomQuestion = function(root) {
        Modal.call(this, root);
        this.contextId = null;
        this.addOnPageId = null;
        this.category = null;
        this.returnUrl = null;
        this.cmid = null;
        this.loadedForm = false;
    };

    ModalAddRandomQuestion.TYPE = 'mod_quiz-quiz-add-random-question';
    ModalAddRandomQuestion.prototype = Object.create(Modal.prototype);
    ModalAddRandomQuestion.prototype.constructor = ModalAddRandomQuestion;

    /**
     * Save the Moodle context id that the question bank is being
     * rendered in.
     *
     * @method setContextId
     * @param {int} id
     */
    ModalAddRandomQuestion.prototype.setContextId = function(id) {
        this.contextId = id;
    };

    /**
     * Retrieve the saved Moodle context id.
     *
     * @method getContextId
     * @return {int}
     */
    ModalAddRandomQuestion.prototype.getContextId = function() {
        return this.contextId;
    };

    /**
     * Set the id of the page that the question should be added to
     * when the user clicks the add to quiz link.
     *
     * @method setAddOnPageId
     * @param {int} id
     */
    ModalAddRandomQuestion.prototype.setAddOnPageId = function(id) {
        this.addOnPageId = id;
        this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);
    };

    /**
     * Returns the saved page id for the question to be added to.
     *
     * @method getAddOnPageId
     * @return {int}
     */
    ModalAddRandomQuestion.prototype.getAddOnPageId = function() {
        return this.addOnPageId;
    };

    /**
     * Set the category for this form. The category is a comma separated
     * category id and category context id.
     *
     * @method setCategory
     * @param {string} category
     */
    ModalAddRandomQuestion.prototype.setCategory = function(category) {
        this.category = category;
    };

    /**
     * Returns the saved category.
     *
     * @method getCategory
     * @return {string}
     */
    ModalAddRandomQuestion.prototype.getCategory = function() {
        return this.category;
    };

    /**
     * Set the return URL for the form.
     *
     * @method setReturnUrl
     * @param {string} url
     */
    ModalAddRandomQuestion.prototype.setReturnUrl = function(url) {
        this.returnUrl = url;
    };

    /**
     * Returns the return URL for the form.
     *
     * @method getReturnUrl
     * @return {string}
     */
    ModalAddRandomQuestion.prototype.getReturnUrl = function() {
        return this.returnUrl;
    };

    /**
     * Set the course module id for the form.
     *
     * @method setCMID
     * @param {int} id
     */
    ModalAddRandomQuestion.prototype.setCMID = function(id) {
        this.cmid = id;
    };

    /**
     * Returns the course module id for the form.
     *
     * @method getCMID
     * @return {int}
     */
    ModalAddRandomQuestion.prototype.getCMID = function() {
        return this.cmid;
    };

    /**
     * Moves a given form element inside (a child of) a given tab element.
     *
     * Hides the 'legend' (e.g. header) element of the form element because the
     * tab has the name.
     *
     * Moves the submit button into a footer element at the bottom of the form
     * element for styling purposes.
     *
     * @method moveFormElementIntoTab
     * @param  {jquery} formElement The form element to move into the tab.
     * @param  {jquey} tabElement The tab element for the form element to move into.
     */
    ModalAddRandomQuestion.prototype.moveFormElementIntoTab = function(formElement, tabElement) {
        var submitButtons = formElement.find(SELECTORS.SUBMIT_BUTTON_ELEMENT);
        var footer = $('<div class="modal-footer mt-1" data-region="footer"></div>');
        // Hide the header because the tabs show us which part of the form we're
        // looking at.
        formElement.find(SELECTORS.FORM_HEADER).addClass('hidden');
        // Move the element inside a tab.
        formElement.wrap(tabElement);
        // Remove the buttons container element.
        submitButtons.closest(SELECTORS.BUTTON_CONTAINER).remove();
        // Put the button inside a footer.
        submitButtons.appendTo(footer);
        // Add the footer to the end of the category form element.
        footer.appendTo(formElement);
    };

    /**
     * Empty the tab content container and move all tabs from the form into the
     * tab container element.
     *
     * @method moveTabsIntoTabContent
     * @param  {jquery} form The form element.
     */
    ModalAddRandomQuestion.prototype.moveTabsIntoTabContent = function(form) {
        // Empty it to remove the loading icon.
        var tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();
        // Make sure all tabs are inside the tab content element.
        form.find('[role="tabpanel"]').wrapAll(tabContent);
    };

    /**
     * Make sure all of the tabs have a cancel button in their fotter to sit along
     * side the submit button.
     *
     * @method moveCancelButtonToTabs
     * @param  {jquey} form The form element.
     */
    ModalAddRandomQuestion.prototype.moveCancelButtonToTabs = function(form) {
        var cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('ml-1');
        var tabFooters = form.find('[data-region="footer"]');
        // Remove the buttons container element.
        cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();
        cancelButton.clone().appendTo(tabFooters);
    };

    /**
     * Load the add random question form in a fragement and perform some transformation
     * on the HTML to convert it into tabs for rendering in the modal.
     *
     * @method loadForm
     * @return {promise} Resolved with form HTML and JS.
     */
    ModalAddRandomQuestion.prototype.loadForm = function() {
        return Fragment.loadFragment(
            'mod_quiz',
            'add_random_question_form',
            this.getContextId(),
            {
                addonpage: this.getAddOnPageId(),
                cat: this.getCategory(),
                returnurl: this.getReturnUrl(),
                cmid: this.getCMID()
            }
        )
        .then(function(html, js) {
            var form = $(html);
            var existingCategoryFormElement = form.find(SELECTORS.EXISTING_CATEGORY_FORM_ELEMENT);
            var existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);
            var newCategoryFormElement = form.find(SELECTORS.NEW_CATEGORY_FORM_ELEMENT);
            var newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);

            // Transform the form into tabs for better rendering in the modal.
            this.moveFormElementIntoTab(existingCategoryFormElement, existingCategoryTab);
            this.moveFormElementIntoTab(newCategoryFormElement, newCategoryTab);
            this.moveTabsIntoTabContent(form);
            this.moveCancelButtonToTabs(form);

            Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);
            return;
        }.bind(this))
        .then(function() {
            // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the
            // page once the modal is hidden.
            FormChangeChecker.disableAllChecks();
            return;
        })
        .fail(Notification.exception);
    };

    /**
     * Override the modal show function to load the form when this modal is first
     * shown.
     *
     * @method show
     */
    ModalAddRandomQuestion.prototype.show = function() {
        Modal.prototype.show.call(this);

        if (!this.loadedForm) {
            this.loadForm();
            this.loadedForm = true;
        }
    };

    // Automatically register with the modal registry the first time this module is
    // imported so that you can create modals of this type using the modal factory.
    if (!registered) {
        ModalRegistry.register(
            ModalAddRandomQuestion.TYPE,
            ModalAddRandomQuestion,
            'mod_quiz/modal_add_random_question'
        );

        registered = true;
    }

    return ModalAddRandomQuestion;
});