Commit | Line | Data |
---|---|---|
5005d8cf 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 | * 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', | |
11970d0a AN |
37 | 'core_message/message_drawer_events', |
38 | 'core/pending', | |
8aca1807 | 39 | 'core/drawer', |
5005d8cf RW |
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, | |
11970d0a | 54 | Events, |
8aca1807 JP |
55 | Pending, |
56 | Drawer | |
5005d8cf RW |
57 | ) { |
58 | ||
59 | var SELECTORS = { | |
45decc81 BB |
60 | DRAWER: '[data-region="right-hand-drawer"]', |
61 | JUMPTO: '.popover-region [data-region="jumpto"]', | |
7cbea160 BB |
62 | PANEL_BODY_CONTAINER: '[data-region="panel-body-container"]', |
63 | PANEL_HEADER_CONTAINER: '[data-region="panel-header-container"]', | |
5005d8cf RW |
64 | VIEW_CONTACT: '[data-region="view-contact"]', |
65 | VIEW_CONTACTS: '[data-region="view-contacts"]', | |
66 | VIEW_CONVERSATION: '[data-region="view-conversation"]', | |
67 | VIEW_GROUP_INFO: '[data-region="view-group-info"]', | |
68 | VIEW_OVERVIEW: '[data-region="view-overview"]', | |
69 | VIEW_SEARCH: '[data-region="view-search"]', | |
70 | VIEW_SETTINGS: '[data-region="view-settings"]', | |
71 | ROUTES: '[data-route]', | |
72 | ROUTES_BACK: '[data-route-back]', | |
73 | HEADER_CONTAINER: '[data-region="header-container"]', | |
74 | BODY_CONTAINER: '[data-region="body-container"]', | |
75 | FOOTER_CONTAINER: '[data-region="footer-container"]', | |
92bc86e4 | 76 | CLOSE_BUTTON: '[data-action="closedrawer"]' |
5005d8cf RW |
77 | }; |
78 | ||
79 | /** | |
80 | * Get elements for route. | |
81 | * | |
cb01c45c | 82 | * @param {String} namespace Unique identifier for the Routes |
5005d8cf RW |
83 | * @param {Object} root The message drawer container. |
84 | * @param {string} selector The route container. | |
85 | * | |
86 | * @return {array} elements Found route container objects. | |
87 | */ | |
cb01c45c | 88 | var getParametersForRoute = function(namespace, root, selector) { |
7cbea160 BB |
89 | |
90 | var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector); | |
91 | if (!header.length) { | |
92 | header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector); | |
93 | } | |
94 | var body = root.find(SELECTORS.BODY_CONTAINER).find(selector); | |
95 | if (!body.length) { | |
96 | body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector); | |
97 | } | |
98 | var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector); | |
5005d8cf | 99 | |
cb01c45c MN |
100 | return [ |
101 | namespace, | |
102 | header.length ? header : null, | |
103 | body.length ? body : null, | |
104 | footer.length ? footer : null | |
105 | ]; | |
5005d8cf RW |
106 | }; |
107 | ||
108 | var routes = [ | |
109 | [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description], | |
110 | [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description], | |
111 | [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description], | |
112 | [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description], | |
113 | [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description], | |
114 | [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description], | |
cb01c45c | 115 | [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description] |
5005d8cf RW |
116 | ]; |
117 | ||
118 | /** | |
119 | * Create routes. | |
120 | * | |
cb01c45c | 121 | * @param {String} namespace Unique identifier for the Routes |
5005d8cf RW |
122 | * @param {Object} root The message drawer container. |
123 | */ | |
cb01c45c | 124 | var createRoutes = function(namespace, root) { |
5005d8cf | 125 | routes.forEach(function(route) { |
cb01c45c | 126 | Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]); |
5005d8cf RW |
127 | }); |
128 | }; | |
129 | ||
130 | /** | |
131 | * Show the message drawer. | |
132 | * | |
cb01c45c | 133 | * @param {string} namespace The route namespace. |
5005d8cf RW |
134 | * @param {Object} root The message drawer container. |
135 | */ | |
cb01c45c | 136 | var show = function(namespace, root) { |
5005d8cf | 137 | if (!root.attr('data-shown')) { |
cb01c45c | 138 | Router.go(namespace, Routes.VIEW_OVERVIEW); |
5005d8cf RW |
139 | root.attr('data-shown', true); |
140 | } | |
141 | ||
8aca1807 | 142 | var drawerRoot = Drawer.getDrawerRoot(root); |
6d9c9356 | 143 | if (drawerRoot.length) { |
8aca1807 JP |
144 | Drawer.show(drawerRoot); |
145 | } | |
5005d8cf RW |
146 | }; |
147 | ||
148 | /** | |
149 | * Hide the message drawer. | |
150 | * | |
151 | * @param {Object} root The message drawer container. | |
152 | */ | |
153 | var hide = function(root) { | |
8aca1807 | 154 | var drawerRoot = Drawer.getDrawerRoot(root); |
6d9c9356 | 155 | if (drawerRoot.length) { |
8aca1807 JP |
156 | Drawer.hide(drawerRoot); |
157 | } | |
5005d8cf RW |
158 | }; |
159 | ||
160 | /** | |
161 | * Check if the drawer is visible. | |
162 | * | |
163 | * @param {Object} root The message drawer container. | |
8aca1807 | 164 | * @return {boolean} |
5005d8cf RW |
165 | */ |
166 | var isVisible = function(root) { | |
8aca1807 | 167 | var drawerRoot = Drawer.getDrawerRoot(root); |
6d9c9356 | 168 | if (drawerRoot.length) { |
8aca1807 JP |
169 | return Drawer.isVisible(drawerRoot); |
170 | } | |
171 | return true; | |
5005d8cf RW |
172 | }; |
173 | ||
45decc81 BB |
174 | /** |
175 | * Set Jump from button | |
176 | * | |
177 | * @param {String} buttonid The originating button id | |
178 | */ | |
179 | var setJumpFrom = function(buttonid) { | |
180 | $(SELECTORS.DRAWER).attr('data-origin', buttonid); | |
181 | }; | |
182 | ||
5005d8cf RW |
183 | /** |
184 | * Listen to and handle events for routing, showing and hiding the message drawer. | |
185 | * | |
cb01c45c | 186 | * @param {string} namespace The route namespace. |
5005d8cf | 187 | * @param {Object} root The message drawer container. |
fd998fc6 | 188 | * @param {bool} alwaysVisible Is this messaging app always shown? |
5005d8cf | 189 | */ |
fd998fc6 | 190 | var registerEventListeners = function(namespace, root, alwaysVisible) { |
5005d8cf RW |
191 | CustomEvents.define(root, [CustomEvents.events.activate]); |
192 | var paramRegex = /^data-route-param-?(\d*)$/; | |
193 | ||
194 | root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) { | |
195 | var element = $(e.target).closest(SELECTORS.ROUTES); | |
196 | var route = element.attr('data-route'); | |
197 | var attributes = []; | |
198 | ||
199 | for (var i = 0; i < element[0].attributes.length; i++) { | |
200 | attributes.push(element[0].attributes[i]); | |
201 | } | |
202 | ||
203 | var paramAttributes = attributes.filter(function(attribute) { | |
204 | var name = attribute.nodeName; | |
205 | var match = paramRegex.test(name); | |
206 | return match; | |
207 | }); | |
208 | paramAttributes.sort(function(a, b) { | |
209 | var aParts = paramRegex.exec(a.nodeName); | |
210 | var bParts = paramRegex.exec(b.nodeName); | |
211 | var aIndex = aParts.length > 1 ? aParts[1] : 0; | |
212 | var bIndex = bParts.length > 1 ? bParts[1] : 0; | |
213 | ||
214 | if (aIndex < bIndex) { | |
215 | return -1; | |
216 | } else if (bIndex < aIndex) { | |
217 | return 1; | |
218 | } else { | |
219 | return 0; | |
220 | } | |
221 | }); | |
222 | ||
223 | var params = paramAttributes.map(function(attribute) { | |
224 | return attribute.nodeValue; | |
225 | }); | |
7cbea160 | 226 | |
cb01c45c | 227 | var routeParams = [namespace, route].concat(params); |
5005d8cf RW |
228 | |
229 | Router.go.apply(null, routeParams); | |
230 | ||
231 | data.originalEvent.preventDefault(); | |
232 | }); | |
233 | ||
234 | root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) { | |
cb01c45c | 235 | Router.back(namespace); |
5005d8cf RW |
236 | |
237 | data.originalEvent.preventDefault(); | |
238 | }); | |
239 | ||
11970d0a AN |
240 | // These are theme-specific to help us fix random behat fails. |
241 | // These events target those events defined in BS3 and BS4 onwards. | |
242 | root.on('hide.bs.collapse', '.collapse', function(e) { | |
243 | var pendingPromise = new Pending(); | |
244 | $(e.target).one('hidden.bs.collapse', function() { | |
245 | pendingPromise.resolve(); | |
246 | }); | |
247 | }); | |
248 | ||
249 | root.on('show.bs.collapse', '.collapse', function(e) { | |
250 | var pendingPromise = new Pending(); | |
251 | $(e.target).one('shown.bs.collapse', function() { | |
252 | pendingPromise.resolve(); | |
253 | }); | |
254 | }); | |
255 | ||
45decc81 | 256 | $(SELECTORS.JUMPTO).focus(function() { |
35bd8622 | 257 | var firstInput = root.find(SELECTORS.CLOSE_BUTTON); |
45decc81 BB |
258 | if (firstInput.length) { |
259 | firstInput.focus(); | |
260 | } else { | |
261 | $(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus(); | |
262 | } | |
263 | }); | |
264 | ||
265 | $(SELECTORS.DRAWER).focus(function() { | |
266 | var button = $(this).attr('data-origin'); | |
267 | if (button) { | |
268 | $('#' + button).focus(); | |
269 | } | |
270 | }); | |
271 | ||
fd998fc6 MN |
272 | if (!alwaysVisible) { |
273 | PubSub.subscribe(Events.SHOW, function() { | |
274 | show(namespace, root); | |
275 | }); | |
5005d8cf | 276 | |
fd998fc6 | 277 | PubSub.subscribe(Events.HIDE, function() { |
5005d8cf | 278 | hide(root); |
fd998fc6 MN |
279 | }); |
280 | ||
45decc81 | 281 | PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) { |
fd998fc6 MN |
282 | if (isVisible(root)) { |
283 | hide(root); | |
45decc81 | 284 | $(SELECTORS.JUMPTO).attr('tabindex', -1); |
fd998fc6 MN |
285 | } else { |
286 | show(namespace, root); | |
45decc81 BB |
287 | setJumpFrom(buttonid); |
288 | $(SELECTORS.JUMPTO).attr('tabindex', 0); | |
fd998fc6 MN |
289 | } |
290 | }); | |
291 | } | |
5005d8cf | 292 | |
45decc81 BB |
293 | PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) { |
294 | setJumpFrom(args.buttonid); | |
cb01c45c | 295 | show(namespace, root); |
45decc81 | 296 | Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid); |
5005d8cf RW |
297 | }); |
298 | ||
92bc86e4 BB |
299 | var closebutton = root.find(SELECTORS.CLOSE_BUTTON); |
300 | closebutton.on(CustomEvents.events.activate, function() { | |
35bd8622 BB |
301 | var button = $(SELECTORS.DRAWER).attr('data-origin'); |
302 | if (button) { | |
303 | $('#' + button).focus(); | |
304 | } | |
92bc86e4 BB |
305 | PubSub.publish(Events.TOGGLE_VISIBILITY); |
306 | }); | |
307 | ||
45decc81 BB |
308 | PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) { |
309 | setJumpFrom(args.buttonid); | |
cb01c45c | 310 | show(namespace, root); |
45decc81 | 311 | Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid); |
1d3535f9 RW |
312 | }); |
313 | ||
5005d8cf | 314 | PubSub.subscribe(Events.SHOW_SETTINGS, function() { |
cb01c45c MN |
315 | show(namespace, root); |
316 | Router.go(namespace, Routes.VIEW_SETTINGS); | |
5005d8cf | 317 | }); |
663ccd58 RW |
318 | |
319 | PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) { | |
320 | var filteredPreferences = preferences.filter(function(preference) { | |
321 | return preference.type == 'message_entertosend'; | |
322 | }); | |
323 | var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null; | |
324 | ||
325 | if (enterToSendPreference) { | |
326 | var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION); | |
327 | viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value); | |
328 | } | |
329 | }); | |
5005d8cf RW |
330 | }; |
331 | ||
332 | /** | |
333 | * Initialise the message drawer. | |
334 | * | |
335 | * @param {Object} root The message drawer container. | |
cb01c45c | 336 | * @param {String} uniqueId Unique identifier for the Routes |
fd998fc6 | 337 | * @param {bool} alwaysVisible Should we show the app now, or wait for the user? |
11970d0a | 338 | * @param {Object} route |
5005d8cf | 339 | */ |
6cb33ce7 | 340 | var init = function(root, uniqueId, alwaysVisible, route) { |
5005d8cf | 341 | root = $(root); |
cb01c45c | 342 | createRoutes(uniqueId, root); |
fd998fc6 | 343 | registerEventListeners(uniqueId, root, alwaysVisible); |
6cb33ce7 | 344 | |
fd998fc6 MN |
345 | if (alwaysVisible) { |
346 | show(uniqueId, root); | |
6cb33ce7 RW |
347 | |
348 | if (route) { | |
349 | var routeParams = route.params || []; | |
350 | routeParams = [uniqueId, route.path].concat(routeParams); | |
351 | Router.go.apply(null, routeParams); | |
fd998fc6 MN |
352 | } |
353 | } | |
5005d8cf RW |
354 | }; |
355 | ||
356 | return { | |
357 | init: init, | |
358 | }; | |
359 | }); |