1e27c2f1d8920689939b66d039045adf654f9a4e
[moodle.git] / lib / amd / src / modal_factory.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  * Create a modal.
18  *
19  * @module     core/modal_factory
20  * @class      modal_factory
21  * @package    core
22  * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
25 define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
26         'core/modal_save_cancel', 'core/modal_cancel', 'core/local/modal/alert',
27         'core/templates', 'core/notification', 'core/custom_interaction_events',
28         'core/pending'],
29     function($, ModalEvents, ModalRegistry, Modal, ModalSaveCancel,
30         ModalCancel, ModalAlert, Templates, Notification, CustomEvents, Pending) {
32     // The templates for each type of modal.
33     var TEMPLATES = {
34         DEFAULT: 'core/modal',
35         SAVE_CANCEL: 'core/modal_save_cancel',
36         CANCEL: 'core/modal_cancel',
37         ALERT: 'core/local/modal/alert',
38     };
40     // The available types of modals.
41     var TYPES = {
42         DEFAULT: 'DEFAULT',
43         SAVE_CANCEL: 'SAVE_CANCEL',
44         CANCEL: 'CANCEL',
45         ALERT: 'ALERT',
46     };
48     // Register the common set of modals.
49     ModalRegistry.register(TYPES.DEFAULT, Modal, TEMPLATES.DEFAULT);
50     ModalRegistry.register(TYPES.SAVE_CANCEL, ModalSaveCancel, TEMPLATES.SAVE_CANCEL);
51     ModalRegistry.register(TYPES.CANCEL, ModalCancel, TEMPLATES.CANCEL);
52     ModalRegistry.register(TYPES.ALERT, ModalAlert, TEMPLATES.ALERT);
54     /**
55      * Set up the events required to show the modal and return focus when the modal
56      * is closed.
57      *
58      * @method setUpTrigger
59      * @param {Promise} modalPromise The modal instance
60      * @param {object} triggerElement The jQuery element to open the modal
61      * @param {object} modalConfig The modal configuration given to the factory
62      */
63     var setUpTrigger = function(modalPromise, triggerElement, modalConfig) {
64         // The element that actually shows the modal.
65         var actualTriggerElement = null;
66         // Check if the client has provided a callback function to be called
67         // before the modal is displayed.
68         var hasPreShowCallback = (typeof modalConfig.preShowCallback == 'function');
69         // Function to handle the trigger element being activated.
70         var triggeredCallback = function(e, data) {
71             var pendingPromise = new Pending('core/modal_factory:setUpTrigger:triggeredCallback');
72             actualTriggerElement = $(e.currentTarget);
73             modalPromise.then(function(modal) {
74                 if (hasPreShowCallback) {
75                     // If the client provided a pre-show callback then execute
76                     // it now before showing the modal.
77                     modalConfig.preShowCallback(actualTriggerElement, modal);
78                 }
80                 modal.show();
82                 return modal;
83             })
84             .then(pendingPromise.resolve);
85             data.originalEvent.preventDefault();
86         };
88         // The trigger element can either be a single element or it can be an
89         // element + selector pair to create a delegated event handler to trigger
90         // the modal.
91         if (Array.isArray(triggerElement)) {
92             var selector = triggerElement[1];
93             triggerElement = triggerElement[0];
95             CustomEvents.define(triggerElement, [CustomEvents.events.activate]);
96             triggerElement.on(CustomEvents.events.activate, selector, triggeredCallback);
97         } else {
98             CustomEvents.define(triggerElement, [CustomEvents.events.activate]);
99             triggerElement.on(CustomEvents.events.activate, triggeredCallback);
100         }
102         modalPromise.then(function(modal) {
103             modal.getRoot().on(ModalEvents.hidden, function() {
104                 // Focus on the trigger element that actually launched the modal.
105                 if (actualTriggerElement !== null) {
106                     actualTriggerElement.focus();
107                 }
108             });
110             return modal;
111         });
112     };
114     /**
115      * Create the correct instance of a modal based on the givem type. Sets up
116      * the trigger between the modal and the trigger element.
117      *
118      * @method createFromElement
119      * @param {object} registryConf A config from the ModalRegistry
120      * @param {object} modalElement The modal HTML jQuery object
121      * @return {object} Modal instance
122      */
123     var createFromElement = function(registryConf, modalElement) {
124         modalElement = $(modalElement);
125         var module = registryConf.module;
126         var modal = new module(modalElement);
128         return modal;
129     };
131     /**
132      * Create the correct modal instance for the given type, including loading
133      * the correct template.
134      *
135      * @method createFromType
136      * @param {object} registryConf A config from the ModalRegistry
137      * @param {object} templateContext The context to render the template with
138      * @return {promise} Resolved with a Modal instance
139      */
140     var createFromType = function(registryConf, templateContext) {
141         var templateName = registryConf.template;
143         var modalPromise = Templates.render(templateName, templateContext)
144             .then(function(html) {
145                 var modalElement = $(html);
146                 return createFromElement(registryConf, modalElement);
147             })
148             .fail(Notification.exception);
150         return modalPromise;
151     };
153     /**
154      * Create a Modal instance.
155      *
156      * @method create
157      * @param {object} modalConfig The configuration to create the modal instance
158      * @param {object} triggerElement The trigger HTML jQuery object
159      * @return {promise} Resolved with a Modal instance
160      */
161     var create = function(modalConfig, triggerElement) {
162         var type = modalConfig.type || TYPES.DEFAULT;
163         var isLarge = modalConfig.large ? true : false;
164         var registryConf = null;
165         var templateContext = {};
167         registryConf = ModalRegistry.get(type);
169         if (!registryConf) {
170             Notification.exception({message: 'Unable to find modal of type: ' + type});
171         }
173         if (typeof modalConfig.templateContext != 'undefined') {
174             templateContext = modalConfig.templateContext;
175         }
177         var modalPromise = createFromType(registryConf, templateContext)
178             .then(function(modal) {
179                 if (typeof modalConfig.title != 'undefined') {
180                     modal.setTitle(modalConfig.title);
181                 }
183                 if (typeof modalConfig.body != 'undefined') {
184                     modal.setBody(modalConfig.body);
185                 }
187                 if (typeof modalConfig.footer != 'undefined') {
188                     modal.setFooter(modalConfig.footer);
189                 }
191                 if (modalConfig.buttons) {
192                     Object.entries(modalConfig.buttons).forEach(function([key, value]) {
193                         modal.setButtonText(key, value);
194                     });
195                 }
197                 if (isLarge) {
198                     modal.setLarge();
199                 }
201                 if (typeof modalConfig.removeOnClose !== 'undefined') {
202                     // If configured remove the modal when hiding it.
203                     modal.setRemoveOnClose(modalConfig.removeOnClose);
204                 }
206                 return modal;
207             });
209         if (typeof triggerElement != 'undefined') {
210             setUpTrigger(modalPromise, triggerElement, modalConfig);
211         }
213         return modalPromise;
214     };
216     return {
217         create: create,
218         types: TYPES,
219     };
220 });