Skip to content

Commit

Permalink
MDL-79061 course: Create a sr-only mutations logger
Browse files Browse the repository at this point in the history
* Create SRLogger that extends from the default Logger class. This
logger outputs the feedback to a sr-only ARIA live region to allow
screen readers to announce the feedback from mutations.
* Update the course editor to use the SRLogger.
  • Loading branch information
junpataleta committed Dec 7, 2023
1 parent d8c0e18 commit d953068
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 4 deletions.
4 changes: 2 additions & 2 deletions course/format/amd/build/local/courseeditor/mutations.min.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion course/format/amd/src/local/courseeditor/mutations.js
Expand Up @@ -16,6 +16,14 @@
import ajax from 'core/ajax';
import {getString} from "core/str";
import log from 'core/log';
import SRLogger from "core/local/reactive/srlogger";

/**
* Flag to determine whether the screen reader-only logger has already been set, so we only need to set it once.
*
* @type {boolean}
*/
let isLoggerSet = false;

/**
* Default mutation manager
Expand Down Expand Up @@ -129,6 +137,11 @@ export default class {
* @return {Object} the log entry
*/
async _getLoggerEntry(stateManager, action, itemIds, data = {}) {
if (!isLoggerSet) {
// In case the logger has not been set from init(), ensure we set the logger.
stateManager.setLogger(new SRLogger());
isLoggerSet = true;
}
const feedbackParams = {
action,
itemType: data.itemType ?? action.split('_')[0],
Expand Down Expand Up @@ -168,10 +181,13 @@ export default class {
* @param {StateManager} stateManager the state manager
*/
init(stateManager) {
// Add a method to prepare the fields when some update is comming from the server.
// Add a method to prepare the fields when some update is coming from the server.
stateManager.addUpdateTypes({
prepareFields: this._prepareFields,
});
// Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.
stateManager.setLogger(new SRLogger());
isLoggerSet = true;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions lib/amd/build/local/reactive/srlogger.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/amd/build/local/reactive/srlogger.min.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions lib/amd/src/local/reactive/srlogger.js
@@ -0,0 +1,75 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Screen reader-only (sr-only) reactive mutations logger class.
*
* This logger can be used by the StateManager to log mutation feedbacks and actions.
* The feedback messages logged by this logger will be rendered in a sr-only, ARIA live region.
*
* @module core/local/reactive/srlogger
* @class SRLogger
* @copyright 2023 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

import Logger from 'core/local/reactive/logger';

/**
* Logger entry structure.
*
* @typedef {object} LoggerEntry
* @property {string} feedbackMessage Feedback message.
*/

/**
* Screen reader-only (sr-only) reactive mutations logger class.
*
* @class SRLogger
*/
export default class SRLogger extends Logger {
/**
* The element ID of the ARIA live region where the logger feedback will be rendered.
*
* @type {string}
*/
static liveRegionId = 'sr-logger-feedback-container';

/**
* Add a log entry.
* @param {LoggerEntry} entry Log entry.
*/
add(entry) {
if (entry.feedbackMessage) {
// Fetch or create an ARIA live region that will serve as the container for the logger feedback.
let loggerFeedback = document.getElementById(SRLogger.liveRegionId);
if (!loggerFeedback) {
loggerFeedback = document.createElement('div');
loggerFeedback.id = SRLogger.liveRegionId;
loggerFeedback.classList.add('sr-only');
loggerFeedback.setAttribute('aria-live', 'polite');
document.body.append(loggerFeedback);
}
// Set the ARIA live region's contents with the feedback.
loggerFeedback.innerHTML = entry.feedbackMessage;

// Clear the feedback message after 4 seconds to avoid the contents from being read out in case the user navigates
// to this region. This is similar to the default timeout of toast messages before disappearing from view.
setTimeout(() => {
loggerFeedback.innerHTML = '';
}, 4000);
}
}
}

0 comments on commit d953068

Please sign in to comment.