MDL-65254 core_message: Fix going back to message view drawer search
[moodle.git] / message / amd / src / message_drawer_view_search.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 search page of the message drawer.
18  *
19  * @module     core_message/message_drawer_view_search
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/notification',
28     'core/pubsub',
29     'core/str',
30     'core/templates',
31     'core_message/message_repository',
32     'core_message/message_drawer_events',
33 ],
34 function(
35     $,
36     CustomEvents,
37     Notification,
38     PubSub,
39     Str,
40     Templates,
41     Repository,
42     Events
43 ) {
45     var MESSAGE_SEARCH_LIMIT = 50;
46     var USERS_SEARCH_LIMIT = 50;
47     var USERS_INITIAL_SEARCH_LIMIT = 3;
49     var SELECTORS = {
50         BLOCK_ICON_CONTAINER: '[data-region="block-icon-container"]',
51         CANCEL_SEARCH_BUTTON: '[data-action="cancel-search"]',
52         CONTACTS_CONTAINER: '[data-region="contacts-container"]',
53         CONTACTS_LIST: '[data-region="contacts-container"] [data-region="list"]',
54         EMPTY_MESSAGE_CONTAINER: '[data-region="empty-message-container"]',
55         LIST: '[data-region="list"]',
56         LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
57         LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]',
58         MESSAGES_LIST: '[data-region="messages-container"] [data-region="list"]',
59         MESSAGES_CONTAINER: '[data-region="messages-container"]',
60         NON_CONTACTS_CONTAINER: '[data-region="non-contacts-container"]',
61         NON_CONTACTS_LIST: '[data-region="non-contacts-container"] [data-region="list"]',
62         SEARCH_ICON_CONTAINER: '[data-region="search-icon-container"]',
63         SEARCH_ACTION: '[data-action="search"]',
64         SEARCH_INPUT: '[data-region="search-input"]',
65         SEARCH_RESULTS_CONTAINER: '[data-region="search-results-container"]',
66         LOAD_MORE_USERS: '[data-action="load-more-users"]',
67         LOAD_MORE_MESSAGES: '[data-action="load-more-messages"]',
68         BUTTON_TEXT: '[data-region="button-text"]',
69         NO_RESULTS_CONTAINTER: '[data-region="no-results-container"]',
70         ALL_CONTACTS_CONTAINER: '[data-region="all-contacts-container"]'
71     };
73     var TEMPLATES = {
74         CONTACTS_LIST: 'core_message/message_drawer_contacts_list',
75         NON_CONTACTS_LIST: 'core_message/message_drawer_non_contacts_list',
76         MESSAGES_LIST: 'core_message/message_drawer_messages_list'
77     };
79     /**
80      * Get the logged in user id.
81      *
82      * @param  {Object} body Search body container element.
83      * @return {Number} User id.
84      */
85     var getLoggedInUserId = function(body) {
86         return body.attr('data-user-id');
87     };
89     /**
90      * Show the no messages container element.
91      *
92      * @param  {Object} body Search body container element.
93      * @return {Object} No messages container element.
94      */
95     var getEmptyMessageContainer = function(body) {
96         return body.find(SELECTORS.EMPTY_MESSAGE_CONTAINER);
97     };
99     /**
100      * Get the search loading icon.
101      *
102      * @param  {Object} header Search header container element.
103      * @return {Object} Loading icon element.
104      */
105     var getLoadingIconContainer = function(header) {
106         return header.find(SELECTORS.LOADING_ICON_CONTAINER);
107     };
109     /**
110      * Get the loading container element.
111      *
112      * @param  {Object} body Search body container element.
113      * @return {Object} Loading container element.
114      */
115     var getLoadingPlaceholder = function(body) {
116         return body.find(SELECTORS.LOADING_PLACEHOLDER);
117     };
119     /**
120      * Get the search icon container.
121      *
122      * @param  {Object} header Search header container element.
123      * @return {Object} Search icon container.
124      */
125     var getSearchIconContainer = function(header) {
126         return header.find(SELECTORS.SEARCH_ICON_CONTAINER);
127     };
129     /**
130      * Get the search input container.
131      *
132      * @param  {Object} header Search header container element.
133      * @return {Object} Search input container.
134      */
135     var getSearchInput = function(header) {
136         return header.find(SELECTORS.SEARCH_INPUT);
137     };
139     /**
140      * Get the search results container.
141      *
142      * @param  {Object} body Search body container element.
143      * @return {Object} Search results container.
144      */
145     var getSearchResultsContainer = function(body) {
146         return body.find(SELECTORS.SEARCH_RESULTS_CONTAINER);
147     };
149     /**
150      * Get the search contacts container.
151      *
152      * @param  {Object} body Search body container element.
153      * @return {Object} Search contacts container.
154      */
155     var getContactsContainer = function(body) {
156         return body.find(SELECTORS.CONTACTS_CONTAINER);
157     };
159     /**
160      * Get the search non contacts container.
161      *
162      * @param  {Object} body Search body container element.
163      * @return {Object} Search non contacts container.
164      */
165     var getNonContactsContainer = function(body) {
166         return body.find(SELECTORS.NON_CONTACTS_CONTAINER);
167     };
169     /**
170      * Get the search messages container.
171      *
172      * @param  {Object} body Search body container element.
173      * @return {Object} Search messages container.
174      */
175     var getMessagesContainer = function(body) {
176         return body.find(SELECTORS.MESSAGES_CONTAINER);
177     };
180     /**
181      * Show the messages empty container.
182      *
183      * @param {Object} body Search body container element.
184      */
185     var showEmptyMessage = function(body) {
186         getEmptyMessageContainer(body).removeClass('hidden');
187     };
189     /**
190      * Hide the messages empty container.
191      *
192      * @param {Object} body Search body container element.
193      */
194     var hideEmptyMessage = function(body) {
195         getEmptyMessageContainer(body).addClass('hidden');
196     };
199     /**
200      * Show the loading icon.
201      *
202      * @param {Object} header Search header container element.
203      */
204     var showLoadingIcon = function(header) {
205         getLoadingIconContainer(header).removeClass('hidden');
206     };
208     /**
209      * Hide the loading icon.
210      *
211      * @param {Object} header Search header container element.
212      */
213     var hideLoadingIcon = function(header) {
214         getLoadingIconContainer(header).addClass('hidden');
215     };
217     /**
218      * Show loading placeholder.
219      *
220      * @param {Object} body Search body container element.
221      */
222     var showLoadingPlaceholder = function(body) {
223         getLoadingPlaceholder(body).removeClass('hidden');
224     };
226     /**
227      * Hide loading placeholder.
228      *
229      * @param {Object} body Search body container element.
230      */
231     var hideLoadingPlaceholder = function(body) {
232         getLoadingPlaceholder(body).addClass('hidden');
233     };
235     /**
236      * Show search icon.
237      *
238      * @param {Object} header Search header container element.
239      */
240     var showSearchIcon = function(header) {
241         getSearchIconContainer(header).removeClass('hidden');
242     };
244     /**
245      * Hide search icon.
246      *
247      * @param {Object} header Search header container element.
248      */
249     var hideSearchIcon = function(header) {
250         getSearchIconContainer(header).addClass('hidden');
251     };
253     /**
254      * Show search results.
255      *
256      * @param {Object} body Search body container element.
257      */
258     var showSearchResults = function(body) {
259         getSearchResultsContainer(body).removeClass('hidden');
260     };
262     /**
263      * Hide search results.
264      *
265      * @param {Object} body Search body container element.
266      */
267     var hideSearchResults = function(body) {
268         getSearchResultsContainer(body).addClass('hidden');
269     };
271     /**
272      * Show the no search results message.
273      *
274      * @param {Object} body Search body container element.
275      */
276     var showNoSearchResults = function(body) {
277         var container = getSearchResultsContainer(body);
278         container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden');
279         container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden');
280         container.find(SELECTORS.NO_RESULTS_CONTAINTER).removeClass('hidden');
281     };
283     /**
284      * Hide the no search results message.
285      *
286      * @param {Object} body Search body container element.
287      */
288     var hideNoSearchResults = function(body) {
289         var container = getSearchResultsContainer(body);
290         container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden');
291         container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden');
292         container.find(SELECTORS.NO_RESULTS_CONTAINTER).addClass('hidden');
293     };
295     /**
296      * Show the whole contacts results area.
297      *
298      * @param {Object} body Search body container element.
299      */
300     var showAllContactsSearchResults = function(body) {
301         var container = getSearchResultsContainer(body);
302         container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden');
303     };
305     /**
306      * Hide the whole contacts results area.
307      *
308      * @param {Object} body Search body container element.
309      */
310     var hideAllContactsSearchResults = function(body) {
311         var container = getSearchResultsContainer(body);
312         container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden');
313     };
315     /**
316      * Show the contacts results.
317      *
318      * @param {Object} body Search body container element.
319      */
320     var showContactsSearchResults = function(body) {
321         var container = getSearchResultsContainer(body);
322         container.find(SELECTORS.CONTACTS_CONTAINER).removeClass('hidden');
323     };
325     /**
326      * Hide the contacts results.
327      *
328      * @param {Object} body Search body container element.
329      */
330     var hideContactsSearchResults = function(body) {
331         var container = getSearchResultsContainer(body);
332         container.find(SELECTORS.CONTACTS_CONTAINER).addClass('hidden');
333     };
335     /**
336      * Show the non contacts results.
337      *
338      * @param {Object} body Search body container element.
339      */
340     var showNonContactsSearchResults = function(body) {
341         var container = getSearchResultsContainer(body);
342         container.find(SELECTORS.NON_CONTACTS_CONTAINER).removeClass('hidden');
343     };
345     /**
346      * Hide the non contacts results.
347      *
348      * @param {Object} body Search body container element.
349      */
350     var hideNonContactsSearchResults = function(body) {
351         var container = getSearchResultsContainer(body);
352         container.find(SELECTORS.NON_CONTACTS_CONTAINER).addClass('hidden');
353     };
355     /**
356      * Show the messages results.
357      *
358      * @param {Object} body Search body container element.
359      */
360     var showMessagesSearchResults = function(body) {
361         var container = getSearchResultsContainer(body);
362         container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden');
363     };
365     /**
366      * Hide the messages results.
367      *
368      * @param {Object} body Search body container element.
369      */
370     var hideMessagesSearchResults = function(body) {
371         var container = getSearchResultsContainer(body);
372         container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden');
373     };
375     /**
376      * Disable the search input.
377      *
378      * @param {Object} header Search header container element.
379      */
380     var disableSearchInput = function(header) {
381         getSearchInput(header).prop('disabled', true);
382     };
384     /**
385      * Enable the search input.
386      *
387      * @param {Object} header Search header container element.
388      */
389     var enableSearchInput = function(header) {
390         getSearchInput(header).prop('disabled', false);
391     };
393     /**
394      * Clear the search input.
395      *
396      * @param {Object} header Search header container element.
397      */
398     var clearSearchInput = function(header) {
399         getSearchInput(header).val('');
400     };
402     /**
403      * Clear all search results
404      *
405      * @param {Object} body Search body container element.
406      */
407     var clearAllSearchResults = function(body) {
408         body.find(SELECTORS.CONTACTS_LIST).empty();
409         body.find(SELECTORS.NON_CONTACTS_LIST).empty();
410         body.find(SELECTORS.MESSAGES_LIST).empty();
411         hideNoSearchResults(body);
412         showAllContactsSearchResults(body);
413         showContactsSearchResults(body);
414         showNonContactsSearchResults(body);
415         showMessagesSearchResults(body);
416         showLoadMoreUsersButton(body);
417         showLoadMoreMessagesButton(body);
418     };
420     /**
421      * Update the body and header to indicate the search is loading.
422      *
423      * @param {Object} header Search header container element.
424      * @param {Object} body Search body container element.
425      */
426     var startLoading = function(header, body) {
427         hideSearchIcon(header);
428         hideEmptyMessage(body);
429         hideSearchResults(body);
430         showLoadingIcon(header);
431         showLoadingPlaceholder(body);
432         disableSearchInput(header);
433     };
435     /**
436      * Update the body and header to indicate the search has stopped loading.
437      *
438      * @param {Object} header Search header container element.
439      * @param {Object} body Search body container element.
440      */
441     var stopLoading = function(header, body) {
442         showSearchIcon(header);
443         hideEmptyMessage(body);
444         showSearchResults(body);
445         hideLoadingIcon(header);
446         hideLoadingPlaceholder(body);
447         enableSearchInput(header);
448     };
450     /**
451      * Show the more users loading icon.
452      *
453      * @param {Object} root The more users container element.
454      */
455     var showUsersLoadingIcon = function(root) {
456         var button = root.find(SELECTORS.LOAD_MORE_USERS);
457         button.prop('disabled', true);
458         button.find(SELECTORS.BUTTON_TEXT).addClass('hidden');
459         button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
460     };
462     /**
463      * Hide the more users loading icon.
464      *
465      * @param {Object} root The more users container element.
466      */
467     var hideUsersLoadingIcon = function(root) {
468         var button = root.find(SELECTORS.LOAD_MORE_USERS);
469         button.prop('disabled', false);
470         button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden');
471         button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
472     };
474     /**
475      * Show the load more users button.
476      *
477      * @param {Object} root The users container element.
478      */
479     var showLoadMoreUsersButton = function(root) {
480         root.find(SELECTORS.LOAD_MORE_USERS).removeClass('hidden');
481     };
483     /**
484      * Hide the load more users button.
485      *
486      * @param {Object} root The users container element.
487      */
488     var hideLoadMoreUsersButton = function(root) {
489         root.find(SELECTORS.LOAD_MORE_USERS).addClass('hidden');
490     };
492     /**
493      * Show the messages are loading icon.
494      *
495      * @param {Object} root Messages root element.
496      */
497     var showMessagesLoadingIcon = function(root) {
498         var button = root.find(SELECTORS.LOAD_MORE_MESSAGES);
499         button.prop('disabled', true);
500         button.find(SELECTORS.BUTTON_TEXT).addClass('hidden');
501         button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
502     };
504     /**
505      * Hide the messages are loading icon.
506      *
507      * @param {Object} root Messages root element.
508      */
509     var hideMessagesLoadingIcon = function(root) {
510         var button = root.find(SELECTORS.LOAD_MORE_MESSAGES);
511         button.prop('disabled', false);
512         button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden');
513         button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
514     };
516     /**
517      * Show the load more messages button.
518      *
519      * @param  {Object} root The messages container element.
520      */
521     var showLoadMoreMessagesButton = function(root) {
522         root.find(SELECTORS.LOAD_MORE_MESSAGES).removeClass('hidden');
523     };
525     /**
526      * Hide the load more messages button.
527      *
528      * @param  {Object} root The messages container element.
529      */
530     var hideLoadMoreMessagesButton = function(root) {
531         root.find(SELECTORS.LOAD_MORE_MESSAGES).addClass('hidden');
532     };
534     /**
535      * Find a contact in the search results.
536      *
537      * @param  {Object} root Search results container element.
538      * @param  {Number} userId User id.
539      * @return {Object} User container element.
540      */
541     var findContact = function(root, userId) {
542         return root.find('[data-contact-user-id="' + userId + '"]');
543     };
545     /**
546      * Add a contact to the search results.
547      *
548      * @param {Object} root Search results container.
549      * @param {Object} contact User in contacts list.
550      */
551     var addContact = function(root, contact) {
552         var nonContactsContainer = getNonContactsContainer(root);
553         var nonContact = findContact(nonContactsContainer, contact.userid);
555         if (nonContact.length) {
556             nonContact.remove();
557             var contactsContainer = getContactsContainer(root);
558             contactsContainer.removeClass('hidden');
559             contactsContainer.find(SELECTORS.LIST).append(nonContact);
560         }
562         if (!nonContactsContainer.find(SELECTORS.LIST).children().length) {
563             nonContactsContainer.addClass('hidden');
564         }
565     };
567     /**
568      * Remove a contact from the contacts results.
569      *
570      * @param {Object} root Search results container.
571      * @param {Object} userId Contact user id.
572      */
573     var removeContact = function(root, userId) {
574         var contactsContainer = getContactsContainer(root);
575         var contact = findContact(contactsContainer, userId);
577         if (contact.length) {
578             contact.remove();
579             var nonContactsContainer = getNonContactsContainer(root);
580             nonContactsContainer.removeClass('hidden');
581             nonContactsContainer.find(SELECTORS.LIST).append(contact);
582         }
584         if (!contactsContainer.find(SELECTORS.LIST).children().length) {
585             contactsContainer.addClass('hidden');
586         }
587     };
589     /**
590      * Show the contact is blocked icon.
591      *
592      * @param {Object} root Search results container.
593      * @param {Object} userId Contact user id.
594      */
595     var blockContact = function(root, userId) {
596         var contact = findContact(root, userId);
597         if (contact.length) {
598             contact.find(SELECTORS.BLOCK_ICON_CONTAINER).removeClass('hidden');
599         }
600     };
602     /**
603      * Hide the contact is blocked icon.
604      *
605      * @param {Object} root Search results container.
606      * @param {Object} userId Contact user id.
607      */
608     var unblockContact = function(root, userId) {
609         var contact = findContact(root, userId);
610         if (contact.length) {
611             contact.find(SELECTORS.BLOCK_ICON_CONTAINER).addClass('hidden');
612         }
613     };
615     /**
616      * Render contacts in the contacts search results.
617      *
618      * @param {Object} root Search results container.
619      * @param {Array} contacts List of contacts.
620      * @return {Promise} Renderer promise.
621      */
622     var renderContacts = function(root, contacts) {
623         var container = getContactsContainer(root);
624         var list = container.find(SELECTORS.LIST);
626         return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: contacts})
627             .then(function(html) {
628                 list.append(html);
629                 return html;
630             });
631     };
633     /**
634      * Render non contacts in the contacts search results.
635      *
636      * @param {Object} root Search results container.
637      * @param {Array} nonContacts List of non contacts.
638      * @return {Promise} Renderer promise.
639      */
640     var renderNonContacts = function(root, nonContacts) {
641         var container = getNonContactsContainer(root);
642         var list = container.find(SELECTORS.LIST);
644         return Templates.render(TEMPLATES.NON_CONTACTS_LIST, {noncontacts: nonContacts})
645             .then(function(html) {
646                 list.append(html);
647                 return html;
648             });
649     };
651     /**
652      * Render messages in the messages search results.
653      *
654      * @param {Object} root Search results container.
655      * @param {Array} messages List of messages.
656      * @return {Promise} Renderer promise.
657      */
658     var renderMessages = function(root, messages) {
659         var container = getMessagesContainer(root);
660         var list = container.find(SELECTORS.LIST);
662         return Templates.render(TEMPLATES.MESSAGES_LIST, {messages: messages})
663             .then(function(html) {
664                 list.append(html);
665                 return html;
666             });
667     };
669     /**
670      * Load more users from the repository and render the results into the users search results.
671      *
672      * @param  {Object} root Search results container.
673      * @param  {Number} loggedInUserId Current logged in user.
674      * @param  {String} text Search text.
675      * @param  {Number} limit Number of users to get.
676      * @param  {Number} offset Load users from
677      * @return {Object} jQuery promise
678      */
679     var loadMoreUsers = function(root, loggedInUserId, text, limit, offset) {
680         var loadedAll = false;
681         showUsersLoadingIcon(root);
683         return Repository.searchUsers(loggedInUserId, text, limit + 1, offset)
684             .then(function(results) {
685                 var contacts = results.contacts;
686                 var noncontacts = results.noncontacts;
688                 if (contacts.length <= limit && noncontacts.length <= limit) {
689                     loadedAll = true;
690                     return {
691                         contacts: contacts,
692                         noncontacts: noncontacts
693                     };
694                 } else {
695                     return {
696                         contacts: contacts.slice(0, limit),
697                         noncontacts: noncontacts.slice(0, limit)
698                     };
699                 }
700             })
701             .then(function(results) {
702                 var contactsCount = results.contacts.length;
703                 var nonContactsCount = results.noncontacts.length;
705                 return $.when(
706                     contactsCount ? renderContacts(root, results.contacts) : true,
707                     nonContactsCount ? renderNonContacts(root, results.noncontacts) : true
708                 )
709                 .then(function() {
710                     return {
711                         contactsCount: contactsCount,
712                         nonContactsCount: nonContactsCount
713                     };
714                 });
715             })
716             .then(function(counts) {
717                 hideUsersLoadingIcon(root);
719                 if (loadedAll) {
720                     hideLoadMoreUsersButton(root);
721                 }
723                 return counts;
724             })
725             .catch(function(error) {
726                 hideUsersLoadingIcon(root);
727                 // Rethrow error for other handlers.
728                 throw error;
729             });
730     };
732     /**
733      * Load more messages from the repository and render the results into the messages search results.
734      *
735      * @param  {Object} root Search results container.
736      * @param  {Number} loggedInUserId Current logged in user.
737      * @param  {String} text Search text.
738      * @param  {Number} limit Number of messages to get.
739      * @param  {Number} offset Load messages from
740      * @return {Object} jQuery promise
741      */
742     var loadMoreMessages = function(root, loggedInUserId, text, limit, offset) {
743         var loadedAll = false;
744         showMessagesLoadingIcon(root);
746         return Repository.searchMessages(loggedInUserId, text, limit + 1, offset)
747             .then(function(results) {
748                 var messages = results.contacts;
750                 if (messages.length <= limit) {
751                     loadedAll = true;
752                     return messages;
753                 } else {
754                     return messages.slice(0, limit);
755                 }
756             })
757             .then(function(messages) {
758                 if (messages.length) {
759                     return renderMessages(root, messages)
760                         .then(function() {
761                             return messages.length;
762                         });
763                 } else {
764                     return messages.length;
765                 }
766             })
767             .then(function(count) {
768                 hideMessagesLoadingIcon(root);
770                 if (loadedAll) {
771                     hideLoadMoreMessagesButton(root);
772                 }
774                 return count;
775             })
776             .catch(function(error) {
777                 hideMessagesLoadingIcon(root);
778                 // Rethrow error for other handlers.
779                 throw error;
780             });
781     };
783     /**
784      * Search for users and messages.
785      *
786      * @param {Object} header Search header container element.
787      * @param {Object} body Search body container element.
788      * @param {String} searchText Search text.
789      * @param {Number} usersLimit The users limit.
790      * @param {Number} usersOffset The users offset.
791      * @param {Number} messagesLimit The message limit.
792      * @param {Number} messagesOffset The message offset.
793      * @return {Object} jQuery promise
794      */
795     var search = function(header, body, searchText, usersLimit, usersOffset, messagesLimit, messagesOffset) {
796         var loggedInUserId = getLoggedInUserId(body);
797         startLoading(header, body);
798         clearAllSearchResults(body);
800         return $.when(
801             loadMoreUsers(body, loggedInUserId, searchText, usersLimit, usersOffset),
802             loadMoreMessages(body, loggedInUserId, searchText, messagesLimit, messagesOffset)
803         )
804         .then(function(userCounts, messagesCount) {
805             var contactsCount = userCounts.contactsCount;
806             var nonContactsCount = userCounts.nonContactsCount;
808             stopLoading(header, body);
810             if (!contactsCount && !nonContactsCount && !messagesCount) {
811                 showNoSearchResults(body);
812             } else {
813                 if (!contactsCount && !nonContactsCount) {
814                     hideAllContactsSearchResults(body);
815                 } else {
816                     if (!contactsCount) {
817                         hideContactsSearchResults(body);
818                     }
820                     if (!nonContactsCount) {
821                         hideNonContactsSearchResults(body);
822                     }
823                 }
825                 if (!messagesCount) {
826                     hideMessagesSearchResults(body);
827                 }
828             }
830             return;
831         });
832     };
835     /**
836      * Listen to and handle events for searching.
837      *
838      * @param {Object} header Search header container element.
839      * @param {Object} body Search body container element.
840      */
841     var registerEventListeners = function(header, body) {
842         var loggedInUserId = getLoggedInUserId(body);
843         var searchInput = getSearchInput(header);
844         var searchText = '';
845         var messagesOffset = 0;
846         var usersOffset = 0;
848         var searchEventHandler = function(e, data) {
849             searchText = searchInput.val().trim();
851             if (searchText !== '') {
852                 messagesOffset = 0;
853                 usersOffset = 0;
854                 search(
855                     header,
856                     body,
857                     searchText,
858                     USERS_INITIAL_SEARCH_LIMIT,
859                     usersOffset,
860                     MESSAGE_SEARCH_LIMIT,
861                     messagesOffset
862                 )
863                 .then(function() {
864                     searchInput.focus();
865                     usersOffset = usersOffset + USERS_INITIAL_SEARCH_LIMIT;
866                     messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;
867                     return;
868                 })
869                 .catch(Notification.exception);
870             }
872             data.originalEvent.preventDefault();
873         };
875         CustomEvents.define(searchInput, [CustomEvents.events.enter]);
876         CustomEvents.define(header, [CustomEvents.events.activate]);
877         CustomEvents.define(body, [CustomEvents.events.activate]);
879         searchInput.on(CustomEvents.events.enter, searchEventHandler);
881         header.on(CustomEvents.events.activate, SELECTORS.SEARCH_ACTION, searchEventHandler);
883         body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_MESSAGES, function(e, data) {
884             if (searchText !== '') {
885                 loadMoreMessages(body, loggedInUserId, searchText, MESSAGE_SEARCH_LIMIT, messagesOffset)
886                     .then(function() {
887                         messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;
888                         return;
889                     })
890                     .catch(Notification.exception);
891             }
892             data.originalEvent.preventDefault();
893         });
895         body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_USERS, function(e, data) {
896             if (searchText !== '') {
897                 loadMoreUsers(body, loggedInUserId, searchText, USERS_SEARCH_LIMIT, usersOffset)
898                     .then(function() {
899                         usersOffset = usersOffset + USERS_SEARCH_LIMIT;
900                         return;
901                     })
902                     .catch(Notification.exception);
903             }
904             data.originalEvent.preventDefault();
905         });
907         header.on(CustomEvents.events.activate, SELECTORS.CANCEL_SEARCH_BUTTON, function() {
908             clearSearchInput(header);
909             showEmptyMessage(body);
910             showSearchIcon(header);
911             hideSearchResults(body);
912             hideLoadingIcon(header);
913             hideLoadingPlaceholder(body);
914             usersOffset = 0;
915             messagesOffset = 0;
916         });
918         PubSub.subscribe(Events.CONTACT_ADDED, function(userId) {
919             addContact(body, userId);
920         });
922         PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) {
923             removeContact(body, userId);
924         });
926         PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) {
927             blockContact(body, userId);
928         });
930         PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) {
931             unblockContact(body, userId);
932         });
933     };
935     /**
936      * Setup the search page.
937      *
938      * @param {string} namespace The route namespace.
939      * @param {Object} header Contacts header container element.
940      * @param {Object} body Contacts body container element.
941      * @return {Object} jQuery promise
942      */
943     var show = function(namespace, header, body) {
944         if (!body.attr('data-init')) {
945             registerEventListeners(header, body);
946             body.attr('data-init', true);
947         }
949         var searchInput = getSearchInput(header);
950         searchInput.focus();
952         return $.Deferred().resolve().promise();
953     };
955     /**
956      * String describing this page used for aria-labels.
957      *
958      * @param {string} namespace The route namespace.
959      * @param {Object} header Contacts header container element.
960      * @return {Object} jQuery promise
961      */
962     var description = function(namespace, header) {
963         var searchInput = getSearchInput(header);
964         var searchText = searchInput.val().trim();
965         return Str.get_string('messagedrawerviewsearch', 'core_message', searchText);
966     };
968     return {
969         show: show,
970         description: description
971     };
972 });