| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Contain the logic for the question bank modal.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     mod_quiz/modal_quiz_question_bank
 | 
        
           |  |  | 20 |  * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
 | 
        
           |  |  | 21 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  */
 | 
        
           |  |  | 23 |   | 
        
           |  |  | 24 | import Modal from './add_question_modal';
 | 
        
           |  |  | 25 | import * as Fragment from 'core/fragment';
 | 
        
           |  |  | 26 | import * as FormChangeChecker from 'core_form/changechecker';
 | 
        
           |  |  | 27 | import * as ModalEvents from 'core/modal_events';
 | 
        
           | 1441 | ariadna | 28 | import * as Notification from 'core/notification';
 | 
        
           | 1 | efrain | 29 |   | 
        
           |  |  | 30 | const SELECTORS = {
 | 
        
           |  |  | 31 |     ADD_TO_QUIZ_CONTAINER: 'td.addtoquizaction',
 | 
        
           |  |  | 32 |     ANCHOR: 'a[href]',
 | 
        
           |  |  | 33 |     PREVIEW_CONTAINER: 'td.previewquestionaction',
 | 
        
           |  |  | 34 |     ADD_QUESTIONS_FORM: 'form#questionsubmit',
 | 
        
           |  |  | 35 |     SORTERS: '.sorters',
 | 
        
           | 1441 | ariadna | 36 |     SWITCH_TO_OTHER_BANK: 'button[data-action="switch-question-bank"]',
 | 
        
           |  |  | 37 |     NEW_BANKMOD_ID: 'data-newmodid',
 | 
        
           |  |  | 38 |     BANK_SEARCH: '#searchbanks',
 | 
        
           |  |  | 39 |     GO_BACK_BUTTON: 'button[data-action="go-back"]',
 | 
        
           |  |  | 40 |     ADD_ON_PAGE_FORM_ELEMENT: 'input[name="addonpage"]',
 | 
        
           |  |  | 41 |     CMID_FORM_ELEMENT: 'form#questionsubmit input[name="cmid"]',
 | 
        
           | 1 | efrain | 42 | };
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 | export default class ModalQuizQuestionBank extends Modal {
 | 
        
           |  |  | 45 |     static TYPE = 'mod_quiz-quiz-question-bank';
 | 
        
           |  |  | 46 |   | 
        
           |  |  | 47 |     /**
 | 
        
           |  |  | 48 |      * Create the question bank modal.
 | 
        
           |  |  | 49 |      *
 | 
        
           | 1441 | ariadna | 50 |      * @param {Number} contextId Current module context id.
 | 
        
           |  |  | 51 |      * @param {Number} bankCmId Current question bank course module id.
 | 
        
           |  |  | 52 |      * @param {Number} quizCmId Current quiz course module id.
 | 
        
           | 1 | efrain | 53 |      */
 | 
        
           | 1441 | ariadna | 54 |     static init(contextId, bankCmId, quizCmId) {
 | 
        
           | 1 | efrain | 55 |         const selector = '.menu [data-action="questionbank"]';
 | 
        
           |  |  | 56 |         document.addEventListener('click', (e) => {
 | 
        
           |  |  | 57 |             const trigger = e.target.closest(selector);
 | 
        
           |  |  | 58 |             if (!trigger) {
 | 
        
           |  |  | 59 |                 return;
 | 
        
           |  |  | 60 |             }
 | 
        
           |  |  | 61 |             e.preventDefault();
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |             ModalQuizQuestionBank.create({
 | 
        
           |  |  | 64 |                 contextId,
 | 
        
           | 1441 | ariadna | 65 |                 quizCmId,
 | 
        
           |  |  | 66 |                 bankCmId,
 | 
        
           | 1 | efrain | 67 |                 title: trigger.dataset.header,
 | 
        
           |  |  | 68 |                 addOnPage: trigger.dataset.addonpage,
 | 
        
           |  |  | 69 |                 templateContext: {
 | 
        
           |  |  | 70 |                     hidden: true,
 | 
        
           |  |  | 71 |                 },
 | 
        
           |  |  | 72 |                 large: true,
 | 
        
           |  |  | 73 |             });
 | 
        
           |  |  | 74 |         });
 | 
        
           |  |  | 75 |     }
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 |     /**
 | 
        
           |  |  | 78 |      * Override the parent show function.
 | 
        
           |  |  | 79 |      *
 | 
        
           |  |  | 80 |      * Reload the body contents when the modal is shown. The current
 | 
        
           |  |  | 81 |      * window URL is used to inform the new content that should be
 | 
        
           |  |  | 82 |      * displayed.
 | 
        
           |  |  | 83 |      *
 | 
        
           |  |  | 84 |      * @method show
 | 
        
           |  |  | 85 |      * @return {void}
 | 
        
           |  |  | 86 |      */
 | 
        
           |  |  | 87 |     show() {
 | 
        
           |  |  | 88 |         this.reloadBodyContent(window.location.search);
 | 
        
           |  |  | 89 |         return super.show(this);
 | 
        
           |  |  | 90 |     }
 | 
        
           |  |  | 91 |   | 
        
           |  |  | 92 |     /**
 | 
        
           |  |  | 93 |      * Replaces the current body contents with a new version of the question
 | 
        
           |  |  | 94 |      * bank.
 | 
        
           |  |  | 95 |      *
 | 
        
           |  |  | 96 |      * The contents of the question bank are generated using the provided
 | 
        
           |  |  | 97 |      * query string.
 | 
        
           |  |  | 98 |      *
 | 
        
           |  |  | 99 |      * @method reloadBodyContent
 | 
        
           |  |  | 100 |      * @param {string} querystring URL encoded string.
 | 
        
           |  |  | 101 |      */
 | 
        
           |  |  | 102 |     reloadBodyContent(querystring) {
 | 
        
           | 1441 | ariadna | 103 |         // Load the question bank fragment to be displayed in the modal and hide the 'go back' button.
 | 
        
           |  |  | 104 |         this.hideFooter();
 | 
        
           |  |  | 105 |         this.setTitle(this.originalTitle);
 | 
        
           | 1 | efrain | 106 |         this.setBody(Fragment.loadFragment(
 | 
        
           |  |  | 107 |             'mod_quiz',
 | 
        
           |  |  | 108 |             'quiz_question_bank',
 | 
        
           |  |  | 109 |             this.getContextId(),
 | 
        
           |  |  | 110 |             {
 | 
        
           |  |  | 111 |                 querystring,
 | 
        
           | 1441 | ariadna | 112 |                 quizcmid: this.quizCmId,
 | 
        
           |  |  | 113 |                 bankcmid: this.bankCmId,
 | 
        
           | 1 | efrain | 114 |             }
 | 
        
           |  |  | 115 |         ));
 | 
        
           |  |  | 116 |     }
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |     /**
 | 
        
           |  |  | 119 |      * Update the URL of the anchor element that the user clicked on to make
 | 
        
           |  |  | 120 |      * sure that the question is added to the correct page.
 | 
        
           |  |  | 121 |      *
 | 
        
           |  |  | 122 |      * @method handleAddToQuizEvent
 | 
        
           |  |  | 123 |      * @param {event} e A JavaScript event
 | 
        
           |  |  | 124 |      * @param {object} anchorElement The anchor element that was triggered
 | 
        
           |  |  | 125 |      */
 | 
        
           |  |  | 126 |     handleAddToQuizEvent(e, anchorElement) {
 | 
        
           |  |  | 127 |         // If the user clicks the plus icon to add the question to the page
 | 
        
           |  |  | 128 |         // directly then we need to intercept the click in order to adjust the
 | 
        
           | 1441 | ariadna | 129 |         // href and include the correct add on page id and cmid before the page is
 | 
        
           | 1 | efrain | 130 |         // redirected.
 | 
        
           | 1441 | ariadna | 131 |         const href = new URL(anchorElement.getAttribute('href'));
 | 
        
           | 1 | efrain | 132 |         href.searchParams.set('addonpage', this.getAddOnPageId());
 | 
        
           | 1441 | ariadna | 133 |         href.searchParams.set('cmid', this.quizCmId);
 | 
        
           |  |  | 134 |         anchorElement.setAttribute('href', href);
 | 
        
           | 1 | efrain | 135 |     }
 | 
        
           |  |  | 136 |   | 
        
           |  |  | 137 |     /**
 | 
        
           |  |  | 138 |      * Set up all of the event handling for the modal.
 | 
        
           |  |  | 139 |      *
 | 
        
           |  |  | 140 |      * @method registerEventListeners
 | 
        
           |  |  | 141 |      */
 | 
        
           |  |  | 142 |     registerEventListeners() {
 | 
        
           |  |  | 143 |         // Apply parent event listeners.
 | 
        
           |  |  | 144 |         super.registerEventListeners(this);
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |         this.getModal().on('submit', SELECTORS.ADD_QUESTIONS_FORM, (e) => {
 | 
        
           |  |  | 147 |             // If the user clicks on the "Add selected questions to the quiz" button to add some questions to the page
 | 
        
           | 1441 | ariadna | 148 |             // then we need to intercept the submit in order to include the correct "add on page id"
 | 
        
           |  |  | 149 |             // and the quizmod id before the form is submitted.
 | 
        
           |  |  | 150 |             const formElement = e.currentTarget;
 | 
        
           |  |  | 151 |             document.querySelector(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).setAttribute('value', this.getAddOnPageId());
 | 
        
           | 1 | efrain | 152 |   | 
        
           | 1441 | ariadna | 153 |             // We also need to set the form cmid & action as the quiz modid as this could be coming from a module that isn't a quiz.
 | 
        
           |  |  | 154 |             document.querySelector(SELECTORS.CMID_FORM_ELEMENT).setAttribute('value', this.quizCmId);
 | 
        
           |  |  | 155 |             const actionUrl = new URL(formElement.getAttribute('action'));
 | 
        
           |  |  | 156 |             actionUrl.searchParams.set('cmid', this.quizCmId);
 | 
        
           |  |  | 157 |             formElement.setAttribute('action', actionUrl.toString());
 | 
        
           | 1 | efrain | 158 |         });
 | 
        
           |  |  | 159 |   | 
        
           | 1441 | ariadna | 160 |         this.getModal().on('click', SELECTORS.SWITCH_TO_OTHER_BANK, () => {
 | 
        
           |  |  | 161 |             this.handleSwitchBankContentReload(SELECTORS.BANK_SEARCH)
 | 
        
           |  |  | 162 |                 .then(function(ModalQuizQuestionBank) {
 | 
        
           |  |  | 163 |                         document.querySelector(SELECTORS.BANK_SEARCH)?.addEventListener('change', (e) => {
 | 
        
           |  |  | 164 |                             const bankCmId = e.currentTarget.value;
 | 
        
           |  |  | 165 |                             if (bankCmId > 0) {
 | 
        
           |  |  | 166 |                                 ModalQuizQuestionBank.bankCmId = bankCmId;
 | 
        
           |  |  | 167 |                                 ModalQuizQuestionBank.reloadBodyContent(window.location.search);
 | 
        
           |  |  | 168 |                             }
 | 
        
           |  |  | 169 |                         });
 | 
        
           |  |  | 170 |                         document.querySelector(SELECTORS.GO_BACK_BUTTON).addEventListener('click', (e) => {
 | 
        
           |  |  | 171 |                             ModalQuizQuestionBank.bankCmId = e.currentTarget.value;
 | 
        
           |  |  | 172 |                             ModalQuizQuestionBank.reloadBodyContent(window.location.search);
 | 
        
           |  |  | 173 |                         });
 | 
        
           |  |  | 174 |                     }
 | 
        
           |  |  | 175 |                 )
 | 
        
           |  |  | 176 |                 .catch(Notification.exception);
 | 
        
           |  |  | 177 |         });
 | 
        
           |  |  | 178 |   | 
        
           | 1 | efrain | 179 |         this.getModal().on('click', SELECTORS.ANCHOR, (e) => {
 | 
        
           | 1441 | ariadna | 180 |             const anchorElement = e.currentTarget;
 | 
        
           | 1 | efrain | 181 |   | 
        
           |  |  | 182 |             // If the anchor element was the add to quiz link.
 | 
        
           | 1441 | ariadna | 183 |             if (anchorElement.closest(SELECTORS.ADD_TO_QUIZ_CONTAINER)) {
 | 
        
           | 1 | efrain | 184 |                 this.handleAddToQuizEvent(e, anchorElement);
 | 
        
           |  |  | 185 |                 return;
 | 
        
           |  |  | 186 |             }
 | 
        
           |  |  | 187 |   | 
        
           |  |  | 188 |             // If the anchor element was a preview question link.
 | 
        
           | 1441 | ariadna | 189 |             if (anchorElement.closest(SELECTORS.PREVIEW_CONTAINER)) {
 | 
        
           | 1 | efrain | 190 |                 return;
 | 
        
           |  |  | 191 |             }
 | 
        
           |  |  | 192 |   | 
        
           |  |  | 193 |             // Sorting links have their own handler.
 | 
        
           | 1441 | ariadna | 194 |             if (anchorElement.closest(SELECTORS.SORTERS)) {
 | 
        
           | 1 | efrain | 195 |                 return;
 | 
        
           |  |  | 196 |             }
 | 
        
           |  |  | 197 |   | 
        
           | 1441 | ariadna | 198 |             if (anchorElement.closest('a[' + SELECTORS.NEW_BANKMOD_ID + ']')) {
 | 
        
           |  |  | 199 |                 this.bankCmId = anchorElement.getAttribute(SELECTORS.NEW_BANKMOD_ID);
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 |                 // We need to clear the filter as we are about to reload the content.
 | 
        
           |  |  | 202 |                 const url = new URL(location.href);
 | 
        
           |  |  | 203 |                 url.searchParams.delete('filter');
 | 
        
           |  |  | 204 |                 history.pushState({}, '', url);
 | 
        
           |  |  | 205 |             }
 | 
        
           |  |  | 206 |   | 
        
           | 1 | efrain | 207 |             // Anything else means reload the pop-up contents.
 | 
        
           |  |  | 208 |             e.preventDefault();
 | 
        
           | 1441 | ariadna | 209 |             this.reloadBodyContent(anchorElement.search);
 | 
        
           | 1 | efrain | 210 |         });
 | 
        
           |  |  | 211 |   | 
        
           |  |  | 212 |         // Disable the form change checker when the body is rendered.
 | 
        
           |  |  | 213 |         this.getRoot().on(ModalEvents.bodyRendered, () => {
 | 
        
           |  |  | 214 |             // Make sure the form change checker is disabled otherwise it'll stop the user from navigating away from the
 | 
        
           |  |  | 215 |             // page once the modal is hidden.
 | 
        
           |  |  | 216 |             FormChangeChecker.disableAllChecks();
 | 
        
           |  |  | 217 |         });
 | 
        
           |  |  | 218 |     }
 | 
        
           |  |  | 219 | }
 | 
        
           |  |  | 220 |   | 
        
           |  |  | 221 | ModalQuizQuestionBank.registerModalType();
 |