1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * This module handles the contacts area of the messaging area.
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
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. */
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']"
55 * @param {Messagearea} messageArea The messaging area object.
57 function Contacts(messageArea) {
58 this.messageArea = messageArea;
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;
87 * Initialise the event listeners.
91 Contacts.prototype._init = function() {
92 CustomEvents.define(this.messageArea.node, [
93 CustomEvents.events.activate,
94 CustomEvents.events.down,
95 CustomEvents.events.up,
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);
109 this.messageArea.onCustomEvent(Events.CONTACTADDED, function(e, userid) {
110 this._addContact(userid);
112 this.messageArea.onCustomEvent(Events.CONTACTBLOCKED, function(e, userid) {
113 this._blockContact(userid);
115 this.messageArea.onCustomEvent(Events.CONTACTUNBLOCKED, function(e, userid) {
116 this._unblockContact(userid);
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
148 CustomEvents.define(this.messageArea.find(SELECTORS.CONTACTS), [
149 CustomEvents.events.scrollBottom
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;
167 Contacts.prototype._startDeleting = function() {
168 this.messageArea.find(SELECTORS.CONTACTSAREA).addClass('editing');
176 Contacts.prototype._stopDeleting = function() {
177 this.messageArea.find(SELECTORS.CONTACTSAREA).removeClass('editing');
181 * Handles viewing the list of conversations.
185 Contacts.prototype._viewConversations = function() {
186 // If conversations is empty then try load some.
187 if (this._numConversationsDisplayed === 0) {
188 this._loadConversations();
191 this.messageArea.find(SELECTORS.CONTACTS).hide();
192 this.messageArea.find(SELECTORS.CONVERSATIONS).show();
196 * Handles viewing the list of contacts.
200 Contacts.prototype._viewContacts = function() {
201 // If contacts is empty then try load some.
202 if (this._numContactsDisplayed === 0) {
203 this._loadContacts();
206 this.messageArea.find(SELECTORS.CONVERSATIONS).hide();
207 this.messageArea.find(SELECTORS.CONTACTS).show();
211 * Handles when a message is sent.
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
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);
231 if (usercontact.length == 0) {
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++;
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 + "']");
255 * Handles loading conversations.
257 * @return {Promise|boolean} The promise resolved when the contact area has been rendered,
260 Contacts.prototype._loadConversations = function() {
261 if (this._isLoadingConversations) {
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);
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);
300 // Mark that we are no longer busy loading data.
301 this._isLoadingConversations = false;
302 }.bind(this)).fail(Notification.exception);
306 * Handles loading contacts.
308 * @return {Promise|boolean} The promise resolved when the contact area has been rendered
311 Contacts.prototype._loadContacts = function() {
312 if (this._isLoadingContacts) {
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);
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);
350 // Mark that we are no longer busy loading data.
351 this._isLoadingContacts = false;
352 }.bind(this)).fail(Notification.exception);
356 * Handles viewing a particular conversation.
358 * @param {Event} event
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.
371 selector = "[data-messageid='" + messageid + "']";
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();
382 * Handles viewing a particular contact.
384 * @param {Event} event
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();
400 * Handles returning a list of items to display.
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
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,
413 userid: this.messageArea.getCurrentUserId(),
414 limitfrom: limitfrom,
423 * Handles deleting a conversation.
425 * @param {Event} event
426 * @param {int} userid The user id belonging to the messages we are deleting.
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();
439 * Handles updating the last message in the contact.
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
446 Contacts.prototype._updateLastMessage = function(event, userid, updatemessage) {
447 // Check if the last message needs updating.
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.
457 this._updateContactText(user, updatemessagetext, sentbyuser);
460 // Now we have done all the deletion we can set the flag back to false.
461 this._stopDeleting();
465 * Handles adding a contact to the list.
469 Contacts.prototype._addContact = function() {
470 this.messageArea.find(SELECTORS.CONTACTS).empty();
471 this._numContactsDisplayed = 0;
472 this._loadContacts();
476 * Handles removing a contact from the list.
478 * @param {String} selector
479 * @param {int} userid
482 Contacts.prototype._removeContact = function(selector, userid) {
483 this._getUserNode(selector, userid).remove();
484 this._numContactsDisplayed--;
488 * Handles marking a contact as blocked on the list.
490 * @param {int} userid
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');
505 * Handles marking a contact as unblocked on the list.
507 * @param {int} userid
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');
522 * Handles retrieving a user node from a list.
524 * @param {String} selector
525 * @param {int} userid
526 * @return {jQuery} The user node
529 Contacts.prototype._getUserNode = function(selector, userid) {
530 return this.messageArea.find(selector + " " + SELECTORS.CONTACT +
531 "[data-userid='" + userid + "']");
535 * Handles selecting a contact in the list.
537 * @param {String} selector
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);
550 * Converts a text message into the text that should be stored in the contact list
552 * @param {String} text
553 * @return {String} The altered text
555 Contacts.prototype._getContactText = function(text) {
556 if (text.length > this._messageLength) {
557 text = text.substr(0, this._messageLength - 3);
561 // Text node prevents script injection through HTML entities.
562 return document.createTextNode(text);
566 * Handles updating the contact text.
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?
573 Contacts.prototype._updateContactText = function(user, text, sentbyuser) {
574 // Get the text we will display on the contact panel.
575 text = this._getContactText(text);
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);
584 user.find(SELECTORS.LASTMESSAGEUSER).empty();
585 user.find(SELECTORS.LASTMESSAGETEXT).empty().append(text);
590 * Shifts focus to the next contact in the list.
592 * @param {event} e The jquery event
593 * @param {object} data Additional event data
595 Contacts.prototype._selectNextContact = function(e, data) {
596 var contact = $(e.target).closest(SELECTORS.CONTACT);
597 var next = contact.next();
600 data.originalEvent.preventDefault();
601 data.originalEvent.stopPropagation();
605 * Shifts focus to the previous contact in the list.
607 * @param {event} e The jquery event
608 * @param {object} data Additional event data
610 Contacts.prototype._selectPreviousContact = function(e, data) {
611 var contact = $(e.target).closest(SELECTORS.CONTACT);
612 var previous = contact.prev();
615 data.originalEvent.preventDefault();
616 data.originalEvent.stopPropagation();
620 * Shifts focus to the next course in the list.
622 * @param {event} e The jquery event
623 * @param {object} data Additional event data
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();
634 * Shifts focus to the previous course in the list.
636 * @param {event} e The jquery event
637 * @param {object} data Additional event data
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();
648 * Shifts focus to the next conversation in the list.
650 * @param {event} e The jquery event
651 * @param {object} data Additional event data
653 Contacts.prototype._selectNextConversation = function(e, data) {
654 var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
655 var next = conversation.next();
658 data.originalEvent.preventDefault();
659 data.originalEvent.stopPropagation();
663 * Shifts focus to the previous conversation in the list.
665 * @param {event} e The jquery event
666 * @param {object} data Additional event data
668 Contacts.prototype._selectPreviousConversation = function(e, data) {
669 var conversation = $(e.target).closest(SELECTORS.VIEWCONVERSATION);
670 var previous = conversation.prev();
673 data.originalEvent.preventDefault();
674 data.originalEvent.stopPropagation();
678 * Flags the search area as seaching.
680 Contacts.prototype._setSearching = function() {
681 $(SELECTORS.SEARCHTEXTAREA).addClass('searching');
685 * Flags the search area as seaching.
687 Contacts.prototype._clearSearching = function() {
688 $(SELECTORS.SEARCHTEXTAREA).removeClass('searching');
692 * Make the messaging area visible.
694 Contacts.prototype._showMessagingArea = function() {
695 this.messageArea.find(SELECTORS.MESSAGINGAREA)
696 .removeClass('hide-messages')
697 .addClass('show-messages');
701 * Hide the messaging area.
703 Contacts.prototype._hideMessagingArea = function() {
704 this.messageArea.find(SELECTORS.MESSAGINGAREA)
705 .removeClass('show-messages')
706 .addClass('hide-messages');