Files
edx-platform/lms/static/js/edxnotes/plugins/accessibility.js
2019-09-24 13:42:56 +05:00

339 lines
14 KiB
JavaScript

/* eslint-disable */
(function(define, undefined) {
'use strict';
define(['jquery',
'underscore',
'annotator_1.2.9',
'edx-ui-toolkit/js/utils/constants',
], function($, _, Annotator, Constants) {
/**
* Adds the Accessibility Plugin
**/
Annotator.Plugin.Accessibility = function() {
_.bindAll(this,
'addAriaAttributes', 'onHighlightKeyDown', 'onViewerKeyDown',
'onEditorKeyDown', 'addDescriptions', 'removeDescription',
'focusOnGrabber', 'showViewer', 'onClose', 'focusOnHighlightedText'
);
// Call the Annotator.Plugin constructor this sets up the element and
// options properties.
Annotator.Plugin.apply(this, arguments);
};
$.extend(Annotator.Plugin.Accessibility.prototype, new Annotator.Plugin(), {
pluginInit: function() {
this.annotator.subscribe('annotationViewerTextField', this.addAriaAttributes);
this.annotator.subscribe('annotationsLoaded', this.addDescriptions);
this.annotator.subscribe('annotationCreated', this.addDescriptions);
this.annotator.subscribe('annotationCreated', this.focusOnHighlightedText);
this.annotator.subscribe('annotationDeleted', this.removeDescription);
this.annotator.element.on('keydown.accessibility.hl', '.annotator-hl', this.onHighlightKeyDown);
this.annotator.element.on('keydown.accessibility.viewer', '.annotator-viewer', this.onViewerKeyDown);
this.annotator.element.on('keydown.accessibility.editor', '.annotator-editor', this.onEditorKeyDown);
this.addFocusGrabber();
this.addTabIndex();
},
destroy: function() {
this.annotator.unsubscribe('annotationViewerTextField', this.addAriaAttributes);
this.annotator.unsubscribe('annotationsLoaded', this.addDescriptions);
this.annotator.unsubscribe('annotationCreated', this.addDescriptions);
this.annotator.unsubscribe('annotationCreated', this.focusOnHighlightedText);
this.annotator.unsubscribe('annotationDeleted', this.removeDescription);
this.annotator.element.off('.accessibility');
this.removeFocusGrabber();
},
addTabIndex: function() {
this.annotator.element
.find('.annotator-edit, .annotator-delete')
.attr('tabindex', 0);
},
addFocusGrabber: function() {
this.focusGrabber = $('<span />', {
class: 'edx-notes-focus-grabber',
tabindex: '-1'
});
this.annotator.wrapper.before(this.focusGrabber); // xss-lint: disable=javascript-jquery-insertion
},
removeFocusGrabber: function() {
if (this.focusGrabber) {
this.focusGrabber.remove();
this.focusGrabber = null;
}
},
focusOnGrabber: function() {
this.annotator.wrapper.siblings('.edx-notes-focus-grabber').focus();
},
addDescriptions: function(annotations) {
if (!_.isArray(annotations)) {
annotations = [annotations];
}
_.each(annotations, function(annotation) {
var id = annotation.id || _.uniqueId();
this.annotator.wrapper.after($('<div />', {
class: 'aria-note-description sr',
id: 'aria-note-description-' + id,
text: Annotator.Util.escape(annotation.text) // xss-lint: disable=javascript-escape
}));
$(annotation.highlights).attr({
'aria-describedby': 'aria-note-description-' + id
});
}, this);
},
removeDescription: function(annotation) {
var id = $(annotation.highlights).attr('aria-describedby');
$('#' + id).remove();
},
addAriaAttributes: function(field, annotation) {
// Add ARIA attributes to associated note ie <div>My note</div>
$(field).attr({
tabindex: -1,
role: 'note',
class: 'annotator-note'
});
},
focusOnHighlightedText: function() {
var viewer = this.annotator.viewer,
editor = this.annotator.editor,
highlight;
try {
if (viewer.isShown()) {
highlight = viewer.annotations[0].highlights[0];
} else if (editor.isShown()) {
highlight = editor.annotation.highlights[0];
}
highlight.focus();
} catch (err) {}
},
getViewerTabControls: function() {
var viewer, note, viewerControls, editButton, delButton, closeButton,
tabControls = [];
// Viewer elements
viewer = this.annotator.element.find('.annotator-viewer');
note = viewer.find('.annotator-note');
viewerControls = viewer.find('.annotator-controls');
editButton = viewerControls.find('.annotator-edit');
delButton = viewerControls.find('.annotator-delete');
closeButton = viewerControls.find('.annotator-close');
tabControls.push(note, editButton, delButton, closeButton);
return tabControls;
},
getEditorTabControls: function() {
var editor, editorControls, textArea, saveButton, cancelButton,
tabControls = [],
annotatorItems,
tagInput = null;
// Editor elements
editor = this.annotator.element.find('.annotator-editor');
editorControls = editor.find('.annotator-controls');
annotatorItems = editor.find('.annotator-listing').find('.annotator-item');
textArea = annotatorItems.first().children('textarea');
saveButton = editorControls.find('.annotator-save');
cancelButton = editorControls.find('.annotator-cancel');
// If the tags plugin is enabled, add the ability to tab into it.
if (annotatorItems.length > 1) {
tagInput = annotatorItems.first().next().children('input');
}
tabControls.push(textArea);
if (tagInput) {
tabControls.push(tagInput);
}
tabControls.push(saveButton, cancelButton);
return tabControls;
},
focusOnNextTabControl: function(tabControls, tabControl) {
var nextIndex;
_.each(tabControls, function(element, index) {
if (element.is(tabControl)) {
nextIndex = index === tabControls.length - 1 ? 0 : index + 1;
tabControls[nextIndex].focus();
}
});
},
focusOnPreviousTabControl: function(tabControls, tabControl) {
var previousIndex;
_.each(tabControls, function(element, index) {
if (element.is(tabControl)) {
previousIndex = index === 0 ? tabControls.length - 1 : index - 1;
tabControls[previousIndex].focus();
}
});
},
showViewer: function(position, annotation) {
annotation = $.makeArray(annotation);
this.annotator.showViewer(annotation, position);
this.annotator.element.find('.annotator-listing').focus();
this.annotator.subscribe('annotationDeleted', this.focusOnGrabber);
},
onClose: function() {
this.focusOnHighlightedText();
this.annotator.unsubscribe('annotationDeleted', this.focusOnGrabber);
},
onHighlightKeyDown: function(event) {
var key = Constants.keyCodes,
keyCode = event.keyCode,
$target = $(event.currentTarget),
annotation, position;
switch (keyCode) {
case key.tab:
// This happens only when coming from notes page
if (this.annotator.viewer.isShown()) {
this.annotator.element.find('.annotator-listing').focus();
event.preventDefault();
event.stopPropagation();
}
break;
case key.enter:
case key.space:
if (!this.annotator.viewer.isShown()) {
position = $target.position();
this.showViewer(position, $target.data('annotation'));
event.preventDefault();
event.stopPropagation();
}
break;
case key.esc:
this.annotator.viewer.hide();
event.preventDefault();
event.stopPropagation();
break;
}
},
onViewerKeyDown: function(event) {
var key = Constants.keyCodes,
keyCode = event.keyCode,
$target = $(event.target),
listing = this.annotator.element.find('.annotator-listing'),
tabControls;
switch (keyCode) {
case key.tab:
tabControls = this.getViewerTabControls();
if (event.shiftKey) { // Tabbing backwards
if ($target.is(listing)) {
_.last(tabControls).focus();
} else {
this.focusOnPreviousTabControl(tabControls, $target);
}
} else { // Tabbing forward
if ($target.is(listing)) {
_.first(tabControls).focus();
} else {
this.focusOnNextTabControl(tabControls, $target);
}
}
event.preventDefault();
event.stopPropagation();
break;
case key.enter:
case key.space:
if ($target.hasClass('annotator-close')) {
this.onClose();
this.annotator.viewer.hide();
event.preventDefault();
}
break;
case key.esc:
this.onClose();
this.annotator.viewer.hide();
event.preventDefault();
break;
}
},
onEditorKeyDown: function(event) {
var key = Constants.keyCodes,
keyCode = event.keyCode,
$target = $(event.target),
editor, form, editorControls, save, cancel,
tabControls;
editor = this.annotator.element.find('.annotator-editor');
form = editor.find('.annotator-widget');
editorControls = editor.find('.annotator-controls');
save = editorControls.find('.annotator-save');
cancel = editorControls.find('.annotator-cancel');
switch (keyCode) {
case key.tab:
tabControls = this.getEditorTabControls();
if (event.shiftKey) { // Tabbing backwards
if ($target.is(form)) {
_.last(tabControls).focus();
} else {
this.focusOnPreviousTabControl(tabControls, $target);
}
} else { // Tabbing forward
if ($target.is(form)) {
_.first(tabControls).focus();
} else {
this.focusOnNextTabControl(tabControls, $target);
}
}
event.preventDefault();
event.stopPropagation();
break;
case key.enter:
if ($target.is(save) || event.metaKey || event.ctrlKey) {
this.onClose();
this.annotator.editor.submit();
} else if ($target.is(cancel)) {
this.onClose();
this.annotator.editor.hide();
} else {
break;
}
event.preventDefault();
break;
case key.space:
if ($target.is(save)) {
this.onClose();
this.annotator.editor.submit();
} else if ($target.is(cancel)) {
this.onClose();
this.annotator.editor.hide();
} else {
break;
}
event.preventDefault();
break;
case key.esc:
this.onClose();
this.annotator.editor.hide();
event.preventDefault();
break;
}
}
});
});
}).call(this, define || RequireJS.define);