HFP-1266 Add accessibility labels to controls
Improve the overall behavior when using a readspeaker. Still missing localization, dialog support and a proper timer.pull/30/head^2
parent
196c112243
commit
f17d7cb4b2
65
card.js
65
card.js
|
@ -21,6 +21,8 @@
|
||||||
var path = H5P.getPath(image.path, id);
|
var path = H5P.getPath(image.path, id);
|
||||||
var width, height, margin, $card, $wrapper, removedState, flippedState;
|
var width, height, margin, $card, $wrapper, removedState, flippedState;
|
||||||
|
|
||||||
|
alt = alt || 'Missing description'; // Default for old games
|
||||||
|
|
||||||
if (image.width !== undefined && image.height !== undefined) {
|
if (image.width !== undefined && image.height !== undefined) {
|
||||||
if (image.width > image.height) {
|
if (image.width > image.height) {
|
||||||
width = '100%';
|
width = '100%';
|
||||||
|
@ -35,10 +37,42 @@
|
||||||
width = height = '100%';
|
width = height = '100%';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the cards label to make it accessible to users with a readspeaker
|
||||||
|
*
|
||||||
|
* @param {boolean} isMatched The card has been matched
|
||||||
|
* @param {boolean} announce Announce the current state of the card
|
||||||
|
* @param {boolean} reset Go back to the default label
|
||||||
|
*/
|
||||||
|
self.updateLabel = function (isMatched, announce, reset) {
|
||||||
|
|
||||||
|
// Determine new label from input params
|
||||||
|
var label = (reset ? 'Unturned' : alt);
|
||||||
|
if (isMatched) {
|
||||||
|
label = 'Match found. ' + label; // TODO l10n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the card's label
|
||||||
|
$wrapper.attr('aria-label', 'Card ' + ($wrapper.index() + 1) + ': ' + label); // TODO l10n
|
||||||
|
|
||||||
|
// Update disabled property
|
||||||
|
$wrapper.attr('aria-disabled', reset ? null : 'true');
|
||||||
|
|
||||||
|
// Announce the label change
|
||||||
|
if (announce) {
|
||||||
|
$wrapper.blur().focus(); // Announce card label
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flip card.
|
* Flip card.
|
||||||
*/
|
*/
|
||||||
self.flip = function () {
|
self.flip = function () {
|
||||||
|
if (flippedState) {
|
||||||
|
$wrapper.blur().focus(); // Announce card label again
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$card.addClass('h5p-flipped');
|
$card.addClass('h5p-flipped');
|
||||||
self.trigger('flip');
|
self.trigger('flip');
|
||||||
flippedState = true;
|
flippedState = true;
|
||||||
|
@ -48,6 +82,7 @@
|
||||||
* Flip card back.
|
* Flip card back.
|
||||||
*/
|
*/
|
||||||
self.flipBack = function () {
|
self.flipBack = function () {
|
||||||
|
self.updateLabel(null, null, true); // Reset card label
|
||||||
$card.removeClass('h5p-flipped');
|
$card.removeClass('h5p-flipped');
|
||||||
flippedState = false;
|
flippedState = false;
|
||||||
};
|
};
|
||||||
|
@ -55,7 +90,7 @@
|
||||||
/**
|
/**
|
||||||
* Remove.
|
* Remove.
|
||||||
*/
|
*/
|
||||||
self.remove = function () {
|
self.remove = function (announce) {
|
||||||
$card.addClass('h5p-matched');
|
$card.addClass('h5p-matched');
|
||||||
removedState = true;
|
removedState = true;
|
||||||
};
|
};
|
||||||
|
@ -64,6 +99,9 @@
|
||||||
* Reset card to natural state
|
* Reset card to natural state
|
||||||
*/
|
*/
|
||||||
self.reset = function () {
|
self.reset = function () {
|
||||||
|
self.updateLabel(null, null, true); // Reset card label
|
||||||
|
flippedState = false;
|
||||||
|
removedState = false;
|
||||||
$card[0].classList.remove('h5p-flipped', 'h5p-matched');
|
$card[0].classList.remove('h5p-flipped', 'h5p-matched');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,11 +129,10 @@
|
||||||
* @param {H5P.jQuery} $container
|
* @param {H5P.jQuery} $container
|
||||||
*/
|
*/
|
||||||
self.appendTo = function ($container) {
|
self.appendTo = function ($container) {
|
||||||
// TODO: Translate alt attr
|
|
||||||
$wrapper = $('<li class="h5p-memory-wrap" tabindex="-1" role="button"><div class="h5p-memory-card">' +
|
$wrapper = $('<li class="h5p-memory-wrap" tabindex="-1" role="button"><div class="h5p-memory-card">' +
|
||||||
'<div class="h5p-front"' + (styles && styles.front ? styles.front : '') + '>' + (styles && styles.backImage ? '' : '<span></span>') + '</div>' +
|
'<div class="h5p-front"' + (styles && styles.front ? styles.front : '') + '>' + (styles && styles.backImage ? '' : '<span></span>') + '</div>' +
|
||||||
'<div class="h5p-back"' + (styles && styles.back ? styles.back : '') + '>' +
|
'<div class="h5p-back"' + (styles && styles.back ? styles.back : '') + '>' +
|
||||||
'<img src="' + path + '" alt="' + (alt || 'Memory Image') + '" style="width:' + width + ';height:' + height + '"/>' +
|
'<img src="' + path + '" alt="' + alt + '" style="width:' + width + ';height:' + height + '"/>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div></li>')
|
'</div></li>')
|
||||||
.appendTo($container)
|
.appendTo($container)
|
||||||
|
@ -103,10 +140,8 @@
|
||||||
switch (event.which) {
|
switch (event.which) {
|
||||||
case 13: // Enter
|
case 13: // Enter
|
||||||
case 32: // Space
|
case 32: // Space
|
||||||
if (!flippedState) {
|
self.flip();
|
||||||
self.flip();
|
event.preventDefault();
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
case 39: // Right
|
case 39: // Right
|
||||||
case 40: // Down
|
case 40: // Down
|
||||||
|
@ -122,6 +157,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$wrapper.attr('aria-label', 'Card ' + ($wrapper.index() + 1) + ': Unturned.'); // TODO l10n
|
||||||
$card = $wrapper.children('.h5p-memory-card')
|
$card = $wrapper.children('.h5p-memory-card')
|
||||||
.children('.h5p-front')
|
.children('.h5p-front')
|
||||||
.click(function () {
|
.click(function () {
|
||||||
|
@ -131,7 +167,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-append to parent container
|
* Re-append to parent container.
|
||||||
*/
|
*/
|
||||||
self.reAppend = function () {
|
self.reAppend = function () {
|
||||||
var parent = $wrapper[0].parentElement;
|
var parent = $wrapper[0].parentElement;
|
||||||
|
@ -139,7 +175,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Make the card accessible when tabbing
|
||||||
*/
|
*/
|
||||||
self.makeTabbable = function () {
|
self.makeTabbable = function () {
|
||||||
if ($wrapper) {
|
if ($wrapper) {
|
||||||
|
@ -148,7 +184,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Prevent tabbing to the card
|
||||||
*/
|
*/
|
||||||
self.makeUntabbable = function () {
|
self.makeUntabbable = function () {
|
||||||
if ($wrapper) {
|
if ($wrapper) {
|
||||||
|
@ -157,7 +193,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Make card tabbable and move focus to it
|
||||||
*/
|
*/
|
||||||
self.setFocus = function () {
|
self.setFocus = function () {
|
||||||
self.makeTabbable();
|
self.makeTabbable();
|
||||||
|
@ -167,10 +203,11 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Check if the card has been removed from the game, i.e. if has
|
||||||
|
* been matched.
|
||||||
*/
|
*/
|
||||||
self.isFlipped = function () {
|
self.isRemoved = function () {
|
||||||
return flippedState;
|
return removedState;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
.h5p-memory-game {
|
.h5p-memory-game {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.h5p-memory-game .h5p-memory-hidden-read {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
.h5p-memory-game > ul {
|
.h5p-memory-game > ul {
|
||||||
list-style: none !important;
|
list-style: none !important;
|
||||||
padding: 0.25em 0.5em !important;
|
padding: 0.25em 0.5em !important;
|
||||||
|
|
|
@ -5,6 +5,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
var CARD_STD_SIZE = 116; // PX
|
var CARD_STD_SIZE = 116; // PX
|
||||||
var STD_FONT_SIZE = 16; // PX
|
var STD_FONT_SIZE = 16; // PX
|
||||||
var LIST_PADDING = 1; // EMs
|
var LIST_PADDING = 1; // EMs
|
||||||
|
var numInstances = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memory Game Constructor
|
* Memory Game Constructor
|
||||||
|
@ -21,11 +22,12 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
// Initialize event inheritance
|
// Initialize event inheritance
|
||||||
EventDispatcher.call(self);
|
EventDispatcher.call(self);
|
||||||
|
|
||||||
var flipped, timer, counter, popup, $feedback, $wrapper, maxWidth, numCols;
|
var flipped, timer, counter, popup, $bottom, $feedback, $wrapper, maxWidth, numCols;
|
||||||
var cards = [];
|
var cards = [];
|
||||||
var flipBacks = []; // Que of cards to be flipped back
|
var flipBacks = []; // Que of cards to be flipped back
|
||||||
var numFlipped = 0;
|
var numFlipped = 0;
|
||||||
var removed = 0;
|
var removed = 0;
|
||||||
|
numInstances++;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if these two cards belongs together.
|
* Check if these two cards belongs together.
|
||||||
|
@ -49,17 +51,17 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove them from the game.
|
|
||||||
card.remove();
|
|
||||||
mate.remove();
|
|
||||||
|
|
||||||
// Update counters
|
// Update counters
|
||||||
numFlipped -= 2;
|
numFlipped -= 2;
|
||||||
removed += 2;
|
removed += 2;
|
||||||
|
|
||||||
var isFinished = (removed === cards.length);
|
var isFinished = (removed === cards.length);
|
||||||
var desc = card.getDescription();
|
|
||||||
|
|
||||||
|
// Remove them from the game.
|
||||||
|
card.remove(!isFinished);
|
||||||
|
mate.remove();
|
||||||
|
|
||||||
|
var desc = card.getDescription();
|
||||||
if (desc !== undefined) {
|
if (desc !== undefined) {
|
||||||
// Pause timer and show desciption.
|
// Pause timer and show desciption.
|
||||||
timer.pause();
|
timer.pause();
|
||||||
|
@ -70,6 +72,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
popup.show(desc, imgs, cardStyles ? cardStyles.back : undefined, function () {
|
popup.show(desc, imgs, cardStyles ? cardStyles.back : undefined, function () {
|
||||||
if (isFinished) {
|
if (isFinished) {
|
||||||
// Game done
|
// Game done
|
||||||
|
card.makeUntabbable();
|
||||||
finished();
|
finished();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -80,6 +83,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
}
|
}
|
||||||
else if (isFinished) {
|
else if (isFinished) {
|
||||||
// Game done
|
// Game done
|
||||||
|
card.makeUntabbable();
|
||||||
finished();
|
finished();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -90,7 +94,8 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
*/
|
*/
|
||||||
var finished = function () {
|
var finished = function () {
|
||||||
timer.stop();
|
timer.stop();
|
||||||
$feedback.addClass('h5p-show');
|
$feedback.addClass('h5p-show'); // Announce
|
||||||
|
$bottom.focus();
|
||||||
|
|
||||||
// Create and trigger xAPI event 'completed'
|
// Create and trigger xAPI event 'completed'
|
||||||
var completedEvent = self.createXAPIEventTemplate('completed');
|
var completedEvent = self.createXAPIEventTemplate('completed');
|
||||||
|
@ -113,7 +118,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
});
|
});
|
||||||
retryButton.classList.add('h5p-memory-transin');
|
retryButton.classList.add('h5p-memory-transin');
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
// Remove class on nextTick to get transition effect
|
// Remove class on nextTick to get transition effectupd
|
||||||
retryButton.classList.remove('h5p-memory-transin');
|
retryButton.classList.remove('h5p-memory-transin');
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
@ -132,9 +137,6 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
|
|
||||||
// Reset cards
|
// Reset cards
|
||||||
removed = 0;
|
removed = 0;
|
||||||
for (var i = 0; i < cards.length; i++) {
|
|
||||||
cards[i].reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove feedback
|
// Remove feedback
|
||||||
$feedback[0].classList.remove('h5p-show');
|
$feedback[0].classList.remove('h5p-show');
|
||||||
|
@ -151,11 +153,15 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
for (var i = 0; i < cards.length; i++) {
|
for (var i = 0; i < cards.length; i++) {
|
||||||
cards[i].reAppend();
|
cards[i].reAppend();
|
||||||
}
|
}
|
||||||
|
for (var j = 0; j < cards.length; j++) {
|
||||||
|
cards[j].reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Scale new layout
|
// Scale new layout
|
||||||
$wrapper.children('ul').children('.h5p-row-break').removeClass('h5p-row-break');
|
$wrapper.children('ul').children('.h5p-row-break').removeClass('h5p-row-break');
|
||||||
maxWidth = -1;
|
maxWidth = -1;
|
||||||
self.trigger('resize');
|
self.trigger('resize');
|
||||||
|
cards[0].setFocus();
|
||||||
}, 600);
|
}, 600);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -197,6 +203,11 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
// Keep track of the number of flipped cards
|
// Keep track of the number of flipped cards
|
||||||
numFlipped++;
|
numFlipped++;
|
||||||
|
|
||||||
|
// Announce the card unless it's the last one and it's correct
|
||||||
|
var isMatched = (flipped === mate);
|
||||||
|
var isLast = ((removed + 2) === cards.length);
|
||||||
|
card.updateLabel(isMatched, !(isMatched && isLast));
|
||||||
|
|
||||||
if (flipped !== undefined) {
|
if (flipped !== undefined) {
|
||||||
var matie = flipped;
|
var matie = flipped;
|
||||||
// Reset the flipped card.
|
// Reset the flipped card.
|
||||||
|
@ -239,7 +250,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
return; // No more cards
|
return; // No more cards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (nextCard.isFlipped());
|
while (nextCard.isRemoved());
|
||||||
|
|
||||||
card.makeUntabbable();
|
card.makeUntabbable();
|
||||||
nextCard.setFocus();
|
nextCard.setFocus();
|
||||||
|
@ -342,24 +353,43 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cards to list
|
// Add cards to list
|
||||||
var $list = $('<ul/>');
|
var $list = $('<ul/>', {
|
||||||
|
role: 'application',
|
||||||
|
'aria-labelledby': 'h5p-intro-' + numInstances
|
||||||
|
});
|
||||||
for (var i = 0; i < cards.length; i++) {
|
for (var i = 0; i < cards.length; i++) {
|
||||||
cards[i].appendTo($list);
|
cards[i].appendTo($list);
|
||||||
}
|
}
|
||||||
cards[0].makeTabbable();
|
cards[0].makeTabbable();
|
||||||
|
|
||||||
if ($list.children().length) {
|
if ($list.children().length) {
|
||||||
|
$('<div/>', {
|
||||||
|
id: 'h5p-intro-' + numInstances,
|
||||||
|
'class': 'h5p-memory-hidden-read',
|
||||||
|
html: 'Memory Game. Find the matching cards.', // TODO: l10n
|
||||||
|
appendTo: $container
|
||||||
|
});
|
||||||
$list.appendTo($container);
|
$list.appendTo($container);
|
||||||
|
|
||||||
$feedback = $('<div class="h5p-feedback">' + parameters.l10n.feedback + '</div>').appendTo($container);
|
$bottom = $('<div/>', {
|
||||||
|
tabindex: '-1',
|
||||||
|
appendTo: $container
|
||||||
|
});
|
||||||
|
$('<div/>', {
|
||||||
|
'class': 'h5p-memory-hidden-read',
|
||||||
|
html: 'All of the cards have been found.', // TODO: l10n
|
||||||
|
appendTo: $bottom
|
||||||
|
});
|
||||||
|
|
||||||
|
$feedback = $('<div class="h5p-feedback">' + parameters.l10n.feedback + '</div>').appendTo($bottom);
|
||||||
|
|
||||||
// Add status bar
|
// Add status bar
|
||||||
var $status = $('<dl class="h5p-status">' +
|
var $status = $('<dl class="h5p-status">' +
|
||||||
'<dt>' + parameters.l10n.timeSpent + '</dt>' +
|
'<dt>' + parameters.l10n.timeSpent + '</dt>' +
|
||||||
'<dd class="h5p-time-spent">0:00</dd>' +
|
'<dd class="h5p-time-spent">0:00</dd>' + // TODO: Add hidden dot ?
|
||||||
'<dt>' + parameters.l10n.cardTurns + '</dt>' +
|
'<dt>' + parameters.l10n.cardTurns + '</dt>' +
|
||||||
'<dd class="h5p-card-turns">0</dd>' +
|
'<dd class="h5p-card-turns">0</dd>' +
|
||||||
'</dl>').appendTo($container);
|
'</dl>').appendTo($bottom);
|
||||||
|
|
||||||
timer = new MemoryGame.Timer($status.find('.h5p-time-spent')[0]);
|
timer = new MemoryGame.Timer($status.find('.h5p-time-spent')[0]);
|
||||||
counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));
|
counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));
|
||||||
|
|
Loading…
Reference in New Issue