1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * This module updates the UI for the conversation page in the message
20 * The module will take a patch from the message_drawer_view_conversation_patcher
21 * module and update the UI to reflect the changes.
23 * This is the only module that ever modifies the UI of the conversation page.
25 * @module core_message/message_drawer_view_conversation_renderer
26 * @copyright 2018 Ryan Wyllie <ryan@moodle.com>
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 'core_message/message_drawer_view_conversation_constants'
46 var SELECTORS = Constants.SELECTORS;
47 var TEMPLATES = Constants.TEMPLATES;
48 var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;
51 * Get the messages container element.
53 * @param {Object} body Conversation body container element.
54 * @return {Object} The messages container element.
56 var getMessagesContainer = function(body) {
57 return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER);
61 * Show the messages container element.
63 * @param {Object} body Conversation body container element.
65 var showMessagesContainer = function(body) {
66 getMessagesContainer(body).removeClass('hidden');
70 * Hide the messages container element.
72 * @param {Object} body Conversation body container element.
74 var hideMessagesContainer = function(body) {
75 getMessagesContainer(body).addClass('hidden');
79 * Get the contact request sent container element.
81 * @param {Object} body Conversation body container element.
82 * @return {Object} The messages container element.
84 var getContactRequestSentContainer = function(body) {
85 return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER);
89 * Hide the contact request sent container element.
91 * @param {Object} body Conversation body container element.
92 * @return {Object} The messages container element.
94 var hideContactRequestSentContainer = function(body) {
95 return getContactRequestSentContainer(body).addClass('hidden');
99 * Get the footer container element.
101 * @param {Object} footer Conversation footer container element.
102 * @return {Object} The footer container element.
104 var getFooterContentContainer = function(footer) {
105 return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER);
109 * Show the footer container element.
111 * @param {Object} footer Conversation footer container element.
113 var showFooterContent = function(footer) {
114 getFooterContentContainer(footer).removeClass('hidden');
118 * Hide the footer container element.
120 * @param {Object} footer Conversation footer container element.
122 var hideFooterContent = function(footer) {
123 getFooterContentContainer(footer).addClass('hidden');
127 * Get the footer edit mode container element.
129 * @param {Object} footer Conversation footer container element.
130 * @return {Object} The footer container element.
132 var getFooterEditModeContainer = function(footer) {
133 return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER);
137 * Show the footer edit mode container element.
139 * @param {Object} footer Conversation footer container element.
141 var showFooterEditMode = function(footer) {
142 getFooterEditModeContainer(footer).removeClass('hidden');
146 * Hide the footer edit mode container element.
148 * @param {Object} footer Conversation footer container element.
150 var hideFooterEditMode = function(footer) {
151 getFooterEditModeContainer(footer).addClass('hidden');
155 * Get the footer placeholder.
157 * @param {Object} footer Conversation footer container element.
158 * @return {Object} The footer placeholder container element.
160 var getFooterPlaceholderContainer = function(footer) {
161 return footer.find(SELECTORS.PLACEHOLDER_CONTAINER);
165 * Show the footer placeholder
167 * @param {Object} footer Conversation footer container element.
169 var showFooterPlaceholder = function(footer) {
170 getFooterPlaceholderContainer(footer).removeClass('hidden');
174 * Hide the footer placeholder
176 * @param {Object} footer Conversation footer container element.
178 var hideFooterPlaceholder = function(footer) {
179 getFooterPlaceholderContainer(footer).addClass('hidden');
183 * Get the footer Require add as contact container element.
185 * @param {Object} footer Conversation footer container element.
186 * @return {Object} The footer Require add as contact container element.
188 var getFooterRequireContactContainer = function(footer) {
189 return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER);
193 * Show the footer add as contact dialogue.
195 * @param {Object} footer Conversation footer container element.
197 var showFooterRequireContact = function(footer) {
198 getFooterRequireContactContainer(footer).removeClass('hidden');
202 * Hide the footer add as contact dialogue.
204 * @param {Object} footer Conversation footer container element.
206 var hideFooterRequireContact = function(footer) {
207 getFooterRequireContactContainer(footer).addClass('hidden');
211 * Get the footer Required to unblock contact container element.
213 * @param {Object} footer Conversation footer container element.
214 * @return {Object} The footer Required to unblock contact container element.
216 var getFooterRequireUnblockContainer = function(footer) {
217 return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER);
221 * Show the footer Required to unblock contact container element.
223 * @param {Object} footer Conversation footer container element.
225 var showFooterRequireUnblock = function(footer) {
226 getFooterRequireUnblockContainer(footer).removeClass('hidden');
230 * Hide the footer Required to unblock contact container element.
232 * @param {Object} footer Conversation footer container element.
234 var hideFooterRequireUnblock = function(footer) {
235 getFooterRequireUnblockContainer(footer).addClass('hidden');
239 * Get the footer Unable to message contact container element.
241 * @param {Object} footer Conversation footer container element.
242 * @return {Object} The footer Unable to message contact container element.
244 var getFooterUnableToMessageContainer = function(footer) {
245 return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER);
249 * Show the footer Unable to message contact container element.
251 * @param {Object} footer Conversation footer container element.
253 var showFooterUnableToMessage = function(footer) {
254 getFooterUnableToMessageContainer(footer).removeClass('hidden');
258 * Hide the footer Unable to message contact container element.
260 * @param {Object} footer Conversation footer container element.
262 var hideFooterUnableToMessage = function(footer) {
263 getFooterUnableToMessageContainer(footer).addClass('hidden');
267 * Hide all header elements.
269 * @param {Object} header Conversation header container element.
271 var hideAllHeaderElements = function(header) {
272 hideHeaderContent(header);
273 hideHeaderEditMode(header);
274 hideHeaderPlaceholder(header);
278 * Hide all footer dialogues and messages.
280 * @param {Object} footer Conversation footer container element.
282 var hideAllFooterElements = function(footer) {
283 hideFooterContent(footer);
284 hideFooterEditMode(footer);
285 hideFooterPlaceholder(footer);
286 hideFooterRequireContact(footer);
287 hideFooterRequireUnblock(footer);
288 hideFooterUnableToMessage(footer);
292 * Get the content placeholder container element.
294 * @param {Object} body Conversation body container element.
295 * @return {Object} The body placeholder container element.
297 var getContentPlaceholderContainer = function(body) {
298 return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER);
302 * Show the content placeholder.
304 * @param {Object} body Conversation body container element.
306 var showContentPlaceholder = function(body) {
307 getContentPlaceholderContainer(body).removeClass('hidden');
311 * Hide the content placeholder.
313 * @param {Object} body Conversation body container element.
315 var hideContentPlaceholder = function(body) {
316 getContentPlaceholderContainer(body).addClass('hidden');
320 * Get the header content container element.
322 * @param {Object} header Conversation header container element.
323 * @return {Object} The header content container element.
325 var getHeaderContent = function(header) {
326 return header.find(SELECTORS.HEADER);
330 * Show the header content.
332 * @param {Object} header Conversation header container element.
334 var showHeaderContent = function(header) {
335 getHeaderContent(header).removeClass('hidden');
339 * Hide the header content.
341 * @param {Object} header Conversation header container element.
343 var hideHeaderContent = function(header) {
344 getHeaderContent(header).addClass('hidden');
348 * Get the header edit mode container element.
350 * @param {Object} header Conversation header container element.
351 * @return {Object} The header content container element.
353 var getHeaderEditMode = function(header) {
354 return header.find(SELECTORS.HEADER_EDIT_MODE);
358 * Show the header edit mode container.
360 * @param {Object} header Conversation header container element.
362 var showHeaderEditMode = function(header) {
363 getHeaderEditMode(header).removeClass('hidden');
367 * Hide the header edit mode container.
369 * @param {Object} header Conversation header container element.
371 var hideHeaderEditMode = function(header) {
372 getHeaderEditMode(header).addClass('hidden');
376 * Get the header placeholder container element.
378 * @param {Object} header Conversation header container element.
379 * @return {Object} The header placeholder container element.
381 var getHeaderPlaceholderContainer = function(header) {
382 return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER);
386 * Show the header placeholder.
388 * @param {Object} header Conversation header container element.
390 var showHeaderPlaceholder = function(header) {
391 getHeaderPlaceholderContainer(header).removeClass('hidden');
395 * Hide the header placeholder.
397 * @param {Object} header Conversation header container element.
399 var hideHeaderPlaceholder = function(header) {
400 getHeaderPlaceholderContainer(header).addClass('hidden');
404 * Get the text input area element.
406 * @param {Object} footer Conversation footer container element.
407 * @return {Object} The footer placeholder container element.
409 var getMessageTextArea = function(footer) {
410 return footer.find(SELECTORS.MESSAGE_TEXT_AREA);
414 * Get a message element.
416 * @param {Object} body Conversation body container element.
417 * @param {Number} messageId the Message id.
418 * @return {Object} A message element from the conversation.
420 var getMessageElement = function(body, messageId) {
421 var messagesContainer = getMessagesContainer(body);
422 return messagesContainer.find('[data-message-id="' + messageId + '"]');
426 * Get the day container element. The day container element holds a list of messages for that day.
428 * @param {Object} body Conversation body container element.
429 * @param {Number} dayTimeCreated Midnight timestamp for the day.
430 * @return {Object} jQuery object
432 var getDayElement = function(body, dayTimeCreated) {
433 var messagesContainer = getMessagesContainer(body);
434 return messagesContainer.find('[data-day-id="' + dayTimeCreated + '"]');
438 * Get the more messages loading icon container element.
440 * @param {Object} body Conversation body container element.
441 * @return {Object} The more messages loading container element.
443 var getMoreMessagesLoadingIconContainer = function(body) {
444 return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER);
448 * Show the more messages loading icon.
450 * @param {Object} body Conversation body container element.
452 var showMoreMessagesLoadingIcon = function(body) {
453 getMoreMessagesLoadingIconContainer(body).removeClass('hidden');
457 * Hide the more messages loading icon.
459 * @param {Object} body Conversation body container element.
461 var hideMoreMessagesLoadingIcon = function(body) {
462 getMoreMessagesLoadingIconContainer(body).addClass('hidden');
466 * Disable the message controls for sending a message.
468 * @param {Object} footer Conversation footer container element.
470 var disableSendMessage = function(footer) {
471 footer.find(SELECTORS.SEND_MESSAGE_BUTTON).prop('disabled', true);
472 getMessageTextArea(footer).prop('disabled', true);
476 * Enable the message controls for sending a message.
478 * @param {Object} footer Conversation footer container element.
480 var enableSendMessage = function(footer) {
481 footer.find(SELECTORS.SEND_MESSAGE_BUTTON).prop('disabled', false);
482 getMessageTextArea(footer).prop('disabled', false);
486 * Show the sending message loading icon and disable sending more.
488 * @param {Object} footer Conversation footer container element.
490 var startSendMessageLoading = function(footer) {
491 disableSendMessage(footer);
492 footer.find(SELECTORS.SEND_MESSAGE_ICON_CONTAINER).addClass('hidden');
493 footer.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
497 * Hide the sending message loading icon and allow sending new messages.
499 * @param {Object} footer Conversation footer container element.
501 var stopSendMessageLoading = function(footer) {
502 enableSendMessage(footer);
503 footer.find(SELECTORS.SEND_MESSAGE_ICON_CONTAINER).removeClass('hidden');
504 footer.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
508 * Clear out message text input and focus the input element.
510 * @param {Object} footer Conversation footer container element.
512 var hasSentMessage = function(footer) {
513 var textArea = getMessageTextArea(footer);
519 * Get the confirm dialogue container element.
521 * @param {Object} root The container element to search.
522 * @return {Object} The confirm dialogue container element.
524 var getConfirmDialogueContainer = function(root) {
525 return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER);
529 * Show the confirm dialogue container element.
531 * @param {Object} root The container element containing a dialogue.
533 var showConfirmDialogueContainer = function(root) {
534 var container = getConfirmDialogueContainer(root);
535 var siblings = container.siblings(':not(.hidden)');
536 siblings.attr('aria-hidden', true);
537 siblings.attr('tabindex', -1);
538 siblings.attr('data-confirm-dialogue-hidden', true);
540 container.removeClass('hidden');
544 * Hide the confirm dialogue container element.
546 * @param {Object} root The container element containing a dialogue.
548 var hideConfirmDialogueContainer = function(root) {
549 var container = getConfirmDialogueContainer(root);
550 var siblings = container.siblings('[data-confirm-dialogue-hidden="true"]');
551 siblings.removeAttr('aria-hidden');
552 siblings.removeAttr('tabindex');
553 siblings.removeAttr('data-confirm-dialogue-hidden');
555 container.addClass('hidden');
559 * Set the number of selected messages.
561 * @param {Object} header The header container element.
562 * @param {Number} value The new number to display.
564 var setMessagesSelectedCount = function(header, value) {
565 getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value);
569 * Format message for the mustache template, transform camelCase properties to lowercase properties.
571 * @param {Array} messages Array of message objects.
572 * @param {Object} datesCache Cache timestamps and their formatted date string.
573 * @return {Array} Messages formated for mustache template.
575 var formatMessagesForTemplate = function(messages, datesCache) {
576 return messages.map(function(message) {
579 isread: message.isRead,
580 fromloggedinuser: message.fromLoggedInUser,
581 userfrom: message.userFrom,
583 formattedtime: datesCache[message.timeCreated]
589 * Create rendering promises for each day containing messages.
591 * @param {Object} header The header container element.
592 * @param {Object} body The body container element.
593 * @param {Object} footer The footer container element.
594 * @param {Array} days Array of days containing messages.
595 * @param {Object} datesCache Cache timestamps and their formatted date string.
596 * @return {Promise} Days rendering promises.
598 var renderAddDays = function(header, body, footer, days, datesCache) {
599 var messagesContainer = getMessagesContainer(body);
600 var daysRenderPromises = days.map(function(data) {
601 return Templates.render(TEMPLATES.DAY, {
602 timestamp: data.value.timestamp,
603 messages: formatMessagesForTemplate(data.value.messages, datesCache)
607 return $.when.apply($, daysRenderPromises).then(function() {
608 // Wait until all of the rendering is done for each of the days
609 // to ensure they are added to the page in the correct order.
610 days.forEach(function(data, index) {
611 daysRenderPromises[index]
612 .then(function(html) {
614 var element = getDayElement(body, data.before.timestamp);
615 return $(html).insertBefore(element);
617 return messagesContainer.append(html);
630 * Add (more) messages to day containers.
632 * @param {Object} header The header container element.
633 * @param {Object} body The body container element.
634 * @param {Object} footer The footer container element.
635 * @param {Array} messages List of messages.
636 * @param {Object} datesCache Cache timestamps and their formatted date string.
637 * @return {Promise} Messages rendering promises.
639 var renderAddMessages = function(header, body, footer, messages, datesCache) {
640 var messagesData = messages.map(function(data) {
643 var formattedMessages = formatMessagesForTemplate(messagesData, datesCache);
645 return Templates.render(TEMPLATES.MESSAGES, {messages: formattedMessages})
646 .then(function(html) {
647 var messageList = $(html);
648 messages.forEach(function(data) {
649 var messageHtml = messageList.find('[data-message-id="' + data.value.id + '"]');
651 var element = getMessageElement(body, data.before.id);
652 return messageHtml.insertBefore(element);
654 var dayContainer = getDayElement(body, data.day.timestamp);
655 var dayMessagesContainer = dayContainer.find(SELECTORS.DAY_MESSAGES_CONTAINER);
656 return dayMessagesContainer.append(messageHtml);
665 * Remove days from conversation.
667 * @param {Object} body The body container element.
668 * @param {Array} days Array of days to be removed.
670 var renderRemoveDays = function(body, days) {
671 days.forEach(function(data) {
672 getDayElement(body, data.timestamp).remove();
677 * Remove messages from conversation.
679 * @param {Object} body The body container element.
680 * @param {Array} messages Array of messages to be removed.
682 var renderRemoveMessages = function(body, messages) {
683 messages.forEach(function(data) {
684 getMessageElement(body, data.id).remove();
689 * Render the full conversation base on input from the statemanager.
691 * This will pre-load all of the formatted timestamps for each message that
692 * needs to render to reduce the number of networks requests.
694 * @param {Object} header The header container element.
695 * @param {Object} body The body container element.
696 * @param {Object} footer The footer container element.
697 * @param {Object} data The conversation diff.
698 * @return {Object} jQuery promise.
700 var renderConversation = function(header, body, footer, data) {
701 var renderingPromises = [];
702 var hasAddDays = data.days.add.length > 0;
703 var hasAddMessages = data.messages.add.length > 0;
704 var timestampsToFormat = [];
705 var datesCachePromise = $.Deferred().resolve({}).promise();
708 // Search for all of the timeCreated values in all of the messages in all of
709 // the days that we need to render.
710 timestampsToFormat = timestampsToFormat.concat(data.days.add.reduce(function(carry, day) {
711 return carry.concat(day.value.messages.map(function(message) {
712 return message.timeCreated;
717 if (hasAddMessages) {
718 // Search for all of the timeCreated values in all of the messages that we
720 timestampsToFormat = timestampsToFormat.concat(data.messages.add.map(function(message) {
721 return message.value.timeCreated;
725 if (timestampsToFormat.length) {
726 // If we have timestamps then pre-load the formatted version of each of them
727 // in a single request to the server. This saves the templates doing multiple
728 // individual requests.
729 datesCachePromise = Str.get_string('strftimetime24', 'core_langconfig')
730 .then(function(format) {
731 var requests = timestampsToFormat.map(function(timestamp) {
733 timestamp: timestamp,
738 return UserDate.get(requests);
740 .then(function(formattedTimes) {
741 return timestampsToFormat.reduce(function(carry, timestamp, index) {
742 carry[timestamp] = formattedTimes[index];
749 renderingPromises.push(datesCachePromise.then(function(datesCache) {
750 return renderAddDays(header, body, footer, data.days.add, datesCache);
754 if (hasAddMessages) {
755 renderingPromises.push(datesCachePromise.then(function(datesCache) {
756 return renderAddMessages(header, body, footer, data.messages.add, datesCache);
760 if (data.days.remove.length > 0) {
761 renderRemoveDays(body, data.days.remove);
764 if (data.messages.remove.length > 0) {
765 renderRemoveMessages(body, data.messages.remove);
768 return $.when.apply($, renderingPromises);
772 * Render the conversation header.
774 * @param {Object} header The header container element.
775 * @param {Object} body The body container element.
776 * @param {Object} footer The footer container element.
777 * @param {Object} data Data for header.
778 * @return {Object} jQuery promise
780 var renderHeader = function(header, body, footer, data) {
781 var headerContainer = getHeaderContent(header);
782 var template = TEMPLATES.HEADER_PUBLIC;
784 if (data.type == CONVERSATION_TYPES.PRIVATE) {
785 template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS;
788 return Templates.render(template, data.context)
789 .then(function(html, js) {
790 Templates.replaceNodeContents(headerContainer, html, js);
796 * Render the conversation footer.
798 * @param {Object} header The header container element.
799 * @param {Object} body The body container element.
800 * @param {Object} footer The footer container element.
801 * @param {Object} data Data for footer.
802 * @return {Object} jQuery promise.
804 var renderFooter = function(header, body, footer, data) {
805 hideAllFooterElements(footer);
809 return showFooterPlaceholder(footer);
811 return Str.get_strings([
813 key: 'requirecontacttomessage',
814 component: 'core_message',
815 param: data.user.fullname
818 key: 'isnotinyourcontacts',
819 component: 'core_message',
820 param: data.user.fullname
823 .then(function(strings) {
824 var title = strings[1];
825 var text = strings[0];
826 var footerContainer = getFooterRequireContactContainer(footer);
827 footerContainer.find(SELECTORS.TITLE).text(title);
828 footerContainer.find(SELECTORS.TEXT).text(text);
829 showFooterRequireContact(footer);
833 return showFooterEditMode(footer);
835 return showFooterContent(footer);
837 return showFooterRequireUnblock(footer);
838 case 'unable-to-message':
839 return showFooterUnableToMessage(footer);
846 * Scroll to a message in the conversation.
848 * @param {Object} header The header container element.
849 * @param {Object} body The body container element.
850 * @param {Object} footer The footer container element.
851 * @param {Number} messageId Message id.
853 var renderScrollToMessage = function(header, body, footer, messageId) {
854 var messagesContainer = getMessagesContainer(body);
855 var messageElement = getMessageElement(body, messageId);
856 var position = messageElement.position();
857 // Scroll the message container down to the top of the message element.
859 var scrollTop = messagesContainer.scrollTop() + position.top;
860 messagesContainer.scrollTop(scrollTop);
865 * Hide or show the conversation header.
867 * @param {Object} header The header container element.
868 * @param {Object} body The body container element.
869 * @param {Object} footer The footer container element.
870 * @param {Bool} isLoadingMembers Members loading.
872 var renderLoadingMembers = function(header, body, footer, isLoadingMembers) {
873 if (isLoadingMembers) {
874 hideHeaderContent(header);
875 showHeaderPlaceholder(header);
877 showHeaderContent(header);
878 hideHeaderPlaceholder(header);
883 * Hide or show loading conversation messages.
885 * @param {Object} header The header container element.
886 * @param {Object} body The body container element.
887 * @param {Object} footer The footer container element.
888 * @param {Bool} isLoadingFirstMessages Messages loading.
890 var renderLoadingFirstMessages = function(header, body, footer, isLoadingFirstMessages) {
891 if (isLoadingFirstMessages) {
892 hideMessagesContainer(body);
893 showContentPlaceholder(body);
895 showMessagesContainer(body);
896 hideContentPlaceholder(body);
901 * Hide or show loading more messages.
903 * @param {Object} header The header container element.
904 * @param {Object} body The body container element.
905 * @param {Object} footer The footer container element.
906 * @param {Bool} isLoading Messages loading.
908 var renderLoadingMessages = function(header, body, footer, isLoading) {
910 showMoreMessagesLoadingIcon(body);
912 hideMoreMessagesLoadingIcon(body);
917 * Activate or deactivate send message controls.
919 * @param {Object} header The header container element.
920 * @param {Object} body The body container element.
921 * @param {Object} footer The footer container element.
922 * @param {Bool} isSending Message sending.
924 var renderSendingMessage = function(header, body, footer, isSending) {
926 startSendMessageLoading(footer);
928 stopSendMessageLoading(footer);
929 hasSentMessage(footer);
934 * Show a confirmation dialogue
936 * @param {Object} header The header container element.
937 * @param {Object} body The body container element.
938 * @param {Object} footer The footer container element.
939 * @param {String} buttonSelectors Selectors for the buttons to show.
940 * @param {String} bodyText Text to show in dialogue.
941 * @param {String} headerText Text to show in dialogue header.
942 * @param {Bool} canCancel Can this dialogue be cancelled.
943 * @param {Bool} skipHeader Skip blanking out the header
945 var showConfirmDialogue = function(
955 var dialogue = getConfirmDialogueContainer(body);
956 var buttons = buttonSelectors.map(function(selector) {
957 return dialogue.find(selector);
959 var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
960 var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
961 var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);
963 dialogue.find('button').addClass('hidden');
966 cancelButton.removeClass('hidden');
968 cancelButton.addClass('hidden');
972 dialogueHeader.removeClass('hidden');
973 dialogueHeader.text(headerText);
975 dialogueHeader.addClass('hidden');
976 dialogueHeader.text('');
979 buttons.forEach(function(button) {
980 button.removeClass('hidden');
983 showConfirmDialogueContainer(footer);
984 showConfirmDialogueContainer(body);
987 showConfirmDialogueContainer(header);
990 dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();
996 * @param {Object} header The header container element.
997 * @param {Object} body The body container element.
998 * @param {Object} footer The footer container element.
999 * @return {Bool} always true.
1001 var hideConfirmDialogue = function(header, body, footer) {
1002 var dialogue = getConfirmDialogueContainer(body);
1003 var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
1004 var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
1005 var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);
1007 hideConfirmDialogueContainer(body);
1008 hideConfirmDialogueContainer(footer);
1009 hideConfirmDialogueContainer(header);
1010 dialogue.find('button').addClass('hidden');
1011 cancelButton.removeClass('hidden');
1013 dialogueHeader.addClass('hidden');
1014 dialogueHeader.text('');
1016 header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus();
1021 * Render the confirm block user dialogue.
1023 * @param {Object} header The header container element.
1024 * @param {Object} body The body container element.
1025 * @param {Object} footer The footer container element.
1026 * @param {Object} user User to block.
1027 * @return {Object} jQuery promise
1029 var renderConfirmBlockUser = function(header, body, footer, user) {
1031 return Str.get_string('blockuserconfirm', 'core_message', user.fullname)
1032 .then(function(string) {
1033 return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_BLOCK], string, '', true, false);
1036 return hideConfirmDialogue(header, body, footer);
1041 * Render the confirm unblock user dialogue.
1043 * @param {Object} header The header container element.
1044 * @param {Object} body The body container element.
1045 * @param {Object} footer The footer container element.
1046 * @param {Object} user User to unblock.
1047 * @return {Object} jQuery promise
1049 var renderConfirmUnblockUser = function(header, body, footer, user) {
1051 return Str.get_string('unblockuserconfirm', 'core_message', user.fullname)
1052 .then(function(string) {
1053 return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_UNBLOCK], string, '', true, false);
1056 return hideConfirmDialogue(header, body, footer);
1061 * Render the add user as contact dialogue.
1063 * @param {Object} header The header container element.
1064 * @param {Object} body The body container element.
1065 * @param {Object} footer The footer container element.
1066 * @param {Object} user User to add as contact.
1067 * @return {Object} jQuery promise
1069 var renderConfirmAddContact = function(header, body, footer, user) {
1071 return Str.get_string('addcontactconfirm', 'core_message', user.fullname)
1072 .then(function(string) {
1073 return showConfirmDialogue(
1077 [SELECTORS.ACTION_CONFIRM_ADD_CONTACT],
1085 return hideConfirmDialogue(header, body, footer);
1090 * Render the remove user from contacts dialogue.
1092 * @param {Object} header The header container element.
1093 * @param {Object} body The body container element.
1094 * @param {Object} footer The footer container element.
1095 * @param {Object} user User to remove from contacts.
1096 * @return {Object} jQuery promise
1098 var renderConfirmRemoveContact = function(header, body, footer, user) {
1100 return Str.get_string('removecontactconfirm', 'core_message', user.fullname)
1101 .then(function(string) {
1102 return showConfirmDialogue(
1106 [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT],
1114 return hideConfirmDialogue(header, body, footer);
1119 * Render the delete selected messages dialogue.
1121 * @param {Object} header The header container element.
1122 * @param {Object} body The body container element.
1123 * @param {Object} footer The footer container element.
1124 * @param {Bool} show If the dialogue should show.
1125 * @return {Object} jQuery promise
1127 var renderConfirmDeleteSelectedMessages = function(header, body, footer, show) {
1129 return Str.get_string('deleteselectedmessagesconfirm', 'core_message')
1130 .then(function(string) {
1131 return showConfirmDialogue(
1135 [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES],
1143 return hideConfirmDialogue(header, body, footer);
1148 * Render the confirm delete conversation dialogue.
1150 * @param {Object} header The header container element.
1151 * @param {Object} body The body container element.
1152 * @param {Object} footer The footer container element.
1153 * @param {Bool} show If the dialogue should show
1154 * @return {Object} jQuery promise
1156 var renderConfirmDeleteConversation = function(header, body, footer, show) {
1158 return Str.get_string('deleteallconfirm', 'core_message')
1159 .then(function(string) {
1160 return showConfirmDialogue(
1164 [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION],
1172 return hideConfirmDialogue(header, body, footer);
1177 * Render the confirm delete conversation dialogue.
1179 * @param {Object} header The header container element.
1180 * @param {Object} body The body container element.
1181 * @param {Object} footer The footer container element.
1182 * @param {Bool} user The other user object.
1183 * @return {Object} jQuery promise
1185 var renderConfirmContactRequest = function(header, body, footer, user) {
1187 return Str.get_string('userwouldliketocontactyou', 'core_message', user.fullname)
1188 .then(function(string) {
1189 var buttonSelectors = [
1190 SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,
1191 SELECTORS.ACTION_DECLINE_CONTACT_REQUEST
1193 return showConfirmDialogue(header, body, footer, buttonSelectors, string, '', false, true);
1196 return hideConfirmDialogue(header, body, footer);
1201 * Show or hide the block / unblock option in the header dropdown menu.
1203 * @param {Object} header The header container element.
1204 * @param {Object} body The body container element.
1205 * @param {Object} footer The footer container element.
1206 * @param {Bool} isBlocked is user blocked.
1208 var renderIsBlocked = function(header, body, footer, isBlocked) {
1210 header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass('hidden');
1211 header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass('hidden');
1213 header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass('hidden');
1214 header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass('hidden');
1219 * Show or hide the favourite / unfavourite option in the header dropdown menu
1220 * and the favourite star in the header title.
1222 * @param {Object} header The header container element.
1223 * @param {Object} body The body container element.
1224 * @param {Object} footer The footer container element.
1225 * @param {Bool} isFavourite is this conversation a favourite.
1227 var renderIsFavourite = function(header, body, footer, state) {
1228 var favouriteIcon = header.find(SELECTORS.FAVOURITE_ICON_CONTAINER);
1229 var addFavourite = header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE);
1230 var removeFavourite = header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE);
1234 favouriteIcon.addClass('hidden');
1235 addFavourite.addClass('hidden');
1236 removeFavourite.addClass('hidden');
1239 favouriteIcon.addClass('hidden');
1240 addFavourite.removeClass('hidden');
1241 removeFavourite.addClass('hidden');
1244 favouriteIcon.removeClass('hidden');
1245 addFavourite.addClass('hidden');
1246 removeFavourite.removeClass('hidden');
1252 * Show or hide the add / remove user as contact option in the header dropdown menu.
1254 * @param {Object} header The header container element.
1255 * @param {Object} body The body container element.
1256 * @param {Object} footer The footer container element.
1257 * @param {Bool} state the contact state.
1259 var renderIsContact = function(header, body, footer, state) {
1260 var addContact = header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT);
1261 var removeContact = header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT);
1264 case 'pending-contact':
1265 addContact.addClass('hidden');
1266 removeContact.addClass('hidden');
1269 addContact.addClass('hidden');
1270 removeContact.removeClass('hidden');
1273 addContact.removeClass('hidden');
1274 removeContact.addClass('hidden');
1280 * Show or hide confirm action from confirm dialogue is loading.
1282 * @param {Object} header The header container element.
1283 * @param {Object} body The body container element.
1284 * @param {Object} footer The footer container element.
1285 * @param {Bool} isLoading confirm action is loading.
1287 var renderLoadingConfirmAction = function(header, body, footer, isLoading) {
1288 var dialogue = getConfirmDialogueContainer(body);
1289 var buttons = dialogue.find('button');
1290 var buttonText = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT);
1291 var loadingIcon = dialogue.find(SELECTORS.LOADING_ICON_CONTAINER);
1294 buttons.prop('disabled', true);
1295 buttonText.addClass('hidden');
1296 loadingIcon.removeClass('hidden');
1298 buttons.prop('disabled', false);
1299 buttonText.removeClass('hidden');
1300 loadingIcon.addClass('hidden');
1305 * Show or hide the header and footer content for edit mode.
1307 * @param {Object} header The header container element.
1308 * @param {Object} body The body container element.
1309 * @param {Object} footer The footer container element.
1310 * @param {Bool} inEditMode In edit mode or not.
1312 var renderInEditMode = function(header, body, footer, inEditMode) {
1313 var messages = null;
1316 messages = body.find(SELECTORS.MESSAGE_NOT_SELECTED);
1317 messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
1318 hideHeaderContent(header);
1319 showHeaderEditMode(header);
1321 messages = getMessagesContainer(body);
1322 messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
1323 messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
1324 showHeaderContent(header);
1325 hideHeaderEditMode(header);
1330 * Select or unselect messages.
1332 * @param {Object} header The header container element.
1333 * @param {Object} body The body container element.
1334 * @param {Object} footer The footer container element.
1335 * @param {Object} data The messages to select or unselect.
1337 var renderSelectedMessages = function(header, body, footer, data) {
1338 var hasSelectedMessages = data.count > 0;
1340 if (data.add.length) {
1341 data.add.forEach(function(messageId) {
1342 var message = getMessageElement(body, messageId);
1343 message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
1344 message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass('hidden');
1345 message.attr('aria-checked', true);
1349 if (data.remove.length) {
1350 data.remove.forEach(function(messageId) {
1351 var message = getMessageElement(body, messageId);
1353 if (hasSelectedMessages) {
1354 message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
1357 message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
1358 message.attr('aria-checked', false);
1362 setMessagesSelectedCount(header, data.count);
1366 * Show or hide the require add contact panel.
1368 * @param {Object} header The header container element.
1369 * @param {Object} body The body container element.
1370 * @param {Object} footer The footer container element.
1371 * @param {Object} data Whether the user has to be added a a contact.
1372 * @return {Object} jQuery promise
1374 var renderRequireAddContact = function(header, body, footer, data) {
1375 if (data.show && !data.hasMessages) {
1376 return Str.get_strings([
1378 key: 'requirecontacttomessage',
1379 component: 'core_message',
1380 param: data.user.fullname
1383 key: 'isnotinyourcontacts',
1384 component: 'core_message',
1385 param: data.user.fullname
1388 .then(function(strings) {
1389 var title = strings[1];
1390 var text = strings[0];
1391 return showConfirmDialogue(
1395 [SELECTORS.ACTION_REQUEST_ADD_CONTACT],
1403 return hideConfirmDialogue(header, body, footer);
1408 * Show or hide the require add contact panel.
1410 * @param {Object} header The header container element.
1411 * @param {Object} body The body container element.
1412 * @param {Object} footer The footer container element.
1413 * @param {Object} userFullName Full name of the other user.
1414 * @return {Object|true} jQuery promise
1416 var renderContactRequestSent = function(header, body, footer, userFullName) {
1417 var container = getContactRequestSentContainer(body);
1419 return Str.get_string('yourcontactrequestpending', 'core_message', userFullName)
1420 .then(function(string) {
1421 container.find(SELECTORS.TEXT).text(string);
1422 container.removeClass('hidden');
1426 container.addClass('hidden');
1432 * Reset the UI to the initial state.
1434 * @param {Object} header The header container element.
1435 * @param {Object} body The body container element.
1436 * @param {Object} footer The footer container element.
1439 var renderReset = function(header, body, footer) {
1440 hideConfirmDialogue(header, body, footer);
1441 hideContactRequestSentContainer(body);
1442 hideAllHeaderElements(header);
1443 showHeaderPlaceholder(header);
1444 hideAllFooterElements(footer);
1445 showFooterPlaceholder(footer);
1449 var render = function(header, body, footer, patch) {
1452 // Resetting the UI needs to come first, if it's required.
1456 // Any async rendering (stuff that requires templates, strings etc) should
1458 conversation: renderConversation,
1459 header: renderHeader,
1460 footer: renderFooter,
1461 confirmBlockUser: renderConfirmBlockUser,
1462 confirmUnblockUser: renderConfirmUnblockUser,
1463 confirmAddContact: renderConfirmAddContact,
1464 confirmRemoveContact: renderConfirmRemoveContact,
1465 confirmDeleteSelectedMessages: renderConfirmDeleteSelectedMessages,
1466 confirmDeleteConversation: renderConfirmDeleteConversation,
1467 confirmContactRequest: renderConfirmContactRequest,
1468 requireAddContact: renderRequireAddContact,
1469 contactRequestSent: renderContactRequestSent
1472 loadingMembers: renderLoadingMembers,
1473 loadingFirstMessages: renderLoadingFirstMessages,
1474 loadingMessages: renderLoadingMessages,
1475 sendingMessage: renderSendingMessage,
1476 isBlocked: renderIsBlocked,
1477 isContact: renderIsContact,
1478 isFavourite: renderIsFavourite,
1479 loadingConfirmAction: renderLoadingConfirmAction,
1480 inEditMode: renderInEditMode
1483 // Scrolling should be last to make sure everything
1484 // on the page is visible.
1485 scrollToMessage: renderScrollToMessage,
1486 selectedMessages: renderSelectedMessages
1489 // Helper function to process each of the configs above.
1490 var processConfig = function(config) {
1493 for (var key in patch) {
1494 if (config.hasOwnProperty(key)) {
1495 var renderFunc = config[key];
1496 var patchValue = patch[key];
1497 results.push(renderFunc(header, body, footer, patchValue));
1504 // The first config is special because it resets the UI.
1505 var renderingPromises = processConfig(configs[0]);
1506 // The second config is special because it contains async rendering.
1507 renderingPromises = renderingPromises.concat(processConfig(configs[1]));
1509 // Wait for the async rendering to complete before processing the
1510 // rest of the configs, in order.
1511 return $.when.apply($, renderingPromises)
1513 for (var i = 2; i < configs.length; i++) {
1514 processConfig(configs[i]);
1519 .catch(Notification.exception);