Merge branch 'MDL-61689-master' of git://github.com/andrewnicols/moodle
[moodle.git] / question / amd / src / edit_tags.js
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/>.
16 /**
17  * A javascript module to handle question tags editing.
18  *
19  * @module     core_question/edit_tags
20  * @copyright  2018 Simey Lameze <simey@moodle.com>
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
23 define([
24             'jquery',
25             'core/fragment',
26             'core/str',
27             'core/modal_events',
28             'core/modal_factory',
29             'core/notification',
30             'core/custom_interaction_events',
31             'core_question/repository',
32             'core_question/selectors',
33         ],
34         function(
35             $,
36             Fragment,
37             Str,
38             ModalEvents,
39             ModalFactory,
40             Notification,
41             CustomEvents,
42             Repository,
43             QuestionSelectors
44         ) {
46     /**
47      * Enable the save button in the footer.
48      *
49      * @param {object} root The container element.
50      * @method enableSaveButton
51      */
52     var enableSaveButton = function(root) {
53         root.find(QuestionSelectors.actions.save).prop('disabled', false);
54     };
56     /**
57      * Disable the save button in the footer.
58      *
59      * @param {object} root The container element.
60      * @method disableSaveButton
61      */
62     var disableSaveButton = function(root) {
63         root.find(QuestionSelectors.actions.save).prop('disabled', true);
64     };
66     /**
67      * Get the serialised form data.
68      *
69      * @method getFormData
70      * @param {object} modal The modal object.
71      * @return {string} serialised form data
72      */
73     var getFormData = function(modal) {
74         return modal.getBody().find('form').serialize();
75     };
77     /**
78      * Set the element state to loading.
79      *
80      * @param {object} root The container element
81      * @method startLoading
82      */
83     var startLoading = function(root) {
84         var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
86         loadingIconContainer.removeClass('hidden');
87     };
89     /**
90      * Remove the loading state from the element.
91      *
92      * @param {object} root The container element
93      * @method stopLoading
94      */
95     var stopLoading = function(root) {
96         var loadingIconContainer = root.find(QuestionSelectors.containers.loadingIcon);
98         loadingIconContainer.addClass('hidden');
99     };
101     /**
102      * Set the context Id data attribute on the modal.
103      *
104      * @param {Promise} modal The modal promise.
105      * @param {int} contextId The context id.
106      */
107     var setContextId = function(modal, contextId) {
108         modal.getBody().attr('data-contextid', contextId);
109     };
111     /**
112      * Get the context Id data attribute value from the modal body.
113      *
114      * @param {Promise} modal The modal promise.
115      * @return {int} The context id.
116      */
117     var getContextId = function(modal) {
118         return modal.getBody().data('contextid');
119     };
121     /**
122      * Set the question Id data attribute on the modal.
123      *
124      * @param {Promise} modal The modal promise.
125      * @param {int} questionId The question Id.
126      */
127     var setQuestionId = function(modal, questionId) {
128         modal.getBody().attr('data-questionid', questionId);
129     };
131     /**
132      * Get the question Id data attribute value from the modal body.
133      *
134      * @param {Promise} modal The modal promise.
135      * @return {int} The question Id.
136      */
137     var getQuestionId = function(modal) {
138         return modal.getBody().data('questionid');
139     };
141     /**
142      * Register event listeners for the module.
143      *
144      * @param {object} root The calendar root element
145      */
146     var registerEventListeners = function(root) {
147         var modalPromise = ModalFactory.create(
148             {
149                 type: ModalFactory.types.SAVE_CANCEL,
150                 large: false
151             },
152             [root, QuestionSelectors.actions.edittags]
153         ).then(function(modal) {
154             // All of this code only executes once, when the modal is
155             // first created. This allows us to add any code that should
156             // only be run once, such as adding event handlers to the modal.
157             Str.get_string('questiontags', 'question')
158                 .then(function(string) {
159                     modal.setTitle(string);
160                     return string;
161                 })
162                 .fail(Notification.exception);
164             modal.getRoot().on(ModalEvents.save, function(e) {
165                 var form = modal.getBody().find('form');
166                 form.submit();
167                 e.preventDefault();
168             });
170             modal.getRoot().on('submit', 'form', function(e) {
171                 save(modal, root).then(function() {
172                     modal.hide();
173                     return;
174                 }).fail(Notification.exception);
176                 // Stop the form from actually submitting and prevent it's
177                 // propagation because we have already handled the event.
178                 e.preventDefault();
179                 e.stopPropagation();
180             });
182             return modal;
183         });
185         // We need to add an event handler to the tags link because there are
186         // multiple links on the page and without adding a listener we don't know
187         // which one the user clicked on the show the modal.
188         root.on(CustomEvents.events.activate, QuestionSelectors.actions.edittags, function(e) {
189             var currentTarget = $(e.currentTarget);
191             var questionId = currentTarget.data('questionid'),
192                 canTag = !!currentTarget.data('cantag'),
193                 contextId = currentTarget.data('contextid');
195             // This code gets called each time the user clicks the tag link
196             // so we can use it to reload the contents of the tag modal.
197             modalPromise.then(function(modal) {
198                 // Display spinner and disable save button.
199                 disableSaveButton(root);
200                 startLoading(root);
202                 var args = {
203                     id: questionId
204                 };
206                 var tagsFragment = Fragment.loadFragment('question', 'tags_form', contextId, args);
207                 modal.setBody(tagsFragment);
209                 tagsFragment.then(function() {
210                         enableSaveButton(root);
211                         return;
212                     })
213                     .always(function() {
214                         // Always hide the loading spinner when the request
215                         // has completed.
216                         stopLoading(root);
217                         return;
218                     })
219                 .fail(Notification.exception);
221                 // Show or hide the save button depending on whether the user
222                 // has the capability to edit the tags.
223                 if (canTag) {
224                     modal.getRoot().find(QuestionSelectors.actions.save).show();
225                 } else {
226                     modal.getRoot().find(QuestionSelectors.actions.save).hide();
227                 }
229                 setQuestionId(modal, questionId);
230                 setContextId(modal, contextId);
232                 return modal;
233             }).fail(Notification.exception);
235             e.preventDefault();
236         });
237     };
239     /**
240      * Send the form data to the server to save question tags.
241      *
242      * @method save
243      * @param {object} modal The modal object.
244      * @param {object} root The container element.
245      * @return {object} A promise
246      */
247     var save = function(modal, root) {
248         // Display spinner and disable save button.
249         disableSaveButton(root);
250         startLoading(root);
252         var formData = getFormData(modal);
253         var questionId = getQuestionId(modal);
254         var contextId = getContextId(modal);
256         // Send the form data to the server for processing.
257         return Repository.submitTagCreateUpdateForm(questionId, contextId, formData)
258             .always(function() {
259                 // Regardless of success or error we should always stop
260                 // the loading icon and re-enable the buttons.
261                 stopLoading(root);
262                 enableSaveButton(root);
263                 return;
264             })
265             .fail(Notification.exception);
266     };
268     return {
269         init: function(root) {
270             root = $(root);
271             registerEventListeners(root);
272         }
273     };
274 });