h5p-memory-game/card.js

336 lines
8.8 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

(function (MemoryGame, EventDispatcher, $) {
/**
* Controls all the operations for each card.
*
* @class H5P.MemoryGame.Card
* @extends H5P.EventDispatcher
* @param {Object} image
* @param {number} id
* @param {string} alt
* @param {Object} l10n Localization
* @param {string} [description]
* @param {Object} [styles]
*/
MemoryGame.Card = function (image, id, alt, l10n, description, styles) {
/** @alias H5P.MemoryGame.Card# */
var self = this;
// Initialize event inheritance
EventDispatcher.call(self);
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%';
height = 'auto';
}
else {
height = '100%';
width = 'auto';
}
}
else {
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 ? l10n.cardUnturned : alt);
if (isMatched) {
label = l10n.cardMatched + ' ' + label;
}
// Update the card's label
$wrapper.attr('aria-label', l10n.cardPrefix.replace('%num', $wrapper.index() + 1) + ' ' + label);
// 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;
};
/**
* Flip card back.
*/
self.flipBack = function () {
self.updateLabel(null, null, true); // Reset card label
$card.removeClass('h5p-flipped');
flippedState = false;
};
/**
* Remove.
*/
self.remove = function (announce) {
$card.addClass('h5p-matched');
removedState = true;
};
/**
* 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');
};
/**
* Get card description.
*
* @returns {string}
*/
self.getDescription = function () {
return description;
};
/**
* Get image clone.
*
* @returns {H5P.jQuery}
*/
self.getImage = function () {
return $card.find('img').clone();
};
/**
* Append card to the given container.
*
* @param {H5P.jQuery} $container
*/
self.appendTo = function ($container) {
$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-back"' + (styles && styles.back ? styles.back : '') + '>' +
'<img src="' + path + '" alt="' + alt + '" style="width:' + width + ';height:' + height + '"/>' +
'</div>' +
'</div></li>')
.appendTo($container)
.on('keydown', function (event) {
switch (event.which) {
case 13: // Enter
case 32: // Space
self.flip();
event.preventDefault();
return;
case 39: // Right
case 40: // Down
// Move focus forward
self.trigger('next');
event.preventDefault();
return;
case 37: // Left
case 38: // Up
// Move focus back
self.trigger('prev');
event.preventDefault();
return;
case 35:
// Move to last card
self.trigger('last');
event.preventDefault();
return;
case 36:
// Move to first card
self.trigger('first');
event.preventDefault();
return;
}
});
$wrapper.attr('aria-label', l10n.cardPrefix.replace('%num', $wrapper.index() + 1) + ' ' + l10n.cardUnturned);
$card = $wrapper.children('.h5p-memory-card')
.children('.h5p-front')
.click(function () {
self.flip();
})
.end();
};
/**
* Re-append to parent container.
*/
self.reAppend = function () {
var parent = $wrapper[0].parentElement;
parent.appendChild($wrapper[0]);
};
/**
* Make the card accessible when tabbing
*/
self.makeTabbable = function () {
if ($wrapper) {
$wrapper.attr('tabindex', '0');
}
};
/**
* Prevent tabbing to the card
*/
self.makeUntabbable = function () {
if ($wrapper) {
$wrapper.attr('tabindex', '-1');
}
};
/**
* Make card tabbable and move focus to it
*/
self.setFocus = function () {
self.makeTabbable();
if ($wrapper) {
$wrapper.focus();
}
};
/**
* Check if the card has been removed from the game, i.e. if has
* been matched.
*/
self.isRemoved = function () {
return removedState;
};
};
// Extends the event dispatcher
MemoryGame.Card.prototype = Object.create(EventDispatcher.prototype);
MemoryGame.Card.prototype.constructor = MemoryGame.Card;
/**
* Check to see if the given object corresponds with the semantics for
* a memory game card.
*
* @param {object} params
* @returns {boolean}
*/
MemoryGame.Card.isValid = function (params) {
return (params !== undefined &&
params.image !== undefined &&
params.image.path !== undefined);
};
/**
* Checks to see if the card parameters should create cards with different
* images.
*
* @param {object} params
* @returns {boolean}
*/
MemoryGame.Card.hasTwoImages = function (params) {
return (params !== undefined &&
params.match !== undefined &&
params.match.path !== undefined);
};
/**
* Determines the theme for how the cards should look
*
* @param {string} color The base color selected
* @param {number} invertShades Factor used to invert shades in case of bad contrast
*/
MemoryGame.Card.determineStyles = function (color, invertShades, backImage) {
var styles = {
front: '',
back: '',
backImage: !!backImage
};
// Create color theme
if (color) {
var frontColor = shade(color, 43.75 * invertShades);
var backColor = shade(color, 56.25 * invertShades);
styles.front += 'color:' + color + ';' +
'background-color:' + frontColor + ';' +
'border-color:' + frontColor +';';
styles.back += 'color:' + color + ';' +
'background-color:' + backColor + ';' +
'border-color:' + frontColor +';';
}
// Add back image for card
if (backImage) {
var backgroundImage = 'background-image:url(' + backImage + ')';
styles.front += backgroundImage;
styles.back += backgroundImage;
}
// Prep style attribute
if (styles.front) {
styles.front = ' style="' + styles.front + '"';
}
if (styles.back) {
styles.back = ' style="' + styles.back + '"';
}
return styles;
};
/**
* Convert hex color into shade depending on given percent
*
* @private
* @param {string} color
* @param {number} percent
* @return {string} new color
*/
var shade = function (color, percent) {
var newColor = '#';
// Determine if we should lighten or darken
var max = (percent < 0 ? 0 : 255);
// Always stay positive
if (percent < 0) {
percent *= -1;
}
percent /= 100;
for (var i = 1; i < 6; i += 2) {
// Grab channel and convert from hex to dec
var channel = parseInt(color.substr(i, 2), 16);
// Calculate new shade and convert back to hex
channel = (Math.round((max - channel) * percent) + channel).toString(16);
// Make sure to always use two digits
newColor += (channel.length < 2 ? '0' + channel : channel);
}
return newColor;
};
})(H5P.MemoryGame, H5P.EventDispatcher, H5P.jQuery);