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