MDL-66477 message: Use the generic drawer for the message drawer
[moodle.git] / message / amd / src / message_drawer.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  * Controls the message drawer.
18  *
19  * @module     core_message/message_drawer
20  * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
23 define(
24 [
25     'jquery',
26     'core/custom_interaction_events',
27     'core/pubsub',
28     'core_message/message_drawer_view_contact',
29     'core_message/message_drawer_view_contacts',
30     'core_message/message_drawer_view_conversation',
31     'core_message/message_drawer_view_group_info',
32     'core_message/message_drawer_view_overview',
33     'core_message/message_drawer_view_search',
34     'core_message/message_drawer_view_settings',
35     'core_message/message_drawer_router',
36     'core_message/message_drawer_routes',
37     'core_message/message_drawer_events',
38     'core/pending',
39     'core/drawer',
40 ],
41 function(
42     $,
43     CustomEvents,
44     PubSub,
45     ViewContact,
46     ViewContacts,
47     ViewConversation,
48     ViewGroupInfo,
49     ViewOverview,
50     ViewSearch,
51     ViewSettings,
52     Router,
53     Routes,
54     Events,
55     Pending,
56     Drawer
57 ) {
59     var SELECTORS = {
60         PANEL_BODY_CONTAINER: '[data-region="panel-body-container"]',
61         PANEL_HEADER_CONTAINER: '[data-region="panel-header-container"]',
62         VIEW_CONTACT: '[data-region="view-contact"]',
63         VIEW_CONTACTS: '[data-region="view-contacts"]',
64         VIEW_CONVERSATION: '[data-region="view-conversation"]',
65         VIEW_GROUP_INFO: '[data-region="view-group-info"]',
66         VIEW_OVERVIEW: '[data-region="view-overview"]',
67         VIEW_SEARCH: '[data-region="view-search"]',
68         VIEW_SETTINGS: '[data-region="view-settings"]',
69         ROUTES: '[data-route]',
70         ROUTES_BACK: '[data-route-back]',
71         HEADER_CONTAINER: '[data-region="header-container"]',
72         BODY_CONTAINER: '[data-region="body-container"]',
73         FOOTER_CONTAINER: '[data-region="footer-container"]',
74     };
76     /**
77      * Get elements for route.
78      *
79      * @param {String} namespace Unique identifier for the Routes
80      * @param {Object} root The message drawer container.
81      * @param {string} selector The route container.
82      *
83      * @return {array} elements Found route container objects.
84     */
85     var getParametersForRoute = function(namespace, root, selector) {
87         var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);
88         if (!header.length) {
89             header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);
90         }
91         var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);
92         if (!body.length) {
93             body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector);
94         }
95         var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector);
97         return [
98             namespace,
99             header.length ? header : null,
100             body.length ? body : null,
101             footer.length ? footer : null
102         ];
103     };
105     var routes = [
106         [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description],
107         [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description],
108         [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description],
109         [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description],
110         [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description],
111         [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description],
112         [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description]
113     ];
115     /**
116      * Create routes.
117      *
118      * @param {String} namespace Unique identifier for the Routes
119      * @param {Object} root The message drawer container.
120      */
121     var createRoutes = function(namespace, root) {
122         routes.forEach(function(route) {
123             Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);
124         });
125     };
127     /**
128      * Show the message drawer.
129      *
130      * @param {string} namespace The route namespace.
131      * @param {Object} root The message drawer container.
132      */
133     var show = function(namespace, root) {
134         if (!root.attr('data-shown')) {
135             Router.go(namespace, Routes.VIEW_OVERVIEW);
136             root.attr('data-shown', true);
137         }
139         var drawerRoot = Drawer.getDrawerRoot(root);
140         if (drawerRoot) {
141             Drawer.show(drawerRoot);
142         }
143     };
145     /**
146      * Hide the message drawer.
147      *
148      * @param {Object} root The message drawer container.
149      */
150     var hide = function(root) {
151         var drawerRoot = Drawer.getDrawerRoot(root);
152         if (drawerRoot) {
153             Drawer.hide(drawerRoot);
154         }
155     };
157     /**
158      * Check if the drawer is visible.
159      *
160      * @param {Object} root The message drawer container.
161      * @return {boolean}
162      */
163     var isVisible = function(root) {
164         var drawerRoot = Drawer.getDrawerRoot(root);
165         if (drawerRoot) {
166             return Drawer.isVisible(drawerRoot);
167         }
168         return true;
169     };
171     /**
172      * Listen to and handle events for routing, showing and hiding the message drawer.
173      *
174      * @param {string} namespace The route namespace.
175      * @param {Object} root The message drawer container.
176      * @param {bool} alwaysVisible Is this messaging app always shown?
177      */
178     var registerEventListeners = function(namespace, root, alwaysVisible) {
179         CustomEvents.define(root, [CustomEvents.events.activate]);
180         var paramRegex = /^data-route-param-?(\d*)$/;
182         root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) {
183             var element = $(e.target).closest(SELECTORS.ROUTES);
184             var route = element.attr('data-route');
185             var attributes = [];
187             for (var i = 0; i < element[0].attributes.length; i++) {
188                 attributes.push(element[0].attributes[i]);
189             }
191             var paramAttributes = attributes.filter(function(attribute) {
192                 var name = attribute.nodeName;
193                 var match = paramRegex.test(name);
194                 return match;
195             });
196             paramAttributes.sort(function(a, b) {
197                 var aParts = paramRegex.exec(a.nodeName);
198                 var bParts = paramRegex.exec(b.nodeName);
199                 var aIndex = aParts.length > 1 ? aParts[1] : 0;
200                 var bIndex = bParts.length > 1 ? bParts[1] : 0;
202                 if (aIndex < bIndex) {
203                     return -1;
204                 } else if (bIndex < aIndex) {
205                     return 1;
206                 } else {
207                     return 0;
208                 }
209             });
211             var params = paramAttributes.map(function(attribute) {
212                 return attribute.nodeValue;
213             });
215             var routeParams = [namespace, route].concat(params);
217             Router.go.apply(null, routeParams);
219             data.originalEvent.preventDefault();
220         });
222         root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {
223             Router.back(namespace);
225             data.originalEvent.preventDefault();
226         });
228         // These are theme-specific to help us fix random behat fails.
229         // These events target those events defined in BS3 and BS4 onwards.
230         root.on('hide.bs.collapse', '.collapse', function(e) {
231             var pendingPromise = new Pending();
232             $(e.target).one('hidden.bs.collapse', function() {
233                 pendingPromise.resolve();
234             });
235         });
237         root.on('show.bs.collapse', '.collapse', function(e) {
238             var pendingPromise = new Pending();
239             $(e.target).one('shown.bs.collapse', function() {
240                 pendingPromise.resolve();
241             });
242         });
244         if (!alwaysVisible) {
245             PubSub.subscribe(Events.SHOW, function() {
246                 show(namespace, root);
247             });
249             PubSub.subscribe(Events.HIDE, function() {
250                 hide(root);
251             });
253             PubSub.subscribe(Events.TOGGLE_VISIBILITY, function() {
254                 if (isVisible(root)) {
255                     hide(root);
256                 } else {
257                     show(namespace, root);
258                 }
259             });
260         }
262         PubSub.subscribe(Events.SHOW_CONVERSATION, function(conversationId) {
263             show(namespace, root);
264             Router.go(namespace, Routes.VIEW_CONVERSATION, conversationId);
265         });
267         PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(userId) {
268             show(namespace, root);
269             Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', userId);
270         });
272         PubSub.subscribe(Events.SHOW_SETTINGS, function() {
273             show(namespace, root);
274             Router.go(namespace, Routes.VIEW_SETTINGS);
275         });
277         PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) {
278             var filteredPreferences = preferences.filter(function(preference) {
279                 return preference.type == 'message_entertosend';
280             });
281             var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;
283             if (enterToSendPreference) {
284                 var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);
285                 viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);
286             }
287         });
288     };
290     /**
291      * Initialise the message drawer.
292      *
293      * @param {Object} root The message drawer container.
294      * @param {String} uniqueId Unique identifier for the Routes
295      * @param {bool} alwaysVisible Should we show the app now, or wait for the user?
296      * @param {Object} route
297      */
298     var init = function(root, uniqueId, alwaysVisible, route) {
299         root = $(root);
300         createRoutes(uniqueId, root);
301         registerEventListeners(uniqueId, root, alwaysVisible);
303         if (alwaysVisible) {
304             show(uniqueId, root);
306             if (route) {
307                 var routeParams = route.params || [];
308                 routeParams = [uniqueId, route.path].concat(routeParams);
309                 Router.go.apply(null, routeParams);
310             }
311         }
312     };
314     return {
315         init: init,
316     };
317 });