diff --git a/card.js b/card.js
index 76a1c73..38d1ffe 100644
--- a/card.js
+++ b/card.js
@@ -21,6 +21,8 @@
var path = H5P.getPath(image.path, id);
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 > image.height) {
width = '100%';
@@ -35,10 +37,42 @@
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.
*/
self.flip = function () {
+ if (flippedState) {
+ $wrapper.blur().focus(); // Announce card label again
+ return;
+ }
+
$card.addClass('h5p-flipped');
self.trigger('flip');
flippedState = true;
@@ -48,6 +82,7 @@
* Flip card back.
*/
self.flipBack = function () {
+ self.updateLabel(null, null, true); // Reset card label
$card.removeClass('h5p-flipped');
flippedState = false;
};
@@ -55,7 +90,7 @@
/**
* Remove.
*/
- self.remove = function () {
+ self.remove = function (announce) {
$card.addClass('h5p-matched');
removedState = true;
};
@@ -64,6 +99,9 @@
* Reset card to natural state
*/
self.reset = function () {
+ self.updateLabel(null, null, true); // Reset card label
+ flippedState = false;
+ removedState = false;
$card[0].classList.remove('h5p-flipped', 'h5p-matched');
};
@@ -91,11 +129,10 @@
* @param {H5P.jQuery} $container
*/
self.appendTo = function ($container) {
- // TODO: Translate alt attr
$wrapper = $('
' +
'
' + (styles && styles.backImage ? '' : '') + '
' +
'
' +
- '
' +
+ '
' +
'
' +
'
')
.appendTo($container)
@@ -103,10 +140,8 @@
switch (event.which) {
case 13: // Enter
case 32: // Space
- if (!flippedState) {
- self.flip();
- event.preventDefault();
- }
+ self.flip();
+ event.preventDefault();
return;
case 39: // Right
case 40: // Down
@@ -122,6 +157,7 @@
return;
}
});
+ $wrapper.attr('aria-label', 'Card ' + ($wrapper.index() + 1) + ': Unturned.'); // TODO l10n
$card = $wrapper.children('.h5p-memory-card')
.children('.h5p-front')
.click(function () {
@@ -131,7 +167,7 @@
};
/**
- * Re-append to parent container
+ * Re-append to parent container.
*/
self.reAppend = function () {
var parent = $wrapper[0].parentElement;
@@ -139,7 +175,7 @@
};
/**
- *
+ * Make the card accessible when tabbing
*/
self.makeTabbable = function () {
if ($wrapper) {
@@ -148,7 +184,7 @@
};
/**
- *
+ * Prevent tabbing to the card
*/
self.makeUntabbable = function () {
if ($wrapper) {
@@ -157,7 +193,7 @@
};
/**
- *
+ * Make card tabbable and move focus to it
*/
self.setFocus = function () {
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 () {
- return flippedState;
+ self.isRemoved = function () {
+ return removedState;
};
};
diff --git a/memory-game.css b/memory-game.css
index 199a07b..4d61392 100644
--- a/memory-game.css
+++ b/memory-game.css
@@ -1,6 +1,14 @@
.h5p-memory-game {
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 {
list-style: none !important;
padding: 0.25em 0.5em !important;
diff --git a/memory-game.js b/memory-game.js
index 894f512..a0cb033 100644
--- a/memory-game.js
+++ b/memory-game.js
@@ -5,6 +5,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
var CARD_STD_SIZE = 116; // PX
var STD_FONT_SIZE = 16; // PX
var LIST_PADDING = 1; // EMs
+ var numInstances = 0;
/**
* Memory Game Constructor
@@ -21,11 +22,12 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// Initialize event inheritance
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 flipBacks = []; // Que of cards to be flipped back
var numFlipped = 0;
var removed = 0;
+ numInstances++;
/**
* Check if these two cards belongs together.
@@ -49,17 +51,17 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
return;
}
- // Remove them from the game.
- card.remove();
- mate.remove();
-
// Update counters
numFlipped -= 2;
removed += 2;
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) {
// Pause timer and show desciption.
timer.pause();
@@ -70,6 +72,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
popup.show(desc, imgs, cardStyles ? cardStyles.back : undefined, function () {
if (isFinished) {
// Game done
+ card.makeUntabbable();
finished();
}
else {
@@ -80,6 +83,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
}
else if (isFinished) {
// Game done
+ card.makeUntabbable();
finished();
}
};
@@ -90,7 +94,8 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
*/
var finished = function () {
timer.stop();
- $feedback.addClass('h5p-show');
+ $feedback.addClass('h5p-show'); // Announce
+ $bottom.focus();
// Create and trigger xAPI event 'completed'
var completedEvent = self.createXAPIEventTemplate('completed');
@@ -113,7 +118,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
});
retryButton.classList.add('h5p-memory-transin');
setTimeout(function () {
- // Remove class on nextTick to get transition effect
+ // Remove class on nextTick to get transition effectupd
retryButton.classList.remove('h5p-memory-transin');
}, 0);
@@ -132,9 +137,6 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// Reset cards
removed = 0;
- for (var i = 0; i < cards.length; i++) {
- cards[i].reset();
- }
// Remove feedback
$feedback[0].classList.remove('h5p-show');
@@ -151,11 +153,15 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
for (var i = 0; i < cards.length; i++) {
cards[i].reAppend();
}
+ for (var j = 0; j < cards.length; j++) {
+ cards[j].reset();
+ }
// Scale new layout
$wrapper.children('ul').children('.h5p-row-break').removeClass('h5p-row-break');
maxWidth = -1;
self.trigger('resize');
+ cards[0].setFocus();
}, 600);
};
@@ -197,6 +203,11 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// Keep track of the number of flipped cards
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) {
var matie = flipped;
// Reset the flipped card.
@@ -239,7 +250,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
return; // No more cards
}
}
- while (nextCard.isFlipped());
+ while (nextCard.isRemoved());
card.makeUntabbable();
nextCard.setFocus();
@@ -342,24 +353,43 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
}
// Add cards to list
- var $list = $('');
+ var $list = $('', {
+ role: 'application',
+ 'aria-labelledby': 'h5p-intro-' + numInstances
+ });
for (var i = 0; i < cards.length; i++) {
cards[i].appendTo($list);
}
cards[0].makeTabbable();
if ($list.children().length) {
+ $('', {
+ id: 'h5p-intro-' + numInstances,
+ 'class': 'h5p-memory-hidden-read',
+ html: 'Memory Game. Find the matching cards.', // TODO: l10n
+ appendTo: $container
+ });
$list.appendTo($container);
- $feedback = $('' + parameters.l10n.feedback + '
').appendTo($container);
+ $bottom = $('', {
+ tabindex: '-1',
+ appendTo: $container
+ });
+ $('', {
+ 'class': 'h5p-memory-hidden-read',
+ html: 'All of the cards have been found.', // TODO: l10n
+ appendTo: $bottom
+ });
+
+ $feedback = $('' + parameters.l10n.feedback + '
').appendTo($bottom);
// Add status bar
var $status = $('' +
'- ' + parameters.l10n.timeSpent + '
' +
- '- 0:00
' +
+ '- 0:00
' + // TODO: Add hidden dot ?
'- ' + parameters.l10n.cardTurns + '
' +
'- 0
' +
- '
').appendTo($container);
+ '').appendTo($bottom);
timer = new MemoryGame.Timer($status.find('.h5p-time-spent')[0]);
counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));