diff --git a/message/amd/src/message_area.js b/message/amd/src/message_area.js index 9439c50d621..5af1b645873 100644 --- a/message/amd/src/message_area.js +++ b/message/amd/src/message_area.js @@ -38,10 +38,12 @@ define(['jquery', 'core_message/message_area_contacts', 'core_message/message_ar /** @type {Object} The list of selectors for the message area. */ Messagearea.prototype.SELECTORS = { + ACTIVECONTACTSTAB: "[data-region='contacts-area'] [role='tab'][aria-selected='true']", BLOCKTIME: "[data-region='blocktime']", CANCELDELETEMESSAGES: "[data-action='cancel-delete-messages']", CONTACT: "[data-region='contact']", CONTACTS: "[data-region='contacts'][data-region-content='contacts']", + CONTACTSPANELS: "[data-region='contacts']", CONVERSATIONS: "[data-region='contacts'][data-region-content='conversations']", DELETECONVERSATIONCHECKBOX: "[data-region='delete-conversation-checkbox']", DELETEMESSAGES: "[data-action='delete-messages']", diff --git a/message/amd/src/message_area_contacts.js b/message/amd/src/message_area_contacts.js index fc9a9a1f2f0..f5f5e8e42a3 100644 --- a/message/amd/src/message_area_contacts.js +++ b/message/amd/src/message_area_contacts.js @@ -67,6 +67,12 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust * @private */ Contacts.prototype._init = function() { + customEvents.define(this.messageArea.node, [ + customEvents.events.activate, + customEvents.events.down, + customEvents.events.up, + ]); + this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSSELECTED, this._viewConversations.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONTACTSSELECTED, this._viewContacts.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.MESSAGESDELETED, this._deleteConversations.bind(this)); @@ -81,9 +87,15 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust this._chooseConversationsToDelete.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.CANCELDELETEMESSAGES, this._cancelConversationsToDelete.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.VIEWCONVERSATION, + this.messageArea.onDelegateEvent(customEvents.events.activate, this.messageArea.SELECTORS.VIEWCONVERSATION, this._viewConversation.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.VIEWPROFILE, this._viewContact.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.activate, this.messageArea.SELECTORS.VIEWPROFILE, + this._viewContact.bind(this)); + + this.messageArea.onDelegateEvent(customEvents.events.up, this.messageArea.SELECTORS.CONTACT, this._selectPreviousContact.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.down, this.messageArea.SELECTORS.CONTACT, this._selectNextContact.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.up, this.messageArea.SELECTORS.VIEWCONVERSATION, this._selectPreviousConversation.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.down, this.messageArea.SELECTORS.VIEWCONVERSATION, this._selectNextConversation.bind(this)); // Now enable the ability to infinitely scroll through conversations and contacts. customEvents.define(this.messageArea.SELECTORS.CONVERSATIONS, [ @@ -446,6 +458,66 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust return text; }; + /** + * Shifts focus to the next contact in the list. + * + * @param {event} e The jquery event + * @param {object} data Additional event data + */ + Contacts.prototype._selectNextContact = function(e, data) { + var contact = $(e.target).closest(this.messageArea.SELECTORS.CONTACT); + var next = contact.next(); + next.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + + /** + * Shifts focus to the previous contact in the list. + * + * @param {event} e The jquery event + * @param {object} data Additional event data + */ + Contacts.prototype._selectPreviousContact = function(e, data) { + var contact = $(e.target).closest(this.messageArea.SELECTORS.CONTACT); + var previous = contact.prev(); + previous.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + + /** + * Shifts focus to the next conversation in the list. + * + * @param {event} e The jquery event + * @param {object} data Additional event data + */ + Contacts.prototype._selectNextConversation = function(e, data) { + var conversation = $(e.target).closest(this.messageArea.SELECTORS.VIEWCONVERSATION); + var next = conversation.next(); + next.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + + /** + * Shifts focus to the previous conversation in the list. + * + * @param {event} e The jquery event + * @param {object} data Additional event data + */ + Contacts.prototype._selectPreviousConversation = function(e, data) { + var conversation = $(e.target).closest(this.messageArea.SELECTORS.VIEWCONVERSATION); + var previous = conversation.prev(); + previous.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + return Contacts; } -); \ No newline at end of file +); diff --git a/message/amd/src/message_area_messages.js b/message/amd/src/message_area_messages.js index ba7da22445e..99a157d6346 100644 --- a/message/amd/src/message_area_messages.js +++ b/message/amd/src/message_area_messages.js @@ -52,14 +52,23 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust * @private */ Messages.prototype._init = function() { + customEvents.define(this.messageArea.node, [ + customEvents.events.activate, + customEvents.events.up, + customEvents.events.down, + ]); + this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONDELETED, this._handleConversationDeleted.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSELECTED, this._viewMessages.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.SENDMESSAGE, this._viewMessages.bind(this)); this.messageArea.onCustomEvent(this.messageArea.EVENTS.CHOOSEMESSAGESTODELETE, this._chooseMessagesToDelete.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.SENDMESSAGE, this._sendMessage.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.DELETEMESSAGES, this._deleteMessages.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.CANCELDELETEMESSAGES, + this.messageArea.onDelegateEvent(customEvents.events.activate, this.messageArea.SELECTORS.SENDMESSAGE, this._sendMessage.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.activate, this.messageArea.SELECTORS.DELETEMESSAGES, this._deleteMessages.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.activate, this.messageArea.SELECTORS.CANCELDELETEMESSAGES, this._cancelMessagesToDelete.bind(this)); + + this.messageArea.onDelegateEvent(customEvents.events.up, this.messageArea.SELECTORS.MESSAGE, this._selectPreviousMessage.bind(this)); + this.messageArea.onDelegateEvent(customEvents.events.down, this.messageArea.SELECTORS.MESSAGE, this._selectNextMessage.bind(this)); }; /** @@ -385,6 +394,46 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust messages.scrollTop(messages[0].scrollHeight); }; + /** + * Select the previous message in the list. + * + * @params {event} e The jquery event + * @params {object} data Extra event data + * @private + */ + Messages.prototype._selectPreviousMessage = function(e, data) { + var currentMessage = $(e.target).closest(this.messageArea.SELECTORS.MESSAGE); + + do { + currentMessage = currentMessage.prev(); + } while (currentMessage.length && !currentMessage.is(this.messageArea.SELECTORS.MESSAGE)); + + currentMessage.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + + /** + * Select the next message in the list. + * + * @params {event} e The jquery event + * @params {object} data Extra event data + * @private + */ + Messages.prototype._selectNextMessage = function(e, data) { + var currentMessage = $(e.target).closest(this.messageArea.SELECTORS.MESSAGE); + + do { + currentMessage = currentMessage.next(); + } while (currentMessage.length && !currentMessage.is(this.messageArea.SELECTORS.MESSAGE)); + + currentMessage.focus(); + + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); + }; + return Messages; } -); \ No newline at end of file +); diff --git a/message/amd/src/message_area_tabs.js b/message/amd/src/message_area_tabs.js index d47147372dc..670185c9f42 100644 --- a/message/amd/src/message_area_tabs.js +++ b/message/amd/src/message_area_tabs.js @@ -21,7 +21,7 @@ * @copyright 2016 Mark Nelson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define([], function() { +define(['core/custom_interaction_events'], function(CustomEvents) { /** * Tabs class. @@ -45,8 +45,45 @@ define([], function() { * @private */ Tabs.prototype._init = function() { - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.VIEWCONVERSATIONS, this._viewConversations.bind(this)); - this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.VIEWCONTACTS, this._viewContacts.bind(this)); + CustomEvents.define(this.messageArea.node, [ + CustomEvents.events.activate, + CustomEvents.events.up, + CustomEvents.events.down, + CustomEvents.events.next, + CustomEvents.events.previous, + CustomEvents.events.ctrlPageUp, + CustomEvents.events.ctrlPageDown, + ]); + + this.messageArea.onDelegateEvent(CustomEvents.events.activate, this.messageArea.SELECTORS.VIEWCONVERSATIONS, + this._viewConversations.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.activate, this.messageArea.SELECTORS.VIEWCONTACTS, + this._viewContacts.bind(this)); + + // Change to the other tab if any arrow keys are pressed, since there are only two tabs. + this.messageArea.onDelegateEvent(CustomEvents.events.up, this.messageArea.SELECTORS.VIEWCONVERSATIONS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.down, this.messageArea.SELECTORS.VIEWCONVERSATIONS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.next, this.messageArea.SELECTORS.VIEWCONVERSATIONS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.previous, this.messageArea.SELECTORS.VIEWCONVERSATIONS, + this._toggleTabs.bind(this)); + // Change to the other tab if any arrow keys are pressed, since there are only two tabs. + this.messageArea.onDelegateEvent(CustomEvents.events.up, this.messageArea.SELECTORS.VIEWCONTACTS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.down, this.messageArea.SELECTORS.VIEWCONTACTS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.next, this.messageArea.SELECTORS.VIEWCONTACTS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.previous, this.messageArea.SELECTORS.VIEWCONTACTS, + this._toggleTabs.bind(this)); + // Tab panel keyboard handling. + this.messageArea.onDelegateEvent(CustomEvents.events.ctrlPageUp, this.messageArea.SELECTORS.CONTACTSPANELS, + this._toggleTabs.bind(this)); + this.messageArea.onDelegateEvent(CustomEvents.events.ctrlPageDown, this.messageArea.SELECTORS.CONTACTSPANELS, + this._toggleTabs.bind(this)); + this.messageArea.onCustomEvent(this.messageArea.EVENTS.CHOOSEMESSAGESTODELETE, function() { this._isDeleting = true; }.bind(this)); @@ -97,9 +134,39 @@ define([], function() { * @private */ Tabs.prototype._selectTab = function(tabselect, tabdeselect) { - this.messageArea.find(tabdeselect).removeClass('selected'); - this.messageArea.find(tabselect).addClass('selected'); + var tabdeselect = this.messageArea.find(tabdeselect); + tabdeselect.removeClass('selected'); + tabdeselect.attr('aria-selected', 'false'); + tabdeselect.attr('tabindex', '-1'); + + var tabselect = this.messageArea.find(tabselect); + tabselect.addClass('selected'); + tabselect.attr('aria-selected', 'true'); + tabselect.attr('tabindex', '0'); + tabselect.focus(); + }; + + /** + * Change to the inactive tab. + * + * @param {event} e The javascript event + * @param {object} data The additional event data + * @private + */ + Tabs.prototype._toggleTabs = function(e, data) { + var activeTab = this.messageArea.find(this.messageArea.SELECTORS.ACTIVECONTACTSTAB); + + if (activeTab.is(this.messageArea.SELECTORS.VIEWCONVERSATIONS)) { + this._viewContacts(); + } else { + this._viewConversations(); + } + + e.preventDefault(); + e.stopPropagation(); + data.originalEvent.preventDefault(); + data.originalEvent.stopPropagation(); }; return Tabs; -}); \ No newline at end of file +}); diff --git a/message/templates/message_area.mustache b/message/templates/message_area.mustache index bdc6ac4d91e..f9ecbfbaca7 100644 --- a/message/templates/message_area.mustache +++ b/message/templates/message_area.mustache @@ -15,12 +15,12 @@
-
+
{{#contacts}} {{> core_message/message_area_contacts_area }} {{/contacts}}
-
+
{{#messages}} {{> core_message/message_area_messages_area }} {{/messages}} @@ -34,4 +34,4 @@ new Messagearea('.messaging-area-container'); } ); -{{/js}} \ No newline at end of file +{{/js}} diff --git a/message/templates/message_area_contact.mustache b/message/templates/message_area_contact.mustache index 3f66dcb4147..6adc71ce479 100644 --- a/message/templates/message_area_contact.mustache +++ b/message/templates/message_area_contact.mustache @@ -1,4 +1,4 @@ -
+
diff --git a/message/templates/message_area_contacts_area.mustache b/message/templates/message_area_contacts_area.mustache index 74aea51e9d5..6b941d82e88 100644 --- a/message/templates/message_area_contacts_area.mustache +++ b/message/templates/message_area_contacts_area.mustache @@ -4,18 +4,55 @@
-
+
+ {{> core_message/message_area_contacts }}
{{! Hidden div to load the other tabs items in via JS when appropriate. }} - +
-
+ -
+ -
\ No newline at end of file +
diff --git a/message/templates/message_area_message.mustache b/message/templates/message_area_message.mustache index 4fa684c8afd..2f35605bca6 100644 --- a/message/templates/message_area_message.mustache +++ b/message/templates/message_area_message.mustache @@ -3,7 +3,7 @@ {{blocktime}}
{{/displayblocktime}} -
+
@@ -15,4 +15,4 @@ {{timesent}}
-
\ No newline at end of file +