Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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 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
) {
44
 
45
    var MESSAGE_SEARCH_LIMIT = 50;
46
    var USERS_SEARCH_LIMIT = 50;
47
    var USERS_INITIAL_SEARCH_LIMIT = 3;
48
 
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
    };
72
 
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
    };
78
 
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
    };
88
 
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
    };
98
 
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
    };
108
 
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
    };
118
 
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
    };
128
 
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
    };
138
 
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
    };
148
 
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
    };
158
 
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
    };
168
 
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
    };
178
 
179
 
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
    };
188
 
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
    };
197
 
198
 
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
    };
207
 
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
    };
216
 
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
    };
225
 
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
    };
234
 
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
    };
243
 
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
    };
252
 
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
    };
261
 
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
    };
270
 
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
    };
282
 
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
    };
294
 
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
    };
304
 
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
    };
314
 
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
    };
324
 
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
    };
334
 
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
    };
344
 
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
    };
354
 
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
    };
364
 
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
    };
374
 
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
    };
383
 
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
    };
392
 
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
    };
401
 
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
    };
419
 
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
    };
434
 
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
    };
449
 
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
    };
461
 
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
    };
473
 
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
    };
482
 
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
    };
491
 
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
    };
503
 
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
    };
515
 
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
    };
524
 
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
    };
533
 
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
    };
544
 
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);
554
 
555
        if (nonContact.length) {
556
            nonContact.remove();
557
            var contactsContainer = getContactsContainer(root);
558
            contactsContainer.removeClass('hidden');
559
            contactsContainer.find(SELECTORS.LIST).append(nonContact);
560
        }
561
 
562
        if (!nonContactsContainer.find(SELECTORS.LIST).children().length) {
563
            nonContactsContainer.addClass('hidden');
564
        }
565
    };
566
 
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);
576
 
577
        if (contact.length) {
578
            contact.remove();
579
            var nonContactsContainer = getNonContactsContainer(root);
580
            nonContactsContainer.removeClass('hidden');
581
            nonContactsContainer.find(SELECTORS.LIST).append(contact);
582
        }
583
 
584
        if (!contactsContainer.find(SELECTORS.LIST).children().length) {
585
            contactsContainer.addClass('hidden');
586
        }
587
    };
588
 
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
    };
601
 
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
    };
614
 
615
    /**
616
     * Highlight words in search results.
617
     *
618
     * @param  {String} content HTML to search.
619
     * @param  {String} searchText Search text.
620
     * @return {String} searchText with search wrapped in matchtext span.
621
     */
622
    var highlightSearch = function(content, searchText) {
623
        if (!content) {
624
            return '';
625
        }
626
        var regex = new RegExp('(' + searchText + ')', 'gi');
627
        return content.replace(regex, '<span class="matchtext">$1</span>');
628
    };
629
 
630
 
631
    /**
632
     * Render contacts in the contacts search results.
633
     *
634
     * @param {Object} root Search results container.
635
     * @param {Array} contacts List of contacts.
636
     * @return {Promise} Renderer promise.
637
     */
638
    var renderContacts = function(root, contacts) {
639
        var container = getContactsContainer(root);
640
        var frompanel = root.attr('data-in-panel');
641
        var list = container.find(SELECTORS.LIST);
642
 
643
        return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: contacts, frompanel: frompanel})
644
            .then(function(html) {
645
                list.append(html);
646
                return html;
647
            });
648
    };
649
 
650
    /**
651
     * Render non contacts in the contacts search results.
652
     *
653
     * @param {Object} root Search results container.
654
     * @param {Array} nonContacts List of non contacts.
655
     * @return {Promise} Renderer promise.
656
     */
657
    var renderNonContacts = function(root, nonContacts) {
658
        var container = getNonContactsContainer(root);
659
        var frompanel = root.attr('data-in-panel');
660
        var list = container.find(SELECTORS.LIST);
661
 
662
        return Templates.render(TEMPLATES.NON_CONTACTS_LIST, {noncontacts: nonContacts, frompanel: frompanel})
663
            .then(function(html) {
664
                list.append(html);
665
                return html;
666
            });
667
    };
668
 
669
    /**
670
     * Render messages in the messages search results.
671
     *
672
     * @param {Object} root Search results container.
673
     * @param {Array} messages List of messages.
674
     * @return {Promise} Renderer promise.
675
     */
676
    var renderMessages = function(root, messages) {
677
        var container = getMessagesContainer(root);
678
        var frompanel = root.attr('data-in-panel');
679
        var list = container.find(SELECTORS.LIST);
680
 
681
        return Templates.render(TEMPLATES.MESSAGES_LIST, {messages: messages, frompanel: frompanel})
682
            .then(function(html) {
683
                list.append(html);
684
                return html;
685
            });
686
    };
687
 
688
    /**
689
     * Load more users from the repository and render the results into the users search results.
690
     *
691
     * @param  {Object} root Search results container.
692
     * @param  {Number} loggedInUserId Current logged in user.
693
     * @param  {String} text Search text.
694
     * @param  {Number} limit Number of users to get.
695
     * @param  {Number} offset Load users from
696
     * @return {Object} jQuery promise
697
     */
698
    var loadMoreUsers = function(root, loggedInUserId, text, limit, offset) {
699
        var loadedAll = false;
700
        showUsersLoadingIcon(root);
701
 
702
        return Repository.searchUsers(loggedInUserId, text, limit + 1, offset)
703
            .then(function(results) {
704
                var contacts = results.contacts;
705
                var noncontacts = results.noncontacts;
706
 
707
                if (contacts.length <= limit && noncontacts.length <= limit) {
708
                    loadedAll = true;
709
                    return {
710
                        contacts: contacts,
711
                        noncontacts: noncontacts
712
                    };
713
                } else {
714
                    return {
715
                        contacts: contacts.slice(0, limit),
716
                        noncontacts: noncontacts.slice(0, limit)
717
                    };
718
                }
719
            })
720
            .then(function(results) {
721
                var contactsCount = results.contacts.length;
722
                var nonContactsCount = results.noncontacts.length;
723
 
724
                if (contactsCount) {
725
                    results.contacts.forEach(function(contact) {
726
                        contact.highlight = highlightSearch(contact.fullname, text);
727
                    });
728
                }
729
 
730
                if (nonContactsCount) {
731
                    results.noncontacts.forEach(function(contact) {
732
                        contact.highlight = highlightSearch(contact.fullname, text);
733
                    });
734
                }
735
 
736
                return $.when(
737
                    contactsCount ? renderContacts(root, results.contacts) : true,
738
                    nonContactsCount ? renderNonContacts(root, results.noncontacts) : true
739
                )
740
                .then(function() {
741
                    return {
742
                        contactsCount: contactsCount,
743
                        nonContactsCount: nonContactsCount
744
                    };
745
                });
746
            })
747
            .then(function(counts) {
748
                hideUsersLoadingIcon(root);
749
 
750
                if (loadedAll) {
751
                    hideLoadMoreUsersButton(root);
752
                }
753
 
754
                return counts;
755
            })
756
            .catch(function(error) {
757
                hideUsersLoadingIcon(root);
758
                // Rethrow error for other handlers.
759
                throw error;
760
            });
761
    };
762
 
763
    /**
764
     * Load more messages from the repository and render the results into the messages search results.
765
     *
766
     * @param  {Object} root Search results container.
767
     * @param  {Number} loggedInUserId Current logged in user.
768
     * @param  {String} text Search text.
769
     * @param  {Number} limit Number of messages to get.
770
     * @param  {Number} offset Load messages from
771
     * @return {Object} jQuery promise
772
     */
773
    var loadMoreMessages = function(root, loggedInUserId, text, limit, offset) {
774
        var loadedAll = false;
775
        showMessagesLoadingIcon(root);
776
 
777
        return Repository.searchMessages(loggedInUserId, text, limit + 1, offset)
778
            .then(function(results) {
779
                var messages = results.contacts;
780
 
781
                if (messages.length <= limit) {
782
                    loadedAll = true;
783
                    return messages;
784
                } else {
785
                    return messages.slice(0, limit);
786
                }
787
            })
788
            .then(function(messages) {
789
                if (messages.length) {
790
                    messages.forEach(function(message) {
791
                        message.lastmessage = highlightSearch(message.lastmessage, text);
792
                    });
793
                    return renderMessages(root, messages)
794
                        .then(function() {
795
                            return messages.length;
796
                        });
797
                } else {
798
                    return messages.length;
799
                }
800
            })
801
            .then(function(count) {
802
                hideMessagesLoadingIcon(root);
803
 
804
                if (loadedAll) {
805
                    hideLoadMoreMessagesButton(root);
806
                }
807
 
808
                return count;
809
            })
810
            .catch(function(error) {
811
                hideMessagesLoadingIcon(root);
812
                // Rethrow error for other handlers.
813
                throw error;
814
            });
815
    };
816
 
817
    /**
818
     * Search for users and messages.
819
     *
820
     * @param {Object} header Search header container element.
821
     * @param {Object} body Search body container element.
822
     * @param {String} searchText Search text.
823
     * @param {Number} usersLimit The users limit.
824
     * @param {Number} usersOffset The users offset.
825
     * @param {Number} messagesLimit The message limit.
826
     * @param {Number} messagesOffset The message offset.
827
     * @return {Object} jQuery promise
828
     */
829
    var search = function(header, body, searchText, usersLimit, usersOffset, messagesLimit, messagesOffset) {
830
        var loggedInUserId = getLoggedInUserId(body);
831
        startLoading(header, body);
832
        clearAllSearchResults(body);
833
 
834
        return $.when(
835
            loadMoreUsers(body, loggedInUserId, searchText, usersLimit, usersOffset),
836
            loadMoreMessages(body, loggedInUserId, searchText, messagesLimit, messagesOffset)
837
        )
838
        .then(function(userCounts, messagesCount) {
839
            var contactsCount = userCounts.contactsCount;
840
            var nonContactsCount = userCounts.nonContactsCount;
841
 
842
            stopLoading(header, body);
843
 
844
            if (!contactsCount && !nonContactsCount && !messagesCount) {
845
                showNoSearchResults(body);
846
            } else {
847
                if (!contactsCount && !nonContactsCount) {
848
                    hideAllContactsSearchResults(body);
849
                } else {
850
                    if (!contactsCount) {
851
                        hideContactsSearchResults(body);
852
                    }
853
 
854
                    if (!nonContactsCount) {
855
                        hideNonContactsSearchResults(body);
856
                    }
857
                }
858
 
859
                if (!messagesCount) {
860
                    hideMessagesSearchResults(body);
861
                }
862
            }
863
 
864
            return;
865
        });
866
    };
867
 
868
 
869
    /**
870
     * Listen to and handle events for searching.
871
     *
872
     * @param {Object} header Search header container element.
873
     * @param {Object} body Search body container element.
874
     */
875
    var registerEventListeners = function(header, body) {
876
        var loggedInUserId = getLoggedInUserId(body);
877
        var searchInput = getSearchInput(header);
878
        var searchText = '';
879
        var messagesOffset = 0;
880
        var usersOffset = 0;
881
 
882
        var searchEventHandler = function(e, data) {
883
            searchText = searchInput.val().trim();
884
 
885
            if (searchText !== '') {
886
                messagesOffset = 0;
887
                usersOffset = 0;
888
                search(
889
                    header,
890
                    body,
891
                    searchText,
892
                    USERS_INITIAL_SEARCH_LIMIT,
893
                    usersOffset,
894
                    MESSAGE_SEARCH_LIMIT,
895
                    messagesOffset
896
                )
897
                .then(function() {
898
                    searchInput.focus();
899
                    usersOffset = usersOffset + USERS_INITIAL_SEARCH_LIMIT;
900
                    messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;
901
                    return;
902
                })
903
                .catch(Notification.exception);
904
            }
905
 
906
            data.originalEvent.preventDefault();
907
        };
908
 
909
        CustomEvents.define(searchInput, [CustomEvents.events.enter]);
910
        CustomEvents.define(header, [CustomEvents.events.activate]);
911
        CustomEvents.define(body, [CustomEvents.events.activate]);
912
 
913
        searchInput.on(CustomEvents.events.enter, searchEventHandler);
914
 
915
        header.on(CustomEvents.events.activate, SELECTORS.SEARCH_ACTION, searchEventHandler);
916
 
917
        body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_MESSAGES, function(e, data) {
918
            if (searchText !== '') {
919
                loadMoreMessages(body, loggedInUserId, searchText, MESSAGE_SEARCH_LIMIT, messagesOffset)
920
                    .then(function() {
921
                        messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;
922
                        return;
923
                    })
924
                    .catch(Notification.exception);
925
            }
926
            data.originalEvent.preventDefault();
927
        });
928
 
929
        body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_USERS, function(e, data) {
930
            if (searchText !== '') {
931
                loadMoreUsers(body, loggedInUserId, searchText, USERS_SEARCH_LIMIT, usersOffset)
932
                    .then(function() {
933
                        usersOffset = usersOffset + USERS_SEARCH_LIMIT;
934
                        return;
935
                    })
936
                    .catch(Notification.exception);
937
            }
938
            data.originalEvent.preventDefault();
939
        });
940
 
941
        header.on(CustomEvents.events.activate, SELECTORS.CANCEL_SEARCH_BUTTON, function() {
942
            clearSearchInput(header);
943
            showEmptyMessage(body);
944
            showSearchIcon(header);
945
            hideSearchResults(body);
946
            hideLoadingIcon(header);
947
            hideLoadingPlaceholder(body);
948
            usersOffset = 0;
949
            messagesOffset = 0;
950
        });
951
 
952
        PubSub.subscribe(Events.CONTACT_ADDED, function(userId) {
953
            addContact(body, userId);
954
        });
955
 
956
        PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) {
957
            removeContact(body, userId);
958
        });
959
 
960
        PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) {
961
            blockContact(body, userId);
962
        });
963
 
964
        PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) {
965
            unblockContact(body, userId);
966
        });
967
    };
968
 
969
    /**
970
     * Setup the search page.
971
     *
972
     * @param {string} namespace The route namespace.
973
     * @param {Object} header Contacts header container element.
974
     * @param {Object} body Contacts body container element.
975
     * @return {Object} jQuery promise
976
     */
977
    var show = function(namespace, header, body) {
978
        if (!body.attr('data-init')) {
979
            registerEventListeners(header, body);
980
            body.attr('data-init', true);
981
        }
982
        var searchInput = getSearchInput(header);
983
        searchInput.focus();
984
 
985
        return $.Deferred().resolve().promise();
986
    };
987
 
988
    /**
989
     * String describing this page used for aria-labels.
990
     *
991
     * @param {string} namespace The route namespace.
992
     * @param {Object} header Contacts header container element.
993
     * @return {Object} jQuery promise
994
     */
995
    var description = function(namespace, header) {
996
        if (typeof header !== 'object') {
997
            return Str.get_string('messagedrawerviewsearch', 'core_message');
998
        }
999
        var searchInput = getSearchInput(header);
1000
        var searchText = searchInput.val().trim();
1001
        return Str.get_string('messagedrawerviewsearch', 'core_message', searchText);
1002
    };
1003
 
1004
    return {
1005
        show: show,
1006
        description: description
1007
    };
1008
});