File: /home/iestorre/moodleAW/moodle310/message/amd/src/message_area_search.js
// 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;
});