MDL-61359 message: Updated sender contact text to text node content
[moodle.git] / message / amd / src / message_area_contacts.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * This module handles the contacts area of the messaging area.
18  *
19  * @module     core_message/message_area_contacts
20  * @package    core_message
21  * @copyright  2016 Mark Nelson <markn@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/custom_interaction_events', 'core/str',
25         'core_message/message_area_events'],
26     function($, Ajax, Templates, Notification, CustomEvents, Str, Events) {
28         /** @type {Object} The list of selectors for the message area. */
29         var SELECTORS = {
30             CONTACT: "[data-region='contact']",
31             CONTACTICONBLOCKED: "[data-region='contact-icon-blocked']",
32             CONTACTS: "[data-region='contacts'][data-region-content='contacts']",
33             CONTACTSAREA: "[data-region='contacts-area']",
34             CONVERSATIONS: "[data-region='contacts'][data-region-content='conversations']",
35             COURSE: "[data-region='course']",
36             LASTMESSAGETEXT: "[data-region='last-message-text']",
37             LASTMESSAGEUSER: "[data-region='last-message-user']",
38             LOADINGICON: '.loading-icon',
39             MESSAGETEXT: "[data-region='message-text']",
40             MESSAGINGAREA: "[data-region='messaging-area']",
41             NOCONTACTS: "[data-region=no-contacts]",
42             SEARCHBOX: "[data-region='search-box']",
43             SEARCHRESULTSAREA: "[data-region='search-results-area']",
44             SEARCHTEXTAREA: "[data-region='search-text-area']",
45             SELECTEDVIEWCONVERSATION: "[data-action='view-contact-msg'].selected",
46             SELECTEDVIEWPROFILE: "[data-action='view-contact-profile'].selected",
47             SHOWMESSAGES: "[data-action='show-messages']",
48             VIEWCONVERSATION: "[data-action='view-contact-msg']",
49             VIEWPROFILE: "[data-action='view-contact-profile']"
50         };
52         /**
53          * Contacts class.
54          *
55          * @param {Messagearea} messageArea The messaging area object.
56          */
57         function Contacts(messageArea) {
58             this.messageArea = messageArea;
59             this._init();
60         }
62         /** @type {Boolean} checks if we are currently loading conversations */
63         Contacts.prototype._isLoadingConversations = false;
65         /** @type {Boolean} checks if we are currently loading contacts */
66         Contacts.prototype._isLoadingContacts = false;
68         /** @type {int} the number of contacts displayed */
69         Contacts.prototype._numContactsDisplayed = 0;
71         /** @type {int} the number of contacts to retrieve */
72         Contacts.prototype._numContactsToRetrieve = 20;
74         /** @type {int} the number of conversations displayed */
75         Contacts.prototype._numConversationsDisplayed = 0;
77         /** @type {int} the number of conversations to retrieve */
78         Contacts.prototype._numConversationsToRetrieve = 20;
80         /** @type {int} the number of chars of the message to show */
81         Contacts.prototype._messageLength = 60;
83         /** @type {Messagearea} The messaging area object. */
84         Contacts.prototype.messageArea = null;
86         /**
87          * Initialise the event listeners.
88          *
89          * @private
90          */
91         Contacts.prototype._init = function() {
92             CustomEvents.define(this.messageArea.node, [
93                 CustomEvents.events.activate,
94                 CustomEvents.events.down,
95                 CustomEvents.events.up,
96             ]);
98             this.messageArea.onCustomEvent(Events.MESSAGESEARCHCANCELED, this._viewConversations.bind(this));
99             this.messageArea.onCustomEvent(Events.USERSSEARCHCANCELED, this._viewContacts.bind(this));
100             this.messageArea.onCustomEvent(Events.CONTACTSSELECTED, this._viewContacts.bind(this));
101             this.messageArea.onCustomEvent(Events.CONVERSATIONDELETED, this._deleteConversation.bind(this));
102             this.messageArea.onCustomEvent(Events.CONVERSATIONSSELECTED, this._viewConversations.bind(this));
103             this.messageArea.onCustomEvent(Events.CONTACTSSELECTED, this._viewContacts.bind(this));
104             this.messageArea.onCustomEvent(Events.MESSAGESDELETED, this._updateLastMessage.bind(this));
105             this.messageArea.onCustomEvent(Events.MESSAGESENT, this._handleMessageSent.bind(this));
106             this.messageArea.onCustomEvent(Events.CONTACTREMOVED, function(e, userid) {
107                 this._removeContact(SELECTORS.CONTACTS, userid);
108             }.bind(this));
109             this.messageArea.onCustomEvent(Events.CONTACTADDED, function(e, userid) {
110                 this._addContact(userid);
111             }.bind(this));
112             this.messageArea.onCustomEvent(Events.CONTACTBLOCKED, function(e, userid) {
113                 this._blockContact(userid);
114             }.bind(this));
115             this.messageArea.onCustomEvent(Events.CONTACTUNBLOCKED, function(e, userid) {
116                 this._unblockContact(userid);
117             }.bind(this));
118             this.messageArea.onCustomEvent(Events.CHOOSEMESSAGESTODELETE,
119                 this._startDeleting.bind(this));
120             this.messageArea.onCustomEvent(Events.CANCELDELETEMESSAGES,
121                 this._stopDeleting.bind(this));
122             this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.VIEWCONVERSATION,
123                 this._viewConversation.bind(this));
124             this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.VIEWPROFILE,
125                 this._viewContact.bind(this));
126             this.messageArea.onDelegateEvent(CustomEvents.events.activate, SELECTORS.SHOWMESSAGES,
127                 this._showMessagingArea.bind(this));
129             this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.CONTACT,
130                 this._selectPreviousContact.bind(this));
131             this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.CONTACT,
132                 this._selectNextContact.bind(this));
133             this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.VIEWCONVERSATION,
134                 this._selectPreviousConversation.bind(this));
135             this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.VIEWCONVERSATION,
136                 this._selectNextConversation.bind(this));
138             this.messageArea.onDelegateEvent(CustomEvents.events.up, SELECTORS.COURSE, this._selectPreviousCourse.bind());
139             this.messageArea.onDelegateEvent(CustomEvents.events.down, SELECTORS.COURSE, this._selectNextCourse.bind());
141             this.messageArea.onDelegateEvent('focus', SELECTORS.SEARCHBOX, this._setSearching.bind(this));
142             this.messageArea.onDelegateEvent('blur', SELECTORS.SEARCHBOX, this._clearSearching.bind(this));
144             // Now enable the ability to infinitely scroll through conversations and contacts.
145             CustomEvents.define(this.messageArea.find(SELECTORS.CONVERSATIONS), [
146                 CustomEvents.events.scrollBottom
147             ]);
148             CustomEvents.define(this.messageArea.find(SELECTORS.CONTACTS), [
149                 CustomEvents.events.scrollBottom
150             ]);
151             this.messageArea.onDelegateEvent(CustomEvents.events.scrollBottom, SELECTORS.CONVERSATIONS,
152                 this._loadConversations.bind(this));
153             this.messageArea.onDelegateEvent(CustomEvents.events.scrollBottom, SELECTORS.CONTACTS,
154                 this._loadContacts.bind(this));
156             if (!this.messageArea.showContactsFirst()) {
157                 // Set the initial number of conversations to retrieve. Otherwise it will display no conversations.
158                 this._numConversationsDisplayed = 20;
159             }
160         };
162         /**
163          * Turn on deleting.
164          *
165          * @private
166          */
167         Contacts.prototype._startDeleting = function() {
168             this.messageArea.find(SELECTORS.CONTACTSAREA).addClass('editing');
169         };
171         /**
172          * Turn off deleting.
173          *
174          * @private
175          */
176         Contacts.prototype._stopDeleting = function() {
177             this.messageArea.find(SELECTORS.CONTACTSAREA).removeClass('editing');
178         };
180         /**
181          * Handles viewing the list of conversations.
182          *
183          * @private
184          */
185         Contacts.prototype._viewConversations = function() {
186             // If conversations is empty then try load some.
187             if (this._numConversationsDisplayed === 0) {
188                 this._loadConversations();
189             }
191             this.messageArea.find(SELECTORS.CONTACTS).hide();
192             this.messageArea.find(SELECTORS.CONVERSATIONS).show();
193         };
195         /**
196          * Handles viewing the list of contacts.
197          *
198          * @private
199          */
200         Contacts.prototype._viewContacts = function() {
201             // If contacts is empty then try load some.
202             if (this._numContactsDisplayed === 0) {
203                 this._loadContacts();
204             }
206             this.messageArea.find(SELECTORS.CONVERSATIONS).hide();
207             this.messageArea.find(SELECTORS.CONTACTS).show();
208         };
210         /**
211          * Handles when a message is sent.
212          *
213          * @param {Event} event The message sent event
214          * @param {int} userid The id of the user who the message was sent to
215          * @param {String} text The message text
216          * @private
217          */
218         Contacts.prototype._handleMessageSent = function(event, userid, text) {
219             // Switch to viewing the conversations.
220             this._viewConversations();
221             // Get the user node.
222             var user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
223             // If the user has not been loaded yet, let's copy the element from contact or search panel to the conversation panel.
224             if (user.length === 0) {
225                 // Let's clone the data on the contact page.
226                 var usercontact = this._getUserNode(SELECTORS.CONTACTS, userid);
227                 if (usercontact.length === 0) {
228                     // No luck, maybe we sent the message to a user we searched for - check search page.
229                     usercontact = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
230                 }
231                 if (usercontact.length == 0) {
232                     // Can't do much.
233                     return;
234                 }
235                 user = usercontact.clone();
236                 // Change the data action attribute.
237                 user.attr('data-action', 'view-contact-msg');
238                 // Remove the 'no conversations' message.
239                 this.messageArea.find(SELECTORS.CONVERSATIONS + " " +
240                     SELECTORS.NOCONTACTS).remove();
241                 // Increment the number of conversations displayed.
242                 this._numConversationsDisplayed++;
243             }
244             // Move the contact to the top of the list.
245             user.prependTo(this.messageArea.find(SELECTORS.CONVERSATIONS));
246             // Scroll to the top.
247             this.messageArea.find(SELECTORS.CONVERSATIONS).scrollTop(0);
248             // Get the new text to show.
249             this._updateContactText(user, text, true);
250             // Ensure user is selected.
251             this._setSelectedUser("[data-userid='" + userid + "']");
252         };
254         /**
255          * Handles loading conversations.
256          *
257          * @return {Promise|boolean} The promise resolved when the contact area has been rendered,
258          * @private
259          */
260         Contacts.prototype._loadConversations = function() {
261             if (this._isLoadingConversations) {
262                 return false;
263             }
265             // Tell the user we are loading items.
266             this._isLoadingConversations = true;
268             // Keep track of the number of contacts
269             var numberreceived = 0;
270             // Add loading icon to the end of the list.
271             return Templates.render('core/loading', {}).then(function(html, js) {
272                 if (this._numConversationsDisplayed) {
273                     Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS),
274                         "<div style='text-align:center'>" + html + "</div>", js);
275                 } else { // No conversations, just replace contents.
276                     Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS),
277                         "<div style='text-align:center'>" + html + "</div>", js);
278                 }
279                 return this._getItems('core_message_data_for_messagearea_conversations',
280                     this._numConversationsDisplayed, this._numConversationsToRetrieve);
281             }.bind(this)).then(function(data) {
282                 numberreceived = data.contacts.length;
283                 data.isconversation = true;
284                 return Templates.render('core_message/message_area_contacts', data);
285             }).then(function(html, js) {
286                 // Remove the loading icon.
287                 this.messageArea.find(SELECTORS.CONVERSATIONS + " " +
288                     SELECTORS.LOADINGICON).remove();
289                 // Only append data if we got data back.
290                 if (numberreceived > 0) {
291                     // Show the new content.
292                     Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS), html, js);
293                     // Increment the number of conversations displayed. We increment by the number of conversations we
294                     // asked to retrieve not by the number that was actually retrieved, see MDL-55870.
295                     this._numConversationsDisplayed += this._numConversationsToRetrieve;
296                 } else if (!this._numConversationsDisplayed) {
297                     // If we didn't receive any contacts and there are currently none, then we want to show a message.
298                     Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONVERSATIONS), html, js);
299                 }
300                 // Mark that we are no longer busy loading data.
301                 this._isLoadingConversations = false;
302             }.bind(this)).fail(Notification.exception);
303         };
305         /**
306          * Handles loading contacts.
307          *
308          * @return {Promise|boolean} The promise resolved when the contact area has been rendered
309          * @private
310          */
311         Contacts.prototype._loadContacts = function() {
312             if (this._isLoadingContacts) {
313                 return false;
314             }
316             // Tell the user we are loading items.
317             this._isLoadingContacts = true;
319             // Keep track of the number of contacts
320             var numberreceived = 0;
321             // Add loading icon to the end of the list.
322             return Templates.render('core/loading', {}).then(function(html, js) {
323                 if (this._numContactsDisplayed) {
324                     Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONTACTS),
325                         "<div style='text-align:center'>" + html + "</div>", js);
326                 } else { // No contacts, just replace contents.
327                     Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONTACTS),
328                         "<div style='text-align:center'>" + html + "</div>", js);
329                 }
330                 return this._getItems('core_message_data_for_messagearea_contacts',
331                     this._numContactsDisplayed, this._numContactsToRetrieve);
332             }.bind(this)).then(function(data) {
333                 numberreceived = data.contacts.length;
334                 data.isconversation = false;
335                 return Templates.render('core_message/message_area_contacts', data);
336             }).then(function(html, js) {
337                 // Remove the loading icon.
338                 this.messageArea.find(SELECTORS.CONTACTS + " " +
339                     SELECTORS.LOADINGICON).remove();
340                 // Only append data if we got data back.
341                 if (numberreceived > 0) {
342                     // Show the new content.
343                     Templates.appendNodeContents(this.messageArea.find(SELECTORS.CONTACTS), html, js);
344                     // Increment the number of contacts displayed.
345                     this._numContactsDisplayed += numberreceived;
346                 } else if (!this._numContactsDisplayed) {
347                     // If we didn't receive any contacts and there are currently none, then we want to show a message.
348                     Templates.replaceNodeContents(this.messageArea.find(SELECTORS.CONTACTS), html, js);
349                 }
350                 // Mark that we are no longer busy loading data.
351                 this._isLoadingContacts = false;
352             }.bind(this)).fail(Notification.exception);
353         };
355         /**
356          * Handles viewing a particular conversation.
357          *
358          * @param {Event} event
359          * @private
360          */
361         Contacts.prototype._viewConversation = function(event) {
362             // Cancel any deletion of messages we may have.
363             this.messageArea.trigger(Events.CANCELDELETEMESSAGES);
365             var userid = $(event.currentTarget).data('userid');
366             var messageid = $(event.currentTarget).data('messageid');
367             var selector = "[data-userid='" + userid + "']";
368             // If we have a specific message id then we did a search and the contact may appear in multiple
369             // places - we don't want to highlight them all.
370             if (messageid) {
371                 selector = "[data-messageid='" + messageid + "']";
372             }
374             this._setSelectedUser(selector);
375             this.messageArea.trigger(Events.CONVERSATIONSELECTED, userid);
376             // Don't highlight the contact because the message region has changed.
377             this.messageArea.find(SELECTORS.SELECTEDVIEWPROFILE).removeClass('selected');
378             this._showMessagingArea();
379         };
381         /**
382          * Handles viewing a particular contact.
383          *
384          * @param {Event} event
385          * @private
386          */
387         Contacts.prototype._viewContact = function(event) {
388             // Cancel any deletion of messages we may have.
389             this.messageArea.trigger(Events.CANCELDELETEMESSAGES);
391             var userid = $(event.currentTarget).data('userid');
392             this._setSelectedUser("[data-userid='" + userid + "']");
393             this.messageArea.trigger(Events.CONTACTSELECTED, userid);
394             // Don't highlight the conversation because the message region has changed.
395             this.messageArea.find(SELECTORS.SELECTEDVIEWCONVERSATION).removeClass('selected');
396             this._showMessagingArea();
397         };
399         /**
400          * Handles returning a list of items to display.
401          *
402          * @param {String} webservice The web service to call
403          * @param {int} limitfrom
404          * @param {int} limitnum
405          * @return {Promise} The promise resolved when the contact area has been rendered
406          * @private
407          */
408         Contacts.prototype._getItems = function(webservice, limitfrom, limitnum) {
409             // Call the web service to return the data we want to view.
410             var promises = Ajax.call([{
411                 methodname: webservice,
412                 args: {
413                     userid: this.messageArea.getCurrentUserId(),
414                     limitfrom: limitfrom,
415                     limitnum: limitnum
416                 }
417             }]);
419             return promises[0];
420         };
422         /**
423          * Handles deleting a conversation.
424          *
425          * @param {Event} event
426          * @param {int} userid The user id belonging to the messages we are deleting.
427          * @private
428          */
429         Contacts.prototype._deleteConversation = function(event, userid) {
430             // Remove the conversation.
431             this._removeContact(SELECTORS.CONVERSATIONS, userid);
432             this._numConversationsDisplayed--;
433             this._hideMessagingArea();
434             // Now we have done all the deletion we can set the flag back to false.
435             this._stopDeleting();
436         };
438         /**
439          * Handles updating the last message in the contact.
440          *
441          * @param {Event} event
442          * @param {int} userid The user id belonging to the messages we are deleting
443          * @param {jQuery|null} updatemessage The message we need to update the contact panel with
444          * @private
445          */
446         Contacts.prototype._updateLastMessage = function(event, userid, updatemessage) {
447             // Check if the last message needs updating.
448             if (updatemessage) {
449                 var user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
450                 var updatemessagetext = updatemessage.find(SELECTORS.MESSAGETEXT).text().trim();
451                 var sentbyuser = false;
452                 if (updatemessage.data('useridto') == userid) {
453                     // Must have been sent by the currently logged in user.
454                     sentbyuser = true;
455                 }
457                 this._updateContactText(user, updatemessagetext, sentbyuser);
458             }
460             // Now we have done all the deletion we can set the flag back to false.
461             this._stopDeleting();
462         };
464         /**
465          * Handles adding a contact to the list.
466          *
467          * @private
468          */
469         Contacts.prototype._addContact = function() {
470             this.messageArea.find(SELECTORS.CONTACTS).empty();
471             this._numContactsDisplayed = 0;
472             this._loadContacts();
473         };
475         /**
476          * Handles removing a contact from the list.
477          *
478          * @param {String} selector
479          * @param {int} userid
480          * @private
481          */
482         Contacts.prototype._removeContact = function(selector, userid) {
483             this._getUserNode(selector, userid).remove();
484             this._numContactsDisplayed--;
485         };
487         /**
488          * Handles marking a contact as blocked on the list.
489          *
490          * @param {int} userid
491          * @private
492          */
493         Contacts.prototype._blockContact = function(userid) {
494             var user = this._getUserNode(SELECTORS.CONTACTS, userid);
495             user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');
497             user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
498             user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');
500             user = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
501             user.find(SELECTORS.CONTACTICONBLOCKED).removeClass('hidden');
502         };
504         /**
505          * Handles marking a contact as unblocked on the list.
506          *
507          * @param {int} userid
508          * @private
509          */
510         Contacts.prototype._unblockContact = function(userid) {
511             var user = this._getUserNode(SELECTORS.CONTACTS, userid);
512             user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');
514             user = this._getUserNode(SELECTORS.CONVERSATIONS, userid);
515             user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');
517             user = this._getUserNode(SELECTORS.SEARCHRESULTSAREA, userid);
518             user.find(SELECTORS.CONTACTICONBLOCKED).addClass('hidden');
519         };
521         /**
522          * Handles retrieving a user node from a list.
523          *
524          * @param {String} selector
525          * @param {int} userid
526          * @return {jQuery} The user node
527          * @private
528          */
529         Contacts.prototype._getUserNode = function(selector, userid) {
530             return this.messageArea.find(selector + " " + SELECTORS.CONTACT +
531                 "[data-userid='" + userid + "']");
532         };
534         /**
535          * Handles selecting a contact in the list.
536          *
537          * @param {String} selector
538          * @private
539          */
540         Contacts.prototype._setSelectedUser = function(selector) {
541             // Remove the 'selected' class from any other contact.
542             this.messageArea.find(SELECTORS.CONTACT).removeClass('selected');
543             this.messageArea.find(SELECTORS.CONTACT).attr('aria-pressed', false);
544             // Set the tab for the user to selected.
545             this.messageArea.find(SELECTORS.CONTACT + selector).addClass('selected');
546             this.messageArea.find(SELECTORS.CONTACT + selector).attr('aria-pressed', true);
547         };
549         /**
550          * Converts a text message into the text that should be stored in the contact list
551          *
552          * @param {String} text
553          * @return {String} The altered text
554          */
555         Contacts.prototype._getContactText = function(text) {
556             if (text.length > this._messageLength) {
557                 text = text.substr(0, this._messageLength - 3);
558                 text += '...';
559             }
561             // Text node prevents script injection through HTML entities.
562             return document.createTextNode(text);
563         };
565         /**
566          * Handles updating the contact text.
567          *
568          * @param {jQuery} user The user to update
569          * @param {String} text The text to update the contact with
570          * @param {Boolean} sentbyuser Was it sent by the currently logged in user?
571          * @private
572          */
573         Contacts.prototype._updateContactText = function(user, text, sentbyuser) {
574             // Get the text we will display on the contact panel.
575             text = this._getContactText(text);
576             if (sentbyuser) {
577                 Str.get_string('you', 'message').done(function(string) {
578                     // Ensure we display that the message is from this user.
579                     user.find(SELECTORS.LASTMESSAGEUSER).empty().append(string);
580                 }).always(function() {
581                     user.find(SELECTORS.LASTMESSAGETEXT).empty().append(text);
582                 });
583             } else {
584                 user.find(SELECTORS.LASTMESSAGEUSER).empty();
585                 user.find(SELECTORS.LASTMESSAGETEXT).empty().append(text);
586             }
587         };
589         /**
590          * Shifts focus to the next contact in the list.
591          *
592          * @param {event} e The jquery event
593          * @param {object} data Additional event data
594          */
595         Contacts.prototype._selectNextContact = function(e, data) {
596             var contact = $(e.target).closest(SELECTORS.CONTACT);
597             var next = contact.next();
598             next.focus();
600             data.originalEvent.preventDefault();
601             data.originalEvent.stopPropagation();
602         };
604         /**
605          * Shifts focus to the previous contact in the list.
606          *
607          * @param {event} e The jquery event
608          * @param {object} data Additional event data
609          */
610         Contacts.prototype._selectPreviousContact = function(e, data) {
611             var contact = $(e.target).closest(SELECTORS.CONTACT);
612             var previous = contact.prev();
613             previous.focus();
615             data.originalEvent.preventDefault();
616             data.originalEvent.stopPropagation();
617         };
619         /**
620          * Shifts focus to the next course in the list.
621          *
622          * @param {event} e The jquery event
623          * @param {object} data Additional event data
624          */
625         Contacts.prototype._selectNextCourse = function(e, data) {
626             var course = $(e.target).closest(SELECTORS.COURSE);
627             course.next().focus();
629             data.originalEvent.preventDefault();
630             data.originalEvent.stopPropagation();
631         };
633         /**
634          * Shifts focus to the previous course in the list.
635          *
636          * @param {event} e The jquery event
637          * @param {object} data Additional event data
638          */
639         Contacts.prototype._selectPreviousCourse = function(e, data) {
640             var course = $(e.target).closest(SELECTORS.COURSE);
641             course.prev().focus();
643             data.originalEvent.preventDefault();
644             data.originalEvent.stopPropagation();
645         };
647         /**
648          * Shifts focus to the next conversation in the list.
649          *
650          * @param {event} e The jquery event
651          * @param {object} data Additional event data
652          */
653         Contacts.prototype._selectNextConversation = function(e, data) {
654             var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
655             var next = conversation.next();
656             next.focus();
658             data.originalEvent.preventDefault();
659             data.originalEvent.stopPropagation();
660         };
662         /**
663          * Shifts focus to the previous conversation in the list.
664          *
665          * @param {event} e The jquery event
666          * @param {object} data Additional event data
667          */
668         Contacts.prototype._selectPreviousConversation = function(e, data) {
669             var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
670             var previous = conversation.prev();
671             previous.focus();
673             data.originalEvent.preventDefault();
674             data.originalEvent.stopPropagation();
675         };
677         /**
678          * Flags the search area as seaching.
679          */
680         Contacts.prototype._setSearching = function() {
681             $(SELECTORS.SEARCHTEXTAREA).addClass('searching');
682         };
684         /**
685          * Flags the search area as seaching.
686          */
687         Contacts.prototype._clearSearching = function() {
688             $(SELECTORS.SEARCHTEXTAREA).removeClass('searching');
689         };
691         /**
692          * Make the messaging area visible.
693          */
694         Contacts.prototype._showMessagingArea = function() {
695             this.messageArea.find(SELECTORS.MESSAGINGAREA)
696                 .removeClass('hide-messages')
697                 .addClass('show-messages');
698         };
700         /**
701          * Hide the messaging area.
702          */
703         Contacts.prototype._hideMessagingArea = function() {
704             this.messageArea.find(SELECTORS.MESSAGINGAREA)
705                 .removeClass('show-messages')
706                 .addClass('hide-messages');
707         };
709         return Contacts;
710     }
711 );