MDL-54687 message: add keyboard navigation to message widget
This commit is contained in:
@@ -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']",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* @copyright 2016 Mark Nelson <[email protected]>
|
||||
* @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;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
</div>
|
||||
<div class="messaging-area">
|
||||
<div class="row-fluid">
|
||||
<div class="span3 contacts-area" data-region="contacts-area">
|
||||
<div class="span3 contacts-area" data-region="contacts-area" role="tablist">
|
||||
{{#contacts}}
|
||||
{{> core_message/message_area_contacts_area }}
|
||||
{{/contacts}}
|
||||
</div>
|
||||
<div class="span9 messages-area" data-region="messages-area">
|
||||
<div class="span9 messages-area" data-region="messages-area" role="log">
|
||||
{{#messages}}
|
||||
{{> core_message/message_area_messages_area }}
|
||||
{{/messages}}
|
||||
@@ -34,4 +34,4 @@
|
||||
new Messagearea('.messaging-area-container');
|
||||
}
|
||||
);
|
||||
{{/js}}
|
||||
{{/js}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="row-fluid contact {{#selected}}selected{{/selected}}" data-action="{{#lastmessage}}view-contact-msg{{/lastmessage}}{{^lastmessage}}view-contact-profile{{/lastmessage}}" data-userid="{{userid}}" data-region="contact">
|
||||
<div class="row-fluid contact {{#selected}}selected{{/selected}}" data-action="{{#lastmessage}}view-contact-msg{{/lastmessage}}{{^lastmessage}}view-contact-profile{{/lastmessage}}" data-userid="{{userid}}" data-region="contact" tabindex="0">
|
||||
<div class="span1 deleteconversationcheckbox" data-region="delete-conversation-checkbox">
|
||||
<input type="checkbox"/>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,55 @@
|
||||
<input type="text" id="filter{{uniqid}}" placeholder="{{#str}}search{{/str}}" value="{{search}}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="contacts" data-region="contacts" data-region-content="{{#conversationsselected}}conversations{{/conversationsselected}}{{^conversationsselected}}contacts{{/conversationsselected}}">
|
||||
<div class="contacts"
|
||||
data-region="contacts"
|
||||
data-region-content="{{#conversationsselected}}conversations{{/conversationsselected}}{{^conversationsselected}}contacts{{/conversationsselected}}"
|
||||
role="tabpanel"
|
||||
{{#conversationsselected}}id="conversations-tab-panel"{{/conversationsselected}}
|
||||
{{^conversationsselected}}id="contacts-tab-panel"{{/conversationsselected}}>
|
||||
|
||||
{{> core_message/message_area_contacts }}
|
||||
</div>
|
||||
{{! Hidden div to load the other tabs items in via JS when appropriate. }}
|
||||
<div class="contacts" data-region="contacts" data-region-content="{{#conversationsselected}}contacts{{/conversationsselected}}{{^conversationsselected}}conversations{{/conversationsselected}}" style="display:none;"></div>
|
||||
<div class="contacts"
|
||||
data-region="contacts"
|
||||
data-region-content="{{#conversationsselected}}contacts{{/conversationsselected}}{{^conversationsselected}}conversations{{/conversationsselected}}"
|
||||
style="display:none;"
|
||||
role="tabpanel"
|
||||
{{#conversationsselected}}id="contacts-tab-panel"{{/conversationsselected}}
|
||||
{{^conversationsselected}}id="conversations-tab-panel"{{/conversationsselected}}>
|
||||
</div>
|
||||
<div class="row-fluid tabs">
|
||||
<div class="span6 tab tabconversations {{#conversationsselected}}selected{{/conversationsselected}}" data-action="conversations-view">
|
||||
<div class="span6 tab tabconversations {{#conversationsselected}}selected{{/conversationsselected}}"
|
||||
data-action="conversations-view"
|
||||
role="tab"
|
||||
aria-controls="conversations-tab-panel"
|
||||
{{#conversationsselected}}
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
{{/conversationsselected}}
|
||||
{{^conversationsselected}}
|
||||
aria-selected="false"
|
||||
tabindex="-1"
|
||||
{{/conversationsselected}}>
|
||||
|
||||
<div>{{#pix}}t/message, moodle{{/pix}}</div>
|
||||
<div>{{#str}}conversations, message{{/str}}</div>
|
||||
</div>
|
||||
<div class="span6 tab tabcontacts {{^conversationsselected}}selected{{/conversationsselected}}" data-action="contacts-view">
|
||||
<div class="span6 tab tabcontacts {{^conversationsselected}}selected{{/conversationsselected}}"
|
||||
data-action="contacts-view"
|
||||
role="tab"
|
||||
aria-controls="contacts-tab-panel"
|
||||
{{#conversationsselected}}
|
||||
aria-selected="false"
|
||||
tabindex="-1"
|
||||
{{/conversationsselected}}
|
||||
{{^conversationsselected}}
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
{{/conversationsselected}}>
|
||||
|
||||
<div>{{#pix}}i/cohort, moodle{{/pix}}</div>
|
||||
<div>{{#str}}contacts, message{{/str}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{blocktime}}
|
||||
</div>
|
||||
{{/displayblocktime}}
|
||||
<div class="message row-fluid" data-region="message" data-blocktime="{{blocktime}}" data-id="{{id}}{{isread}}">
|
||||
<div class="message row-fluid" data-region="message" data-blocktime="{{blocktime}}" data-id="{{id}}{{isread}}" tabindex="0">
|
||||
<div class="span1 deletemessagecheckbox" data-region="delete-message-checkbox">
|
||||
<input type="checkbox" data-messageid="{{id}}" data-messageread="{{isread}}"/>
|
||||
</div>
|
||||
@@ -15,4 +15,4 @@
|
||||
{{timesent}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user