MDL-60621 js: Add ability to set a modal to be scrollable or not
[moodle.git] / lib / amd / src / modal_factory.js
CommitLineData
2bcef559
RW
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 * 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 */
0531ce1a 25define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
b810c84f 26 'core/modal_save_cancel', 'core/modal_cancel', 'core/local/modal/alert',
604887ce
AN
27 'core/templates', 'core/notification', 'core/custom_interaction_events',
28 'core/pending'],
c5681f7f 29 function($, ModalEvents, ModalRegistry, Modal, ModalSaveCancel,
b810c84f 30 ModalCancel, ModalAlert, Templates, Notification, CustomEvents, Pending) {
2bcef559
RW
31
32 // The templates for each type of modal.
33 var TEMPLATES = {
34 DEFAULT: 'core/modal',
35 SAVE_CANCEL: 'core/modal_save_cancel',
be2247fb 36 CANCEL: 'core/modal_cancel',
b810c84f 37 ALERT: 'core/local/modal/alert',
2bcef559
RW
38 };
39
2bcef559
RW
40 // The available types of modals.
41 var TYPES = {
42 DEFAULT: 'DEFAULT',
43 SAVE_CANCEL: 'SAVE_CANCEL',
be2247fb 44 CANCEL: 'CANCEL',
b810c84f 45 ALERT: 'ALERT',
2bcef559
RW
46 };
47
0531ce1a
RW
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);
0531ce1a 51 ModalRegistry.register(TYPES.CANCEL, ModalCancel, TEMPLATES.CANCEL);
b810c84f 52 ModalRegistry.register(TYPES.ALERT, ModalAlert, TEMPLATES.ALERT);
0531ce1a 53
2bcef559
RW
54 /**
55 * Set up the events required to show the modal and return focus when the modal
56 * is closed.
57 *
58 * @method setUpTrigger
72835770 59 * @param {Promise} modalPromise The modal instance
2bcef559 60 * @param {object} triggerElement The jQuery element to open the modal
2803c868 61 * @param {object} modalConfig The modal configuration given to the factory
2bcef559 62 */
2803c868
RW
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) {
604887ce 71 var pendingPromise = new Pending('core/modal_factory:setUpTrigger:triggeredCallback');
2803c868 72 actualTriggerElement = $(e.currentTarget);
72835770 73 modalPromise.then(function(modal) {
2803c868
RW
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 }
79
80 modal.show();
72835770
AN
81
82 return modal;
604887ce
AN
83 })
84 .then(pendingPromise.resolve);
2803c868
RW
85 data.originalEvent.preventDefault();
86 };
87
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];
94
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);
2bcef559 100 }
2803c868
RW
101
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 });
109
110 return modal;
111 });
2bcef559
RW
112 };
113
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
0531ce1a 119 * @param {object} registryConf A config from the ModalRegistry
2bcef559 120 * @param {object} modalElement The modal HTML jQuery object
2bcef559
RW
121 * @return {object} Modal instance
122 */
72835770 123 var createFromElement = function(registryConf, modalElement) {
2bcef559 124 modalElement = $(modalElement);
0531ce1a
RW
125 var module = registryConf.module;
126 var modal = new module(modalElement);
2bcef559
RW
127
128 return modal;
129 };
130
131 /**
132 * Create the correct modal instance for the given type, including loading
2803c868 133 * the correct template.
2bcef559
RW
134 *
135 * @method createFromType
0531ce1a 136 * @param {object} registryConf A config from the ModalRegistry
2803c868 137 * @param {object} templateContext The context to render the template with
2bcef559
RW
138 * @return {promise} Resolved with a Modal instance
139 */
2803c868 140 var createFromType = function(registryConf, templateContext) {
0531ce1a 141 var templateName = registryConf.template;
2bcef559 142
72835770 143 var modalPromise = Templates.render(templateName, templateContext)
2bcef559
RW
144 .then(function(html) {
145 var modalElement = $(html);
72835770 146 return createFromElement(registryConf, modalElement);
2bcef559
RW
147 })
148 .fail(Notification.exception);
72835770 149
72835770 150 return modalPromise;
2bcef559
RW
151 };
152
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;
e13cbf9e
MG
164 // If 'scrollable' is not configured, set the modal to be scrollable by default.
165 var isScrollable = modalConfig.hasOwnProperty('scrollable') ? modalConfig.scrollable : true;
0531ce1a 166 var registryConf = null;
a50768b9 167 var templateContext = {};
0531ce1a
RW
168
169 registryConf = ModalRegistry.get(type);
2bcef559 170
0531ce1a
RW
171 if (!registryConf) {
172 Notification.exception({message: 'Unable to find modal of type: ' + type});
2bcef559
RW
173 }
174
a50768b9
RW
175 if (typeof modalConfig.templateContext != 'undefined') {
176 templateContext = modalConfig.templateContext;
177 }
178
2803c868 179 var modalPromise = createFromType(registryConf, templateContext)
2bcef559
RW
180 .then(function(modal) {
181 if (typeof modalConfig.title != 'undefined') {
182 modal.setTitle(modalConfig.title);
183 }
184
185 if (typeof modalConfig.body != 'undefined') {
186 modal.setBody(modalConfig.body);
187 }
188
189 if (typeof modalConfig.footer != 'undefined') {
190 modal.setFooter(modalConfig.footer);
191 }
192
92810f7a
AN
193 if (modalConfig.buttons) {
194 Object.entries(modalConfig.buttons).forEach(function([key, value]) {
195 modal.setButtonText(key, value);
196 });
197 }
198
2bcef559
RW
199 if (isLarge) {
200 modal.setLarge();
201 }
202
fa6101ba
AN
203 if (typeof modalConfig.removeOnClose !== 'undefined') {
204 // If configured remove the modal when hiding it.
205 modal.setRemoveOnClose(modalConfig.removeOnClose);
206 }
207
e13cbf9e
MG
208 modal.setScrollable(isScrollable);
209
2bcef559
RW
210 return modal;
211 });
2803c868
RW
212
213 if (typeof triggerElement != 'undefined') {
214 setUpTrigger(modalPromise, triggerElement, modalConfig);
215 }
216
217 return modalPromise;
2bcef559
RW
218 };
219
220 return {
221 create: create,
222 types: TYPES,
223 };
224});