/
var
/
www
/
html
/
gte
/
message
/
amd
/
src
/
Upload File
HOME
// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * The module handles searching contacts. * * @module core_message/message_area_search * @package core_message * @copyright 2016 Mark Nelson <markn@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/custom_interaction_events', 'core_message/message_area_events'], function($, Ajax, Templates, Notification, Str, CustomEvents, Events) { /** @type {Object} The list of selectors for the message area. */ var SELECTORS = { CONTACTS: "[data-region='contacts'][data-region-content='contacts']", CONTACTSAREA: "[data-region='contacts-area']", CONVERSATIONS: "[data-region='contacts'][data-region-content='conversations']", DELETESEARCHFILTER: "[data-region='search-filter-area']", LOADINGICON: '.loading-icon', SEARCHBOX: "[data-region='search-box']", SEARCHFILTER: "[data-region='search-filter']", SEARCHFILTERAREA: "[data-region='search-filter-area']", SEARCHRESULTSAREA: "[data-region='search-results-area']", SEARCHTEXTAREA: "[data-region='search-text-area']", SEARCHUSERSINCOURSE: "[data-action='search-users-in-course']", }; /** * Search class. * * @param {Messagearea} messageArea The messaging area object. */ function Search(messageArea) { this.messageArea = messageArea; this._init(); } /** @type {Messagearea} The messaging area object. */ Search.prototype.messageArea = null; /** @type {String} The area we are searching in. */ Search.prototype._searchArea = null; /** @type {String} The id of the course we are searching in (if any). */ Search.prototype._courseid = null; /** @type {Boolean} checks if we are currently loading */ Search.prototype._isLoading = false; /** @type {String} The number of messages displayed. */ Search.prototype._numMessagesDisplayed = 0; /** @type {String} The number of messages to retrieve. */ Search.prototype._numMessagesToRetrieve = 20; /** @type {String} The number of users displayed. */ Search.prototype._numUsersDisplayed = 0; /** @type {String} The number of users to retrieve. */ Search.prototype._numUsersToRetrieve = 20; /** @type {Array} The type of available search areas. **/ Search.prototype._searchAreas = { MESSAGES: 'messages', USERS: 'users', USERSINCOURSE: 'usersincourse' }; /** @type {int} The timeout before performing an ajax search */ Search.prototype._requestTimeout = null; /** * Initialise the event listeners. * * @private */ Search.prototype._init = function() { // Handle searching for text. this.messageArea.find(SELECTORS.SEARCHTEXTAREA).on('input', this._searchRequest.bind(this)); // Handle clicking on a course in the list of users. this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.SEARCHUSERSINCOURSE, function(e) { this._setFilter($(e.currentTarget).html()); this._setPlaceholderText('searchforuser'); this._clearSearchArea(); this._searchArea = this._searchAreas.USERSINCOURSE; this._courseid = $(e.currentTarget).data('courseid'); this._searchUsersInCourse(); this.messageArea.find(SELECTORS.SEARCHBOX).focus(); }.bind(this)); // Handle deleting the search filter. this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.DELETESEARCHFILTER, function() { this._hideSearchResults(); // Filter has been removed, so we don't want to be searching in a course anymore. this._searchArea = this._searchAreas.USERS; this._setPlaceholderText('searchforuserorcourse'); // Go back the contacts. this.messageArea.trigger(Events.USERSSEARCHCANCELED); this.messageArea.find(SELECTORS.SEARCHBOX).focus(); }.bind(this)); // Handle events that occur outside this module. this.messageArea.onCustomEvent(Events.CONVERSATIONSSELECTED, function() { this._hideSearchResults(); this._searchArea = this._searchAreas.MESSAGES; this._setPlaceholderText('searchmessages'); }.bind(this)); this.messageArea.onCustomEvent(Events.CONTACTSSELECTED, function() { this._hideSearchResults(); this._searchArea = this._searchAreas.USERS; this._setPlaceholderText('searchforuserorcourse'); }.bind(this)); this.messageArea.onCustomEvent(Events.MESSAGESENT, function() { this._hideSearchResults(); this._searchArea = this._searchAreas.MESSAGES; this._setPlaceholderText('searchmessages'); }.bind(this)); // Event listeners for scrolling through messages and users in courses. CustomEvents.define(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), [ CustomEvents.events.scrollBottom ]); this.messageArea.onDelegateEvent(CustomEvents.events.scrollBottom, SELECTORS.SEARCHRESULTSAREA, function() { if (this._searchArea == this._searchAreas.MESSAGES) { this._searchMessages(); } else if (this._searchArea == this._searchAreas.USERSINCOURSE) { this._searchUsersInCourse(); } }.bind(this) ); // Set the initial search area. this._searchArea = (this.messageArea.showContactsFirst()) ? this._searchAreas.USERS : this._searchAreas.MESSAGES; }; /** * Handles when search requests are sent. * * @private */ Search.prototype._searchRequest = function() { var str = this.messageArea.find(SELECTORS.SEARCHTEXTAREA + ' input').val(); if (this._requestTimeout) { clearTimeout(this._requestTimeout); } if (str.trim() === '') { // If nothing we being searched then we need to display the usual data. if (this._searchArea == this._searchAreas.MESSAGES) { this._hideSearchResults(); this.messageArea.trigger(Events.MESSAGESEARCHCANCELED); } else if (this._searchArea == this._searchAreas.USERS) { this._hideSearchResults(); this.messageArea.trigger(Events.USERSSEARCHCANCELED); } else if (this._searchArea == this._searchAreas.USERSINCOURSE) { // We are still searching in a course, so need to list all the users again. this._clearSearchArea(); this._searchUsersInCourse(); } return; } this.messageArea.find(SELECTORS.CONVERSATIONS).hide(); this.messageArea.find(SELECTORS.CONTACTS).hide(); this.messageArea.find(SELECTORS.SEARCHRESULTSAREA).show(); if (this._searchArea == this._searchAreas.MESSAGES) { this._requestTimeout = setTimeout(function() { this._clearSearchArea(); this._numMessagesDisplayed = 0; this._searchMessages(); }.bind(this), 300); } else if (this._searchArea == this._searchAreas.USERSINCOURSE) { this._requestTimeout = setTimeout(function() { this._clearSearchArea(); this._numUsersDisplayed = 0; this._searchUsersInCourse(); }.bind(this), 300); } else { // Must be searching for users and courses this._requestTimeout = setTimeout(function() { this._clearSearchArea(); this._numUsersDisplayed = 0; this._searchUsers(); }.bind(this), 300); } }; /** * Handles searching for messages. * * @private * @return {Promise|boolean} The promise resolved when the search area has been rendered */ Search.prototype._searchMessages = function() { if (this._isLoading) { return false; } var str = this.messageArea.find(SELECTORS.SEARCHBOX).val(); // Tell the user we are loading items. this._isLoading = true; // Call the web service to get our data. var promises = Ajax.call([{ methodname: 'core_message_data_for_messagearea_search_messages', args: { userid: this.messageArea.getCurrentUserId(), search: str, limitfrom: this._numMessagesDisplayed, limitnum: this._numMessagesToRetrieve } }]); // Keep track of the number of messages var numberreceived = 0; // Add loading icon to the end of the list. return Templates.render('core/loading', {}).then(function(html, js) { Templates.appendNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), "<div style='text-align:center'>" + html + "</div>", js); return promises[0]; }.bind(this)).then(function(data) { numberreceived = data.contacts.length; return Templates.render('core_message/message_area_message_search_results', data); }).then(function(html, js) { // Remove the loading icon. this.messageArea.find(SELECTORS.SEARCHRESULTSAREA + " " + SELECTORS.LOADINGICON).remove(); // Only append data if we got data back. if (numberreceived > 0) { // Show the new content. Templates.appendNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), html, js); // Increment the number of contacts displayed. this._numMessagesDisplayed += numberreceived; } else if (this._numMessagesDisplayed == 0) { // Must have nothing to begin with. // Replace the new content. Templates.replaceNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), html, js); } // Mark that we are no longer busy loading data. this._isLoading = false; }.bind(this)).fail(Notification.exception); }; /** * Handles searching for users. * * @private * @return {Promise} The promise resolved when the search area has been rendered */ Search.prototype._searchUsers = function() { var str = this.messageArea.find(SELECTORS.SEARCHBOX).val(); // Call the web service to get our data. var promises = Ajax.call([{ methodname: 'core_message_data_for_messagearea_search_users', args: { userid: this.messageArea.getCurrentUserId(), search: str, limitnum: this._numUsersToRetrieve } }]); // Perform the search and replace the content. return Templates.render('core/loading', {}).then(function(html, js) { Templates.replaceNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), "<div style='text-align:center'>" + html + "</div>", js); return promises[0]; }.bind(this)).then(function(data) { if (data.contacts.length > 0) { data.hascontacts = true; } if (data.courses.length > 0) { data.hascourses = true; } if (data.noncontacts.length > 0) { data.hasnoncontacts = true; } return Templates.render('core_message/message_area_user_search_results', data); }).then(function(html, js) { Templates.replaceNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), html, js); }.bind(this)).fail(Notification.exception); }; /** * Handles searching for users in a course. * * @private * @return {Promise|boolean} The promise resolved when the search area has been rendered */ Search.prototype._searchUsersInCourse = function() { if (this._isLoading) { return false; } var str = this.messageArea.find(SELECTORS.SEARCHBOX).val(); // Tell the user we are loading items. this._isLoading = true; // Call the web service to get our data. var promises = Ajax.call([{ methodname: 'core_message_data_for_messagearea_search_users_in_course', args: { userid: this.messageArea.getCurrentUserId(), courseid: this._courseid, search: str, limitfrom: this._numUsersDisplayed, limitnum: this._numUsersToRetrieve } }]); // Keep track of the number of contacts var numberreceived = 0; // Add loading icon to the end of the list. return Templates.render('core/loading', {}).then(function(html, js) { Templates.appendNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), "<div style='text-align:center'>" + html + "</div>", js); return promises[0]; }.bind(this)).then(function(data) { numberreceived = data.contacts.length; if (numberreceived > 0) { data.hascontacts = true; } return Templates.render('core_message/message_area_user_search_results', data); }).then(function(html, js) { // Remove the loading icon. this.messageArea.find(SELECTORS.SEARCHRESULTSAREA + " " + SELECTORS.LOADINGICON).remove(); // Only append data if we got data back. if (numberreceived > 0) { // Show the new content. Templates.appendNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), html, js); // Increment the number of contacts displayed. this._numUsersDisplayed += numberreceived; } else if (this._numUsersDisplayed == 0) { // Must have nothing to begin with. // Replace the new content. Templates.replaceNodeContents(this.messageArea.find(SELECTORS.SEARCHRESULTSAREA), html, js); } // Mark that we are no longer busy loading data. this._isLoading = false; }.bind(this)).fail(Notification.exception); }; /** * Sets placeholder text for search input. * * @private * @param {String} text The placeholder text * @return {Promise} The promise resolved when the placeholder text has been set */ Search.prototype._setPlaceholderText = function(text) { return Str.get_string(text, 'message').then(function(s) { this.messageArea.find(SELECTORS.SEARCHTEXTAREA + ' input').attr('placeholder', s); }.bind(this)); }; /** * Sets filter for search input. * * @private * @param {String} text The filter text */ Search.prototype._setFilter = function(text) { this.messageArea.find(SELECTORS.SEARCHBOX).val(''); this.messageArea.find(SELECTORS.CONTACTSAREA).addClass('searchfilter'); this.messageArea.find(SELECTORS.SEARCHFILTERAREA).show(); this.messageArea.find(SELECTORS.SEARCHFILTER).html(text); Str.get_string('removecoursefilter', 'message', text).then(function(languagestring) { this.messageArea.find(SELECTORS.SEARCHFILTERAREA).attr('aria-label', languagestring); }.bind(this)).catch(Notification.exception); }; /** * Hides filter for search input. * * @private */ Search.prototype._clearFilters = function() { this.messageArea.find(SELECTORS.CONTACTSAREA).removeClass('searchfilter'); this.messageArea.find(SELECTORS.SEARCHFILTER).empty(); this.messageArea.find(SELECTORS.SEARCHFILTERAREA).hide(); this.messageArea.find(SELECTORS.SEARCHFILTERAREA).removeAttr('aria-label'); }; /** * Handles clearing the search area. * * @private */ Search.prototype._clearSearchArea = function() { this.messageArea.find(SELECTORS.SEARCHRESULTSAREA).empty(); }; /** * Handles hiding the search area. * * @private */ Search.prototype._hideSearchResults = function() { this._clearFilters(); this.messageArea.find(SELECTORS.SEARCHTEXTAREA + ' input').val(''); this._clearSearchArea(); this.messageArea.find(SELECTORS.SEARCHRESULTSAREA).hide(); }; return Search; });