H5P.AdvancedText = (function ($, EventDispatcher) { /** * A simple library for displaying text with advanced styling. * * @class H5P.AdvancedText * @param {Object} parameters * @param {Object} [parameters.text='New text'] * @param {number} id */ function AdvancedText(parameters, id) { var self = this; EventDispatcher.call(this); var html = (parameters.text === undefined ? '<em>New text</em>' : parameters.text); /** * Wipe container and add text html. * * @alias H5P.AdvancedText#attach * @param {H5P.jQuery} $container */ self.attach = function ($container) { $container.addClass('h5p-advanced-text').html(html); }; } AdvancedText.prototype = Object.create(EventDispatcher.prototype); AdvancedText.prototype.constructor = AdvancedText; return AdvancedText; })(H5P.jQuery, H5P.EventDispatcher); ; var H5P = H5P || {}; /** * Transition contains helper function relevant for transitioning */ H5P.Transition = (function ($) { /** * @class * @namespace H5P */ Transition = {}; /** * @private */ Transition.transitionEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'transition': 'transitionend', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd' }; /** * @private */ Transition.cache = []; /** * Get the vendor property name for an event * * @function H5P.Transition.getVendorPropertyName * @static * @private * @param {string} prop Generic property name * @return {string} Vendor specific property name */ Transition.getVendorPropertyName = function (prop) { if (Transition.cache[prop] !== undefined) { return Transition.cache[prop]; } var div = document.createElement('div'); // Handle unprefixed versions (FF16+, for example) if (prop in div.style) { Transition.cache[prop] = prop; } else { var prefixes = ['Moz', 'Webkit', 'O', 'ms']; var prop_ = prop.charAt(0).toUpperCase() + prop.substr(1); if (prop in div.style) { Transition.cache[prop] = prop; } else { for (var i = 0; i < prefixes.length; ++i) { var vendorProp = prefixes[i] + prop_; if (vendorProp in div.style) { Transition.cache[prop] = vendorProp; break; } } } } return Transition.cache[prop]; }; /** * Get the name of the transition end event * * @static * @private * @return {string} description */ Transition.getTransitionEndEventName = function () { return Transition.transitionEndEventNames[Transition.getVendorPropertyName('transition')] || undefined; }; /** * Helper function for listening on transition end events * * @function H5P.Transition.onTransitionEnd * @static * @param {domElement} $element The element which is transitioned * @param {function} callback The callback to be invoked when transition is finished * @param {number} timeout Timeout in milliseconds. Fallback if transition event is never fired */ Transition.onTransitionEnd = function ($element, callback, timeout) { // Fallback on 1 second if transition event is not supported/triggered timeout = timeout || 1000; Transition.transitionEndEventName = Transition.transitionEndEventName || Transition.getTransitionEndEventName(); var callbackCalled = false; var doCallback = function () { if (callbackCalled) { return; } $element.off(Transition.transitionEndEventName, callback); callbackCalled = true; clearTimeout(timer); callback(); }; var timer = setTimeout(function () { doCallback(); }, timeout); $element.on(Transition.transitionEndEventName, function () { doCallback(); }); }; /** * Wait for a transition - when finished, invokes next in line * * @private * * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition * @param {number} index The index for current transition */ var runSequence = function (transitions, index) { if (index >= transitions.length) { return; } var transition = transitions[index]; H5P.Transition.onTransitionEnd(transition.$element, function () { if (transition.end) { transition.end(); } if (transition.break !== true) { runSequence(transitions, index+1); } }, transition.timeout || undefined); }; /** * Run a sequence of transitions * * @function H5P.Transition.sequence * @static * @param {Object[]} transitions Array of transitions * @param {H5P.jQuery} transitions[].$element Dom element transition is performed on * @param {number=} transitions[].timeout Timeout fallback if transition end never is triggered * @param {bool=} transitions[].break If true, sequence breaks after this transition */ Transition.sequence = function (transitions) { runSequence(transitions, 0); }; return Transition; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a help text dialog */ H5P.JoubelHelpTextDialog = (function ($) { var numInstances = 0; /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @param {string} closeButtonTitle The title for the close button * @return {H5P.jQuery} */ function JoubelHelpTextDialog(header, message, closeButtonTitle) { H5P.EventDispatcher.call(this); var self = this; numInstances++; var headerId = 'joubel-help-text-header-' + numInstances; var helpTextId = 'joubel-help-text-body-' + numInstances; var $helpTextDialogBox = $('<div>', { 'class': 'joubel-help-text-dialog-box', 'role': 'dialog', 'aria-labelledby': headerId, 'aria-describedby': helpTextId }); $('<div>', { 'class': 'joubel-help-text-dialog-background' }).appendTo($helpTextDialogBox); var $helpTextDialogContainer = $('<div>', { 'class': 'joubel-help-text-dialog-container' }).appendTo($helpTextDialogBox); $('<div>', { 'class': 'joubel-help-text-header', 'id': headerId, 'role': 'header', 'html': header }).appendTo($helpTextDialogContainer); $('<div>', { 'class': 'joubel-help-text-body', 'id': helpTextId, 'html': message, 'role': 'document', 'tabindex': 0 }).appendTo($helpTextDialogContainer); var handleClose = function () { $helpTextDialogBox.remove(); self.trigger('closed'); }; var $closeButton = $('<div>', { 'class': 'joubel-help-text-remove', 'role': 'button', 'title': closeButtonTitle, 'tabindex': 1, 'click': handleClose, 'keydown': function (event) { // 32 - space, 13 - enter if ([32, 13].indexOf(event.which) !== -1) { event.preventDefault(); handleClose(); } } }).appendTo($helpTextDialogContainer); /** * Get the DOM element * @return {HTMLElement} */ self.getElement = function () { return $helpTextDialogBox; }; self.focus = function () { $closeButton.focus(); }; } JoubelHelpTextDialog.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelHelpTextDialog.prototype.constructor = JoubelHelpTextDialog; return JoubelHelpTextDialog; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating auto-disappearing dialogs */ H5P.JoubelMessageDialog = (function ($) { /** * Display a pop-up containing a message. * * @param {H5P.jQuery} $container The container which message dialog will be appended to * @param {string} message The message * @return {H5P.jQuery} */ function JoubelMessageDialog ($container, message) { var timeout; var removeDialog = function () { $warning.remove(); clearTimeout(timeout); $container.off('click.messageDialog'); }; // Create warning popup: var $warning = $('<div/>', { 'class': 'joubel-message-dialog', text: message }).appendTo($container); // Remove after 3 seconds or if user clicks anywhere in $container: timeout = setTimeout(removeDialog, 3000); $container.on('click.messageDialog', removeDialog); return $warning; } return JoubelMessageDialog; })(H5P.jQuery); ; var H5P = H5P || {}; /** * Class responsible for creating a circular progress bar */ H5P.JoubelProgressCircle = (function ($) { /** * Constructor for the Progress Circle * * @param {Number} number The amount of progress to display * @param {string} progressColor Color for the progress meter * @param {string} backgroundColor Color behind the progress meter */ function ProgressCircle(number, progressColor, fillColor, backgroundColor) { progressColor = progressColor || '#1a73d9'; fillColor = fillColor || '#f0f0f0'; backgroundColor = backgroundColor || '#ffffff'; var progressColorRGB = this.hexToRgb(progressColor); //Verify number try { number = Number(number); if (number === '') { throw 'is empty'; } if (isNaN(number)) { throw 'is not a number'; } } catch (e) { number = 'err'; } //Draw circle if (number > 100) { number = 100; } // We can not use rgba, since they will stack on top of each other. // Instead we create the equivalent of the rgba color // and applies this to the activeborder and background color. var progressColorString = 'rgb(' + parseInt(progressColorRGB.r, 10) + ',' + parseInt(progressColorRGB.g, 10) + ',' + parseInt(progressColorRGB.b, 10) + ')'; // Circle wrapper var $wrapper = $('<div/>', { 'class': "joubel-progress-circle-wrapper" }); //Active border indicates progress var $activeBorder = $('<div/>', { 'class': "joubel-progress-circle-active-border" }).appendTo($wrapper); //Background circle var $backgroundCircle = $('<div/>', { 'class': "joubel-progress-circle-circle" }).appendTo($activeBorder); //Progress text/number $('<span/>', { 'text': number + '%', 'class': "joubel-progress-circle-percentage" }).appendTo($backgroundCircle); var deg = number * 3.6; if (deg <= 180) { $activeBorder.css('background-image', 'linear-gradient(' + (90 + deg) + 'deg, transparent 50%, ' + fillColor + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } else { $activeBorder.css('background-image', 'linear-gradient(' + (deg - 90) + 'deg, transparent 50%, ' + progressColorString + ' 50%),' + 'linear-gradient(90deg, ' + fillColor + ' 50%, transparent 50%)') .css('border', '2px solid' + backgroundColor) .css('background-color', progressColorString); } this.$activeBorder = $activeBorder; this.$backgroundCircle = $backgroundCircle; this.$wrapper = $wrapper; this.initResizeFunctionality(); return $wrapper; } /** * Initializes resize functionality for the progress circle */ ProgressCircle.prototype.initResizeFunctionality = function () { var self = this; $(window).resize(function () { // Queue resize setTimeout(function () { self.resize(); }); }); // First resize setTimeout(function () { self.resize(); }, 0); }; /** * Resize function makes progress circle grow or shrink relative to parent container */ ProgressCircle.prototype.resize = function () { var $parent = this.$wrapper.parent(); if ($parent !== undefined && $parent) { // Measurements var fontSize = parseInt($parent.css('font-size'), 10); // Static sizes var fontSizeMultiplum = 3.75; var progressCircleWidthPx = parseInt((fontSize / 4.5), 10) % 2 === 0 ? parseInt((fontSize / 4.5), 10) + 4 : parseInt((fontSize / 4.5), 10) + 5; var progressCircleOffset = progressCircleWidthPx / 2; var width = fontSize * fontSizeMultiplum; var height = fontSize * fontSizeMultiplum; this.$activeBorder.css({ 'width': width, 'height': height }); this.$backgroundCircle.css({ 'width': width - progressCircleWidthPx, 'height': height - progressCircleWidthPx, 'top': progressCircleOffset, 'left': progressCircleOffset }); } }; /** * Hex to RGB conversion * @param hex * @returns {{r: Number, g: Number, b: Number}} */ ProgressCircle.prototype.hexToRgb = function (hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; return ProgressCircle; }(H5P.jQuery)); ; var H5P = H5P || {}; H5P.SimpleRoundedButton = (function ($) { /** * Creates a new tip */ function SimpleRoundedButton(text) { var $simpleRoundedButton = $('<div>', { 'class': 'joubel-simple-rounded-button', 'title': text, 'role': 'button', 'tabindex': '0' }).keydown(function (e) { // 32 - space, 13 - enter if ([32, 13].indexOf(e.which) !== -1) { $(this).click(); e.preventDefault(); } }); $('<span>', { 'class': 'joubel-simple-rounded-button-text', 'html': text }).appendTo($simpleRoundedButton); return $simpleRoundedButton; } return SimpleRoundedButton; }(H5P.jQuery)); ; var H5P = H5P || {}; /** * Class responsible for creating speech bubbles */ H5P.JoubelSpeechBubble = (function ($) { var $currentSpeechBubble; var $currentContainer; var $tail; var $innerTail; var removeSpeechBubbleTimeout; var currentMaxWidth; var DEFAULT_MAX_WIDTH = 400; var iDevice = navigator.userAgent.match(/iPod|iPhone|iPad/g) ? true : false; /** * Creates a new speech bubble * * @param {H5P.jQuery} $container The speaking object * @param {string} text The text to display * @param {number} maxWidth The maximum width of the bubble * @return {H5P.JoubelSpeechBubble} */ function JoubelSpeechBubble($container, text, maxWidth) { maxWidth = maxWidth || DEFAULT_MAX_WIDTH; currentMaxWidth = maxWidth; $currentContainer = $container; this.isCurrent = function ($tip) { return $tip.is($currentContainer); }; this.remove = function () { remove(); }; var fadeOutSpeechBubble = function ($speechBubble) { if (!$speechBubble) { return; } // Stop removing bubble clearTimeout(removeSpeechBubbleTimeout); $speechBubble.removeClass('show'); setTimeout(function () { if ($speechBubble) { $speechBubble.remove(); $speechBubble = undefined; } }, 500); }; if ($currentSpeechBubble !== undefined) { remove(); } var $h5pContainer = getH5PContainer($container); // Make sure we fade out old speech bubble fadeOutSpeechBubble($currentSpeechBubble); // Create bubble $tail = $('<div class="joubel-speech-bubble-tail"></div>'); $innerTail = $('<div class="joubel-speech-bubble-inner-tail"></div>'); var $innerBubble = $( '<div class="joubel-speech-bubble-inner">' + '<div class="joubel-speech-bubble-text">' + text + '</div>' + '</div>' ).prepend($innerTail); $currentSpeechBubble = $( '<div class="joubel-speech-bubble" aria-live="assertive">' ).append([$tail, $innerBubble]) .appendTo($h5pContainer); // Show speech bubble with transition setTimeout(function () { $currentSpeechBubble.addClass('show'); }, 0); position($currentSpeechBubble, $currentContainer, maxWidth, $tail, $innerTail); // Handle click to close H5P.$body.on('mousedown.speechBubble', handleOutsideClick); // Handle window resizing H5P.$window.on('resize', '', handleResize); // Handle clicks when inside IV which blocks bubbling. $container.parents('.h5p-dialog') .on('mousedown.speechBubble', handleOutsideClick); if (iDevice) { H5P.$body.css('cursor', 'pointer'); } return this; } // Remove speechbubble if it belongs to a dom element that is about to be hidden H5P.externalDispatcher.on('domHidden', function (event) { if ($currentSpeechBubble !== undefined && event.data.$dom.find($currentContainer).length !== 0) { remove(); } }); /** * Returns the closest h5p container for the given DOM element. * * @param {object} $container jquery element * @return {object} the h5p container (jquery element) */ function getH5PContainer($container) { var $h5pContainer = $container.closest('.h5p-frame'); // Check closest h5p frame first, then check for container in case there is no frame. if (!$h5pContainer.length) { $h5pContainer = $container.closest('.h5p-container'); } return $h5pContainer; } /** * Event handler that is called when the window is resized. */ function handleResize() { position($currentSpeechBubble, $currentContainer, currentMaxWidth, $tail, $innerTail); } /** * Repositions the speech bubble according to the position of the container. * * @param {object} $currentSpeechbubble the speech bubble that should be positioned * @param {object} $container the container to which the speech bubble should point * @param {number} maxWidth the maximum width of the speech bubble * @param {object} $tail the tail (the triangle that points to the referenced container) * @param {object} $innerTail the inner tail (the triangle that points to the referenced container) */ function position($currentSpeechBubble, $container, maxWidth, $tail, $innerTail) { var $h5pContainer = getH5PContainer($container); // Calculate offset between the button and the h5p frame var offset = getOffsetBetween($h5pContainer, $container); var direction = (offset.bottom > offset.top ? 'bottom' : 'top'); var tipWidth = offset.outerWidth * 0.9; // Var needs to be renamed to make sense var bubbleWidth = tipWidth > maxWidth ? maxWidth : tipWidth; var bubblePosition = getBubblePosition(bubbleWidth, offset); var tailPosition = getTailPosition(bubbleWidth, bubblePosition, offset, $container.width()); // Need to set font-size, since element is appended to body. // Using same font-size as parent. In that way it will grow accordingly // when resizing var fontSize = 16;//parseFloat($parent.css('font-size')); // Set width and position of speech bubble $currentSpeechBubble.css(bubbleCSS( direction, bubbleWidth, bubblePosition, fontSize )); var preparedTailCSS = tailCSS(direction, tailPosition); $tail.css(preparedTailCSS); $innerTail.css(preparedTailCSS); } /** * Static function for removing the speechbubble */ var remove = function () { H5P.$body.off('mousedown.speechBubble'); H5P.$window.off('resize', '', handleResize); $currentContainer.parents('.h5p-dialog').off('mousedown.speechBubble'); if (iDevice) { H5P.$body.css('cursor', ''); } if ($currentSpeechBubble !== undefined) { // Apply transition, then remove speech bubble $currentSpeechBubble.removeClass('show'); // Make sure we remove any old timeout before reassignment clearTimeout(removeSpeechBubbleTimeout); removeSpeechBubbleTimeout = setTimeout(function () { $currentSpeechBubble.remove(); $currentSpeechBubble = undefined; }, 500); } // Don't return false here. If the user e.g. clicks a button when the bubble is visible, // we want the bubble to disapear AND the button to receive the event }; /** * Remove the speech bubble and container reference */ function handleOutsideClick(event) { if (event.target === $currentContainer[0]) { return; // Button clicks are not outside clicks } remove(); // There is no current container when a container isn't clicked $currentContainer = undefined; } /** * Calculate position for speech bubble * * @param {number} bubbleWidth The width of the speech bubble * @param {object} offset * @return {object} Return position for the speech bubble */ function getBubblePosition(bubbleWidth, offset) { var bubblePosition = {}; var tailOffset = 9; var widthOffset = bubbleWidth / 2; // Calculate top position bubblePosition.top = offset.top + offset.innerHeight; // Calculate bottom position bubblePosition.bottom = offset.bottom + offset.innerHeight + tailOffset; // Calculate left position if (offset.left < widthOffset) { bubblePosition.left = 3; } else if ((offset.left + widthOffset) > offset.outerWidth) { bubblePosition.left = offset.outerWidth - bubbleWidth - 3; } else { bubblePosition.left = offset.left - widthOffset + (offset.innerWidth / 2); } return bubblePosition; } /** * Calculate position for speech bubble tail * * @param {number} bubbleWidth The width of the speech bubble * @param {object} bubblePosition Speech bubble position * @param {object} offset * @param {number} iconWidth The width of the tip icon * @return {object} Return position for the tail */ function getTailPosition(bubbleWidth, bubblePosition, offset, iconWidth) { var tailPosition = {}; // Magic numbers. Tuned by hand so that the tail fits visually within // the bounds of the speech bubble. var leftBoundary = 9; var rightBoundary = bubbleWidth - 20; tailPosition.left = offset.left - bubblePosition.left + (iconWidth / 2) - 6; if (tailPosition.left < leftBoundary) { tailPosition.left = leftBoundary; } if (tailPosition.left > rightBoundary) { tailPosition.left = rightBoundary; } tailPosition.top = -6; tailPosition.bottom = -6; return tailPosition; } /** * Return bubble CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {number} width The width of the speech bubble * @param {object} position Speech bubble position * @param {number} fontSize The size of the bubbles font * @return {object} Return CSS */ function bubbleCSS(direction, width, position, fontSize) { if (direction === 'top') { return { width: width + 'px', bottom: position.bottom + 'px', left: position.left + 'px', fontSize: fontSize + 'px', top: '' }; } else { return { width: width + 'px', top: position.top + 'px', left: position.left + 'px', fontSize: fontSize + 'px', bottom: '' }; } } /** * Return tail CSS for the desired growth direction * * @param {string} direction The direction the speech bubble will grow * @param {object} position Tail position * @return {object} Return CSS */ function tailCSS(direction, position) { if (direction === 'top') { return { bottom: position.bottom + 'px', left: position.left + 'px', top: '' }; } else { return { top: position.top + 'px', left: position.left + 'px', bottom: '' }; } } /** * Calculates the offset between an element inside a container and the * container. Only works if all the edges of the inner element are inside the * outer element. * Width/height of the elements is included as a convenience. * * @param {H5P.jQuery} $outer * @param {H5P.jQuery} $inner * @return {object} Position offset */ function getOffsetBetween($outer, $inner) { var outer = $outer[0].getBoundingClientRect(); var inner = $inner[0].getBoundingClientRect(); return { top: inner.top - outer.top, right: outer.right - inner.right, bottom: outer.bottom - inner.bottom, left: inner.left - outer.left, innerWidth: inner.width, innerHeight: inner.height, outerWidth: outer.width, outerHeight: outer.height }; } return JoubelSpeechBubble; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelThrobber = (function ($) { /** * Creates a new tip */ function JoubelThrobber() { // h5p-throbber css is described in core var $throbber = $('<div/>', { 'class': 'h5p-throbber' }); return $throbber; } return JoubelThrobber; }(H5P.jQuery)); ; H5P.JoubelTip = (function ($) { var $conv = $('<div/>'); /** * Creates a new tip element. * * NOTE that this may look like a class but it doesn't behave like one. * It returns a jQuery object. * * @param {string} tipHtml The text to display in the popup * @param {Object} [behaviour] Options * @param {string} [behaviour.tipLabel] Set to use a custom label for the tip button (you want this for good A11Y) * @param {boolean} [behaviour.helpIcon] Set to 'true' to Add help-icon classname to Tip button (changes the icon) * @param {boolean} [behaviour.showSpeechBubble] Set to 'false' to disable functionality (you may this in the editor) * @param {boolean} [behaviour.tabcontrol] Set to 'true' if you plan on controlling the tabindex in the parent (tabindex="-1") * @return {H5P.jQuery|undefined} Tip button jQuery element or 'undefined' if invalid tip */ function JoubelTip(tipHtml, behaviour) { // Keep track of the popup that appears when you click the Tip button var speechBubble; // Parse tip html to determine text var tipText = $conv.html(tipHtml).text().trim(); if (tipText === '') { return; // The tip has no textual content, i.e. it's invalid. } // Set default behaviour behaviour = $.extend({ tipLabel: tipText, helpIcon: false, showSpeechBubble: true, tabcontrol: false }, behaviour); // Create Tip button var $tipButton = $('<div/>', { class: 'joubel-tip-container' + (behaviour.showSpeechBubble ? '' : ' be-quiet'), 'aria-label': behaviour.tipLabel, 'aria-expanded': false, role: 'button', tabindex: (behaviour.tabcontrol ? -1 : 0), click: function (event) { // Toggle show/hide popup toggleSpeechBubble(); event.preventDefault(); }, keydown: function (event) { if (event.which === 32 || event.which === 13) { // Space & enter key // Toggle show/hide popup toggleSpeechBubble(); event.stopPropagation(); event.preventDefault(); } else { // Any other key // Toggle hide popup toggleSpeechBubble(false); } }, // Add markup to render icon html: '<span class="joubel-icon-tip-normal ' + (behaviour.helpIcon ? ' help-icon': '') + '">' + '<span class="h5p-icon-shadow"></span>' + '<span class="h5p-icon-speech-bubble"></span>' + '<span class="h5p-icon-info"></span>' + '</span>' // IMPORTANT: All of the markup elements must have 'pointer-events: none;' }); const $tipAnnouncer = $('<div>', { 'class': 'hidden-but-read', 'aria-live': 'polite', appendTo: $tipButton, }); /** * Tip button interaction handler. * Toggle show or hide the speech bubble popup when interacting with the * Tip button. * * @private * @param {boolean} [force] 'true' shows and 'false' hides. */ var toggleSpeechBubble = function (force) { if (speechBubble !== undefined && speechBubble.isCurrent($tipButton)) { // Hide current popup speechBubble.remove(); speechBubble = undefined; $tipButton.attr('aria-expanded', false); $tipAnnouncer.html(''); } else if (force !== false && behaviour.showSpeechBubble) { // Create and show new popup speechBubble = H5P.JoubelSpeechBubble($tipButton, tipHtml); $tipButton.attr('aria-expanded', true); $tipAnnouncer.html(tipHtml); } }; return $tipButton; } return JoubelTip; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelSlider = (function ($) { /** * Creates a new Slider * * @param {object} [params] Additional parameters */ function JoubelSlider(params) { H5P.EventDispatcher.call(this); this.$slider = $('<div>', $.extend({ 'class': 'h5p-joubel-ui-slider' }, params)); this.$slides = []; this.currentIndex = 0; this.numSlides = 0; } JoubelSlider.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelSlider.prototype.constructor = JoubelSlider; JoubelSlider.prototype.addSlide = function ($content) { $content.addClass('h5p-joubel-ui-slide').css({ 'left': (this.numSlides*100) + '%' }); this.$slider.append($content); this.$slides.push($content); this.numSlides++; if(this.numSlides === 1) { $content.addClass('current'); } }; JoubelSlider.prototype.attach = function ($container) { $container.append(this.$slider); }; JoubelSlider.prototype.move = function (index) { var self = this; if(index === 0) { self.trigger('first-slide'); } if(index+1 === self.numSlides) { self.trigger('last-slide'); } self.trigger('move'); var $previousSlide = self.$slides[this.currentIndex]; H5P.Transition.onTransitionEnd(this.$slider, function () { $previousSlide.removeClass('current'); self.trigger('moved'); }); this.$slides[index].addClass('current'); var translateX = 'translateX(' + (-index*100) + '%)'; this.$slider.css({ '-webkit-transform': translateX, '-moz-transform': translateX, '-ms-transform': translateX, 'transform': translateX }); this.currentIndex = index; }; JoubelSlider.prototype.remove = function () { this.$slider.remove(); }; JoubelSlider.prototype.next = function () { if(this.currentIndex+1 >= this.numSlides) { return; } this.move(this.currentIndex+1); }; JoubelSlider.prototype.previous = function () { this.move(this.currentIndex-1); }; JoubelSlider.prototype.first = function () { this.move(0); }; JoubelSlider.prototype.last = function () { this.move(this.numSlides-1); }; return JoubelSlider; })(H5P.jQuery); ; var H5P = H5P || {}; /** * @module */ H5P.JoubelScoreBar = (function ($) { /* Need to use an id for the star SVG since that is the only way to reference SVG filters */ var idCounter = 0; /** * Creates a score bar * @class H5P.JoubelScoreBar * @param {number} maxScore Maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @param {string} [helpText] Score explanation * @param {string} [scoreExplanationButtonLabel] Label for score explanation button */ function JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel) { var self = this; self.maxScore = maxScore; self.score = 0; idCounter++; /** * @const {string} */ self.STAR_MARKUP = '<svg tabindex="-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 63.77 53.87" aria-hidden="true" focusable="false">' + '<title>star</title>' + '<filter tabindex="-1" id="h5p-joubelui-score-bar-star-inner-shadow-' + idCounter + '" x0="-50%" y0="-50%" width="200%" height="200%">' + '<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"></feGaussianBlur>' + '<feOffset dy="2" dx="4"></feOffset>' + '<feComposite in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowDiff"></feComposite>' + '<feFlood flood-color="#ffe95c" flood-opacity="1"></feFlood>' + '<feComposite in2="shadowDiff" operator="in"></feComposite>' + '<feComposite in2="SourceGraphic" operator="over" result="firstfilter"></feComposite>' + '<feGaussianBlur in="firstfilter" stdDeviation="3" result="blur2"></feGaussianBlur>' + '<feOffset dy="-2" dx="-4"></feOffset>' + '<feComposite in2="firstfilter" operator="arithmetic" k2="-1" k3="1" result="shadowDiff"></feComposite>' + '<feFlood flood-color="#ffe95c" flood-opacity="1"></feFlood>' + '<feComposite in2="shadowDiff" operator="in"></feComposite>' + '<feComposite in2="firstfilter" operator="over"></feComposite>' + '</filter>' + '<path tabindex="-1" class="h5p-joubelui-score-bar-star-shadow" d="M35.08,43.41V9.16H20.91v0L9.51,10.85,9,10.93C2.8,12.18,0,17,0,21.25a11.22,11.22,0,0,0,3,7.48l8.73,8.53-1.07,6.16Z"/>' + '<g tabindex="-1">' + '<path tabindex="-1" class="h5p-joubelui-score-bar-star-border" d="M61.36,22.8,49.72,34.11l2.78,16a2.6,2.6,0,0,1,.05.64c0,.85-.37,1.6-1.33,1.6A2.74,2.74,0,0,1,49.94,52L35.58,44.41,21.22,52a2.93,2.93,0,0,1-1.28.37c-.91,0-1.33-.75-1.33-1.6,0-.21.05-.43.05-.64l2.78-16L9.8,22.8A2.57,2.57,0,0,1,9,21.25c0-1,1-1.33,1.81-1.49l16.07-2.35L34.09,2.83c.27-.59.85-1.33,1.55-1.33s1.28.69,1.55,1.33l7.21,14.57,16.07,2.35c.75.11,1.81.53,1.81,1.49A3.07,3.07,0,0,1,61.36,22.8Z"/>' + '<path tabindex="-1" class="h5p-joubelui-score-bar-star-fill" d="M61.36,22.8,49.72,34.11l2.78,16a2.6,2.6,0,0,1,.05.64c0,.85-.37,1.6-1.33,1.6A2.74,2.74,0,0,1,49.94,52L35.58,44.41,21.22,52a2.93,2.93,0,0,1-1.28.37c-.91,0-1.33-.75-1.33-1.6,0-.21.05-.43.05-.64l2.78-16L9.8,22.8A2.57,2.57,0,0,1,9,21.25c0-1,1-1.33,1.81-1.49l16.07-2.35L34.09,2.83c.27-.59.85-1.33,1.55-1.33s1.28.69,1.55,1.33l7.21,14.57,16.07,2.35c.75.11,1.81.53,1.81,1.49A3.07,3.07,0,0,1,61.36,22.8Z"/>' + '<path tabindex="-1" filter="url(#h5p-joubelui-score-bar-star-inner-shadow-' + idCounter + ')" class="h5p-joubelui-score-bar-star-fill-full-score" d="M61.36,22.8,49.72,34.11l2.78,16a2.6,2.6,0,0,1,.05.64c0,.85-.37,1.6-1.33,1.6A2.74,2.74,0,0,1,49.94,52L35.58,44.41,21.22,52a2.93,2.93,0,0,1-1.28.37c-.91,0-1.33-.75-1.33-1.6,0-.21.05-.43.05-.64l2.78-16L9.8,22.8A2.57,2.57,0,0,1,9,21.25c0-1,1-1.33,1.81-1.49l16.07-2.35L34.09,2.83c.27-.59.85-1.33,1.55-1.33s1.28.69,1.55,1.33l7.21,14.57,16.07,2.35c.75.11,1.81.53,1.81,1.49A3.07,3.07,0,0,1,61.36,22.8Z"/>' + '</g>' + '</svg>'; /** * @function appendTo * @memberOf H5P.JoubelScoreBar# * @param {H5P.jQuery} $wrapper Dom container */ self.appendTo = function ($wrapper) { self.$scoreBar.appendTo($wrapper); }; /** * Create the text representation of the scorebar . * * @private * @return {string} */ var createLabel = function (score) { if (!label) { return ''; } return label.replace(':num', score).replace(':total', self.maxScore); }; /** * Creates the html for this widget * * @method createHtml * @private */ var createHtml = function () { // Container div self.$scoreBar = $('<div>', { 'class': 'h5p-joubelui-score-bar', }); var $visuals = $('<div>', { 'class': 'h5p-joubelui-score-bar-visuals', appendTo: self.$scoreBar }); // The progress bar wrapper self.$progressWrapper = $('<div>', { 'class': 'h5p-joubelui-score-bar-progress-wrapper', appendTo: $visuals }); self.$progress = $('<div>', { 'class': 'h5p-joubelui-score-bar-progress', 'html': createLabel(self.score), appendTo: self.$progressWrapper }); // The star $('<div>', { 'class': 'h5p-joubelui-score-bar-star', html: self.STAR_MARKUP }).appendTo($visuals); // The score container var $numerics = $('<div>', { 'class': 'h5p-joubelui-score-numeric', appendTo: self.$scoreBar, 'aria-hidden': true }); // The current score self.$scoreCounter = $('<span>', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-number-counter', text: 0, appendTo: $numerics }); // The separator $('<span>', { 'class': 'h5p-joubelui-score-number-separator', text: '/', appendTo: $numerics }); // Max score self.$maxScore = $('<span>', { 'class': 'h5p-joubelui-score-number h5p-joubelui-score-max', text: self.maxScore, appendTo: $numerics }); if (helpText) { H5P.JoubelUI.createTip(helpText, { tipLabel: scoreExplanationButtonLabel ? scoreExplanationButtonLabel : helpText, helpIcon: true }).appendTo(self.$scoreBar); self.$scoreBar.addClass('h5p-score-bar-has-help'); } }; /** * Set the current score * @method setScore * @memberOf H5P.JoubelScoreBar# * @param {number} score */ self.setScore = function (score) { // Do nothing if score hasn't changed if (score === self.score) { return; } self.score = score > self.maxScore ? self.maxScore : score; self.updateVisuals(); }; /** * Increment score * @method incrementScore * @memberOf H5P.JoubelScoreBar# * @param {number=} incrementBy Optional parameter, defaults to 1 */ self.incrementScore = function (incrementBy) { self.setScore(self.score + (incrementBy || 1)); }; /** * Set the max score * @method setMaxScore * @memberOf H5P.JoubelScoreBar# * @param {number} maxScore The max score */ self.setMaxScore = function (maxScore) { self.maxScore = maxScore; }; /** * Updates the progressbar visuals * @memberOf H5P.JoubelScoreBar# * @method updateVisuals */ self.updateVisuals = function () { self.$progress.html(createLabel(self.score)); self.$scoreCounter.text(self.score); self.$maxScore.text(self.maxScore); setTimeout(function () { // Start the progressbar animation self.$progress.css({ width: ((self.score / self.maxScore) * 100) + '%' }); H5P.Transition.onTransitionEnd(self.$progress, function () { // If fullscore fill the star and start the animation self.$scoreBar.toggleClass('h5p-joubelui-score-bar-full-score', self.score === self.maxScore); self.$scoreBar.toggleClass('h5p-joubelui-score-bar-animation-active', self.score === self.maxScore); // Only allow the star animation to run once self.$scoreBar.one("animationend", function() { self.$scoreBar.removeClass("h5p-joubelui-score-bar-animation-active"); }); }, 600); }, 300); }; /** * Removes all classes * @method reset */ self.reset = function () { self.$scoreBar.removeClass('h5p-joubelui-score-bar-full-score'); }; createHtml(); } return JoubelScoreBar; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.JoubelProgressbar = (function ($) { /** * Joubel progressbar class * @method JoubelProgressbar * @constructor * @param {number} steps Number of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" */ function JoubelProgressbar(steps, options) { H5P.EventDispatcher.call(this); var self = this; this.options = $.extend({ progressText: 'Slide :num of :total' }, options); this.currentStep = 0; this.steps = steps; this.$progressbar = $('<div>', { 'class': 'h5p-joubelui-progressbar' }); this.$background = $('<div>', { 'class': 'h5p-joubelui-progressbar-background' }).appendTo(this.$progressbar); } JoubelProgressbar.prototype = Object.create(H5P.EventDispatcher.prototype); JoubelProgressbar.prototype.constructor = JoubelProgressbar; JoubelProgressbar.prototype.updateAria = function () { var self = this; if (this.options.disableAria) { return; } if (!this.$currentStatus) { this.$currentStatus = $('<div>', { 'class': 'h5p-joubelui-progressbar-slide-status-text', 'aria-live': 'assertive' }).appendTo(this.$progressbar); } var interpolatedProgressText = self.options.progressText .replace(':num', self.currentStep) .replace(':total', self.steps); this.$currentStatus.html(interpolatedProgressText); }; /** * Appends to a container * @method appendTo * @param {H5P.jquery} $container */ JoubelProgressbar.prototype.appendTo = function ($container) { this.$progressbar.appendTo($container); }; /** * Update progress * @method setProgress * @param {number} step */ JoubelProgressbar.prototype.setProgress = function (step) { // Check for valid value: if (step > this.steps || step < 0) { return; } this.currentStep = step; this.$background.css({ width: ((this.currentStep/this.steps)*100) + '%' }); this.updateAria(); }; /** * Increment progress with 1 * @method next */ JoubelProgressbar.prototype.next = function () { this.setProgress(this.currentStep+1); }; /** * Reset progressbar * @method reset */ JoubelProgressbar.prototype.reset = function () { this.setProgress(0); }; /** * Check if last step is reached * @method isLastStep * @return {Boolean} */ JoubelProgressbar.prototype.isLastStep = function () { return this.steps === this.currentStep; }; return JoubelProgressbar; })(H5P.jQuery); ; var H5P = H5P || {}; /** * H5P Joubel UI library. * * This is a utility library, which does not implement attach. I.e, it has to bee actively used by * other libraries * @module */ H5P.JoubelUI = (function ($) { /** * The internal object to return * @class H5P.JoubelUI * @static */ function JoubelUI() {} /* Public static functions */ /** * Create a tip icon * @method H5P.JoubelUI.createTip * @param {string} text The textual tip * @param {Object} params Parameters * @return {H5P.JoubelTip} */ JoubelUI.createTip = function (text, params) { return new H5P.JoubelTip(text, params); }; /** * Create message dialog * @method H5P.JoubelUI.createMessageDialog * @param {H5P.jQuery} $container The dom container * @param {string} message The message * @return {H5P.JoubelMessageDialog} */ JoubelUI.createMessageDialog = function ($container, message) { return new H5P.JoubelMessageDialog($container, message); }; /** * Create help text dialog * @method H5P.JoubelUI.createHelpTextDialog * @param {string} header The textual header * @param {string} message The textual message * @param {string} closeButtonTitle The title for the close button * @return {H5P.JoubelHelpTextDialog} */ JoubelUI.createHelpTextDialog = function (header, message, closeButtonTitle) { return new H5P.JoubelHelpTextDialog(header, message, closeButtonTitle); }; /** * Create progress circle * @method H5P.JoubelUI.createProgressCircle * @param {number} number The progress (0 to 100) * @param {string} progressColor The progress color in hex value * @param {string} fillColor The fill color in hex value * @param {string} backgroundColor The background color in hex value * @return {H5P.JoubelProgressCircle} */ JoubelUI.createProgressCircle = function (number, progressColor, fillColor, backgroundColor) { return new H5P.JoubelProgressCircle(number, progressColor, fillColor, backgroundColor); }; /** * Create throbber for loading * @method H5P.JoubelUI.createThrobber * @return {H5P.JoubelThrobber} */ JoubelUI.createThrobber = function () { return new H5P.JoubelThrobber(); }; /** * Create simple rounded button * @method H5P.JoubelUI.createSimpleRoundedButton * @param {string} text The button label * @return {H5P.SimpleRoundedButton} */ JoubelUI.createSimpleRoundedButton = function (text) { return new H5P.SimpleRoundedButton(text); }; /** * Create Slider * @method H5P.JoubelUI.createSlider * @param {Object} [params] Parameters * @return {H5P.JoubelSlider} */ JoubelUI.createSlider = function (params) { return new H5P.JoubelSlider(params); }; /** * Create Score Bar * @method H5P.JoubelUI.createScoreBar * @param {number=} maxScore The maximum score * @param {string} [label] Makes it easier for readspeakers to identify the scorebar * @return {H5P.JoubelScoreBar} */ JoubelUI.createScoreBar = function (maxScore, label, helpText, scoreExplanationButtonLabel) { return new H5P.JoubelScoreBar(maxScore, label, helpText, scoreExplanationButtonLabel); }; /** * Create Progressbar * @method H5P.JoubelUI.createProgressbar * @param {number=} numSteps The total numer of steps * @param {Object} [options] Additional options * @param {boolean} [options.disableAria] Disable readspeaker assistance * @param {string} [options.progressText] A progress text for describing * current progress out of total progress for readspeakers. * e.g. "Slide :num of :total" * @return {H5P.JoubelProgressbar} */ JoubelUI.createProgressbar = function (numSteps, options) { return new H5P.JoubelProgressbar(numSteps, options); }; /** * Create standard Joubel button * * @method H5P.JoubelUI.createButton * @param {object} params * May hold any properties allowed by jQuery. If href is set, an A tag * is used, if not a button tag is used. * @return {H5P.jQuery} The jquery element created */ JoubelUI.createButton = function(params) { var type = 'button'; if (params.href) { type = 'a'; } else { params.type = 'button'; } if (params.class) { params.class += ' h5p-joubelui-button'; } else { params.class = 'h5p-joubelui-button'; } return $('<' + type + '/>', params); }; /** * Fix for iframe scoll bug in IOS. When focusing an element that doesn't have * focus support by default the iframe will scroll the parent frame so that * the focused element is out of view. This varies dependening on the elements * of the parent frame. */ if (H5P.isFramed && !H5P.hasiOSiframeScrollFix && /iPad|iPhone|iPod/.test(navigator.userAgent)) { H5P.hasiOSiframeScrollFix = true; // Keep track of original focus function var focus = HTMLElement.prototype.focus; // Override the original focus HTMLElement.prototype.focus = function () { // Only focus the element if it supports it natively if ( (this instanceof HTMLAnchorElement || this instanceof HTMLInputElement || this instanceof HTMLSelectElement || this instanceof HTMLTextAreaElement || this instanceof HTMLButtonElement || this instanceof HTMLIFrameElement || this instanceof HTMLAreaElement) && // HTMLAreaElement isn't supported by Safari yet. !this.getAttribute('role')) { // Focus breaks if a different role has been set // In theory this.isContentEditable should be able to recieve focus, // but it didn't work when tested. // Trigger the original focus with the proper context focus.call(this); } }; } return JoubelUI; })(H5P.jQuery); ; H5P.Question = (function ($, EventDispatcher, JoubelUI) { /** * Extending this class make it alot easier to create tasks for other * content types. * * @class H5P.Question * @extends H5P.EventDispatcher * @param {string} type */ function Question(type) { var self = this; // Inheritance EventDispatcher.call(self); // Register default section order self.order = ['video', 'image', 'introduction', 'content', 'explanation', 'feedback', 'scorebar', 'buttons', 'read']; // Keep track of registered sections var sections = {}; // Buttons var buttons = {}; var buttonOrder = []; // Wrapper when attached var $wrapper; // Click element var clickElement; // ScoreBar var scoreBar; // Keep track of the feedback's visual status. var showFeedback; // Keep track of which buttons are scheduled for hiding. var buttonsToHide = []; // Keep track of which buttons are scheduled for showing. var buttonsToShow = []; // Keep track of the hiding and showing of buttons. var toggleButtonsTimer; var toggleButtonsTransitionTimer; var buttonTruncationTimer; // Keeps track of initialization of question var initialized = false; /** * @type {Object} behaviour Behaviour of Question * @property {Boolean} behaviour.disableFeedback Set to true to disable feedback section */ var behaviour = { disableFeedback: false, disableReadSpeaker: false }; // Keeps track of thumb state var imageThumb = true; // Keeps track of image transitions var imageTransitionTimer; // Keep track of whether sections is transitioning. var sectionsIsTransitioning = false; // Keep track of auto play state var disableAutoPlay = false; // Feedback transition timer var feedbackTransitionTimer; // Used when reading messages to the user var $read, readText; /** * Register section with given content. * * @private * @param {string} section ID of the section * @param {(string|H5P.jQuery)} [content] */ var register = function (section, content) { sections[section] = {}; var $e = sections[section].$element = $('<div/>', { 'class': 'h5p-question-' + section, }); if (content) { $e[content instanceof $ ? 'append' : 'html'](content); } }; /** * Update registered section with content. * * @private * @param {string} section ID of the section * @param {(string|H5P.jQuery)} content */ var update = function (section, content) { if (content instanceof $) { sections[section].$element.html('').append(content); } else { sections[section].$element.html(content); } }; /** * Insert element with given ID into the DOM. * * @private * @param {array|Array|string[]} order * List with ordered element IDs * @param {string} id * ID of the element to be inserted * @param {Object} elements * Maps ID to the elements * @param {H5P.jQuery} $container * Parent container of the elements */ var insert = function (order, id, elements, $container) { // Try to find an element id should be after for (var i = 0; i < order.length; i++) { if (order[i] === id) { // Found our pos while (i > 0 && (elements[order[i - 1]] === undefined || !elements[order[i - 1]].isVisible)) { i--; } if (i === 0) { // We are on top. elements[id].$element.prependTo($container); } else { // Add after element elements[id].$element.insertAfter(elements[order[i - 1]].$element); } elements[id].isVisible = true; break; } } }; /** * Make feedback into a popup and position relative to click. * * @private * @param {string} [closeText] Text for the close button */ var makeFeedbackPopup = function (closeText) { var $element = sections.feedback.$element; var $parent = sections.content.$element; var $click = (clickElement != null ? clickElement.$element : null); $element.appendTo($parent).addClass('h5p-question-popup'); if (sections.scorebar) { sections.scorebar.$element.appendTo($element); } $parent.addClass('h5p-has-question-popup'); // Draw the tail var $tail = $('<div/>', { 'class': 'h5p-question-feedback-tail' }).hide() .appendTo($parent); // Draw the close button var $close = $('<div/>', { 'class': 'h5p-question-feedback-close', 'tabindex': 0, 'title': closeText, on: { click: function (event) { $element.remove(); $tail.remove(); event.preventDefault(); }, keydown: function (event) { switch (event.which) { case 13: // Enter case 32: // Space $element.remove(); $tail.remove(); event.preventDefault(); } } } }).hide().appendTo($element); if ($click != null) { if ($click.hasClass('correct')) { $element.addClass('h5p-question-feedback-correct'); $close.show(); sections.buttons.$element.hide(); } else { sections.buttons.$element.appendTo(sections.feedback.$element); } } positionFeedbackPopup($element, $click); }; /** * Position the feedback popup. * * @private * @param {H5P.jQuery} $element Feedback div * @param {H5P.jQuery} $click Visual click div */ var positionFeedbackPopup = function ($element, $click) { var $container = $element.parent(); var $tail = $element.siblings('.h5p-question-feedback-tail'); var popupWidth = $element.outerWidth(); var popupHeight = setElementHeight($element); var space = 15; var disableTail = false; var positionY = $container.height() / 2 - popupHeight / 2; var positionX = $container.width() / 2 - popupWidth / 2; var tailX = 0; var tailY = 0; var tailRotation = 0; if ($click != null) { // Edge detection for click, takes space into account var clickNearTop = ($click[0].offsetTop < space); var clickNearBottom = ($click[0].offsetTop + $click.height() > $container.height() - space); var clickNearLeft = ($click[0].offsetLeft < space); var clickNearRight = ($click[0].offsetLeft + $click.width() > $container.width() - space); // Click is not in a corner or close to edge, calculate position normally positionX = $click[0].offsetLeft - popupWidth / 2 + $click.width() / 2; positionY = $click[0].offsetTop - popupHeight - space; tailX = positionX + popupWidth / 2 - $tail.width() / 2; tailY = positionY + popupHeight - ($tail.height() / 2); tailRotation = 225; // If popup is outside top edge, position under click instead if (popupHeight + space > $click[0].offsetTop) { positionY = $click[0].offsetTop + $click.height() + space; tailY = positionY - $tail.height() / 2 ; tailRotation = 45; } // If popup is outside left edge, position left if (positionX < 0) { positionX = 0; } // If popup is outside right edge, position right if (positionX + popupWidth > $container.width()) { positionX = $container.width() - popupWidth; } // Special cases such as corner clicks, or close to an edge, they override X and Y positions if met if (clickNearTop && (clickNearLeft || clickNearRight)) { positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() : -popupWidth); positionY = $click[0].offsetTop + $click.height(); disableTail = true; } else if (clickNearBottom && (clickNearLeft || clickNearRight)) { positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() : -popupWidth); positionY = $click[0].offsetTop - popupHeight; disableTail = true; } else if (!clickNearTop && !clickNearBottom) { if (clickNearLeft || clickNearRight) { positionY = $click[0].offsetTop - popupHeight / 2 + $click.width() / 2; positionX = $click[0].offsetLeft + (clickNearLeft ? $click.width() + space : -popupWidth + -space); // Make sure this does not position the popup off screen if (positionX < 0) { positionX = 0; disableTail = true; } else { tailX = positionX + (clickNearLeft ? - $tail.width() / 2 : popupWidth - $tail.width() / 2); tailY = positionY + popupHeight / 2 - $tail.height() / 2; tailRotation = (clickNearLeft ? 315 : 135); } } } // Contain popup from overflowing bottom edge if (positionY + popupHeight > $container.height()) { positionY = $container.height() - popupHeight; if (popupHeight > $container.height() - ($click[0].offsetTop + $click.height() + space)) { disableTail = true; } } } else { disableTail = true; } // Contain popup from ovreflowing top edge if (positionY < 0) { positionY = 0; } $element.css({top: positionY, left: positionX}); $tail.css({top: tailY, left: tailX}); if (!disableTail) { $tail.css({ 'left': tailX, 'top': tailY, 'transform': 'rotate(' + tailRotation + 'deg)' }).show(); } else { $tail.hide(); } }; /** * Set element max height, used for animations. * * @param {H5P.jQuery} $element */ var setElementHeight = function ($element) { if (!$element.is(':visible')) { // No animation $element.css('max-height', 'none'); return; } // If this element is shown in the popup, we can't set width to 100%, // since it already has a width set in CSS var isFeedbackPopup = $element.hasClass('h5p-question-popup'); // Get natural element height var $tmp = $element.clone() .css({ 'position': 'absolute', 'max-height': 'none', 'width': isFeedbackPopup ? '' : '100%' }) .appendTo($element.parent()); // Need to take margins into account when calculating available space var sideMargins = parseFloat($element.css('margin-left')) + parseFloat($element.css('margin-right')); var tmpElWidth = $tmp.css('width') ? $tmp.css('width') : '100%'; $tmp.css('width', 'calc(' + tmpElWidth + ' - ' + sideMargins + 'px)'); // Apply height to element var h = Math.round($tmp.get(0).getBoundingClientRect().height); var fontSize = parseFloat($element.css('fontSize')); var relativeH = h / fontSize; $element.css('max-height', relativeH + 'em'); $tmp.remove(); if (h > 0 && sections.buttons && sections.buttons.$element === $element) { // Make sure buttons section is visible showSection(sections.buttons); // Resize buttons after resizing button section setTimeout(resizeButtons, 150); } return h; }; /** * Does the actual job of hiding the buttons scheduled for hiding. * * @private * @param {boolean} [relocateFocus] Find a new button to focus */ var hideButtons = function (relocateFocus) { for (var i = 0; i < buttonsToHide.length; i++) { hideButton(buttonsToHide[i].id); } buttonsToHide = []; if (relocateFocus) { self.focusButton(); } }; /** * Does the actual hiding. * @private * @param {string} buttonId */ var hideButton = function (buttonId) { // Using detach() vs hide() makes it harder to cheat. buttons[buttonId].$element.detach(); buttons[buttonId].isVisible = false; }; /** * Shows the buttons on the next tick. This is to avoid buttons flickering * If they're both added and removed on the same tick. * * @private */ var toggleButtons = function () { // If no buttons section, return if (sections.buttons === undefined) { return; } // Clear transition timer, reevaluate if buttons will be detached clearTimeout(toggleButtonsTransitionTimer); // Show buttons for (var i = 0; i < buttonsToShow.length; i++) { insert(buttonOrder, buttonsToShow[i].id, buttons, sections.buttons.$element); buttons[buttonsToShow[i].id].isVisible = true; } buttonsToShow = []; // Hide buttons var numToHide = 0; var relocateFocus = false; for (var j = 0; j < buttonsToHide.length; j++) { var button = buttons[buttonsToHide[j].id]; if (button.isVisible) { numToHide += 1; } if (button.$element.is(':focus')) { // Move focus to the first visible button. relocateFocus = true; } } var animationTimer = 150; if (sections.feedback && sections.feedback.$element.hasClass('h5p-question-popup')) { animationTimer = 0; } if (numToHide === sections.buttons.$element.children().length) { // All buttons are going to be hidden. Hide container using transition. hideSection(sections.buttons); // Detach buttons hideButtons(relocateFocus); } else { hideButtons(relocateFocus); // Show button section if (!sections.buttons.$element.is(':empty')) { showSection(sections.buttons); setElementHeight(sections.buttons.$element); // Trigger resize after animation toggleButtonsTransitionTimer = setTimeout(function () { self.trigger('resize'); }, animationTimer); } // Resize buttons to fit container resizeButtons(); } toggleButtonsTimer = undefined; }; /** * Allows for scaling of the question image. */ var scaleImage = function () { var $imgSection = sections.image.$element; clearTimeout(imageTransitionTimer); // Add this here to avoid initial transition of the image making // content overflow. Alternatively we need to trigger a resize. $imgSection.addClass('animatable'); if (imageThumb) { // Expand image $(this).attr('aria-expanded', true); $imgSection.addClass('h5p-question-image-fill-width'); imageThumb = false; imageTransitionTimer = setTimeout(function () { self.trigger('resize'); }, 600); } else { // Scale down image $(this).attr('aria-expanded', false); $imgSection.removeClass('h5p-question-image-fill-width'); imageThumb = true; imageTransitionTimer = setTimeout(function () { self.trigger('resize'); }, 600); } }; /** * Get scrollable ancestor of element * * @private * @param {H5P.jQuery} $element * @param {Number} [currDepth=0] Current recursive calls to ancestor, stop at maxDepth * @param {Number} [maxDepth=5] Maximum depth for finding ancestor. * @returns {H5P.jQuery} Parent element that is scrollable */ var findScrollableAncestor = function ($element, currDepth, maxDepth) { if (!currDepth) { currDepth = 0; } if (!maxDepth) { maxDepth = 5; } // Check validation of element or if we have reached document root if (!$element || !($element instanceof $) || document === $element.get(0) || currDepth >= maxDepth) { return; } if ($element.css('overflow-y') === 'auto') { return $element; } else { return findScrollableAncestor($element.parent(), currDepth + 1, maxDepth); } }; /** * Scroll to bottom of Question. * * @private */ var scrollToBottom = function () { if (!$wrapper || ($wrapper.hasClass('h5p-standalone') && !H5P.isFullscreen)) { return; // No scroll } var scrollableAncestor = findScrollableAncestor($wrapper); // Scroll to bottom of scrollable ancestor if (scrollableAncestor) { scrollableAncestor.animate({ scrollTop: $wrapper.css('height') }, "slow"); } }; /** * Resize buttons to fit container width * * @private */ var resizeButtons = function () { if (!buttons || !sections.buttons) { return; } var go = function () { // Don't do anything if button elements are not visible yet if (!sections.buttons.$element.is(':visible')) { return; } // Width of all buttons var buttonsWidth = { max: 0, min: 0, current: 0 }; for (var i in buttons) { var button = buttons[i]; if (button.isVisible) { setButtonWidth(buttons[i]); buttonsWidth.max += button.width.max; buttonsWidth.min += button.width.min; buttonsWidth.current += button.isTruncated ? button.width.min : button.width.max; } } var makeButtonsFit = function (availableWidth) { if (buttonsWidth.max < availableWidth) { // It is room for everyone on the right side of the score bar (without truncating) if (buttonsWidth.max !== buttonsWidth.current) { // Need to make everyone big restoreButtonLabels(buttonsWidth.current, availableWidth); } return true; } else if (buttonsWidth.min < availableWidth) { // Is it room for everyone on the right side of the score bar with truncating? if (buttonsWidth.current > availableWidth) { removeButtonLabels(buttonsWidth.current, availableWidth); } else { restoreButtonLabels(buttonsWidth.current, availableWidth); } return true; } return false; }; toggleFullWidthScorebar(false); var buttonSectionWidth = Math.floor(sections.buttons.$element.width()) - 1; if (!makeButtonsFit(buttonSectionWidth)) { // If we get here we need to wrap: toggleFullWidthScorebar(true); buttonSectionWidth = Math.floor(sections.buttons.$element.width()) - 1; makeButtonsFit(buttonSectionWidth); } }; // If visible, resize right away if (sections.buttons.$element.is(':visible')) { go(); } else { // If not visible, try on the next tick // Clear button truncation timer if within a button truncation function if (buttonTruncationTimer) { clearTimeout(buttonTruncationTimer); } buttonTruncationTimer = setTimeout(function () { buttonTruncationTimer = undefined; go(); }, 0); } }; var toggleFullWidthScorebar = function (enabled) { if (sections.scorebar && sections.scorebar.$element && sections.scorebar.$element.hasClass('h5p-question-visible')) { sections.buttons.$element.addClass('has-scorebar'); sections.buttons.$element.toggleClass('wrap', enabled); sections.scorebar.$element.toggleClass('full-width', enabled); } else { sections.buttons.$element.removeClass('has-scorebar'); } }; /** * Remove button labels until they use less than max width. * * @private * @param {Number} buttonsWidth Total width of all buttons * @param {Number} maxButtonsWidth Max width allowed for buttons */ var removeButtonLabels = function (buttonsWidth, maxButtonsWidth) { // Reverse traversal for (var i = buttonOrder.length - 1; i >= 0; i--) { var buttonId = buttonOrder[i]; var button = buttons[buttonId]; if (!button.isTruncated && button.isVisible) { var $button = button.$element; buttonsWidth -= button.width.max - button.width.min; // Remove label button.$element.attr('aria-label', $button.text()).html('').addClass('truncated'); button.isTruncated = true; if (buttonsWidth <= maxButtonsWidth) { // Buttons are small enough. return; } } } }; /** * Restore button labels until it fills maximum possible width without exceeding the max width. * * @private * @param {Number} buttonsWidth Total width of all buttons * @param {Number} maxButtonsWidth Max width allowed for buttons */ var restoreButtonLabels = function (buttonsWidth, maxButtonsWidth) { for (var i = 0; i < buttonOrder.length; i++) { var buttonId = buttonOrder[i]; var button = buttons[buttonId]; if (button.isTruncated && button.isVisible) { // Calculate new total width of buttons with a static pixel for consistency cross-browser buttonsWidth += button.width.max - button.width.min + 1; if (buttonsWidth > maxButtonsWidth) { return; } // Restore label button.$element.html(button.text); button.$element.removeClass('truncated'); button.isTruncated = false; } } }; /** * Helper function for finding index of keyValue in array * * @param {String} keyValue Value to be found * @param {String} key In key * @param {Array} array In array * @returns {number} */ var existsInArray = function (keyValue, key, array) { var i; for (i = 0; i < array.length; i++) { if (array[i][key] === keyValue) { return i; } } return -1; }; /** * Show a section * @param {Object} section */ var showSection = function (section) { section.$element.addClass('h5p-question-visible'); section.isVisible = true; }; /** * Hide a section * @param {Object} section */ var hideSection = function (section) { section.$element.css('max-height', ''); section.isVisible = false; setTimeout(function () { // Only hide if section hasn't been set to visible in the meantime if (!section.isVisible) { section.$element.removeClass('h5p-question-visible'); } }, 150); }; /** * Set behaviour for question. * * @param {Object} options An object containing behaviour that will be extended by Question */ self.setBehaviour = function (options) { $.extend(behaviour, options); }; /** * A video to display above the task. * * @param {object} params */ self.setVideo = function (params) { sections.video = { $element: $('<div/>', { 'class': 'h5p-question-video' }) }; if (disableAutoPlay && params.params.playback) { params.params.playback.autoplay = false; } // Never fit to wrapper if (!params.params.visuals) { params.params.visuals = {}; } params.params.visuals.fit = false; sections.video.instance = H5P.newRunnable(params, self.contentId, sections.video.$element, true); var fromVideo = false; // Hack to avoid never ending loop sections.video.instance.on('resize', function () { fromVideo = true; self.trigger('resize'); fromVideo = false; }); self.on('resize', function () { if (!fromVideo) { sections.video.instance.trigger('resize'); } }); return self; }; /** * Will stop any playback going on in the task. */ self.pause = function () { if (sections.video && sections.video.isVisible) { sections.video.instance.pause(); } }; /** * Start playback of video */ self.play = function () { if (sections.video && sections.video.isVisible) { sections.video.instance.play(); } }; /** * Disable auto play, useful in editors. */ self.disableAutoPlay = function () { disableAutoPlay = true; }; /** * Add task image. * * @param {string} path Relative * @param {Object} [options] Options object * @param {string} [options.alt] Text representation * @param {string} [options.title] Hover text * @param {Boolean} [options.disableImageZooming] Set as true to disable image zooming */ self.setImage = function (path, options) { options = options ? options : {}; sections.image = {}; // Image container sections.image.$element = $('<div/>', { 'class': 'h5p-question-image h5p-question-image-fill-width' }); // Inner wrap var $imgWrap = $('<div/>', { 'class': 'h5p-question-image-wrap', appendTo: sections.image.$element }); // Image element var $img = $('<img/>', { src: H5P.getPath(path, self.contentId), alt: (options.alt === undefined ? '' : options.alt), title: (options.title === undefined ? '' : options.title), on: { load: function () { self.trigger('imageLoaded', this); self.trigger('resize'); } }, appendTo: $imgWrap }); // Disable image zooming if (options.disableImageZooming) { $img.css('maxHeight', 'none'); // Make sure we are using the correct amount of width at all times var determineImgWidth = function () { // Remove margins if natural image width is bigger than section width var imageSectionWidth = sections.image.$element.get(0).getBoundingClientRect().width; // Do not transition, for instant measurements $imgWrap.css({ '-webkit-transition': 'none', 'transition': 'none' }); // Margin as translateX on both sides of image. var diffX = 2 * ($imgWrap.get(0).getBoundingClientRect().left - sections.image.$element.get(0).getBoundingClientRect().left); if ($img.get(0).naturalWidth >= imageSectionWidth - diffX) { sections.image.$element.addClass('h5p-question-image-fill-width'); } else { // Use margin for small res images sections.image.$element.removeClass('h5p-question-image-fill-width'); } // Reset transition rules $imgWrap.css({ '-webkit-transition': '', 'transition': '' }); }; // Determine image width if ($img.is(':visible')) { determineImgWidth(); } else { $img.on('load', determineImgWidth); } // Skip adding zoom functionality return; } var sizeDetermined = false; var determineSize = function () { if (sizeDetermined || !$img.is(':visible')) { return; // Try again next time. } $imgWrap.addClass('h5p-question-image-scalable') .attr('aria-expanded', false) .attr('role', 'button') .attr('tabIndex', '0') .on('click', function (event) { if (event.which === 1) { scaleImage.apply(this); // Left mouse button click } }).on('keypress', function (event) { if (event.which === 32) { event.preventDefault(); // Prevent default behaviour; page scroll down scaleImage.apply(this); // Space bar pressed } }); sections.image.$element.removeClass('h5p-question-image-fill-width'); sizeDetermined = true; // Prevent any futher events }; self.on('resize', determineSize); return self; }; /** * Add the introduction section. * * @param {(string|H5P.jQuery)} content */ self.setIntroduction = function (content) { register('introduction', content); return self; }; /** * Add the content section. * * @param {(string|H5P.jQuery)} content * @param {Object} [options] * @param {string} [options.class] */ self.setContent = function (content, options) { register('content', content); if (options && options.class) { sections.content.$element.addClass(options.class); } return self; }; /** * Force readspeaker to read text. Useful when you have to use * setTimeout for animations. */ self.read = function (content) { if (!$read) { return; // Not ready yet } if (readText) { // Combine texts if called multiple times readText += (readText.substr(-1, 1) === '.' ? ' ' : '. ') + content; } else { readText = content; } // Set text $read.html(readText); setTimeout(function () { // Stop combining when done reading readText = null; $read.html(''); }, 100); }; /** * Read feedback */ self.readFeedback = function () { var invalidFeedback = behaviour.disableReadSpeaker || !showFeedback || !sections.feedback || !sections.feedback.$element; if (invalidFeedback) { return; } var $feedbackText = $('.h5p-question-feedback-content-text', sections.feedback.$element); if ($feedbackText && $feedbackText.html() && $feedbackText.html().length) { self.read($feedbackText.html()); } }; /** * Remove feedback * * @return {H5P.Question} */ self.removeFeedback = function () { clearTimeout(feedbackTransitionTimer); if (sections.feedback && showFeedback) { showFeedback = false; // Hide feedback & scorebar hideSection(sections.scorebar); hideSection(sections.feedback); sectionsIsTransitioning = true; // Detach after transition feedbackTransitionTimer = setTimeout(function () { // Avoiding Transition.onTransitionEnd since it will register multiple events, and there's no way to cancel it if the transition changes back to "show" while the animation is happening. if (!showFeedback) { sections.feedback.$element.children().detach(); sections.scorebar.$element.children().detach(); // Trigger resize after animation self.trigger('resize'); } sectionsIsTransitioning = false; scoreBar.setScore(0); }, 150); if ($wrapper) { $wrapper.find('.h5p-question-feedback-tail').remove(); } } return self; }; /** * Set feedback message. * * @param {string} [content] * @param {number} score The score * @param {number} maxScore The maximum score for this question * @param {string} [scoreBarLabel] Makes it easier for readspeakers to identify the scorebar * @param {string} [helpText] Help text that describes the score inside a tip icon * @param {object} [popupSettings] Extra settings for popup feedback * @param {boolean} [popupSettings.showAsPopup] Should the feedback display as popup? * @param {string} [popupSettings.closeText] Translation for close button text * @param {object} [popupSettings.click] Element representing where user clicked on screen */ self.setFeedback = function (content, score, maxScore, scoreBarLabel, helpText, popupSettings, scoreExplanationButtonLabel) { // Feedback is disabled if (behaviour.disableFeedback) { return self; } // Need to toggle buttons right away to avoid flickering/blinking // Note: This means content types should invoke hide/showButton before setFeedback toggleButtons(); clickElement = (popupSettings != null && popupSettings.click != null ? popupSettings.click : null); clearTimeout(feedbackTransitionTimer); var $feedback = $('<div>', { 'class': 'h5p-question-feedback-container' }); var $feedbackContent = $('<div>', { 'class': 'h5p-question-feedback-content' }).appendTo($feedback); // Feedback text $('<div>', { 'class': 'h5p-question-feedback-content-text', 'html': content }).appendTo($feedbackContent); var $scorebar = $('<div>', { 'class': 'h5p-question-scorebar-container' }); if (scoreBar === undefined) { scoreBar = JoubelUI.createScoreBar(maxScore, scoreBarLabel, helpText, scoreExplanationButtonLabel); } scoreBar.appendTo($scorebar); $feedbackContent.toggleClass('has-content', content !== undefined && content.length > 0); // Feedback for readspeakers if (!behaviour.disableReadSpeaker && scoreBarLabel) { self.read(scoreBarLabel.replace(':num', score).replace(':total', maxScore) + '. ' + (content ? content : '')); } showFeedback = true; if (sections.feedback) { // Update section update('feedback', $feedback); update('scorebar', $scorebar); } else { // Create section register('feedback', $feedback); register('scorebar', $scorebar); if (initialized && $wrapper) { insert(self.order, 'feedback', sections, $wrapper); insert(self.order, 'scorebar', sections, $wrapper); } } showSection(sections.feedback); showSection(sections.scorebar); resizeButtons(); if (popupSettings != null && popupSettings.showAsPopup == true) { makeFeedbackPopup(popupSettings.closeText); scoreBar.setScore(score); } else { // Show feedback section feedbackTransitionTimer = setTimeout(function () { setElementHeight(sections.feedback.$element); setElementHeight(sections.scorebar.$element); sectionsIsTransitioning = true; // Scroll to bottom after showing feedback scrollToBottom(); // Trigger resize after animation feedbackTransitionTimer = setTimeout(function () { sectionsIsTransitioning = false; self.trigger('resize'); scoreBar.setScore(score); }, 150); }, 0); } return self; }; /** * Set feedback content (no animation). * * @param {string} content * @param {boolean} [extendContent] True will extend content, instead of replacing it */ self.updateFeedbackContent = function (content, extendContent) { if (sections.feedback && sections.feedback.$element) { if (extendContent) { content = $('.h5p-question-feedback-content', sections.feedback.$element).html() + ' ' + content; } // Update feedback content html $('.h5p-question-feedback-content', sections.feedback.$element).html(content).addClass('has-content'); // Make sure the height is correct setElementHeight(sections.feedback.$element); // Need to trigger resize when feedback has finished transitioning setTimeout(self.trigger.bind(self, 'resize'), 150); } return self; }; /** * Set the content of the explanation / feedback panel * * @param {Object} data * @param {string} data.correct * @param {string} data.wrong * @param {string} data.text * @param {string} title Title for explanation panel * * @return {H5P.Question} */ self.setExplanation = function (data, title) { if (data) { var explainer = new H5P.Question.Explainer(title, data); if (sections.explanation) { // Update section update('explanation', explainer.getElement()); } else { register('explanation', explainer.getElement()); if (initialized && $wrapper) { insert(self.order, 'explanation', sections, $wrapper); } } } else if (sections.explanation) { // Hide explanation section sections.explanation.$element.children().detach(); } return self; }; /** * Checks to see if button is registered. * * @param {string} id * @returns {boolean} */ self.hasButton = function (id) { return (buttons[id] !== undefined); }; /** * @typedef {Object} ConfirmationDialog * @property {boolean} [enable] Must be true to show confirmation dialog * @property {Object} [instance] Instance that uses confirmation dialog * @property {jQuery} [$parentElement] Append to this element. * @property {Object} [l10n] Translatable fields * @property {string} [l10n.header] Header text * @property {string} [l10n.body] Body text * @property {string} [l10n.cancelLabel] * @property {string} [l10n.confirmLabel] */ /** * Register buttons for the task. * * @param {string} id * @param {string} text label * @param {function} clicked * @param {boolean} [visible=true] * @param {Object} [options] Options for button * @param {Object} [extras] Extra options * @param {ConfirmationDialog} [extras.confirmationDialog] Confirmation dialog * @param {Object} [extras.contentData] Content data * @params {string} [extras.textIfSubmitting] Text to display if submitting */ self.addButton = function (id, text, clicked, visible, options, extras) { if (buttons[id]) { return self; // Already registered } if (sections.buttons === undefined) { // We have buttons, register wrapper register('buttons'); if (initialized) { insert(self.order, 'buttons', sections, $wrapper); } } extras = extras || {}; extras.confirmationDialog = extras.confirmationDialog || {}; options = options || {}; var confirmationDialog = self.addConfirmationDialogToButton(extras.confirmationDialog, clicked); /** * Handle button clicks through both mouse and keyboard * @private */ var handleButtonClick = function () { if (extras.confirmationDialog.enable && confirmationDialog) { // Show popups section if used if (!extras.confirmationDialog.$parentElement) { sections.popups.$element.removeClass('hidden'); } confirmationDialog.show($e.position().top); } else { clicked(); } }; const isSubmitting = extras.contentData && extras.contentData.standalone && (extras.contentData.isScoringEnabled || extras.contentData.isReportingEnabled); if (isSubmitting && extras.textIfSubmitting) { text = extras.textIfSubmitting; } buttons[id] = { isTruncated: false, text: text, isVisible: false }; // The button might be <button> or <a> // (dependent on options.href set or not) var isAnchorTag = (options.href !== undefined); var $e = buttons[id].$element = JoubelUI.createButton($.extend({ 'class': 'h5p-question-' + id, html: text, title: text, on: { click: function (event) { handleButtonClick(); if (isAnchorTag) { event.preventDefault(); } } } }, options)); buttonOrder.push(id); // The button might be <button> or <a>. If <a>, the space key is not // triggering the click event, must therefore handle this here: if (isAnchorTag) { $e.on('keypress', function (event) { if (event.which === 32) { // Space handleButtonClick(); event.preventDefault(); } }); } if (visible === undefined || visible) { // Button should be visible $e.appendTo(sections.buttons.$element); buttons[id].isVisible = true; showSection(sections.buttons); } return self; }; var setButtonWidth = function (button) { var $button = button.$element; var $tmp = $button.clone() .css({ 'position': 'absolute', 'white-space': 'nowrap', 'max-width': 'none' }).removeClass('truncated') .html(button.text) .appendTo($button.parent()); // Calculate max width (button including text) button.width = { max: Math.ceil($tmp.outerWidth() + parseFloat($tmp.css('margin-left')) + parseFloat($tmp.css('margin-right'))) }; // Calculate min width (truncated, icon only) $tmp.html('').addClass('truncated'); button.width.min = Math.ceil($tmp.outerWidth() + parseFloat($tmp.css('margin-left')) + parseFloat($tmp.css('margin-right'))); $tmp.remove(); }; /** * Add confirmation dialog to button * @param {ConfirmationDialog} options * A confirmation dialog that will be shown before click handler of button * is triggered * @param {function} clicked * Click handler of button * @return {H5P.ConfirmationDialog|undefined} * Confirmation dialog if enabled */ self.addConfirmationDialogToButton = function (options, clicked) { options = options || {}; if (!options.enable) { return; } // Confirmation dialog var confirmationDialog = new H5P.ConfirmationDialog({ instance: options.instance, headerText: options.l10n.header, dialogText: options.l10n.body, cancelText: options.l10n.cancelLabel, confirmText: options.l10n.confirmLabel }); // Determine parent element if (options.$parentElement) { confirmationDialog.appendTo(options.$parentElement.get(0)); } else { // Create popup section and append to that if (sections.popups === undefined) { register('popups'); if (initialized) { insert(self.order, 'popups', sections, $wrapper); } sections.popups.$element.addClass('hidden'); self.order.push('popups'); } confirmationDialog.appendTo(sections.popups.$element.get(0)); } // Add event listeners confirmationDialog.on('confirmed', function () { if (!options.$parentElement) { sections.popups.$element.addClass('hidden'); } clicked(); // Trigger to content type self.trigger('confirmed'); }); confirmationDialog.on('canceled', function () { if (!options.$parentElement) { sections.popups.$element.addClass('hidden'); } // Trigger to content type self.trigger('canceled'); }); return confirmationDialog; }; /** * Show registered button with given identifier. * * @param {string} id * @param {Number} [priority] */ self.showButton = function (id, priority) { var aboutToBeHidden = existsInArray(id, 'id', buttonsToHide) !== -1; if (buttons[id] === undefined || (buttons[id].isVisible === true && !aboutToBeHidden)) { return self; } priority = priority || 0; // Skip if already being shown var indexToShow = existsInArray(id, 'id', buttonsToShow); if (indexToShow !== -1) { // Update priority if (buttonsToShow[indexToShow].priority < priority) { buttonsToShow[indexToShow].priority = priority; } return self; } // Check if button is going to be hidden on next tick var exists = existsInArray(id, 'id', buttonsToHide); if (exists !== -1) { // Skip hiding if higher priority if (buttonsToHide[exists].priority <= priority) { buttonsToHide.splice(exists, 1); buttonsToShow.push({id: id, priority: priority}); } } // If button is not shown else if (!buttons[id].$element.is(':visible')) { // Show button on next tick buttonsToShow.push({id: id, priority: priority}); } if (!toggleButtonsTimer) { toggleButtonsTimer = setTimeout(toggleButtons, 0); } return self; }; /** * Hide registered button with given identifier. * * @param {string} id * @param {number} [priority] */ self.hideButton = function (id, priority) { var aboutToBeShown = existsInArray(id, 'id', buttonsToShow) !== -1; if (buttons[id] === undefined || (buttons[id].isVisible === false && !aboutToBeShown)) { return self; } priority = priority || 0; // Skip if already being hidden var indexToHide = existsInArray(id, 'id', buttonsToHide); if (indexToHide !== -1) { // Update priority if (buttonsToHide[indexToHide].priority < priority) { buttonsToHide[indexToHide].priority = priority; } return self; } // Check if buttons is going to be shown on next tick var exists = existsInArray(id, 'id', buttonsToShow); if (exists !== -1) { // Skip showing if higher priority if (buttonsToShow[exists].priority <= priority) { buttonsToShow.splice(exists, 1); buttonsToHide.push({id: id, priority: priority}); } } else if (!buttons[id].$element.is(':visible')) { // Make sure it is detached in case the container is hidden. hideButton(id); } else { // Hide button on next tick. buttonsToHide.push({id: id, priority: priority}); } if (!toggleButtonsTimer) { toggleButtonsTimer = setTimeout(toggleButtons, 0); } return self; }; /** * Set focus to the given button. If no button is given the first visible * button gets focused. This is useful if you lose focus. * * @param {string} [id] */ self.focusButton = function (id) { if (id === undefined) { // Find first button that is visible. for (var i = 0; i < buttonOrder.length; i++) { var button = buttons[buttonOrder[i]]; if (button && button.isVisible) { // Give that button focus button.$element.focus(); break; } } } else if (buttons[id] && buttons[id].$element.is(':visible')) { // Set focus to requested button buttons[id].$element.focus(); } return self; }; /** * Toggle readspeaker functionality * @param {boolean} [disable] True to disable, false to enable. */ self.toggleReadSpeaker = function (disable) { behaviour.disableReadSpeaker = disable || !behaviour.disableReadSpeaker; }; /** * Set new element for section. * * @param {String} id * @param {H5P.jQuery} $element */ self.insertSectionAtElement = function (id, $element) { if (sections[id] === undefined) { register(id); } sections[id].parent = $element; // Insert section if question is not initialized if (!initialized) { insert([id], id, sections, $element); } return self; }; /** * Attach content to given container. * * @param {H5P.jQuery} $container */ self.attach = function ($container) { if (self.isRoot()) { self.setActivityStarted(); } // The first time we attach we also create our DOM elements. if ($wrapper === undefined) { if (self.registerDomElements !== undefined && (self.registerDomElements instanceof Function || typeof self.registerDomElements === 'function')) { // Give the question type a chance to register before attaching self.registerDomElements(); } // Create section for reading messages $read = $('<div/>', { 'aria-live': 'polite', 'class': 'h5p-hidden-read' }); register('read', $read); self.trigger('registerDomElements'); } // Prepare container $wrapper = $container; $container.html('') .addClass('h5p-question h5p-' + type); // Add sections in given order var $sections = []; for (var i = 0; i < self.order.length; i++) { var section = self.order[i]; if (sections[section]) { if (sections[section].parent) { // Section has a different parent sections[section].$element.appendTo(sections[section].parent); } else { $sections.push(sections[section].$element); } sections[section].isVisible = true; } } // Only append once to DOM for optimal performance $container.append($sections); // Let others react to dom changes self.trigger('domChanged', { '$target': $container, 'library': self.libraryInfo.machineName, 'contentId': self.contentId, 'key': 'newLibrary' }, {'bubbles': true, 'external': true}); // ?? initialized = true; return self; }; /** * Detach all sections from their parents */ self.detachSections = function () { // Deinit Question initialized = false; // Detach sections for (var section in sections) { sections[section].$element.detach(); } return self; }; // Listen for resize self.on('resize', function () { // Allow elements to attach and set their height before resizing if (!sectionsIsTransitioning && sections.feedback && showFeedback) { // Resize feedback to fit setElementHeight(sections.feedback.$element); } // Re-position feedback popup if in use var $element = sections.feedback; var $click = clickElement; if ($element != null && $element.$element != null && $click != null && $click.$element != null) { setTimeout(function () { positionFeedbackPopup($element.$element, $click.$element); }, 10); } resizeButtons(); }); } // Inheritance Question.prototype = Object.create(EventDispatcher.prototype); Question.prototype.constructor = Question; /** * Determine the overall feedback to display for the question. * Returns empty string if no matching range is found. * * @param {Object[]} feedbacks * @param {number} scoreRatio * @return {string} */ Question.determineOverallFeedback = function (feedbacks, scoreRatio) { scoreRatio = Math.floor(scoreRatio * 100); for (var i = 0; i < feedbacks.length; i++) { var feedback = feedbacks[i]; var hasFeedback = (feedback.feedback !== undefined && feedback.feedback.trim().length !== 0); if (feedback.from <= scoreRatio && feedback.to >= scoreRatio && hasFeedback) { return feedback.feedback; } } return ''; }; return Question; })(H5P.jQuery, H5P.EventDispatcher, H5P.JoubelUI); ; H5P.Question.Explainer = (function ($) { /** * Constructor * * @class * @param {string} title * @param {array} explanations */ function Explainer(title, explanations) { var self = this; /** * Create the DOM structure */ var createHTML = function () { self.$explanation = $('<div>', { 'class': 'h5p-question-explanation-container' }); // Add title: $('<div>', { 'class': 'h5p-question-explanation-title', role: 'heading', html: title, appendTo: self.$explanation }); var $explanationList = $('<ul>', { 'class': 'h5p-question-explanation-list', appendTo: self.$explanation }); for (var i = 0; i < explanations.length; i++) { var feedback = explanations[i]; var $explanationItem = $('<li>', { 'class': 'h5p-question-explanation-item', appendTo: $explanationList }); var $content = $('<div>', { 'class': 'h5p-question-explanation-status' }); if (feedback.correct) { $('<span>', { 'class': 'h5p-question-explanation-correct', html: feedback.correct, appendTo: $content }); } if (feedback.wrong) { $('<span>', { 'class': 'h5p-question-explanation-wrong', html: feedback.wrong, appendTo: $content }); } $content.appendTo($explanationItem); if (feedback.text) { $('<div>', { 'class': 'h5p-question-explanation-text', html: feedback.text, appendTo: $explanationItem }); } } }; createHTML(); /** * Return the container HTMLElement * * @return {HTMLElement} */ self.getElement = function () { return self.$explanation; }; } return Explainer; })(H5P.jQuery); ; (function (Question) { /** * Makes it easy to add animated score points for your question type. * * @class H5P.Question.ScorePoints */ Question.ScorePoints = function () { var self = this; var elements = []; var showElementsTimer; /** * Create the element that displays the score point element for questions. * * @param {boolean} isCorrect * @return {HTMLElement} */ self.getElement = function (isCorrect) { var element = document.createElement('div'); element.classList.add(isCorrect ? 'h5p-question-plus-one' : 'h5p-question-minus-one'); element.classList.add('h5p-question-hidden-one'); elements.push(element); // Schedule display animation of all added elements if (showElementsTimer) { clearTimeout(showElementsTimer); } showElementsTimer = setTimeout(showElements, 0); return element; }; /** * @private */ var showElements = function () { // Determine delay between triggering animations var delay = 0; var increment = 150; var maxTime = 1000; if (elements.length && elements.length > Math.ceil(maxTime / increment)) { // Animations will run for more than ~1 second, reduce it. increment = maxTime / elements.length; } for (var i = 0; i < elements.length; i++) { // Use timer to trigger show setTimeout(showElement(elements[i]), delay); // Increse delay for next element delay += increment; } }; /** * Trigger transition animation for the given element * * @private * @param {HTMLElement} element * @return {function} */ var showElement = function (element) { return function () { element.classList.remove('h5p-question-hidden-one'); }; }; }; })(H5P.Question); ; H5P.TrueFalse = (function ($, Question) { 'use strict'; // Maximum score for True False var MAX_SCORE = 1; /** * Enum containing the different states this content type can exist in * * @enum */ var State = Object.freeze({ ONGOING: 1, FINISHED_WRONG: 2, FINISHED_CORRECT: 3, INTERNAL_SOLUTION: 4, EXTERNAL_SOLUTION: 5 }); /** * Button IDs */ var Button = Object.freeze({ CHECK: 'check-answer', TRYAGAIN: 'try-again', SHOW_SOLUTION: 'show-solution' }); /** * Initialize module. * * @class H5P.TrueFalse * @extends H5P.Question * @param {Object} options * @param {number} id Content identification * @param {Object} contentData Task specific content data */ function TrueFalse(options, id, contentData) { var self = this; // Inheritance Question.call(self, 'true-false'); var params = $.extend(true, { question: 'No question text provided', correct: 'true', l10n: { trueText: 'True', falseText: 'False', score: 'You got @score of @total points', checkAnswer: 'Check', submitAnswer: 'Submit', showSolutionButton: 'Show solution', tryAgain: 'Retry', wrongAnswerMessage: 'Wrong answer', correctAnswerMessage: 'Correct answer', scoreBarLabel: 'You got :num out of :total points', a11yCheck: 'Check the answers. The responses will be marked as correct, incorrect, or unanswered.', a11yShowSolution: 'Show the solution. The task will be marked with its correct solution.', a11yRetry: 'Retry the task. Reset all responses and start the task over again.', }, behaviour: { enableRetry: true, enableSolutionsButton: true, enableCheckButton: true, confirmCheckDialog: false, confirmRetryDialog: false, autoCheck: false } }, options); // Counter used to create unique id for this question TrueFalse.counter = (TrueFalse.counter === undefined ? 0 : TrueFalse.counter + 1); // A unique ID is needed for aria label var domId = 'h5p-tfq' + H5P.TrueFalse.counter; // saves the content id this.contentId = id; this.contentData = contentData; // The radio group var answerGroup = new H5P.TrueFalse.AnswerGroup(domId, params.correct, params.l10n); if (contentData.previousState !== undefined && contentData.previousState.answer !== undefined) { answerGroup.check(contentData.previousState.answer); } answerGroup.on('selected', function () { self.triggerXAPI('interacted'); if (params.behaviour.autoCheck) { checkAnswer(); triggerXAPIAnswered(); } }); /** * Create the answers * * @method createAnswers * @private * @return {H5P.jQuery} */ var createAnswers = function () { return answerGroup.getDomElement(); }; /** * Register buttons * * @method registerButtons * @private */ var registerButtons = function () { var $content = $('[data-content-id="' + self.contentId + '"].h5p-content'); var $containerParents = $content.parents('.h5p-container'); // select find container to attach dialogs to var $container; if($containerParents.length !== 0) { // use parent highest up if any $container = $containerParents.last(); } else if($content.length !== 0){ $container = $content; } else { $container = $(document.body); } // Show solution button if (params.behaviour.enableSolutionsButton === true) { self.addButton(Button.SHOW_SOLUTION, params.l10n.showSolutionButton, function () { self.showSolutions(true); }, false, { 'aria-label': params.l10n.a11yShowSolution, }); } // Check button if (!params.behaviour.autoCheck && params.behaviour.enableCheckButton) { self.addButton(Button.CHECK, params.l10n.checkAnswer, function () { checkAnswer(); triggerXAPIAnswered(); }, true, { 'aria-label': params.l10n.a11yCheck }, { confirmationDialog: { enable: params.behaviour.confirmCheckDialog, l10n: params.confirmCheck, instance: self, $parentElement: $container }, contentData: self.contentData, textIfSubmitting: params.l10n.submitAnswer, }); } // Try again button if (params.behaviour.enableRetry === true) { self.addButton(Button.TRYAGAIN, params.l10n.tryAgain, function () { self.resetTask(); }, true, { 'aria-label': params.l10n.a11yRetry, }, { confirmationDialog: { enable: params.behaviour.confirmRetryDialog, l10n: params.confirmRetry, instance: self, $parentElement: $container } }); } toggleButtonState(State.ONGOING); }; /** * Creates and triggers the xAPI answered event * * @method triggerXAPIAnswered * @private * @fires xAPIEvent */ var triggerXAPIAnswered = function () { var xAPIEvent = self.createXAPIEventTemplate('answered'); addQuestionToXAPI(xAPIEvent); addResponseToXAPI(xAPIEvent); self.trigger(xAPIEvent); }; /** * Add the question itself to the definition part of an xAPIEvent * * @method addQuestionToXAPI * @param {XAPIEvent} xAPIEvent * @private */ var addQuestionToXAPI = function(xAPIEvent) { var definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); definition.description = { // Remove tags, must wrap in div tag because jQuery 1.9 will crash if the string isn't wrapped in a tag. 'en-US': $('<div>' + params.question + '</div>').text() }; definition.type = 'http://adlnet.gov/expapi/activities/cmi.interaction'; definition.interactionType = 'true-false'; definition.correctResponsesPattern = [getCorrectAnswer()]; }; /** * Returns the correct answer * * @method getCorrectAnswer * @private * @return {String} */ var getCorrectAnswer = function () { return (params.correct === 'true' ? 'true' : 'false'); }; /** * Returns the wrong answer * * @method getWrongAnswer * @private * @return {String} */ var getWrongAnswer = function () { return (params.correct === 'false' ? 'true' : 'false'); }; /** * Add the response part to an xAPI event * * @method addResponseToXAPI * @private * @param {H5P.XAPIEvent} xAPIEvent * The xAPI event we will add a response to */ var addResponseToXAPI = function(xAPIEvent) { var isCorrect = answerGroup.isCorrect(); xAPIEvent.setScoredResult(isCorrect ? MAX_SCORE : 0, MAX_SCORE, self, true, isCorrect); xAPIEvent.data.statement.result.response = (isCorrect ? getCorrectAnswer() : getWrongAnswer()); }; /** * Toggles btton visibility dependent of current state * * @method toggleButtonVisibility * @private * @param {String} buttonId * @param {Boolean} visible */ var toggleButtonVisibility = function (buttonId, visible) { if (visible === true) { self.showButton(buttonId); } else { self.hideButton(buttonId); } }; /** * Toggles buttons state * * @method toggleButtonState * @private * @param {String} state */ var toggleButtonState = function (state) { toggleButtonVisibility(Button.SHOW_SOLUTION, state === State.FINISHED_WRONG); toggleButtonVisibility(Button.CHECK, state === State.ONGOING); toggleButtonVisibility(Button.TRYAGAIN, state === State.FINISHED_WRONG || state === State.INTERNAL_SOLUTION); }; /** * Check if answer is correct or wrong, and update visuals accordingly * * @method checkAnswer * @private */ var checkAnswer = function () { // Create feedback widget var score = self.getScore(); var scoreText; toggleButtonState(score === MAX_SCORE ? State.FINISHED_CORRECT : State.FINISHED_WRONG); if (score === MAX_SCORE && params.behaviour.feedbackOnCorrect) { scoreText = params.behaviour.feedbackOnCorrect; } else if (score === 0 && params.behaviour.feedbackOnWrong) { scoreText = params.behaviour.feedbackOnWrong; } else { scoreText = params.l10n.score; } // Replace relevant variables: scoreText = scoreText.replace('@score', score).replace('@total', MAX_SCORE); self.setFeedback(scoreText, score, MAX_SCORE, params.l10n.scoreBarLabel); answerGroup.reveal(); }; /** * Registers this question type's DOM elements before they are attached. * Called from H5P.Question. * * @method registerDomElements * @private */ self.registerDomElements = function () { var self = this; // Check for task media var media = params.media; if (media && media.type && media.type.library) { media = media.type; var type = media.library.split(' ')[0]; if (type === 'H5P.Image') { if (media.params.file) { // Register task image self.setImage(media.params.file.path, { disableImageZooming: params.media.disableImageZooming || false, alt: media.params.alt }); } } else if (type === 'H5P.Video') { if (media.params.sources) { // Register task video self.setVideo(media); } } } // Add task question text self.setIntroduction('<div id="' + domId + '">' + params.question + '</div>'); // Register task content area self.$content = createAnswers(); self.setContent(self.$content); // ... and buttons registerButtons(); }; /** * Implements resume (save content state) * * @method getCurrentState * @public * @returns {object} object containing answer */ self.getCurrentState = function () { return {answer: answerGroup.getAnswer()}; }; /** * Used for contracts. * Checks if the parent program can proceed. Always true. * * @method getAnswerGiven * @public * @returns {Boolean} true */ self.getAnswerGiven = function () { return answerGroup.hasAnswered(); }; /** * Used for contracts. * Checks the current score for this task. * * @method getScore * @public * @returns {Number} The current score. */ self.getScore = function () { return answerGroup.isCorrect() ? MAX_SCORE : 0; }; /** * Used for contracts. * Checks the maximum score for this task. * * @method getMaxScore * @public * @returns {Number} The maximum score. */ self.getMaxScore = function () { return MAX_SCORE; }; /** * Get title of task * * @method getTitle * @public * @returns {string} title */ self.getTitle = function () { return H5P.createTitle((self.contentData && self.contentData.metadata && self.contentData.metadata.title) ? self.contentData.metadata.title : 'True-False'); }; /** * Used for contracts. * Show the solution. * * @method showSolutions * @public */ self.showSolutions = function (internal) { checkAnswer(); answerGroup.showSolution(); toggleButtonState(internal ? State.INTERNAL_SOLUTION : State.EXTERNAL_SOLUTION); }; /** * Used for contracts. * Resets the complete task back to its' initial state. * * @method resetTask * @public */ self.resetTask = function () { answerGroup.reset(); self.removeFeedback(); toggleButtonState(State.ONGOING); }; /** * Get xAPI data. * Contract used by report rendering engine. * * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6} */ self.getXAPIData = function(){ var xAPIEvent = this.createXAPIEventTemplate('answered'); this.addQuestionToXAPI(xAPIEvent); this.addResponseToXAPI(xAPIEvent); return { statement: xAPIEvent.data.statement }; }; /** * Add the question itself to the definition part of an xAPIEvent */ self.addQuestionToXAPI = function(xAPIEvent) { var definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); $.extend(definition, this.getxAPIDefinition()); }; /** * Generate xAPI object definition used in xAPI statements. * @return {Object} */ self.getxAPIDefinition = function () { var definition = {}; definition.interactionType = 'true-false'; definition.type = 'http://adlnet.gov/expapi/activities/cmi.interaction'; definition.description = { 'en-US': $('<div>' + params.question + '</div>').text() }; definition.correctResponsesPattern = [getCorrectAnswer()]; return definition; }; /** * Add the response part to an xAPI event * * @param {H5P.XAPIEvent} xAPIEvent * The xAPI event we will add a response to */ self.addResponseToXAPI = function (xAPIEvent) { var isCorrect = answerGroup.isCorrect(); var rawUserScore = isCorrect ? MAX_SCORE : 0; var currentResponse = ''; xAPIEvent.setScoredResult(rawUserScore, MAX_SCORE, self, true, isCorrect); if(self.getCurrentState().answer !== undefined) { currentResponse += answerGroup.isCorrect() ? getCorrectAnswer() : getWrongAnswer(); } xAPIEvent.data.statement.result.response = currentResponse; }; } // Inheritance TrueFalse.prototype = Object.create(Question.prototype); TrueFalse.prototype.constructor = TrueFalse; return TrueFalse; })(H5P.jQuery, H5P.Question); ; H5P.TrueFalse.AnswerGroup = (function ($, EventDispatcher) { 'use strict'; /** * Initialize module. * * @class H5P.TrueFalse.AnswerGroup * @extends H5P.EventDispatcher * @param {String} domId Id for label * @param {String} correctOption Correct option ('true' or 'false') * @param {Object} l10n Object containing all interface translations */ function AnswerGroup(domId, correctOption, l10n) { var self = this; EventDispatcher.call(self); var $answers = $('<div>', { 'class': 'h5p-true-false-answers', role: 'radiogroup', 'aria-labelledby': domId }); var answer; var trueAnswer = new H5P.TrueFalse.Answer(l10n.trueText, l10n.correctAnswerMessage, l10n.wrongAnswerMessage); var falseAnswer = new H5P.TrueFalse.Answer(l10n.falseText, l10n.correctAnswerMessage, l10n.wrongAnswerMessage); var correctAnswer = (correctOption === 'true' ? trueAnswer : falseAnswer); var wrongAnswer = (correctOption === 'false' ? trueAnswer : falseAnswer); // Handle checked var handleChecked = function (newAnswer, other) { return function () { answer = newAnswer; other.uncheck(); self.trigger('selected'); }; }; trueAnswer.on('checked', handleChecked(true, falseAnswer)); falseAnswer.on('checked', handleChecked(false, trueAnswer)); // Handle switches (using arrow keys) var handleInvert = function (newAnswer, other) { return function () { answer = newAnswer; other.check(); self.trigger('selected'); }; }; trueAnswer.on('invert', handleInvert(false, falseAnswer)); falseAnswer.on('invert', handleInvert(true, trueAnswer)); // Handle tabbing var handleTabable = function(other, tabable) { return function () { // If one of them are checked, that one should get tabfocus if (!tabable || !self.hasAnswered() || other.isChecked()) { other.tabable(tabable); } }; }; // Need to remove tabIndex on the other alternative on focus trueAnswer.on('focus', handleTabable(falseAnswer, false)); falseAnswer.on('focus', handleTabable(trueAnswer, false)); // Need to make both alternatives tabable on blur: trueAnswer.on('blur', handleTabable(falseAnswer, true)); falseAnswer.on('blur', handleTabable(trueAnswer, true)); $answers.append(trueAnswer.getDomElement()); $answers.append(falseAnswer.getDomElement()); /** * Get hold of the DOM element representing this thingy * @method getDomElement * @return {jQuery} */ self.getDomElement = function () { return $answers; }; /** * Programatic check * @method check * @param {[type]} answer [description] */ self.check = function (answer) { if (answer) { trueAnswer.check(); } else { falseAnswer.check(); } }; /** * Return current answer * @method getAnswer * @return {Boolean} undefined if no answer if given */ self.getAnswer = function () { return answer; }; /** * Check if user has answered question yet * @method hasAnswered * @return {Boolean} */ self.hasAnswered = function () { return answer !== undefined; }; /** * Is answer correct? * @method isCorrect * @return {Boolean} */ self.isCorrect = function () { return correctAnswer.isChecked(); }; /** * Enable user input * * @method enable */ self.enable = function () { trueAnswer.enable().tabable(true); falseAnswer.enable(); }; /** * Disable user input * * @method disable */ self.disable = function () { trueAnswer.disable(); falseAnswer.disable(); }; /** * Reveal correct/wrong answer * * @method reveal */ self.reveal = function () { if (self.hasAnswered()) { if (self.isCorrect()) { correctAnswer.markCorrect(); } else { wrongAnswer.markWrong(); } } self.disable(); }; /** * Reset task * @method reset */ self.reset = function () { trueAnswer.reset(); falseAnswer.reset(); self.enable(); answer = undefined; }; /** * Show the solution * @method showSolution * @return {[type]} */ self.showSolution = function () { correctAnswer.markCorrect(); wrongAnswer.unmark(); }; } // Inheritance AnswerGroup.prototype = Object.create(EventDispatcher.prototype); AnswerGroup.prototype.constructor = AnswerGroup; return AnswerGroup; })(H5P.jQuery, H5P.EventDispatcher); ; H5P.TrueFalse.Answer = (function ($, EventDispatcher) { 'use strict'; var Keys = { ENTER: 13, SPACE: 32, LEFT_ARROW: 37, UP_ARROW: 38, RIGHT_ARROW: 39, DOWN_ARROW: 40 }; /** * Initialize module. * * @class H5P.TrueFalse.Answer * @extends H5P.EventDispatcher * @param {String} text Label * @param {String} correctMessage Message read by readspeaker when correct alternative is chosen * @param {String} wrongMessage Message read by readspeaker when wrong alternative is chosen */ function Answer (text, correctMessage, wrongMessage) { var self = this; EventDispatcher.call(self); var checked = false; var enabled = true; var $answer = $('<div>', { 'class': 'h5p-true-false-answer', role: 'radio', 'aria-checked': false, html: text + '<span class="aria-label"></span>', tabindex: 0, // Tabable by default click: function (event) { // Handle left mouse (or tap on touch devices) if (event.which === 1) { self.check(); } }, keydown: function (event) { if (!enabled) { return; } if ([Keys.SPACE, Keys.ENTER].indexOf(event.keyCode) !== -1) { self.check(); } else if ([Keys.LEFT_ARROW, Keys.UP_ARROW, Keys.RIGHT_ARROW, Keys.DOWN_ARROW].indexOf(event.keyCode) !== -1) { self.uncheck(); self.trigger('invert'); } }, focus: function () { self.trigger('focus'); }, blur: function () { self.trigger('blur'); } }); var $ariaLabel = $answer.find('.aria-label'); // A bug in Chrome 54 makes the :after icons (V and X) not beeing rendered. // Doing this in a timeout solves this // Might be removed when Chrome 56 is out var chromeBugFixer = function (callback) { setTimeout(function () { callback(); }, 0); }; /** * Return the dom element representing the alternative * * @public * @method getDomElement * @return {H5P.jQuery} */ self.getDomElement = function () { return $answer; }; /** * Unchecks the alternative * * @public * @method uncheck * @return {H5P.TrueFalse.Answer} */ self.uncheck = function () { if (enabled) { $answer.blur(); checked = false; chromeBugFixer(function () { $answer.attr('aria-checked', checked); }); } return self; }; /** * Set tabable or not * @method tabable * @param {Boolean} enabled * @return {H5P.TrueFalse.Answer} */ self.tabable = function (enabled) { $answer.attr('tabIndex', enabled ? 0 : null); return self; }; /** * Checks the alternative * * @method check * @return {H5P.TrueFalse.Answer} */ self.check = function () { if (enabled) { checked = true; chromeBugFixer(function () { $answer.attr('aria-checked', checked); }); self.trigger('checked'); $answer.focus(); } return self; }; /** * Is this alternative checked? * * @method isChecked * @return {boolean} */ self.isChecked = function () { return checked; }; /** * Enable alternative * * @method enable * @return {H5P.TrueFalse.Answer} */ self.enable = function () { $answer.attr({ 'aria-disabled': '', tabIndex: 0 }); enabled = true; return self; }; /** * Disables alternative * * @method disable * @return {H5P.TrueFalse.Answer} */ self.disable = function () { $answer.attr({ 'aria-disabled': true, tabIndex: null }); enabled = false; return self; }; /** * Reset alternative * * @method reset * @return {H5P.TrueFalse.Answer} */ self.reset = function () { self.enable(); self.uncheck(); self.unmark(); $ariaLabel.html(''); return self; }; /** * Marks this alternative as the wrong one * * @method markWrong * @return {H5P.TrueFalse.Answer} */ self.markWrong = function () { chromeBugFixer(function () { $answer.addClass('wrong'); }); $ariaLabel.html('.' + wrongMessage); return self; }; /** * Marks this alternative as the wrong one * * @method markCorrect * @return {H5P.TrueFalse.Answer} */ self.markCorrect = function () { chromeBugFixer(function () { $answer.addClass('correct'); }); $ariaLabel.html('.' + correctMessage); return self; }; self.unmark = function () { chromeBugFixer(function () { $answer.removeClass('wrong correct'); }); return self; }; } // Inheritance Answer.prototype = Object.create(EventDispatcher.prototype); Answer.prototype.constructor = Answer; return Answer; })(H5P.jQuery, H5P.EventDispatcher); ; /*! * @license SoundJS * Visit http://createjs.com/ for documentation, updates and examples. * * Copyright (c) 2011-2015 gskinner.com, inc. * * Distributed under the terms of the MIT license. * http://www.opensource.org/licenses/mit-license.html * * This notice shall be included in all copies or substantial portions of the Software. */ /**! * SoundJS FlashAudioPlugin also includes swfobject (http://code.google.com/p/swfobject/) */ var old = this.createjs; this.createjs=this.createjs||{},function(){var a=createjs.SoundJS=createjs.SoundJS||{};a.version="0.6.2",a.buildDate="Thu, 26 Nov 2015 20:44:31 GMT"}(),this.createjs=this.createjs||{},createjs.extend=function(a,b){"use strict";function c(){this.constructor=a}return c.prototype=b.prototype,a.prototype=new c},this.createjs=this.createjs||{},createjs.promote=function(a,b){"use strict";var c=a.prototype,d=Object.getPrototypeOf&&Object.getPrototypeOf(c)||c.__proto__;if(d){c[(b+="_")+"constructor"]=d.constructor;for(var e in d)c.hasOwnProperty(e)&&"function"==typeof d[e]&&(c[b+e]=d[e])}return a},this.createjs=this.createjs||{},createjs.indexOf=function(a,b){"use strict";for(var c=0,d=a.length;d>c;c++)if(b===a[c])return c;return-1},this.createjs=this.createjs||{},function(){"use strict";createjs.proxy=function(a,b){var c=Array.prototype.slice.call(arguments,2);return function(){return a.apply(b,Array.prototype.slice.call(arguments,0).concat(c))}}}(),this.createjs=this.createjs||{},function(){"use strict";function BrowserDetect(){throw"BrowserDetect cannot be instantiated"}var a=BrowserDetect.agent=window.navigator.userAgent;BrowserDetect.isWindowPhone=a.indexOf("IEMobile")>-1||a.indexOf("Windows Phone")>-1,BrowserDetect.isFirefox=a.indexOf("Firefox")>-1,BrowserDetect.isOpera=null!=window.opera,BrowserDetect.isChrome=a.indexOf("Chrome")>-1,BrowserDetect.isIOS=(a.indexOf("iPod")>-1||a.indexOf("iPhone")>-1||a.indexOf("iPad")>-1)&&!BrowserDetect.isWindowPhone,BrowserDetect.isAndroid=a.indexOf("Android")>-1&&!BrowserDetect.isWindowPhone,BrowserDetect.isBlackberry=a.indexOf("Blackberry")>-1,createjs.BrowserDetect=BrowserDetect}(),this.createjs=this.createjs||{},function(){"use strict";function EventDispatcher(){this._listeners=null,this._captureListeners=null}var a=EventDispatcher.prototype;EventDispatcher.initialize=function(b){b.addEventListener=a.addEventListener,b.on=a.on,b.removeEventListener=b.off=a.removeEventListener,b.removeAllEventListeners=a.removeAllEventListeners,b.hasEventListener=a.hasEventListener,b.dispatchEvent=a.dispatchEvent,b._dispatchEvent=a._dispatchEvent,b.willTrigger=a.willTrigger},a.addEventListener=function(a,b,c){var d;d=c?this._captureListeners=this._captureListeners||{}:this._listeners=this._listeners||{};var e=d[a];return e&&this.removeEventListener(a,b,c),e=d[a],e?e.push(b):d[a]=[b],b},a.on=function(a,b,c,d,e,f){return b.handleEvent&&(c=c||b,b=b.handleEvent),c=c||this,this.addEventListener(a,function(a){b.call(c,a,e),d&&a.remove()},f)},a.removeEventListener=function(a,b,c){var d=c?this._captureListeners:this._listeners;if(d){var e=d[a];if(e)for(var f=0,g=e.length;g>f;f++)if(e[f]==b){1==g?delete d[a]:e.splice(f,1);break}}},a.off=a.removeEventListener,a.removeAllEventListeners=function(a){a?(this._listeners&&delete this._listeners[a],this._captureListeners&&delete this._captureListeners[a]):this._listeners=this._captureListeners=null},a.dispatchEvent=function(a,b,c){if("string"==typeof a){var d=this._listeners;if(!(b||d&&d[a]))return!0;a=new createjs.Event(a,b,c)}else a.target&&a.clone&&(a=a.clone());try{a.target=this}catch(e){}if(a.bubbles&&this.parent){for(var f=this,g=[f];f.parent;)g.push(f=f.parent);var h,i=g.length;for(h=i-1;h>=0&&!a.propagationStopped;h--)g[h]._dispatchEvent(a,1+(0==h));for(h=1;i>h&&!a.propagationStopped;h++)g[h]._dispatchEvent(a,3)}else this._dispatchEvent(a,2);return!a.defaultPrevented},a.hasEventListener=function(a){var b=this._listeners,c=this._captureListeners;return!!(b&&b[a]||c&&c[a])},a.willTrigger=function(a){for(var b=this;b;){if(b.hasEventListener(a))return!0;b=b.parent}return!1},a.toString=function(){return"[EventDispatcher]"},a._dispatchEvent=function(a,b){var c,d=1==b?this._captureListeners:this._listeners;if(a&&d){var e=d[a.type];if(!e||!(c=e.length))return;try{a.currentTarget=this}catch(f){}try{a.eventPhase=b}catch(f){}a.removed=!1,e=e.slice();for(var g=0;c>g&&!a.immediatePropagationStopped;g++){var h=e[g];h.handleEvent?h.handleEvent(a):h(a),a.removed&&(this.off(a.type,h,1==b),a.removed=!1)}}},createjs.EventDispatcher=EventDispatcher}(),this.createjs=this.createjs||{},function(){"use strict";function Event(a,b,c){this.type=a,this.target=null,this.currentTarget=null,this.eventPhase=0,this.bubbles=!!b,this.cancelable=!!c,this.timeStamp=(new Date).getTime(),this.defaultPrevented=!1,this.propagationStopped=!1,this.immediatePropagationStopped=!1,this.removed=!1}var a=Event.prototype;a.preventDefault=function(){this.defaultPrevented=this.cancelable&&!0},a.stopPropagation=function(){this.propagationStopped=!0},a.stopImmediatePropagation=function(){this.immediatePropagationStopped=this.propagationStopped=!0},a.remove=function(){this.removed=!0},a.clone=function(){return new Event(this.type,this.bubbles,this.cancelable)},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[Event (type="+this.type+")]"},createjs.Event=Event}(),this.createjs=this.createjs||{},function(){"use strict";function ErrorEvent(a,b,c){this.Event_constructor("error"),this.title=a,this.message=b,this.data=c}var a=createjs.extend(ErrorEvent,createjs.Event);a.clone=function(){return new createjs.ErrorEvent(this.title,this.message,this.data)},createjs.ErrorEvent=createjs.promote(ErrorEvent,"Event")}(),this.createjs=this.createjs||{},function(){"use strict";function ProgressEvent(a,b){this.Event_constructor("progress"),this.loaded=a,this.total=null==b?1:b,this.progress=0==b?0:this.loaded/this.total}var a=createjs.extend(ProgressEvent,createjs.Event);a.clone=function(){return new createjs.ProgressEvent(this.loaded,this.total)},createjs.ProgressEvent=createjs.promote(ProgressEvent,"Event")}(window),this.createjs=this.createjs||{},function(){"use strict";function LoadItem(){this.src=null,this.type=null,this.id=null,this.maintainOrder=!1,this.callback=null,this.data=null,this.method=createjs.LoadItem.GET,this.values=null,this.headers=null,this.withCredentials=!1,this.mimeType=null,this.crossOrigin=null,this.loadTimeout=b.LOAD_TIMEOUT_DEFAULT}var a=LoadItem.prototype={},b=LoadItem;b.LOAD_TIMEOUT_DEFAULT=8e3,b.create=function(a){if("string"==typeof a){var c=new LoadItem;return c.src=a,c}if(a instanceof b)return a;if(a instanceof Object&&a.src)return null==a.loadTimeout&&(a.loadTimeout=b.LOAD_TIMEOUT_DEFAULT),a;throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},createjs.LoadItem=b}(),function(){var a={};a.ABSOLUTE_PATT=/^(?:\w+:)?\/{2}/i,a.RELATIVE_PATT=/^[.\/]*?\//i,a.EXTENSION_PATT=/\/?[^\/]+\.(\w{1,5})$/i,a.parseURI=function(b){var c={absolute:!1,relative:!1};if(null==b)return c;var d=b.indexOf("?");d>-1&&(b=b.substr(0,d));var e;return a.ABSOLUTE_PATT.test(b)?c.absolute=!0:a.RELATIVE_PATT.test(b)&&(c.relative=!0),(e=b.match(a.EXTENSION_PATT))&&(c.extension=e[1].toLowerCase()),c},a.formatQueryString=function(a,b){if(null==a)throw new Error("You must specify data.");var c=[];for(var d in a)c.push(d+"="+escape(a[d]));return b&&(c=c.concat(b)),c.join("&")},a.buildPath=function(a,b){if(null==b)return a;var c=[],d=a.indexOf("?");if(-1!=d){var e=a.slice(d+1);c=c.concat(e.split("&"))}return-1!=d?a.slice(0,d)+"?"+this.formatQueryString(b,c):a+"?"+this.formatQueryString(b,c)},a.isCrossDomain=function(a){var b=document.createElement("a");b.href=a.src;var c=document.createElement("a");c.href=location.href;var d=""!=b.hostname&&(b.port!=c.port||b.protocol!=c.protocol||b.hostname!=c.hostname);return d},a.isLocal=function(a){var b=document.createElement("a");return b.href=a.src,""==b.hostname&&"file:"==b.protocol},a.isBinary=function(a){switch(a){case createjs.AbstractLoader.IMAGE:case createjs.AbstractLoader.BINARY:return!0;default:return!1}},a.isImageTag=function(a){return a instanceof HTMLImageElement},a.isAudioTag=function(a){return window.HTMLAudioElement?a instanceof HTMLAudioElement:!1},a.isVideoTag=function(a){return window.HTMLVideoElement?a instanceof HTMLVideoElement:!1},a.isText=function(a){switch(a){case createjs.AbstractLoader.TEXT:case createjs.AbstractLoader.JSON:case createjs.AbstractLoader.MANIFEST:case createjs.AbstractLoader.XML:case createjs.AbstractLoader.CSS:case createjs.AbstractLoader.SVG:case createjs.AbstractLoader.JAVASCRIPT:case createjs.AbstractLoader.SPRITESHEET:return!0;default:return!1}},a.getTypeByExtension=function(a){if(null==a)return createjs.AbstractLoader.TEXT;switch(a.toLowerCase()){case"jpeg":case"jpg":case"gif":case"png":case"webp":case"bmp":return createjs.AbstractLoader.IMAGE;case"ogg":case"mp3":case"webm":return createjs.AbstractLoader.SOUND;case"mp4":case"webm":case"ts":return createjs.AbstractLoader.VIDEO;case"json":return createjs.AbstractLoader.JSON;case"xml":return createjs.AbstractLoader.XML;case"css":return createjs.AbstractLoader.CSS;case"js":return createjs.AbstractLoader.JAVASCRIPT;case"svg":return createjs.AbstractLoader.SVG;default:return createjs.AbstractLoader.TEXT}},createjs.RequestUtils=a}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractLoader(a,b,c){this.EventDispatcher_constructor(),this.loaded=!1,this.canceled=!1,this.progress=0,this.type=c,this.resultFormatter=null,this._item=a?createjs.LoadItem.create(a):null,this._preferXHR=b,this._result=null,this._rawResult=null,this._loadedItems=null,this._tagSrcAttribute=null,this._tag=null}var a=createjs.extend(AbstractLoader,createjs.EventDispatcher),b=AbstractLoader;b.POST="POST",b.GET="GET",b.BINARY="binary",b.CSS="css",b.IMAGE="image",b.JAVASCRIPT="javascript",b.JSON="json",b.JSONP="jsonp",b.MANIFEST="manifest",b.SOUND="sound",b.VIDEO="video",b.SPRITESHEET="spritesheet",b.SVG="svg",b.TEXT="text",b.XML="xml",a.getItem=function(){return this._item},a.getResult=function(a){return a?this._rawResult:this._result},a.getTag=function(){return this._tag},a.setTag=function(a){this._tag=a},a.load=function(){this._createRequest(),this._request.on("complete",this,this),this._request.on("progress",this,this),this._request.on("loadStart",this,this),this._request.on("abort",this,this),this._request.on("timeout",this,this),this._request.on("error",this,this);var a=new createjs.Event("initialize");a.loader=this._request,this.dispatchEvent(a),this._request.load()},a.cancel=function(){this.canceled=!0,this.destroy()},a.destroy=function(){this._request&&(this._request.removeAllEventListeners(),this._request.destroy()),this._request=null,this._item=null,this._rawResult=null,this._result=null,this._loadItems=null,this.removeAllEventListeners()},a.getLoadedItems=function(){return this._loadedItems},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.TagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._createTag=function(){return null},a._sendLoadStart=function(){this._isCanceled()||this.dispatchEvent("loadstart")},a._sendProgress=function(a){if(!this._isCanceled()){var b=null;"number"==typeof a?(this.progress=a,b=new createjs.ProgressEvent(this.progress)):(b=a,this.progress=a.loaded/a.total,b.progress=this.progress,(isNaN(this.progress)||1/0==this.progress)&&(this.progress=0)),this.hasEventListener("progress")&&this.dispatchEvent(b)}},a._sendComplete=function(){if(!this._isCanceled()){this.loaded=!0;var a=new createjs.Event("complete");a.rawResult=this._rawResult,null!=this._result&&(a.result=this._result),this.dispatchEvent(a)}},a._sendError=function(a){!this._isCanceled()&&this.hasEventListener("error")&&(null==a&&(a=new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY")),this.dispatchEvent(a))},a._isCanceled=function(){return null==window.createjs||this.canceled?!0:!1},a.resultFormatter=null,a.handleEvent=function(a){switch(a.type){case"complete":this._rawResult=a.target._response;var b=this.resultFormatter&&this.resultFormatter(this);b instanceof Function?b.call(this,createjs.proxy(this._resultFormatSuccess,this),createjs.proxy(this._resultFormatFailed,this)):(this._result=b||this._rawResult,this._sendComplete());break;case"progress":this._sendProgress(a);break;case"error":this._sendError(a);break;case"loadstart":this._sendLoadStart();break;case"abort":case"timeout":this._isCanceled()||this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_"+a.type.toUpperCase()+"_ERROR"))}},a._resultFormatSuccess=function(a){this._result=a,this._sendComplete()},a._resultFormatFailed=function(a){this._sendError(a)},a.buildPath=function(a,b){return createjs.RequestUtils.buildPath(a,b)},a.toString=function(){return"[PreloadJS AbstractLoader]"},createjs.AbstractLoader=createjs.promote(AbstractLoader,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractMediaLoader(a,b,c){this.AbstractLoader_constructor(a,b,c),this.resultFormatter=this._formatResult,this._tagSrcAttribute="src",this.on("initialize",this._updateXHR,this)}var a=createjs.extend(AbstractMediaLoader,createjs.AbstractLoader);a.load=function(){this._tag||(this._tag=this._createTag(this._item.src)),this._tag.preload="auto",this._tag.load(),this.AbstractLoader_load()},a._createTag=function(){},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.MediaTagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._updateXHR=function(a){a.loader.setResponseType&&a.loader.setResponseType("blob")},a._formatResult=function(a){if(this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.onstalled=null,this._preferXHR){var b=window.URL||window.webkitURL,c=a.getResult(!0);a.getTag().src=b.createObjectURL(c)}return a.getTag()},createjs.AbstractMediaLoader=createjs.promote(AbstractMediaLoader,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractRequest=function(a){this._item=a},a=createjs.extend(AbstractRequest,createjs.EventDispatcher);a.load=function(){},a.destroy=function(){},a.cancel=function(){},createjs.AbstractRequest=createjs.promote(AbstractRequest,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function TagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this),this._addedToDOM=!1,this._startTagVisibility=null}var a=createjs.extend(TagRequest,createjs.AbstractRequest);a.load=function(){this._tag.onload=createjs.proxy(this._handleTagComplete,this),this._tag.onreadystatechange=createjs.proxy(this._handleReadyStateChange,this),this._tag.onerror=createjs.proxy(this._handleError,this);var a=new createjs.Event("initialize");a.loader=this._tag,this.dispatchEvent(a),this._hideTag(),this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout),this._tag[this._tagSrcAttribute]=this._item.src,null==this._tag.parentNode&&(window.document.body.appendChild(this._tag),this._addedToDOM=!0)},a.destroy=function(){this._clean(),this._tag=null,this.AbstractRequest_destroy()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleError=function(){this._clean(),this.dispatchEvent("error")},a._handleTagComplete=function(){this._rawResult=this._tag,this._result=this.resultFormatter&&this.resultFormatter(this)||this._rawResult,this._clean(),this._showTag(),this.dispatchEvent("complete")},a._handleTimeout=function(){this._clean(),this.dispatchEvent(new createjs.Event("timeout"))},a._clean=function(){this._tag.onload=null,this._tag.onreadystatechange=null,this._tag.onerror=null,this._addedToDOM&&null!=this._tag.parentNode&&this._tag.parentNode.removeChild(this._tag),clearTimeout(this._loadTimeout)},a._hideTag=function(){this._startTagVisibility=this._tag.style.visibility,this._tag.style.visibility="hidden"},a._showTag=function(){this._tag.style.visibility=this._startTagVisibility},a._handleStalled=function(){},createjs.TagRequest=createjs.promote(TagRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function MediaTagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this)}var a=createjs.extend(MediaTagRequest,createjs.TagRequest);a.load=function(){var a=createjs.proxy(this._handleStalled,this);this._stalledCallback=a;var b=createjs.proxy(this._handleProgress,this);this._handleProgress=b,this._tag.addEventListener("stalled",a),this._tag.addEventListener("progress",b),this._tag.addEventListener&&this._tag.addEventListener("canplaythrough",this._loadedHandler,!1),this.TagRequest_load()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleStalled=function(){},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._clean=function(){this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.removeEventListener("stalled",this._stalledCallback),this._tag.removeEventListener("progress",this._progressCallback),this.TagRequest__clean()},createjs.MediaTagRequest=createjs.promote(MediaTagRequest,"TagRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function XHRRequest(a){this.AbstractRequest_constructor(a),this._request=null,this._loadTimeout=null,this._xhrLevel=1,this._response=null,this._rawResponse=null,this._canceled=!1,this._handleLoadStartProxy=createjs.proxy(this._handleLoadStart,this),this._handleProgressProxy=createjs.proxy(this._handleProgress,this),this._handleAbortProxy=createjs.proxy(this._handleAbort,this),this._handleErrorProxy=createjs.proxy(this._handleError,this),this._handleTimeoutProxy=createjs.proxy(this._handleTimeout,this),this._handleLoadProxy=createjs.proxy(this._handleLoad,this),this._handleReadyStateChangeProxy=createjs.proxy(this._handleReadyStateChange,this),!this._createXHR(a)}var a=createjs.extend(XHRRequest,createjs.AbstractRequest);XHRRequest.ACTIVEX_VERSIONS=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],a.getResult=function(a){return a&&this._rawResponse?this._rawResponse:this._response},a.cancel=function(){this.canceled=!0,this._clean(),this._request.abort()},a.load=function(){if(null==this._request)return void this._handleError();null!=this._request.addEventListener?(this._request.addEventListener("loadstart",this._handleLoadStartProxy,!1),this._request.addEventListener("progress",this._handleProgressProxy,!1),this._request.addEventListener("abort",this._handleAbortProxy,!1),this._request.addEventListener("error",this._handleErrorProxy,!1),this._request.addEventListener("timeout",this._handleTimeoutProxy,!1),this._request.addEventListener("load",this._handleLoadProxy,!1),this._request.addEventListener("readystatechange",this._handleReadyStateChangeProxy,!1)):(this._request.onloadstart=this._handleLoadStartProxy,this._request.onprogress=this._handleProgressProxy,this._request.onabort=this._handleAbortProxy,this._request.onerror=this._handleErrorProxy,this._request.ontimeout=this._handleTimeoutProxy,this._request.onload=this._handleLoadProxy,this._request.onreadystatechange=this._handleReadyStateChangeProxy),1==this._xhrLevel&&(this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout));try{this._item.values&&this._item.method!=createjs.AbstractLoader.GET?this._item.method==createjs.AbstractLoader.POST&&this._request.send(createjs.RequestUtils.formatQueryString(this._item.values)):this._request.send()}catch(a){this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND",null,a))}},a.setResponseType=function(a){"blob"===a&&(a=window.URL?"blob":"arraybuffer",this._responseType=a),this._request.responseType=a},a.getAllResponseHeaders=function(){return this._request.getAllResponseHeaders instanceof Function?this._request.getAllResponseHeaders():null},a.getResponseHeader=function(a){return this._request.getResponseHeader instanceof Function?this._request.getResponseHeader(a):null},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._handleLoadStart=function(){clearTimeout(this._loadTimeout),this.dispatchEvent("loadstart")},a._handleAbort=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED",null,a))},a._handleError=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent(a.message))},a._handleReadyStateChange=function(){4==this._request.readyState&&this._handleLoad()},a._handleLoad=function(){if(!this.loaded){this.loaded=!0;var a=this._checkError();if(a)return void this._handleError(a);if(this._response=this._getResponse(),"arraybuffer"===this._responseType)try{this._response=new Blob([this._response])}catch(b){if(window.BlobBuilder=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,"TypeError"===b.name&&window.BlobBuilder){var c=new BlobBuilder;c.append(this._response),this._response=c.getBlob()}}this._clean(),this.dispatchEvent(new createjs.Event("complete"))}},a._handleTimeout=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT",null,a))},a._checkError=function(){var a=parseInt(this._request.status);switch(a){case 404:case 0:return new Error(a)}return null},a._getResponse=function(){if(null!=this._response)return this._response;if(null!=this._request.response)return this._request.response;try{if(null!=this._request.responseText)return this._request.responseText}catch(a){}try{if(null!=this._request.responseXML)return this._request.responseXML}catch(a){}return null},a._createXHR=function(a){var b=createjs.RequestUtils.isCrossDomain(a),c={},d=null;if(window.XMLHttpRequest)d=new XMLHttpRequest,b&&void 0===d.withCredentials&&window.XDomainRequest&&(d=new XDomainRequest);else{for(var e=0,f=s.ACTIVEX_VERSIONS.length;f>e;e++){var g=s.ACTIVEX_VERSIONS[e];try{d=new ActiveXObject(g);break}catch(h){}}if(null==d)return!1}null==a.mimeType&&createjs.RequestUtils.isText(a.type)&&(a.mimeType="text/plain; charset=utf-8"),a.mimeType&&d.overrideMimeType&&d.overrideMimeType(a.mimeType),this._xhrLevel="string"==typeof d.responseType?2:1;var i=null;if(i=a.method==createjs.AbstractLoader.GET?createjs.RequestUtils.buildPath(a.src,a.values):a.src,d.open(a.method||createjs.AbstractLoader.GET,i,!0),b&&d instanceof XMLHttpRequest&&1==this._xhrLevel&&(c.Origin=location.origin),a.values&&a.method==createjs.AbstractLoader.POST&&(c["Content-Type"]="application/x-www-form-urlencoded"),b||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest"),a.headers)for(var j in a.headers)c[j]=a.headers[j];for(j in c)d.setRequestHeader(j,c[j]);return d instanceof XMLHttpRequest&&void 0!==a.withCredentials&&(d.withCredentials=a.withCredentials),this._request=d,!0},a._clean=function(){clearTimeout(this._loadTimeout),null!=this._request.removeEventListener?(this._request.removeEventListener("loadstart",this._handleLoadStartProxy),this._request.removeEventListener("progress",this._handleProgressProxy),this._request.removeEventListener("abort",this._handleAbortProxy),this._request.removeEventListener("error",this._handleErrorProxy),this._request.removeEventListener("timeout",this._handleTimeoutProxy),this._request.removeEventListener("load",this._handleLoadProxy),this._request.removeEventListener("readystatechange",this._handleReadyStateChangeProxy)):(this._request.onloadstart=null,this._request.onprogress=null,this._request.onabort=null,this._request.onerror=null,this._request.ontimeout=null,this._request.onload=null,this._request.onreadystatechange=null)},a.toString=function(){return"[PreloadJS XHRRequest]"},createjs.XHRRequest=createjs.promote(XHRRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function SoundLoader(a,b){this.AbstractMediaLoader_constructor(a,b,createjs.AbstractLoader.SOUND),createjs.RequestUtils.isAudioTag(a)?this._tag=a:createjs.RequestUtils.isAudioTag(a.src)?this._tag=a:createjs.RequestUtils.isAudioTag(a.tag)&&(this._tag=createjs.RequestUtils.isAudioTag(a)?a:a.src),null!=this._tag&&(this._preferXHR=!1)}var a=createjs.extend(SoundLoader,createjs.AbstractMediaLoader),b=SoundLoader;b.canLoadItem=function(a){return a.type==createjs.AbstractLoader.SOUND},a._createTag=function(a){var b=document.createElement("audio");return b.autoplay=!1,b.preload="none",b.src=a,b},createjs.SoundLoader=createjs.promote(SoundLoader,"AbstractMediaLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var PlayPropsConfig=function(){this.interrupt=null,this.delay=null,this.offset=null,this.loop=null,this.volume=null,this.pan=null,this.startTime=null,this.duration=null},a=PlayPropsConfig.prototype={},b=PlayPropsConfig;b.create=function(a){if(a instanceof b||a instanceof Object){var c=new createjs.PlayPropsConfig;return c.set(a),c}throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[PlayPropsConfig]"},createjs.PlayPropsConfig=b}(),this.createjs=this.createjs||{},function(){"use strict";function Sound(){throw"Sound cannot be instantiated"}function a(a,b){this.init(a,b)}var b=Sound;b.INTERRUPT_ANY="any",b.INTERRUPT_EARLY="early",b.INTERRUPT_LATE="late",b.INTERRUPT_NONE="none",b.PLAY_INITED="playInited",b.PLAY_SUCCEEDED="playSucceeded",b.PLAY_INTERRUPTED="playInterrupted",b.PLAY_FINISHED="playFinished",b.PLAY_FAILED="playFailed",b.SUPPORTED_EXTENSIONS=["mp3","ogg","opus","mpeg","wav","m4a","mp4","aiff","wma","mid"],b.EXTENSION_MAP={m4a:"mp4"},b.FILE_PATTERN=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([\/.]*?(?:[^?]+)?\/)?((?:[^\/?]+)\.(\w+))(?:\?(\S+)?)?$/,b.defaultInterruptBehavior=b.INTERRUPT_NONE,b.alternateExtensions=[],b.activePlugin=null,b._masterVolume=1,Object.defineProperty(b,"volume",{get:function(){return this._masterVolume},set:function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)}}),b._masterMute=!1,Object.defineProperty(b,"muted",{get:function(){return this._masterMute},set:function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0}}),Object.defineProperty(b,"capabilities",{get:function(){return null==b.activePlugin?null:b.activePlugin._capabilities},set:function(){return!1}}),b._pluginsRegistered=!1,b._lastID=0,b._instances=[],b._idHash={},b._preloadHash={},b._defaultPlayPropsHash={},b.addEventListener=null,b.removeEventListener=null,b.removeAllEventListeners=null,b.dispatchEvent=null,b.hasEventListener=null,b._listeners=null,createjs.EventDispatcher.initialize(b),b.getPreloadHandlers=function(){return{callback:createjs.proxy(b.initLoad,b),types:["sound"],extensions:b.SUPPORTED_EXTENSIONS}},b._handleLoadComplete=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!0,b.hasEventListener("fileload")){var a=new createjs.Event("fileload");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._handleLoadError=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!1,b.hasEventListener("fileerror")){var a=new createjs.Event("fileerror");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._registerPlugin=function(a){return a.isSupported()?(b.activePlugin=new a,!0):!1},b.registerPlugins=function(a){b._pluginsRegistered=!0;for(var c=0,d=a.length;d>c;c++)if(b._registerPlugin(a[c]))return!0;return!1},b.initializeDefaultPlugins=function(){return null!=b.activePlugin?!0:b._pluginsRegistered?!1:b.registerPlugins([createjs.WebAudioPlugin,createjs.HTMLAudioPlugin])?!0:!1},b.isReady=function(){return null!=b.activePlugin},b.getCapabilities=function(){return null==b.activePlugin?null:b.activePlugin._capabilities},b.getCapability=function(a){return null==b.activePlugin?null:b.activePlugin._capabilities[a]},b.initLoad=function(a){return b._registerSound(a)},b._registerSound=function(c){if(!b.initializeDefaultPlugins())return!1;var d;if(c.src instanceof Object?(d=b._parseSrc(c.src),d.src=c.path+d.src):d=b._parsePath(c.src),null==d)return!1;c.src=d.src,c.type="sound";var e=c.data,f=null;if(null!=e&&(isNaN(e.channels)?isNaN(e)||(f=parseInt(e)):f=parseInt(e.channels),e.audioSprite))for(var g,h=e.audioSprite.length;h--;)g=e.audioSprite[h],b._idHash[g.id]={src:c.src,startTime:parseInt(g.startTime),duration:parseInt(g.duration)},g.defaultPlayProps&&(b._defaultPlayPropsHash[g.id]=createjs.PlayPropsConfig.create(g.defaultPlayProps));null!=c.id&&(b._idHash[c.id]={src:c.src});var i=b.activePlugin.register(c);return a.create(c.src,f),null!=e&&isNaN(e)?c.data.channels=f||a.maxPerChannel():c.data=f||a.maxPerChannel(),i.type&&(c.type=i.type),c.defaultPlayProps&&(b._defaultPlayPropsHash[c.src]=createjs.PlayPropsConfig.create(c.defaultPlayProps)),i},b.registerSound=function(a,c,d,e,f){var g={src:a,id:c,data:d,defaultPlayProps:f};a instanceof Object&&a.src&&(e=c,g=a),g=createjs.LoadItem.create(g),g.path=e,null==e||g.src instanceof Object||(g.src=e+a);var h=b._registerSound(g);if(!h)return!1;if(b._preloadHash[g.src]||(b._preloadHash[g.src]=[]),b._preloadHash[g.src].push(g),1==b._preloadHash[g.src].length)h.on("complete",createjs.proxy(this._handleLoadComplete,this)),h.on("error",createjs.proxy(this._handleLoadError,this)),b.activePlugin.preload(h);else if(1==b._preloadHash[g.src][0])return!0;return g},b.registerSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.registerSound(a[d].src,a[d].id,a[d].data,b,a[d].defaultPlayProps);return c},b.removeSound=function(c,d){if(null==b.activePlugin)return!1;c instanceof Object&&c.src&&(c=c.src);var e;if(c instanceof Object?e=b._parseSrc(c):(c=b._getSrcById(c).src,e=b._parsePath(c)),null==e)return!1;c=e.src,null!=d&&(c=d+c);for(var f in b._idHash)b._idHash[f].src==c&&delete b._idHash[f];return a.removeSrc(c),delete b._preloadHash[c],b.activePlugin.removeSound(c),!0},b.removeSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.removeSound(a[d].src,b);return c},b.removeAllSounds=function(){b._idHash={},b._preloadHash={},a.removeAll(),b.activePlugin&&b.activePlugin.removeAllSounds()},b.loadComplete=function(a){if(!b.isReady())return!1;var c=b._parsePath(a);return a=c?b._getSrcById(c.src).src:b._getSrcById(a).src,void 0==b._preloadHash[a]?!1:1==b._preloadHash[a][0]},b._parsePath=function(a){"string"!=typeof a&&(a=a.toString());var c=a.match(b.FILE_PATTERN);if(null==c)return!1;for(var d=c[4],e=c[5],f=b.capabilities,g=0;!f[e];)if(e=b.alternateExtensions[g++],g>b.alternateExtensions.length)return null;a=a.replace("."+c[5],"."+e);var h={name:d,src:a,extension:e};return h},b._parseSrc=function(a){var c={name:void 0,src:void 0,extension:void 0},d=b.capabilities;for(var e in a)if(a.hasOwnProperty(e)&&d[e]){c.src=a[e],c.extension=e;break}if(!c.src)return!1;var f=c.src.lastIndexOf("/");return c.name=-1!=f?c.src.slice(f+1):c.src,c},b.play=function(a,c,d,e,f,g,h,i,j){var k;k=createjs.PlayPropsConfig.create(c instanceof Object||c instanceof createjs.PlayPropsConfig?c:{interrupt:c,delay:d,offset:e,loop:f,volume:g,pan:h,startTime:i,duration:j});var l=b.createInstance(a,k.startTime,k.duration),m=b._playInstance(l,k);return m||l._playFailed(),l},b.createInstance=function(c,d,e){if(!b.initializeDefaultPlugins())return new createjs.DefaultSoundInstance(c,d,e);var f=b._defaultPlayPropsHash[c];c=b._getSrcById(c);var g=b._parsePath(c.src),h=null; return null!=g&&null!=g.src?(a.create(g.src),null==d&&(d=c.startTime),h=b.activePlugin.create(g.src,d,e||c.duration),f=f||b._defaultPlayPropsHash[g.src],f&&h.applyPlayProps(f)):h=new createjs.DefaultSoundInstance(c,d,e),h.uniqueId=b._lastID++,h},b.stop=function(){for(var a=this._instances,b=a.length;b--;)a[b].stop()},b.setVolume=function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)},b.getVolume=function(){return this._masterVolume},b.setMute=function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0},b.getMute=function(){return this._masterMute},b.setDefaultPlayProps=function(a,c){a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]=createjs.PlayPropsConfig.create(c)},b.getDefaultPlayProps=function(a){return a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]},b._playInstance=function(a,c){var d=b._defaultPlayPropsHash[a.src]||{};if(null==c.interrupt&&(c.interrupt=d.interrupt||b.defaultInterruptBehavior),null==c.delay&&(c.delay=d.delay||0),null==c.offset&&(c.offset=a.getPosition()),null==c.loop&&(c.loop=a.loop),null==c.volume&&(c.volume=a.volume),null==c.pan&&(c.pan=a.pan),0==c.delay){var e=b._beginPlaying(a,c);if(!e)return!1}else{var f=setTimeout(function(){b._beginPlaying(a,c)},c.delay);a.delayTimeoutId=f}return this._instances.push(a),!0},b._beginPlaying=function(b,c){if(!a.add(b,c.interrupt))return!1;var d=b._beginPlaying(c);if(!d){var e=createjs.indexOf(this._instances,b);return e>-1&&this._instances.splice(e,1),!1}return!0},b._getSrcById=function(a){return b._idHash[a]||{src:a}},b._playFinished=function(b){a.remove(b);var c=createjs.indexOf(this._instances,b);c>-1&&this._instances.splice(c,1)},createjs.Sound=Sound,a.channels={},a.create=function(b,c){var d=a.get(b);return null==d?(a.channels[b]=new a(b,c),!0):!1},a.removeSrc=function(b){var c=a.get(b);return null==c?!1:(c._removeAll(),delete a.channels[b],!0)},a.removeAll=function(){for(var b in a.channels)a.channels[b]._removeAll();a.channels={}},a.add=function(b,c){var d=a.get(b.src);return null==d?!1:d._add(b,c)},a.remove=function(b){var c=a.get(b.src);return null==c?!1:(c._remove(b),!0)},a.maxPerChannel=function(){return c.maxDefault},a.get=function(b){return a.channels[b]};var c=a.prototype;c.constructor=a,c.src=null,c.max=null,c.maxDefault=100,c.length=0,c.init=function(a,b){this.src=a,this.max=b||this.maxDefault,-1==this.max&&(this.max=this.maxDefault),this._instances=[]},c._get=function(a){return this._instances[a]},c._add=function(a,b){return this._getSlot(b,a)?(this._instances.push(a),this.length++,!0):!1},c._remove=function(a){var b=createjs.indexOf(this._instances,a);return-1==b?!1:(this._instances.splice(b,1),this.length--,!0)},c._removeAll=function(){for(var a=this.length-1;a>=0;a--)this._instances[a].stop()},c._getSlot=function(a){var b,c;if(a!=Sound.INTERRUPT_NONE&&(c=this._get(0),null==c))return!0;for(var d=0,e=this.max;e>d;d++){if(b=this._get(d),null==b)return!0;if(b.playState==Sound.PLAY_FINISHED||b.playState==Sound.PLAY_INTERRUPTED||b.playState==Sound.PLAY_FAILED){c=b;break}a!=Sound.INTERRUPT_NONE&&(a==Sound.INTERRUPT_EARLY&&b.getPosition()<c.getPosition()||a==Sound.INTERRUPT_LATE&&b.getPosition()>c.getPosition())&&(c=b)}return null!=c?(c._interrupt(),this._remove(c),!0):!1},c.toString=function(){return"[Sound SoundChannel]"}}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractSoundInstance=function(a,b,c,d){this.EventDispatcher_constructor(),this.src=a,this.uniqueId=-1,this.playState=null,this.delayTimeoutId=null,this._volume=1,Object.defineProperty(this,"volume",{get:this.getVolume,set:this.setVolume}),this._pan=0,Object.defineProperty(this,"pan",{get:this.getPan,set:this.setPan}),this._startTime=Math.max(0,b||0),Object.defineProperty(this,"startTime",{get:this.getStartTime,set:this.setStartTime}),this._duration=Math.max(0,c||0),Object.defineProperty(this,"duration",{get:this.getDuration,set:this.setDuration}),this._playbackResource=null,Object.defineProperty(this,"playbackResource",{get:this.getPlaybackResource,set:this.setPlaybackResource}),d!==!1&&d!==!0&&this.setPlaybackResource(d),this._position=0,Object.defineProperty(this,"position",{get:this.getPosition,set:this.setPosition}),this._loop=0,Object.defineProperty(this,"loop",{get:this.getLoop,set:this.setLoop}),this._muted=!1,Object.defineProperty(this,"muted",{get:this.getMuted,set:this.setMuted}),this._paused=!1,Object.defineProperty(this,"paused",{get:this.getPaused,set:this.setPaused})},a=createjs.extend(AbstractSoundInstance,createjs.EventDispatcher);a.play=function(a,b,c,d,e,f){var g;return g=createjs.PlayPropsConfig.create(a instanceof Object||a instanceof createjs.PlayPropsConfig?a:{interrupt:a,delay:b,offset:c,loop:d,volume:e,pan:f}),this.playState==createjs.Sound.PLAY_SUCCEEDED?(this.applyPlayProps(g),void(this._paused&&this.setPaused(!1))):(this._cleanUp(),createjs.Sound._playInstance(this,g),this)},a.stop=function(){return this._position=0,this._paused=!1,this._handleStop(),this._cleanUp(),this.playState=createjs.Sound.PLAY_FINISHED,this},a.destroy=function(){this._cleanUp(),this.src=null,this.playbackResource=null,this.removeAllEventListeners()},a.applyPlayProps=function(a){return null!=a.offset&&this.setPosition(a.offset),null!=a.loop&&this.setLoop(a.loop),null!=a.volume&&this.setVolume(a.volume),null!=a.pan&&this.setPan(a.pan),null!=a.startTime&&(this.setStartTime(a.startTime),this.setDuration(a.duration)),this},a.toString=function(){return"[AbstractSoundInstance]"},a.getPaused=function(){return this._paused},a.setPaused=function(a){return a!==!0&&a!==!1||this._paused==a||1==a&&this.playState!=createjs.Sound.PLAY_SUCCEEDED?void 0:(this._paused=a,a?this._pause():this._resume(),clearTimeout(this.delayTimeoutId),this)},a.setVolume=function(a){return a==this._volume?this:(this._volume=Math.max(0,Math.min(1,a)),this._muted||this._updateVolume(),this)},a.getVolume=function(){return this._volume},a.setMuted=function(a){return a===!0||a===!1?(this._muted=a,this._updateVolume(),this):void 0},a.getMuted=function(){return this._muted},a.setPan=function(a){return a==this._pan?this:(this._pan=Math.max(-1,Math.min(1,a)),this._updatePan(),this)},a.getPan=function(){return this._pan},a.getPosition=function(){return this._paused||this.playState!=createjs.Sound.PLAY_SUCCEEDED||(this._position=this._calculateCurrentPosition()),this._position},a.setPosition=function(a){return this._position=Math.max(0,a),this.playState==createjs.Sound.PLAY_SUCCEEDED&&this._updatePosition(),this},a.getStartTime=function(){return this._startTime},a.setStartTime=function(a){return a==this._startTime?this:(this._startTime=Math.max(0,a||0),this._updateStartTime(),this)},a.getDuration=function(){return this._duration},a.setDuration=function(a){return a==this._duration?this:(this._duration=Math.max(0,a||0),this._updateDuration(),this)},a.setPlaybackResource=function(a){return this._playbackResource=a,0==this._duration&&this._setDurationFromSource(),this},a.getPlaybackResource=function(){return this._playbackResource},a.getLoop=function(){return this._loop},a.setLoop=function(a){null!=this._playbackResource&&(0!=this._loop&&0==a?this._removeLooping(a):0==this._loop&&0!=a&&this._addLooping(a)),this._loop=a},a._sendEvent=function(a){var b=new createjs.Event(a);this.dispatchEvent(b)},a._cleanUp=function(){clearTimeout(this.delayTimeoutId),this._handleCleanUp(),this._paused=!1,createjs.Sound._playFinished(this)},a._interrupt=function(){this._cleanUp(),this.playState=createjs.Sound.PLAY_INTERRUPTED,this._sendEvent("interrupted")},a._beginPlaying=function(a){return this.setPosition(a.offset),this.setLoop(a.loop),this.setVolume(a.volume),this.setPan(a.pan),null!=a.startTime&&(this.setStartTime(a.startTime),this.setDuration(a.duration)),null!=this._playbackResource&&this._position<this._duration?(this._paused=!1,this._handleSoundReady(),this.playState=createjs.Sound.PLAY_SUCCEEDED,this._sendEvent("succeeded"),!0):(this._playFailed(),!1)},a._playFailed=function(){this._cleanUp(),this.playState=createjs.Sound.PLAY_FAILED,this._sendEvent("failed")},a._handleSoundComplete=function(){return this._position=0,0!=this._loop?(this._loop--,this._handleLoop(),void this._sendEvent("loop")):(this._cleanUp(),this.playState=createjs.Sound.PLAY_FINISHED,void this._sendEvent("complete"))},a._handleSoundReady=function(){},a._updateVolume=function(){},a._updatePan=function(){},a._updateStartTime=function(){},a._updateDuration=function(){},a._setDurationFromSource=function(){},a._calculateCurrentPosition=function(){},a._updatePosition=function(){},a._removeLooping=function(){},a._addLooping=function(){},a._pause=function(){},a._resume=function(){},a._handleStop=function(){},a._handleCleanUp=function(){},a._handleLoop=function(){},createjs.AbstractSoundInstance=createjs.promote(AbstractSoundInstance,"EventDispatcher"),createjs.DefaultSoundInstance=createjs.AbstractSoundInstance}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractPlugin=function(){this._capabilities=null,this._loaders={},this._audioSources={},this._soundInstances={},this._volume=1,this._loaderClass,this._soundInstanceClass},a=AbstractPlugin.prototype;AbstractPlugin._capabilities=null,AbstractPlugin.isSupported=function(){return!0},a.register=function(a){var b=this._loaders[a.src];return b&&!b.canceled?this._loaders[a.src]:(this._audioSources[a.src]=!0,this._soundInstances[a.src]=[],b=new this._loaderClass(a),b.on("complete",this._handlePreloadComplete,this),this._loaders[a.src]=b,b)},a.preload=function(a){a.on("error",this._handlePreloadError,this),a.load()},a.isPreloadStarted=function(a){return null!=this._audioSources[a]},a.isPreloadComplete=function(a){return!(null==this._audioSources[a]||1==this._audioSources[a])},a.removeSound=function(a){if(this._soundInstances[a]){for(var b=this._soundInstances[a].length;b--;){var c=this._soundInstances[a][b];c.destroy()}delete this._soundInstances[a],delete this._audioSources[a],this._loaders[a]&&this._loaders[a].destroy(),delete this._loaders[a]}},a.removeAllSounds=function(){for(var a in this._audioSources)this.removeSound(a)},a.create=function(a,b,c){this.isPreloadStarted(a)||this.preload(this.register(a));var d=new this._soundInstanceClass(a,b,c,this._audioSources[a]);return this._soundInstances[a].push(d),d},a.setVolume=function(a){return this._volume=a,this._updateVolume(),!0},a.getVolume=function(){return this._volume},a.setMute=function(){return this._updateVolume(),!0},a.toString=function(){return"[AbstractPlugin]"},a._handlePreloadComplete=function(a){var b=a.target.getItem().src;this._audioSources[b]=a.result;for(var c=0,d=this._soundInstances[b].length;d>c;c++){var e=this._soundInstances[b][c];e.setPlaybackResource(this._audioSources[b])}},a._handlePreloadError=function(){},a._updateVolume=function(){},createjs.AbstractPlugin=AbstractPlugin}(),this.createjs=this.createjs||{},function(){"use strict";function a(a){this.AbstractLoader_constructor(a,!0,createjs.AbstractLoader.SOUND)}var b=createjs.extend(a,createjs.AbstractLoader);a.context=null,b.toString=function(){return"[WebAudioLoader]"},b._createRequest=function(){this._request=new createjs.XHRRequest(this._item,!1),this._request.setResponseType("arraybuffer")},b._sendComplete=function(){a.context.decodeAudioData(this._rawResult,createjs.proxy(this._handleAudioDecoded,this),createjs.proxy(this._sendError,this))},b._handleAudioDecoded=function(a){this._result=a,this.AbstractLoader__sendComplete()},createjs.WebAudioLoader=createjs.promote(a,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";function WebAudioSoundInstance(a,c,d,e){this.AbstractSoundInstance_constructor(a,c,d,e),this.gainNode=b.context.createGain(),this.panNode=b.context.createPanner(),this.panNode.panningModel=b._panningModel,this.panNode.connect(this.gainNode),this._updatePan(),this.sourceNode=null,this._soundCompleteTimeout=null,this._sourceNodeNext=null,this._playbackStartTime=0,this._endedHandler=createjs.proxy(this._handleSoundComplete,this)}var a=createjs.extend(WebAudioSoundInstance,createjs.AbstractSoundInstance),b=WebAudioSoundInstance;b.context=null,b._scratchBuffer=null,b.destinationNode=null,b._panningModel="equalpower",a.destroy=function(){this.AbstractSoundInstance_destroy(),this.panNode.disconnect(0),this.panNode=null,this.gainNode.disconnect(0),this.gainNode=null},a.toString=function(){return"[WebAudioSoundInstance]"},a._updatePan=function(){this.panNode.setPosition(this._pan,0,-.5)},a._removeLooping=function(){this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext)},a._addLooping=function(){this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0))},a._setDurationFromSource=function(){this._duration=1e3*this.playbackResource.duration},a._handleCleanUp=function(){this.sourceNode&&this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext)),0!=this.gainNode.numberOfOutputs&&this.gainNode.disconnect(0),clearTimeout(this._soundCompleteTimeout),this._playbackStartTime=0},a._cleanUpAudioNode=function(a){if(a){a.stop(0),a.disconnect(0);try{a.buffer=b._scratchBuffer}catch(c){}a=null}return a},a._handleSoundReady=function(){this.gainNode.connect(b.destinationNode);var a=.001*this._duration,c=.001*this._position;c>a&&(c=a),this.sourceNode=this._createAndPlayAudioNode(b.context.currentTime-a,c),this._playbackStartTime=this.sourceNode.startTime-c,this._soundCompleteTimeout=setTimeout(this._endedHandler,1e3*(a-c)),0!=this._loop&&(this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0))},a._createAndPlayAudioNode=function(a,c){var d=b.context.createBufferSource();d.buffer=this.playbackResource,d.connect(this.panNode);var e=.001*this._duration;return d.startTime=a+e,d.start(d.startTime,c+.001*this._startTime,e-c),d},a._pause=function(){this._position=1e3*(b.context.currentTime-this._playbackStartTime),this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext),0!=this.gainNode.numberOfOutputs&&this.gainNode.disconnect(0),clearTimeout(this._soundCompleteTimeout)},a._resume=function(){this._handleSoundReady()},a._updateVolume=function(){var a=this._muted?0:this._volume;a!=this.gainNode.gain.value&&(this.gainNode.gain.value=a)},a._calculateCurrentPosition=function(){return 1e3*(b.context.currentTime-this._playbackStartTime)},a._updatePosition=function(){this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext),clearTimeout(this._soundCompleteTimeout),this._paused||this._handleSoundReady()},a._handleLoop=function(){this._cleanUpAudioNode(this.sourceNode),this.sourceNode=this._sourceNodeNext,this._playbackStartTime=this.sourceNode.startTime,this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0),this._soundCompleteTimeout=setTimeout(this._endedHandler,this._duration)},a._updateDuration=function(){this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._pause(),this._resume())},createjs.WebAudioSoundInstance=createjs.promote(WebAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function WebAudioPlugin(){this.AbstractPlugin_constructor(),this._panningModel=b._panningModel,this.context=b.context,this.dynamicsCompressorNode=this.context.createDynamicsCompressor(),this.dynamicsCompressorNode.connect(this.context.destination),this.gainNode=this.context.createGain(),this.gainNode.connect(this.dynamicsCompressorNode),createjs.WebAudioSoundInstance.destinationNode=this.gainNode,this._capabilities=b._capabilities,this._loaderClass=createjs.WebAudioLoader,this._soundInstanceClass=createjs.WebAudioSoundInstance,this._addPropsToClasses()}var a=createjs.extend(WebAudioPlugin,createjs.AbstractPlugin),b=WebAudioPlugin;b._capabilities=null,b._panningModel="equalpower",b.context=null,b._scratchBuffer=null,b._unlocked=!1,b.isSupported=function(){var a=createjs.BrowserDetect.isIOS||createjs.BrowserDetect.isAndroid||createjs.BrowserDetect.isBlackberry;return"file:"!=location.protocol||a||this._isFileXHRSupported()?(b._generateCapabilities(),null==b.context?!1:!0):!1},b.playEmptySound=function(){if(null!=b.context){var a=b.context.createBufferSource();a.buffer=b._scratchBuffer,a.connect(b.context.destination),a.start(0,0,0)}},b._isFileXHRSupported=function(){var a=!0,b=new XMLHttpRequest;try{b.open("GET","WebAudioPluginTest.fail",!1)}catch(c){return a=!1}b.onerror=function(){a=!1},b.onload=function(){a=404==this.status||200==this.status||0==this.status&&""!=this.response};try{b.send()}catch(c){a=!1}return a},b._generateCapabilities=function(){if(null==b._capabilities){var a=document.createElement("audio");if(null==a.canPlayType)return null;if(null==b.context)if(window.AudioContext)b.context=new AudioContext;else{if(!window.webkitAudioContext)return null;b.context=new webkitAudioContext}null==b._scratchBuffer&&(b._scratchBuffer=b.context.createBuffer(1,1,22050)),b._compatibilitySetUp(),"ontouchstart"in window&&"running"!=b.context.state&&(b._unlock(),document.addEventListener("mousedown",b._unlock,!0),document.addEventListener("touchend",b._unlock,!0)),b._capabilities={panning:!0,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}b.context.destination.numberOfChannels<2&&(b._capabilities.panning=!1)}},b._compatibilitySetUp=function(){if(b._panningModel="equalpower",!b.context.createGain){b.context.createGain=b.context.createGainNode;var a=b.context.createBufferSource();a.__proto__.start=a.__proto__.noteGrainOn,a.__proto__.stop=a.__proto__.noteOff,b._panningModel=0}},b._unlock=function(){b._unlocked||(b.playEmptySound(),"running"==b.context.state&&(document.removeEventListener("mousedown",b._unlock,!0),document.removeEventListener("touchend",b._unlock,!0),b._unlocked=!0))},a.toString=function(){return"[WebAudioPlugin]"},a._addPropsToClasses=function(){var a=this._soundInstanceClass;a.context=this.context,a._scratchBuffer=b._scratchBuffer,a.destinationNode=this.gainNode,a._panningModel=this._panningModel,this._loaderClass.context=this.context},a._updateVolume=function(){var a=createjs.Sound._masterMute?0:this._volume;a!=this.gainNode.gain.value&&(this.gainNode.gain.value=a)},createjs.WebAudioPlugin=createjs.promote(WebAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioTagPool(){throw"HTMLAudioTagPool cannot be instantiated"}function a(){this._tags=[]}var b=HTMLAudioTagPool;b._tags={},b._tagPool=new a,b._tagUsed={},b.get=function(a){var c=b._tags[a];return null==c?(c=b._tags[a]=b._tagPool.get(),c.src=a):b._tagUsed[a]?(c=b._tagPool.get(),c.src=a):b._tagUsed[a]=!0,c},b.set=function(a,c){c==b._tags[a]?b._tagUsed[a]=!1:b._tagPool.set(c)},b.remove=function(a){var c=b._tags[a];return null==c?!1:(b._tagPool.set(c),delete b._tags[a],delete b._tagUsed[a],!0)},b.getDuration=function(a){var c=b._tags[a];return null!=c&&c.duration?1e3*c.duration:0},createjs.HTMLAudioTagPool=HTMLAudioTagPool;var c=a.prototype;c.constructor=a,c.get=function(){var a;return a=0==this._tags.length?this._createTag():this._tags.pop(),null==a.parentNode&&document.body.appendChild(a),a},c.set=function(a){var b=createjs.indexOf(this._tags,a);-1==b&&(this._tags.src=null,this._tags.push(a))},c.toString=function(){return"[TagPool]"},c._createTag=function(){var a=document.createElement("audio");return a.autoplay=!1,a.preload="none",a}}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioSoundInstance(a,b,c,d){this.AbstractSoundInstance_constructor(a,b,c,d),this._audioSpriteStopTime=null,this._delayTimeoutId=null,this._endedHandler=createjs.proxy(this._handleSoundComplete,this),this._readyHandler=createjs.proxy(this._handleTagReady,this),this._stalledHandler=createjs.proxy(this._playFailed,this),this._audioSpriteEndHandler=createjs.proxy(this._handleAudioSpriteLoop,this),this._loopHandler=createjs.proxy(this._handleSoundComplete,this),c?this._audioSpriteStopTime=.001*(b+c):this._duration=createjs.HTMLAudioTagPool.getDuration(this.src)}var a=createjs.extend(HTMLAudioSoundInstance,createjs.AbstractSoundInstance);a.setMasterVolume=function(){this._updateVolume()},a.setMasterMute=function(){this._updateVolume()},a.toString=function(){return"[HTMLAudioSoundInstance]"},a._removeLooping=function(){null!=this._playbackResource&&(this._playbackResource.loop=!1,this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._addLooping=function(){null==this._playbackResource||this._audioSpriteStopTime||(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.loop=!0)},a._handleCleanUp=function(){var a=this._playbackResource;if(null!=a){a.pause(),a.loop=!1,a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1);try{a.currentTime=this._startTime}catch(b){}createjs.HTMLAudioTagPool.set(this.src,a),this._playbackResource=null}},a._beginPlaying=function(a){return this._playbackResource=createjs.HTMLAudioTagPool.get(this.src),this.AbstractSoundInstance__beginPlaying(a)},a._handleSoundReady=function(){if(4!==this._playbackResource.readyState){var a=this._playbackResource;return a.addEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),a.addEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),a.preload="auto",void a.load()}this._updateVolume(),this._playbackResource.currentTime=.001*(this._startTime+this._position),this._audioSpriteStopTime?this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1):(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),0!=this._loop&&(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.loop=!0)),this._playbackResource.play()},a._handleTagReady=function(){this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),this._handleSoundReady()},a._pause=function(){this._playbackResource.pause()},a._resume=function(){this._playbackResource.play()},a._updateVolume=function(){if(null!=this._playbackResource){var a=this._muted||createjs.Sound._masterMute?0:this._volume*createjs.Sound._masterVolume;a!=this._playbackResource.volume&&(this._playbackResource.volume=a)}},a._calculateCurrentPosition=function(){return 1e3*this._playbackResource.currentTime-this._startTime},a._updatePosition=function(){this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._handleSetPositionSeek,!1);try{this._playbackResource.currentTime=.001*(this._position+this._startTime)}catch(a){this._handleSetPositionSeek(null)}},a._handleSetPositionSeek=function(){null!=this._playbackResource&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._handleSetPositionSeek,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._handleAudioSpriteLoop=function(){this._playbackResource.currentTime<=this._audioSpriteStopTime||(this._playbackResource.pause(),0==this._loop?this._handleSoundComplete(null):(this._position=0,this._loop--,this._playbackResource.currentTime=.001*this._startTime,this._paused||this._playbackResource.play(),this._sendEvent("loop")))},a._handleLoop=function(){0==this._loop&&(this._playbackResource.loop=!1,this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._updateStartTime=function(){this._audioSpriteStopTime=.001*(this._startTime+this._duration),this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1))},a._updateDuration=function(){this._audioSpriteStopTime=.001*(this._startTime+this._duration),this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1))},a._setDurationFromSource=function(){this._duration=createjs.HTMLAudioTagPool.getDuration(this.src),this._playbackResource=null},createjs.HTMLAudioSoundInstance=createjs.promote(HTMLAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioPlugin(){this.AbstractPlugin_constructor(),this.defaultNumChannels=2,this._capabilities=b._capabilities,this._loaderClass=createjs.SoundLoader,this._soundInstanceClass=createjs.HTMLAudioSoundInstance}var a=createjs.extend(HTMLAudioPlugin,createjs.AbstractPlugin),b=HTMLAudioPlugin;b.MAX_INSTANCES=30,b._AUDIO_READY="canplaythrough",b._AUDIO_ENDED="ended",b._AUDIO_SEEKED="seeked",b._AUDIO_STALLED="stalled",b._TIME_UPDATE="timeupdate",b._capabilities=null,b.isSupported=function(){return b._generateCapabilities(),null!=b._capabilities},b._generateCapabilities=function(){if(null==b._capabilities){var a=document.createElement("audio");if(null==a.canPlayType)return null;b._capabilities={panning:!1,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}}},a.register=function(a){var b=createjs.HTMLAudioTagPool.get(a.src),c=this.AbstractPlugin_register(a);return c.setTag(b),c},a.removeSound=function(a){this.AbstractPlugin_removeSound(a),createjs.HTMLAudioTagPool.remove(a)},a.create=function(a,b,c){var d=this.AbstractPlugin_create(a,b,c);return d.setPlaybackResource(null),d},a.toString=function(){return"[HTMLAudioPlugin]"},a.setVolume=a.getVolume=a.setMute=null,createjs.HTMLAudioPlugin=createjs.promote(HTMLAudioPlugin,"AbstractPlugin")}(); H5P.SoundJS = this.createjs.Sound; this.createjs = old || this.createjs; ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.StopWatch = (function () { /** * @class {H5P.SingleChoiceSet.StopWatch} * @constructor */ function StopWatch() { /** * @property {number} duration in ms */ this.duration = 0; } /** * Starts the stop watch * * @public * @return {H5P.SingleChoiceSet.StopWatch} */ StopWatch.prototype.start = function () { /** * @property {number} */ this.startTime = Date.now(); return this; }; /** * Stops the stopwatch, and returns the duration in seconds. * * @public * @return {number} */ StopWatch.prototype.stop = function () { this.duration = this.duration + Date.now() - this.startTime; return this.passedTime(); }; /** * Sets the duration to 0 * * @public */ StopWatch.prototype.reset = function () { this.duration = 0; }; /** * Returns the passed time in seconds * * @public * @return {number} */ StopWatch.prototype.passedTime = function () { return Math.round(this.duration / 10) / 100; }; return StopWatch; })(); ; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.SoundEffects = (function () { let isDefined = false; const SoundEffects = { types: [ 'positive-short', 'negative-short' ] }; const players = {}; /** * Setup defined sounds * * @param {string} libraryPath * @return {boolean} True if setup was successfull, otherwise false */ SoundEffects.setup = function (libraryPath) { if (isDefined) { return false; } isDefined = true; SoundEffects.types.forEach(async function (type) { const player = new Audio(); const extension = player.canPlayType('audio/ogg') ? 'ogg' : 'mp3'; const response = await fetch(libraryPath + 'sounds/' + type + '.' + extension); const data = await response.blob(); player.src = URL.createObjectURL(data); players[type] = player; }); return true; }; /** * Play a sound * * @param {string} type Name of the sound as defined in [SoundEffects.types]{@link H5P.SoundEffects.SoundEffects#types} * @param {number} delay Delay in milliseconds */ SoundEffects.play = function (type, delay) { if (!players[type]) { return; } setTimeout(function () { players[type].play(); }, delay || 0); }; return SoundEffects; })(); ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.XApiEventBuilder = (function ($, EventDispatcher) { /** * @typedef {object} LocalizedString * @property {string} en-US */ /** * @class {H5P.SingleChoiceSet.XApiEventDefinitionBuilder} * @constructor */ function XApiEventDefinitionBuilder() { EventDispatcher.call(this); /** * @property {object} attributes * @property {string} attributes.name * @property {string} attributes.description * @property {string} attributes.interactionType * @property {string} attributes.correctResponsesPattern * @property {object} attributes.optional */ this.attributes = {}; } XApiEventDefinitionBuilder.prototype = Object.create(EventDispatcher.prototype); XApiEventDefinitionBuilder.prototype.constructor = XApiEventDefinitionBuilder; /** * Sets name * @param {string} name * @return {XApiEventDefinitionBuilder} */ XApiEventDefinitionBuilder.prototype.name = function (name) { this.attributes.name = name; return this; }; /** * Question text and any additional information to generate the report. * @param {string} description * @return {XApiEventDefinitionBuilder} */ XApiEventDefinitionBuilder.prototype.description = function (description) { this.attributes.description = description; return this; }; /** * Type of the interaction. * @param {string} interactionType * @see {@link https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#interaction-types|xAPI Spec} * @return {XApiEventDefinitionBuilder} */ XApiEventDefinitionBuilder.prototype.interactionType = function (interactionType) { this.attributes.interactionType = interactionType; return this; }; /** * A pattern for determining the correct answers of the interaction * @param {string[]} correctResponsesPattern * @see {@link https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#response-patterns|xAPI Spec} * @return {XApiEventDefinitionBuilder} */ XApiEventDefinitionBuilder.prototype.correctResponsesPattern = function (correctResponsesPattern) { this.attributes.correctResponsesPattern = correctResponsesPattern; return this; }; /** * Sets optional attributes * @param {object} optional Can have one of the following configuration objects: choices, scale, source, target, steps * @return {XApiEventDefinitionBuilder} */ XApiEventDefinitionBuilder.prototype.optional = function (optional) { this.attributes.optional = optional; return this; }; /** * @return {object} */ XApiEventDefinitionBuilder.prototype.build = function () { var definition = {}; // sets attributes setAttribute(definition, 'name', localizeToEnUS(this.attributes.name)); setAttribute(definition, 'description', localizeToEnUS(this.attributes.description)); setAttribute(definition, 'interactionType', this.attributes.interactionType); setAttribute(definition, 'correctResponsesPattern', this.attributes.correctResponsesPattern); setAttribute(definition, 'type', 'http://adlnet.gov/expapi/activities/cmi.interaction'); // adds the optional object to the definition if (this.attributes.optional) { $.extend(definition, this.attributes.optional); } return definition; }; // ----------------------------------------------------- /** * * @constructor */ function XApiEventResultBuilder() { EventDispatcher.call(this); /** * @property {object} attributes * @property {string} attributes.completion * @property {boolean} attributes.success * @property {boolean} attributes.response * @property {number} attributes.rawScore * @property {number} attributes.maxScore */ this.attributes = {}; } XApiEventResultBuilder.prototype = Object.create(EventDispatcher.prototype); XApiEventResultBuilder.prototype.constructor = XApiEventResultBuilder; /** * @param {boolean} completion * @return {XApiEventResultBuilder} */ XApiEventResultBuilder.prototype.completion = function (completion) { this.attributes.completion = completion; return this; }; /** * @param {boolean} success * @return {XApiEventResultBuilder} */ XApiEventResultBuilder.prototype.success = function (success) { this.attributes.success = success; return this; }; /** * @param {number} duration The duraction in seconds * @return {XApiEventResultBuilder} */ XApiEventResultBuilder.prototype.duration = function (duration) { this.attributes.duration = duration; return this; }; /** * Sets response * @param {string|string[]} response * @return {XApiEventResultBuilder} */ XApiEventResultBuilder.prototype.response = function (response) { this.attributes.response = (typeof response === 'string') ? response : response.join('[,]'); return this; }; /** * Sets the score, and max score * @param {number} score * @param {number} maxScore * @return {XApiEventResultBuilder} */ XApiEventResultBuilder.prototype.score = function (score, maxScore) { this.attributes.rawScore = score; this.attributes.maxScore = maxScore; return this; }; /** * Builds the result object * @return {object} */ XApiEventResultBuilder.prototype.build = function () { var result = {}; setAttribute(result, 'response', this.attributes.response); setAttribute(result, 'completion', this.attributes.completion); setAttribute(result, 'success', this.attributes.success); if (isDefined(this.attributes.duration)) { setAttribute(result, 'duration','PT' + this.attributes.duration + 'S'); } // sets score if (isDefined(this.attributes.rawScore)) { result.score = {}; setAttribute(result.score, 'raw', this.attributes.rawScore); if (isDefined(this.attributes.maxScore) && this.attributes.maxScore > 0) { setAttribute(result.score, 'min', 0); setAttribute(result.score, 'max', this.attributes.maxScore); setAttribute(result.score, 'min', 0); setAttribute(result.score, 'scaled', Math.round(this.attributes.rawScore / this.attributes.maxScore * 10000) / 10000); } } return result; }; // ----------------------------------------------------- /** * @class {H5P.SingleChoiceSet.XApiEventBuilder} */ function XApiEventBuilder() { EventDispatcher.call(this); /** * @property {object} attributes * @property {string} attributes.contentId * @property {string} attributes.subContentId */ this.attributes = {}; } XApiEventBuilder.prototype = Object.create(EventDispatcher.prototype); XApiEventBuilder.prototype.constructor = XApiEventBuilder; /** * @param {object} verb * * @public * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.verb = function (verb) { this.attributes.verb = verb; return this; }; /** * @param {string} name * @param {string} mbox * @param {string} objectType * * @public * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.actor = function (name, mbox, objectType) { this.attributes.actor = { name: name, mbox: mbox, objectType: objectType }; return this; }; /** * Sets contentId * @param {string} contentId * @param {string} [subContentId] * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.contentId = function (contentId, subContentId) { this.attributes.contentId = contentId; this.attributes.subContentId = subContentId; return this; }; /** * Sets parent in context * * @param {string} parentContentId * @param {string} [parentSubContentId] * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.context = function (parentContentId, parentSubContentId) { this.attributes.parentContentId = parentContentId; this.attributes.parentSubContentId = parentSubContentId; return this; }; /** * @param {object} result * * @public * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.result = function (result) { this.attributes.result = result; return this; }; /** * @param {object} objectDefinition * * @public * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.prototype.objectDefinition = function (objectDefinition) { this.attributes.objectDefinition = objectDefinition; return this; }; /** * Returns the buildt event * @public * @return {H5P.XAPIEvent} */ XApiEventBuilder.prototype.build = function () { var event = new H5P.XAPIEvent(); event.setActor(); event.setVerb(this.attributes.verb); // sets context if (this.attributes.parentContentId || this.attributes.parentSubContentId) { event.data.statement.context = { 'contextActivities': { 'parent': [ { 'id': getContentXAPIId(this.attributes.parentContentId, this.attributes.parentSubContentId), 'objectType': "Activity" } ] } }; } event.data.statement.object = { 'id': getContentXAPIId(this.attributes.contentId, this.attributes.subContentId), 'objectType': 'Activity' }; setAttribute(event.data, 'actor', this.attributes.actor); setAttribute(event.data.statement, 'result', this.attributes.result); setAttribute(event.data.statement.object, 'definition', this.attributes.objectDefinition); // sets h5p specific attributes if (event.data.statement.object.definition && (this.attributes.contentId || this.attributes.subContentId)) { var extensions = event.data.statement.object.definition.extensions = {}; setAttribute(extensions, 'http://h5p.org/x-api/h5p-local-content-id', this.attributes.contentId); setAttribute(extensions, 'http://h5p.org/x-api/h5p-subContentId', this.attributes.subContentId); } return event; }; /** * Creates a Localized String object for en-US * * @param str * @return {LocalizedString} */ var localizeToEnUS = function (str) { if (str != undefined) { return { 'en-US': cleanString(str) }; } }; /** * Generates an id for the content * @param {string} contentId * @param {string} [subContentId] * * @see {@link https://github.com/h5p/h5p-php-library/blob/master/js/h5p-x-api-event.js#L240-L249} * @return {string} */ var getContentXAPIId = function (contentId, subContentId) { const cid = 'cid-' + contentId; if (contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents[cid]) { var id = H5PIntegration.contents[cid].url; if (subContentId) { id += '?subContentId=' + subContentId; } return id; } }; /** * Removes html elements from string * * @param {string} str * @return {string} */ var cleanString = function (str) { return $('<div>' + str + '</div>').text().trim(); }; var isDefined = function (val) { return typeof val !== 'undefined'; }; function setAttribute(obj, key, value, required) { if (isDefined(value)) { obj[key] = value; } else if (required) { console.error("xApiEventBuilder: No value for [" + key + "] in", obj); } } /** * Creates a new XApiEventBuilder * * @public * @static * @return {H5P.SingleChoiceSet.XApiEventBuilder} */ XApiEventBuilder.create = function () { return new XApiEventBuilder(); }; /** * Creates a new XApiEventDefinitionBuilder * * @public * @static * @return {XApiEventDefinitionBuilder} */ XApiEventBuilder.createDefinition = function () { return new XApiEventDefinitionBuilder(); }; /** * Creates a new XApiEventDefinitionBuilder * * @public * @static * @return {XApiEventResultBuilder} */ XApiEventBuilder.createResult = function () { return new XApiEventResultBuilder(); }; /** * Returns choice to be used with 'cmi.interaction' for Activity of type 'choice' * * @param {string} id * @param {string} description * * @public * @static * @see {@link https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#choice|xAPI-Spec} * @return {object} */ XApiEventBuilder.createChoice = function (id, description) { return { id: id, description: localizeToEnUS(description) }; }; /** * Takes an array of correct ids, and joins them to a 'correct response pattern' * * @param {string[]} ids * * @public * @static * @see {@link https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#choice|xAPI-Spec} * @return {string} */ XApiEventBuilder.createCorrectResponsePattern = function (ids) { return ids.join('[,]'); }; /** * Interaction types * * @readonly * @enum {String} */ XApiEventBuilder.interactionTypes = { CHOICE: 'choice', COMPOUND: 'compound', FILL_IN: 'fill-in', MATCHING: 'matching', TRUE_FALSE: 'true-false' }; /** * Verbs * * @readonly * @enum {String} */ XApiEventBuilder.verbs = { ANSWERED: 'answered' }; return XApiEventBuilder; })(H5P.jQuery, H5P.EventDispatcher); ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; /** * SingleChoiceResultSlide - Represents the result slide */ H5P.SingleChoiceSet.ResultSlide = (function ($, EventDispatcher) { /** * @constructor * @param {number} maxscore Max score */ function ResultSlide(maxscore) { EventDispatcher.call(this); this.$feedbackContainer = $('<div>', { 'class': 'h5p-sc-feedback-container', 'tabindex': '-1' }); this.$buttonContainer = $('<div/>', { 'class': 'h5p-sc-button-container' }); var $resultContainer = $('<div/>', { 'class': 'h5p-sc-result-container' }).append(this.$feedbackContainer) .append(this.$buttonContainer); this.$resultSlide = $('<div>', { 'class': 'h5p-sc-slide h5p-sc-set-results', 'css': {left: (maxscore * 100) + '%'} }).append($resultContainer); } // inherits from EventDispatchers prototype ResultSlide.prototype = Object.create(EventDispatcher.prototype); // set the constructor ResultSlide.prototype.constructor = ResultSlide; /** * Focus feedback container. */ ResultSlide.prototype.focusScore = function () { this.$feedbackContainer.focus(); }; /** * Append the resultslide to a container * * @param {jQuery} $container The container * @return {jQuery} This dom element */ ResultSlide.prototype.appendTo = function ($container) { this.$resultSlide.appendTo($container); return this.$resultSlide; }; return ResultSlide; })(H5P.jQuery, H5P.EventDispatcher); ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.SolutionView = (function ($, EventDispatcher) { /** * Constructor function. */ function SolutionView(id, choices, l10n) { EventDispatcher.call(this); var self = this; self.id = id; this.choices = choices; self.l10n = l10n; this.$solutionView = $('<div>', { 'class': 'h5p-sc-solution-view' }); // Add header this.$header = $('<div>', { 'class': 'h5p-sc-solution-view-header' }).appendTo(this.$solutionView); this.$title = $('<div>', { 'class': 'h5p-sc-solution-view-title', 'html': l10n.solutionViewTitle, 'tabindex': '-1' }); this.$title = this.addAriaPunctuation(this.$title); this.$header.append(this.$title); // Close solution view button $('<button>', { 'role': 'button', 'aria-label': l10n.closeButtonLabel + '.', 'class': 'h5p-joubelui-button h5p-sc-close-solution-view', 'click': function () { self.hide(); } }).appendTo(this.$header); self.populate(); } /** * Will append the solution view to a container DOM * @param {jQuery} $container The DOM object to append to */ SolutionView.prototype.appendTo = function ($container) { this.$solutionView.appendTo($container); }; /** * Shows the solution view */ SolutionView.prototype.show = function () { var self = this; self.$solutionView.addClass('visible'); self.$title.focus(); $(document).on('keyup.solutionview', function (event) { if (event.keyCode === 27) { // Escape self.hide(); $(document).off('keyup.solutionview'); } }); }; /** * Hides the solution view */ SolutionView.prototype.hide = function () { this.$solutionView.removeClass('visible'); this.trigger('hide', this); }; /** * Populates the solution view */ SolutionView.prototype.populate = function () { var self = this; self.$choices = $('<dl>', { 'class': 'h5p-sc-solution-choices', 'tabindex': -1, }); this.choices.forEach(function (choice, index) { if (choice.question && choice.answers && choice.answers.length !== 0) { var $question = self.addAriaPunctuation($('<dt>', { 'class': 'h5p-sc-solution-question', html: '<span class="h5p-hidden-read">' + self.l10n.solutionListQuestionNumber.replace(':num', index + 1) + '</span>' + choice.question })); self.$choices.append($question); var $answer = self.addAriaPunctuation($('<dd>', { 'class': 'h5p-sc-solution-answer', html: choice.answers[0] })); self.$choices.append($answer); } }); self.$choices.appendTo(this.$solutionView); }; /** * If a jQuery elements text is missing punctuation, add an aria-label to the element * containing the text, and adding an extra "period"-symbol at the end. * * @param {jQuery} $element A jQuery-element * @returns {jQuery} The mutated jQuery-element */ SolutionView.prototype.addAriaPunctuation = function ($element) { var text = $element.text().trim(); if (!this.hasPunctuation(text)) { $element.attr('aria-label', text + '.'); } return $element; }; /** * Checks if a string ends with punctuation * * @private * @param {String} text Input string */ SolutionView.prototype.hasPunctuation = function (text) { return /[,.?!]$/.test(text); }; return SolutionView; })(H5P.jQuery, H5P.EventDispatcher); ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.Alternative = (function ($, EventDispatcher) { /** * @constructor * * @param {object} options Options for the alternative */ function Alternative(options) { EventDispatcher.call(this); var self = this; this.options = options; var triggerAlternativeSelected = function (event) { self.trigger('alternative-selected', { correct: self.options.correct, $element: self.$alternative, answerIndex: self.options.answerIndex }); event.preventDefault(); }; this.$alternative = $('<li>', { 'class': 'h5p-sc-alternative h5p-sc-is-' + (this.options.correct ? 'correct' : 'wrong'), 'role': 'radio', 'tabindex': -1, 'on': { 'keydown': function (event) { switch (event.which) { case 13: // Enter case 32: // Space // Answer question triggerAlternativeSelected(event); break; case 35: // End radio button // Go to previous Option self.trigger('lastOption', event); event.preventDefault(); break; case 36: // Home radio button // Go to previous Option self.trigger('firstOption', event); event.preventDefault(); break; case 37: // Left Arrow case 38: // Up Arrow // Go to previous Option self.trigger('previousOption', event); event.preventDefault(); break; case 39: // Right Arrow case 40: // Down Arrow // Go to next Option self.trigger('nextOption', event); event.preventDefault(); break; } } }, 'focus': function (event) { self.trigger('focus', event); }, 'click': triggerAlternativeSelected }); this.$alternative.append($('<div>', { 'class': 'h5p-sc-progressbar' })); this.$alternative.append($('<div>', { 'class': 'h5p-sc-label', 'html': this.options.text })); this.$alternative.append($('<div>', { 'class': 'h5p-sc-status' })); this.$alternative.append($('<div>', { 'class': 'h5p-sc-a11y', 'aria-hidden': 'true' })); } Alternative.prototype = Object.create(EventDispatcher.prototype); Alternative.prototype.constructor = Alternative; /** * Is this alternative the correct one? * * @return {boolean} Correct or not? */ Alternative.prototype.isCorrect = function () { return this.options.correct; }; /** * Move focus to this option. */ Alternative.prototype.focus = function () { this.$alternative.focus(); }; /** * Makes it possible to tab your way to this option. */ Alternative.prototype.tabbable = function () { this.$alternative.attr('tabindex', 0); }; /** * Make sure it's NOT possible to tab your way to this option. */ Alternative.prototype.notTabbable = function () { this.$alternative.attr('tabindex', -1); }; /** * Append the alternative to a DOM container * * @param {jQuery} $container The Dom element to append to * @return {jQuery} This dom element */ Alternative.prototype.appendTo = function ($container) { $container.append(this.$alternative); return this.$alternative; }; return Alternative; })(H5P.jQuery, H5P.EventDispatcher); ; var H5P = H5P || {}; H5P.SingleChoiceSet = H5P.SingleChoiceSet || {}; H5P.SingleChoiceSet.SingleChoice = (function ($, EventDispatcher, Alternative) { /** * Constructor function. */ function SingleChoice(options, index, id, isAutoConfinue) { EventDispatcher.call(this); // Extend defaults with provided options this.options = $.extend(true, {}, { question: '', answers: [] }, options); this.isAutoConfinue = isAutoConfinue; // Keep provided id. this.index = index; this.id = id; this.answered = false; for (var i = 0; i < this.options.answers.length; i++) { this.options.answers[i] = { text: this.options.answers[i], correct: i === 0, answerIndex: i }; } // Randomize alternatives this.options.answers = H5P.shuffleArray(this.options.answers); } SingleChoice.prototype = Object.create(EventDispatcher.prototype); SingleChoice.prototype.constructor = SingleChoice; /** * appendTo function invoked to append SingleChoice to container * * @param {jQuery} $container * @param {boolean} isCurrent Current slide we are on */ SingleChoice.prototype.appendTo = function ($container, isCurrent) { var self = this; this.$container = $container; // Index of the currently focused option. var focusedOption; this.$choice = $('<div>', { 'class': 'h5p-sc-slide h5p-sc' + (isCurrent ? ' h5p-sc-current-slide' : ''), css: {'left': (self.index * 100) + '%'} }); var questionId = 'single-choice-' + self.id + '-question-' + self.index; this.$choice.append($('<div>', { 'id': questionId, 'class': 'h5p-sc-question', 'html': this.options.question })); var $alternatives = $('<ul>', { 'class': 'h5p-sc-alternatives', 'role': 'radiogroup', 'aria-labelledby': questionId }); /** * List of Alternatives * * @type {Alternative[]} */ this.alternatives = self.options.answers.map(function (opts) { return new Alternative(opts); }); /** * Handles click on an alternative */ var handleAlternativeSelected = function (event) { var $element = event.data.$element; var correct = event.data.correct; var answerIndex = event.data.answerIndex; if ($element.parent().hasClass('h5p-sc-selected')) { return; } self.trigger('alternative-selected', { correct: correct, index: self.index, answerIndex: answerIndex, currentIndex: $element.index() }); H5P.Transition.onTransitionEnd($element.find('.h5p-sc-progressbar'), function () { $element.addClass('h5p-sc-drummed'); self.showResult(correct, answerIndex); }, 700); $element.addClass('h5p-sc-selected').parent().addClass('h5p-sc-selected'); // indicate that this question is anwered this.setAnswered(true); }; /** * Handles focusing one of the options, making the rest non-tabbable. * @private */ var handleFocus = function (answer, index) { // Keep track of currently focused option focusedOption = index; // remove tabbable all alternatives self.alternatives.forEach(function (alternative) { alternative.notTabbable(); }); answer.tabbable(); }; /** * Handles moving the focus from the current option to the previous option. * @private */ var handlePreviousOption = function () { if (focusedOption === 0) { // wrap around to last this.focusOnAlternative(self.alternatives.length - 1); } else { this.focusOnAlternative(focusedOption - 1); } }; /** * Handles moving the focus from the current option to the next option. * @private */ var handleNextOption = function () { if ((focusedOption === this.alternatives.length - 1)) { // wrap around to first this.focusOnAlternative(0); } else { this.focusOnAlternative(focusedOption + 1); } }; /** * Handles moving the focus to the first option * @private */ var handleFirstOption = function () { this.focusOnAlternative(0); }; /** * Handles moving the focus to the last option * @private */ var handleLastOption = function () { this.focusOnAlternative(self.alternatives.length - 1); }; for (var i = 0; i < this.alternatives.length; i++) { var alternative = this.alternatives[i]; if (i === 0) { alternative.tabbable(); } alternative.appendTo($alternatives); alternative.on('focus', handleFocus.bind(this, alternative, i), this); alternative.on('alternative-selected', handleAlternativeSelected, this); alternative.on('previousOption', handlePreviousOption, this); alternative.on('nextOption', handleNextOption, this); alternative.on('firstOption', handleFirstOption, this); alternative.on('lastOption', handleLastOption, this); } this.$choice.append($alternatives); $container.append(this.$choice); return this.$choice; }; /** * Focus on an alternative by index * * @param {Number} index The index of the alternative to focus on */ SingleChoice.prototype.focusOnAlternative = function (index) { if (!this.answered || !this.isAutoConfinue) { this.alternatives[index].focus(); } }; /** * Sets if the question was answered * * @param {Boolean} answered If this question was answered */ SingleChoice.prototype.setAnswered = function (answered) { this.answered = answered; }; /** * Reveals the result for a question * * @param {boolean} correct True uf answer was correct, otherwise false * @param {number} answerIndex Original index of answer */ SingleChoice.prototype.showResult = function (correct, answerIndex) { var self = this; var $correctAlternative = self.$choice.find('.h5p-sc-is-correct'); H5P.Transition.onTransitionEnd($correctAlternative, function () { self.trigger('finished', { correct: correct, index: self.index, answerIndex: answerIndex }); self.setAriaAttributes(); }, 600); // Reveal corrects and wrong self.$choice.find('.h5p-sc-is-wrong').addClass('h5p-sc-reveal-wrong'); $correctAlternative.addClass('h5p-sc-reveal-correct'); }; /** * Reset a11y text for selected options */ SingleChoice.prototype.resetA11yText = function () { var self = this; self.$choice.find('.h5p-sc-a11y').text(''); }; /** * Make a11y text readable for screen reader */ SingleChoice.prototype.setA11yTextReadable = function () { var self = this; self.$choice.find('.h5p-sc-a11y').attr('aria-hidden', false); }; /** * Set aria attributes for choice */ SingleChoice.prototype.setAriaAttributes = function () { var self = this; // A11y mode is enabled if (!self.isAutoConfinue) { self.$choice.find('.h5p-sc-alternative.h5p-sc-selected').attr('aria-checked', true); self.$choice.find('.h5p-sc-alternative').attr('aria-disabled', true); } } /** * Reset aria attributes */ SingleChoice.prototype.resetAriaAttributes = function () { var self = this; // A11y mode is enabled if (!self.isAutoConfinue) { const alternative = self.$choice.find('.h5p-sc-alternative'); alternative.removeAttr('aria-disabled'); alternative.removeAttr('aria-checked'); } }; return SingleChoice; })(H5P.jQuery, H5P.EventDispatcher, H5P.SingleChoiceSet.Alternative); ; var H5P = H5P || {}; H5P.SingleChoiceSet = (function ($, UI, Question, SingleChoice, SolutionView, ResultSlide, SoundEffects, XApiEventBuilder, StopWatch) { /** * @constructor * @extends Question * @param {object} options Options for single choice set * @param {string} contentId H5P instance id * @param {Object} contentData H5P instance data */ function SingleChoiceSet(options, contentId, contentData) { var self = this; // Extend defaults with provided options this.contentId = contentId; this.contentData = contentData; /** * The users input on the questions. Uses the same index as this.options.choices * @type {number[]} */ this.userResponses = []; Question.call(this, 'single-choice-set'); this.options = $.extend(true, {}, { choices: [], overallFeedback: [], behaviour: { autoContinue: true, timeoutCorrect: 2000, timeoutWrong: 3000, soundEffectsEnabled: true, enableRetry: true, enableSolutionsButton: true, passPercentage: 100 } }, options); if (contentData && contentData.previousState !== undefined) { this.currentIndex = contentData.previousState.progress; this.results = contentData.previousState.answers; this.userResponses = contentData.previousState.userResponses !== undefined ? contentData.previousState.userResponses : []; } this.currentIndex = this.currentIndex || 0; this.results = this.results || { corrects: 0, wrongs: 0 }; if (!this.options.behaviour.autoContinue) { this.options.behaviour.timeoutCorrect = 0; this.options.behaviour.timeoutWrong = 0; } /** * @property {StopWatch[]} Stop watches for tracking duration of slides */ this.stopWatches = []; this.startStopWatch(this.currentIndex); this.muted = (this.options.behaviour.soundEffectsEnabled === false); this.l10n = H5P.jQuery.extend({ correctText: 'Correct!', incorrectText: 'Incorrect!', shouldSelect: "Should have been selected", shouldNotSelect: "Should not have been selected", nextButtonLabel: 'Next question', showSolutionButtonLabel: 'Show solution', retryButtonLabel: 'Retry', closeButtonLabel: 'Close', solutionViewTitle: 'Solution', slideOfTotal: 'Slide :num of :total', muteButtonLabel: "Mute feedback sound", scoreBarLabel: 'You got :num out of :total points', solutionListQuestionNumber: 'Question :num', a11yShowSolution: 'Show the solution. The task will be marked with its correct solution.', a11yRetry: 'Retry the task. Reset all responses and start the task over again.', }, options.l10n !== undefined ? options.l10n : {}); this.$container = $('<div>', { 'class': 'h5p-sc-set-wrapper navigatable' + (!this.options.behaviour.autoContinue ? ' next-button-mode' : '') }); this.$slides = []; // An array containing the SingleChoice instances this.choices = []; /** * Keeps track of buttons that will be hidden * @type {Array} */ self.buttonsToBeHidden = []; /** * The solution dialog * @type {SolutionView} */ this.solutionView = new SolutionView(contentId, this.options.choices, this.l10n); this.$choices = $('<div>', { 'class': 'h5p-sc-set h5p-sc-animate' }); // sometimes an empty object is in the choices this.options.choices = this.options.choices.filter(function (choice) { return choice !== undefined && !!choice.answers; }); var numQuestions = this.options.choices.length; // Create progressbar self.progressbar = UI.createProgressbar(numQuestions + 1, { progressText: this.l10n.slideOfTotal }); self.progressbar.setProgress(this.currentIndex); for (var i = 0; i < this.options.choices.length; i++) { var choice = new SingleChoice(this.options.choices[i], i, this.contentId, this.options.behaviour.autoContinue); choice.on('finished', this.handleQuestionFinished, this); choice.on('alternative-selected', this.handleAlternativeSelected, this); choice.appendTo(this.$choices, (i === this.currentIndex)); this.choices.push(choice); this.$slides.push(choice.$choice); } this.resultSlide = new ResultSlide(this.options.choices.length); this.resultSlide.appendTo(this.$choices); this.resultSlide.on('retry', function() { self.resetTask(true); }, this); this.resultSlide.on('view-solution', this.handleViewSolution, this); this.$slides.push(this.resultSlide.$resultSlide); this.on('resize', this.resize, this); // Use the correct starting slide this.recklessJump(this.currentIndex); if (this.options.choices.length === this.currentIndex) { // Make sure results slide is displayed this.resultSlide.$resultSlide.addClass('h5p-sc-current-slide'); this.setScore(this.results.corrects, true); } if (!this.muted) { setTimeout(function () { SoundEffects.setup(self.getLibraryFilePath('')); }, 1); } /** * Override Question's hideButton function * to be able to hide buttons after delay * * @override * @param {string} id */ this.superHideButton = self.hideButton; this.hideButton = (function () { return function (id) { if (!self.scoreTimeout) { return self.superHideButton(id); } self.buttonsToBeHidden.push(id); return this; }; })(); } SingleChoiceSet.prototype = Object.create(Question.prototype); SingleChoiceSet.prototype.constructor = SingleChoiceSet; /** * Set if a element is tabbable or not * * @param {jQuery} $element The element * @param {boolean} tabbable If element should be tabbable * @returns {jQuery} The element */ SingleChoiceSet.prototype.setTabbable = function ($element, tabbable) { if ($element) { $element.attr('tabindex', tabbable ? 0 : -1); } }; /** * Handle alternative selected, i.e play sound if sound effects are enabled * * @method handleAlternativeSelected * @param {Object} event Event that was fired */ SingleChoiceSet.prototype.handleAlternativeSelected = function (event) { var self = this; this.lastAnswerIsCorrect = event.data.correct; self.toggleNextButton(true); // Keep track of num correct/wrong answers this.results[this.lastAnswerIsCorrect ? 'corrects' : 'wrongs']++; self.triggerXAPI('interacted'); // Read and set a11y friendly texts self.readA11yFriendlyText(event.data.index, event.data.currentIndex) if (!this.muted) { // Can't play it after the transition end is received, since this is not // accepted on iPad. Therefore we are playing it here with a delay instead SoundEffects.play(this.lastAnswerIsCorrect ? 'positive-short' : 'negative-short', 700); } }; /** * Handler invoked when question is done * * @param {object} event An object containing a single boolean property: "correct". */ SingleChoiceSet.prototype.handleQuestionFinished = function (event) { var self = this; var index = event.data.index; // saves user response var userResponse = self.userResponses[index] = event.data.answerIndex; // trigger answered event var duration = this.stopStopWatch(index); var xapiEvent = self.createXApiAnsweredEvent(self.options.choices[index], userResponse, duration); self.trigger(xapiEvent); self.continue(index); }; /** * Setup auto continue */ SingleChoiceSet.prototype.continue = function (index) { var self = this; self.choices[index].setA11yTextReadable(); if (!self.options.behaviour.autoContinue) { // Set focus to next button self.$nextButton.focus(); return; } var timeout; var letsMove = function () { // Handle impatient users self.$container.off('click.impatient keydown.impatient'); clearTimeout(timeout); self.next(); }; timeout = setTimeout(function () { letsMove(); }, self.lastAnswerIsCorrect ? self.options.behaviour.timeoutCorrect : self.options.behaviour.timeoutWrong); self.onImpatientUser(letsMove); }; /** * Listen to impatience * @param {Function} action Callback */ SingleChoiceSet.prototype.onImpatientUser = function (action) { this.$container.off('click.impatient keydown.impatient'); this.$container.one('click.impatient', action); this.$container.one('keydown.impatient', function (event) { // If return, space or right arrow if ([13,32,39].indexOf(event.which)) { action(); } }); }; /** * Go to next slide */ SingleChoiceSet.prototype.next = function () { this.move(this.currentIndex + 1); }; /** * Creates an xAPI answered event * * @param {object} question * @param {number} userAnswer * @param {number} duration * * @return {H5P.XAPIEvent} */ SingleChoiceSet.prototype.createXApiAnsweredEvent = function (question, userAnswer, duration) { var self = this; var types = XApiEventBuilder.interactionTypes; // creates the definition object var definition = XApiEventBuilder.createDefinition() .interactionType(types.CHOICE) .description(question.question) .correctResponsesPattern(self.getXApiCorrectResponsePattern()) .optional( self.getXApiChoices(question.answers)) .build(); // create the result object var result = XApiEventBuilder.createResult() .response(userAnswer.toString()) .duration(duration) .score((userAnswer === 0) ? 1 : 0, 1) .completion(true) .success(userAnswer === 0) .build(); return XApiEventBuilder.create() .verb(XApiEventBuilder.verbs.ANSWERED) .objectDefinition(definition) .context(self.contentId, self.subContentId) .contentId(self.contentId, question.subContentId) .result(result) .build(); }; /** * Returns the 'correct response pattern' for xApi * * @return {string[]} */ SingleChoiceSet.prototype.getXApiCorrectResponsePattern = function () { return [XApiEventBuilder.createCorrectResponsePattern([(0).toString()])]; // is always '0' for SCS }; /** * Returns the choices array for xApi statements * * @param {String[]} answers * * @return {{ choices: []}} */ SingleChoiceSet.prototype.getXApiChoices = function (answers) { var choices = answers.map(function (answer, index) { return XApiEventBuilder.createChoice(index.toString(), answer); }); return { choices: choices }; }; /** * Handles buttons that are queued for hiding */ SingleChoiceSet.prototype.handleQueuedButtonChanges = function () { var self = this; if (self.buttonsToBeHidden.length) { self.buttonsToBeHidden.forEach(function (id) { self.superHideButton(id); }); } self.buttonsToBeHidden = []; }; /** * Set score and feedback * * @params {Number} score Number of correct answers */ SingleChoiceSet.prototype.setScore = function (score, noXAPI) { var self = this; if (!self.choices.length) { return; } var feedbackText = determineOverallFeedback(self.options.overallFeedback , score / self.options.choices.length) .replace(':numcorrect', score) .replace(':maxscore', self.options.choices.length.toString()); self.setFeedback(feedbackText, score, self.options.choices.length, self.l10n.scoreBarLabel); if (score === self.options.choices.length) { self.hideButton('try-again'); self.hideButton('show-solution'); } else { self.showButton('try-again'); self.showButton('show-solution'); } self.handleQueuedButtonChanges(); self.scoreTimeout = undefined; if (!noXAPI) { self.triggerXAPIScored(score, self.options.choices.length, 'completed', true, (100 * score / self.options.choices.length) >= self.options.behaviour.passPercentage); } self.trigger('resize'); }; /** * Handler invoked when view solution is selected */ SingleChoiceSet.prototype.handleViewSolution = function () { var self = this; var $tryAgainButton = $('.h5p-question-try-again', self.$container); var $showSolutionButton = $('.h5p-question-show-solution', self.$container); var buttons = [self.$muteButton, $tryAgainButton, $showSolutionButton]; // remove tabbable for buttons in result view buttons.forEach(function (button) { self.setTabbable(button, false); }); self.solutionView.on('hide', function () { // re-add tabbable for buttons in result view buttons.forEach(function (button) { self.setTabbable(button, true); }); self.toggleAriaVisibility(true); // Focus on first button when closing solution view self.focusButton(); }); self.solutionView.show(); self.toggleAriaVisibility(false); }; /** * Toggle elements visibility to Assistive Technologies * * @param {boolean} enable Make elements visible */ SingleChoiceSet.prototype.toggleAriaVisibility = function (enable) { var self = this; var ariaHidden = enable ? '' : 'true'; if (self.$muteButton) { self.$muteButton.attr('aria-hidden', ariaHidden); } self.progressbar.$progressbar.attr('aria-hidden', ariaHidden); self.$choices.attr('aria-hidden', ariaHidden); }; /** * Register DOM elements before they are attached. * Called from H5P.Question. */ SingleChoiceSet.prototype.registerDomElements = function () { // Register task content area. this.setContent(this.createQuestion()); // Register buttons with question. this.addButtons(); // Insert feedback and buttons section on the result slide this.insertSectionAtElement('feedback', this.resultSlide.$feedbackContainer); this.insertSectionAtElement('scorebar', this.resultSlide.$feedbackContainer); this.insertSectionAtElement('buttons', this.resultSlide.$buttonContainer); // Question is finished if (this.options.choices.length === this.currentIndex) { this.trigger('question-finished'); } this.trigger('resize'); }; /** * Add Buttons to question. */ SingleChoiceSet.prototype.addButtons = function () { var self = this; if (this.options.behaviour.enableRetry) { this.addButton('try-again', this.l10n.retryButtonLabel, function () { self.resetTask(true); }, self.results.corrects !== self.options.choices.length, { 'aria-label': this.l10n.a11yRetry, }); } if (this.options.behaviour.enableSolutionsButton) { this.addButton('show-solution', this.l10n.showSolutionButtonLabel, function () { self.showSolutions(); }, self.results.corrects !== self.options.choices.length, { 'aria-label': this.l10n.a11yShowSolution, }); } }; /** * Create main content */ SingleChoiceSet.prototype.createQuestion = function () { var self = this; self.progressbar.appendTo(self.$container); self.$container.append(self.$choices); function toggleMute(event) { var $button = $(event.target); event.preventDefault(); self.muted = !self.muted; $button.attr('aria-pressed', self.muted); } // Keep this out of H5P.Question, since we are moving the button & feedback // region to the last slide if (!this.options.behaviour.autoContinue) { var handleNextClick = function () { if (self.$nextButton.attr('aria-disabled') !== 'true') { self.next(); } }; self.$nextButton = UI.createButton({ 'class': 'h5p-ssc-next-button', 'aria-label': self.l10n.nextButtonLabel, click: handleNextClick, keydown: function (event) { switch (event.which) { case 13: // Enter case 32: // Space handleNextClick(); event.preventDefault(); } }, appendTo: self.$container }); self.toggleNextButton(false); } if (self.options.behaviour.soundEffectsEnabled) { self.$muteButton = $('<div>', { 'class': 'h5p-sc-sound-control', 'tabindex': 0, 'role': 'button', 'aria-label': self.l10n.muteButtonLabel, 'aria-pressed': false, 'on': { 'keydown': function (event) { switch (event.which) { case 13: // Enter case 32: // Space toggleMute(event); break; } } }, 'click': toggleMute, prependTo: self.$container }); } // Append solution view - hidden by default: self.solutionView.appendTo(self.$container); self.resize(); // Hide all other slides than the current one: self.$container.addClass('initialized'); return self.$container; }; /** * Resize if something outside resizes */ SingleChoiceSet.prototype.resize = function () { var self = this; var maxHeight = 0; self.choices.forEach(function (choice) { var choiceHeight = choice.$choice.outerHeight(); maxHeight = choiceHeight > maxHeight ? choiceHeight : maxHeight; }); // Set minimum height for choices self.$choices.css({minHeight: maxHeight + 'px'}); }; /** * Disable/enable the next button * @param {boolean} enable */ SingleChoiceSet.prototype.toggleNextButton = function (enable) { if (this.$nextButton) { this.$nextButton.attr('aria-disabled', !enable); } }; /** * Will jump to the given slide without any though to animations, * current slide etc. * * @public */ SingleChoiceSet.prototype.recklessJump = function (index) { var tX = 'translateX(' + (-index * 100) + '%)'; this.$choices.css({ '-webkit-transform': tX, '-moz-transform': tX, '-ms-transform': tX, 'transform': tX }); this.progressbar.setProgress(index + 1); }; /** * Move to slide n * @param {number} index The slide number to move to * @param {boolean} moveFocus True to set focus on first alternative */ SingleChoiceSet.prototype.move = function (index, moveFocus = true) { var self = this; if (index === this.currentIndex || index > self.$slides.length-1) { return; } var $previousSlide = self.$slides[self.currentIndex]; var $currentChoice = self.choices[index]; var $currentSlide = self.$slides[index]; var isResultSlide = (index >= self.choices.length); self.toggleNextButton(false); H5P.Transition.onTransitionEnd(self.$choices, function () { $previousSlide.removeClass('h5p-sc-current-slide'); // on slides with answers focus on first alternative // if content is root and not on result slide - always move focus if (!isResultSlide && (moveFocus || self.isRoot())) { $currentChoice.focusOnAlternative(0); } // on last slide, focus on try again button else { self.resultSlide.focusScore(); } }, 600); // if should show result slide if (isResultSlide) { self.setScore(self.results.corrects); } self.$container.toggleClass('navigatable', !isResultSlide); // start timing of new slide this.startStopWatch(index); // move to slide $currentSlide.addClass('h5p-sc-current-slide'); self.recklessJump(index); self.currentIndex = index; }; /** * Starts a stopwatch for indexed slide * * @param {number} index */ SingleChoiceSet.prototype.startStopWatch = function (index) { this.stopWatches[index] = this.stopWatches[index] || new StopWatch(); this.stopWatches[index].start(); }; /** * Stops a stopwatch for indexed slide * * @param {number} index */ SingleChoiceSet.prototype.stopStopWatch = function (index) { if (this.stopWatches[index]) { this.stopWatches[index].stop(); } }; /** * Returns the passed time in seconds of a stopwatch on an indexed slide, * or 0 if not existing * * @param {number} index * @return {number} */ SingleChoiceSet.prototype.timePassedInStopWatch = function (index) { if (this.stopWatches[index] !== undefined) { return this.stopWatches[index].passedTime(); } else { // if not created, return no passed time, return 0; } }; /** * Returns the time the user has spent on all questions so far * * @return {number} */ SingleChoiceSet.prototype.getTotalPassedTime = function () { return this.stopWatches .filter(function (watch) { return watch != undefined; }) .reduce(function (sum, watch) { return sum + watch.passedTime(); }, 0); }; /** * The following functions implements the CP and IV - Contracts v 1.0 documented here: * http://h5p.org/node/1009 */ SingleChoiceSet.prototype.getScore = function () { return this.results.corrects; }; SingleChoiceSet.prototype.getMaxScore = function () { return this.options.choices.length; }; SingleChoiceSet.prototype.getAnswerGiven = function () { return (this.results.corrects + this.results.wrongs) > 0; }; SingleChoiceSet.prototype.getTitle = function () { return H5P.createTitle((this.contentData && this.contentData.metadata && this.contentData.metadata.title) ? this.contentData.metadata.title : 'Single Choice Set'); }; /** * Retrieves the xAPI data necessary for generating result reports. * * @return {object} */ SingleChoiceSet.prototype.getXAPIData = function () { var self = this; // create array with userAnswer var children = self.options.choices.map(function (question, index) { var userResponse = self.userResponses[index] >= 0 ? self.userResponses[index] : ''; var duration = self.timePassedInStopWatch(index); var event = self.createXApiAnsweredEvent(question, userResponse, duration); return { statement: event.data.statement }; }); var result = XApiEventBuilder.createResult() .score(self.getScore(), self.getMaxScore()) .duration(self.getTotalPassedTime()) .build(); // creates the definition object var definition = XApiEventBuilder.createDefinition() .interactionType(XApiEventBuilder.interactionTypes.COMPOUND) .build(); var xAPIEvent = XApiEventBuilder.create() .verb(XApiEventBuilder.verbs.ANSWERED) .contentId(self.contentId, self.subContentId) .context(self.getParentAttribute('contentId'), self.getParentAttribute('subContentId')) .objectDefinition(definition) .result(result) .build(); return { statement: xAPIEvent.data.statement, children: children }; }; /** * Returns an attribute from this.parent if it exists * * @param {string} attributeName * @return {*|undefined} */ SingleChoiceSet.prototype.getParentAttribute = function (attributeName) { var self = this; if (self.parent !== undefined) { return self.parent[attributeName]; } }; SingleChoiceSet.prototype.showSolutions = function () { this.handleViewSolution(); }; /** * Reset all answers. This is equal to refreshing the quiz * @param {boolean} moveFocus True to move the focus * This prevents loss of focus if reset from within content */ SingleChoiceSet.prototype.resetTask = function (moveFocus = false) { var self = this; // Close solution view if visible: this.solutionView.hide(); // Reset the user's answers var classes = ['h5p-sc-reveal-wrong', 'h5p-sc-reveal-correct', 'h5p-sc-selected', 'h5p-sc-drummed', 'h5p-sc-correct-answer']; for (var i = 0; i < classes.length; i++) { this.$choices.find('.' + classes[i]).removeClass(classes[i]); } this.results = { corrects: 0, wrongs: 0 }; this.choices.forEach(function (choice) { choice.setAnswered(false); choice.resetA11yText(); choice.resetAriaAttributes(); }); this.stopWatches.forEach(function (stopWatch) { if (stopWatch) { stopWatch.reset(); } }); this.move(0, moveFocus); // Reset userResponses as well this.userResponses = []; // Wait for transition, then remove feedback. H5P.Transition.onTransitionEnd(this.$choices, function () { self.removeFeedback(); }, 600); }; /** * Clever comment. * * @public * @returns {object} */ SingleChoiceSet.prototype.getCurrentState = function () { return this.userResponses.length > 0 ? { progress: this.currentIndex, answers: this.results, userResponses: this.userResponses } : undefined; }; /** * Generate A11y friendly text * * @param {number} index * @param {number} currentIndex */ SingleChoiceSet.prototype.readA11yFriendlyText = function (index, currentIndex) { var self = this; var correctAnswer = self.$choices.find('.h5p-sc-is-correct')[index].textContent.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim(); let selectedOptionText = this.lastAnswerIsCorrect ? self.l10n.correctText : self.l10n.incorrectText; // Announce by ARIA label if (!self.options.behaviour.autoContinue) { // Set text for a11y selectedOptionText = this.lastAnswerIsCorrect ? self.l10n.correctText + self.l10n.shouldSelect : self.l10n.incorrectText + self.l10n.shouldNotSelect; self.$choices.find('.h5p-sc-current-slide .h5p-sc-is-correct .h5p-sc-a11y').text(self.l10n.shouldSelect); self.$choices.find('.h5p-sc-current-slide .h5p-sc-is-wrong .h5p-sc-a11y').text(self.l10n.shouldNotSelect); self.$choices.find('.h5p-sc-current-slide .h5p-sc-alternative').eq(currentIndex).find('.h5p-sc-a11y').text(selectedOptionText); // Utilize same variable for the read text selectedOptionText = this.lastAnswerIsCorrect ? self.l10n.correctText : self.l10n.incorrectText + correctAnswer + self.l10n.shouldSelect; } self.read(selectedOptionText); }; /** * Determine the overall feedback to display for the question. * Returns empty string if no matching range is found. * * @param {Object[]} feedbacks * @param {number} scoreRatio * @return {string} */ var determineOverallFeedback = function (feedbacks, scoreRatio) { scoreRatio = Math.floor(scoreRatio * 100); for (var i = 0; i < feedbacks.length; i++) { var feedback = feedbacks[i]; var hasFeedback = (feedback.feedback !== undefined && feedback.feedback.trim().length !== 0); if (feedback.from <= scoreRatio && feedback.to >= scoreRatio && hasFeedback) { return feedback.feedback; } } return ''; }; return SingleChoiceSet; })(H5P.jQuery, H5P.JoubelUI, H5P.Question, H5P.SingleChoiceSet.SingleChoice, H5P.SingleChoiceSet.SolutionView, H5P.SingleChoiceSet.ResultSlide, H5P.SingleChoiceSet.SoundEffects, H5P.SingleChoiceSet.XApiEventBuilder, H5P.SingleChoiceSet.StopWatch); ; var H5P = H5P || {}; /** * H5P-Text Utilities * * Some functions that can be useful when dealing with texts in H5P. * * @param {H5P.jQuery} $ */ H5P.TextUtilities = function () { 'use strict'; /** * Create Text Utilities. * * Might be needed later. * * @constructor */ function TextUtilities () { } // Inheritance TextUtilities.prototype = Object.create(H5P.EventDispatcher.prototype); TextUtilities.prototype.constructor = TextUtilities; /** @constant {object} */ TextUtilities.WORD_DELIMITER = /[\s.?!,\';\"]/g; /** * Check if a candidate string is considered isolated (in a larger string) by * checking the symbol before and after the candidate. * * @param {string} candidate - String to be looked for. * @param {string} text - (Larger) string that should contain candidate. * @param {object} params - Parameters. * @param {object} params.delimiter - Regular expression containing symbols used to isolate the candidate. * @return {boolean} True if string is isolated. */ TextUtilities.isIsolated = function (candidate, text, params) { // Sanitization if (!candidate || !text) { return; } var delimiter = (!!params && !!params.delimiter) ? params.delimiter : TextUtilities.WORD_DELIMITER; var pos = (!!params && !!params.index && typeof params.index === 'number') ? params.index : text.indexOf(candidate); if (pos < 0 || pos > text.length-1) { return false; } var pred = (pos === 0 ? '' : text[pos - 1].replace(delimiter, '')); var succ = (pos + candidate.length === text.length ? '' : text[pos + candidate.length].replace(delimiter, '')); if (pred !== '' || succ !== '') { return false; } return true; }; /** * Check whether two strings are considered to be similar. * The similarity is temporarily computed by word length and number of number of operations * required to change one word into the other (Damerau-Levenshtein). It's subject to * change, cmp. https://github.com/otacke/udacity-machine-learning-engineer/blob/master/submissions/capstone_proposals/h5p_fuzzy_blanks.md * * @param {String} string1 - String #1. * @param {String} string2 - String #2. * @param {object} params - Parameters. * @return {boolean} True, if strings are considered to be similar. */ TextUtilities.areSimilar = function (string1, string2) { // Sanitization if (!string1 || typeof string1 !== 'string') { return; } if (!string2 || typeof string2 !== 'string') { return; } // Just temporariliy this unflexible. Will be configurable via params. var length = Math.min(string1.length, string2.length); var levenshtein = H5P.TextUtilities.computeLevenshteinDistance(string1, string2, true); if (levenshtein === 0) { return true; } if ((length > 9) && (levenshtein <= 2)) { return true; } if ((length > 3) && (levenshtein <= 1)) { return true; } return false; }; /** * Compute the (Damerau-)Levenshtein distance for two strings. * * The (Damerau-)Levenshtein distance that is returned is equivalent to the * number of operations that are necessary to transform one string into the * other. Consequently, lower numbers indicate higher similarity between the * two strings. * * While the Levenshtein distance counts deletions, insertions and mismatches, * the Damerau-Levenshtein distance also counts swapping two characters as * only one operation (instead of two mismatches), because this seems to * happen quite often. * * See http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance for details * * @public * @param {string} str1 - String no. 1. * @param {string} str2 - String no. 2. * @param {boolean} [countSwapping=false] - If true, swapping chars will count as operation. * @returns {number} Distance. */ TextUtilities.computeLevenshteinDistance = function(str1, str2, countSwapping) { // sanity checks if (typeof str1 !== 'string' || typeof str2 !== 'string') { return undefined; } if (countSwapping && typeof countSwapping !== 'boolean') { countSwapping = false; } // degenerate cases if (str1 === str2) { return 0; } if (str1.length === 0) { return str2.length; } if (str2.length === 0) { return str1.length; } // counter variables var i, j; // indicates characters that don't match var cost; // matrix for storing distances var distance = []; // initialization for (i = 0; i <= str1.length; i++) { distance[i] = [i]; } for (j = 0; j <= str2.length; j++) { distance[0][j] = j; } // computation for (i = 1; i <= str1.length; i++) { for (j = 1; j <= str2.length; j++) { cost = (str1[i-1] === str2[j-1]) ? 0 : 1; distance[i][j] = Math.min( distance[i-1][j] + 1, // deletion distance[i][j-1] + 1, // insertion distance[i-1][j-1] + cost // mismatch ); // in Damerau-Levenshtein distance, transpositions are operations if (countSwapping) { if (i > 1 && j > 1 && str1[i-1] === str2[j-2] && str1[i-2] === str2[j-1]) { distance[i][j] = Math.min(distance[i][j], distance[i-2][j-2] + cost); } } } } return distance[str1.length][str2.length]; }; /** * Compute the Jaro(-Winkler) distance for two strings. * * The Jaro(-Winkler) distance will return a value between 0 and 1 indicating * the similarity of two strings. The higher the value, the more similar the * strings are. * * See https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance for details * * It seems that a more generalized implementation of Winkler's modification * can improve the results. This might be implemented later. * http://disi.unitn.it/~p2p/RelatedWork/Matching/Hermans_bnaic-2012.pdf * * @public * @param {string} str1 - String no. 1. * @param {string} str2 - String no. 2. * @param {boolean} [favorSameStart=false] - If true, strings with same start get higher distance value. * @param {boolean} [longTolerance=false] - If true, Winkler's tolerance for long words will be used. * @returns {number} Distance. */ TextUtilities.computeJaroDistance = function(str1, str2, favorSameStart, longTolerance) { // sanity checks if (typeof str1 !== 'string' || typeof str2 !== 'string') { return undefined; } if (favorSameStart && typeof favorSameStart !== 'boolean') { favorSameStart = false; } if (longTolerance && typeof longTolerance !== 'boolean') { longTolerance = false; } // degenerate cases if (str1.length === 0 || str2.length === 0) { return 0; } if (str1 === str2) { return 1; } // counter variables var i, j, k; // number of matches between both strings var matches = 0; // number of transpositions between both strings var transpositions = 0; // The Jaro-Winkler distance var distance = 0; // length of common prefix up to 4 chars var l = 0; // scaling factor, should not exceed 0.25 (Winkler default = 0.1) var p = 0.1; // will be used often var str1Len = str1.length; var str2Len = str2.length; // determines the distance that still counts as a match var matchWindow = Math.floor(Math.max(str1Len, str2Len) / 2)- 1; // will store matches var str1Flags = new Array(str1Len); var str2Flags = new Array(str2Len); // count matches for (i = 0; i < str1Len; i++) { var start = (i >= matchWindow) ? i - matchWindow : 0; var end = (i + matchWindow <= (str2Len - 1)) ? (i + matchWindow) : (str2Len - 1); for (j = start; j <= end; j++) { if (str1Flags[i] !== true && str2Flags[j] !== true && str1[i] === str2[j]) { str1Flags[i] = str2Flags[j] = true; matches += 1; break; } } } if (matches === 0) { return 0; } // count transpositions k = 0; for (i = 0; i < str1Len; i++) { if (!str1Flags[i]) { continue; } while (!str2Flags[k]) { k += 1; } if (str1[i] !== str2[k]) { transpositions += 1; } k += 1; } transpositions = transpositions / 2; // compute Jaro distance distance = (matches/str1Len + matches/str2Len + (matches - transpositions) / matches) / 3; // modification used by Winkler if (favorSameStart) { if (distance > 0.7 && str1Len > 3 && str2Len > 3) { while (str1[l] === str2[l] && l < 4) { l += 1; } distance = distance + l * p * (1 - distance); // modification for long words if (longTolerance) { if (Math.max(str1Len, str2Len) > 4 && matches > l + 1 && 2 * matches >= Math.max(str1Len, str2Len) + l) { distance += ((1.0 - distance) * ((matches - l - 1) / (str1Len + str2Len - 2 * l + 2))); } } } } return distance; }; /** * Check whether a text contains a string, but fuzzy. * * This function is naive. It moves a window of needle's length (+2) * over the haystack's text and each move compares for similarity using * a given string metric. This will be slow for long texts!!! * * TODO: You might want to look into the bitap algorithm or experiment * with regexps * * @param {String} needle - String to look for. * @param {String} haystack - Text to look in. */ TextUtilities.fuzzyContains = function (needle, haystack) { return this.fuzzyFind(needle, haystack).contains; }; /** * Find the first position of a fuzzy string within a text * @param {String} needle - String to look for. * @param {String} haystack - Text to look in. */ TextUtilities.fuzzyIndexOf = function (needle, haystack) { return this.fuzzyFind(needle, haystack).indexOf; }; /** * Find the first fuzzy match of a string within a text * @param {String} needle - String to look for. * @param {String} haystack - Text to look in. */ TextUtilities.fuzzyMatch = function (needle, haystack) { return this.fuzzyFind(needle, haystack).match; }; /** * Find a fuzzy string with in a text. * TODO: This could be cleaned ... * @param {String} needle - String to look for. * @param {String} haystack - Text to look in. * @param {object} params - Parameters. */ TextUtilities.fuzzyFind = function (needle, haystack, params) { // Sanitization if (!needle || typeof needle !== 'string') { return false; } if (!haystack || typeof haystack !== 'string') { return false; } if (params === undefined || params.windowSize === undefined || typeof params.windowSize !== 'number') { params = {'windowSize': 3}; } var match; var found = haystack.split(' ').some(function(hay) { match = hay; return H5P.TextUtilities.areSimilar(needle, hay); }); if (found) { return {'contains' : found, 'match': match, 'index': haystack.indexOf(match)}; } // This is not used for single words but for phrases for (var i = 0; i < haystack.length - needle.length + 1; i++) { var hay = []; for (var j = 0; j < params.windowSize; j++) { hay[j] = haystack.substr(i, needle.length + j); } // Checking isIsolated will e.g. prevent finding beginnings of words for (var j = 0; j < hay.length; j++) { if (TextUtilities.isIsolated(hay[j], haystack) && TextUtilities.areSimilar(hay[j], needle)) { match = hay[j]; found = true; break; } } if (found) { break; } } if (!found) { match = undefined; } return {'contains' : found, 'match': match, 'index': haystack.indexOf(match)}; }; return TextUtilities; }(); ; /*global H5P*/ H5P.Blanks = (function ($, Question) { /** * @constant * @default */ var STATE_ONGOING = 'ongoing'; var STATE_CHECKING = 'checking'; var STATE_SHOWING_SOLUTION = 'showing-solution'; var STATE_FINISHED = 'finished'; const XAPI_ALTERNATIVE_EXTENSION = 'https://h5p.org/x-api/alternatives'; const XAPI_CASE_SENSITIVITY = 'https://h5p.org/x-api/case-sensitivity'; const XAPI_REPORTING_VERSION_EXTENSION = 'https://h5p.org/x-api/h5p-reporting-version'; /** * @typedef {Object} Params * Parameters/configuration object for Blanks * * @property {Object} Params.behaviour * @property {string} Params.behaviour.confirmRetryDialog * @property {string} Params.behaviour.confirmCheckDialog * * @property {Object} Params.confirmRetry * @property {string} Params.confirmRetry.header * @property {string} Params.confirmRetry.body * @property {string} Params.confirmRetry.cancelLabel * @property {string} Params.confirmRetry.confirmLabel * * @property {Object} Params.confirmCheck * @property {string} Params.confirmCheck.header * @property {string} Params.confirmCheck.body * @property {string} Params.confirmCheck.cancelLabel * @property {string} Params.confirmCheck.confirmLabel */ /** * Initialize module. * * @class H5P.Blanks * @extends H5P.Question * @param {Params} params * @param {number} id Content identification * @param {Object} contentData Task specific content data */ function Blanks(params, id, contentData) { var self = this; // Inheritance Question.call(self, 'blanks'); // IDs this.contentId = id; this.contentData = contentData; this.params = $.extend(true, {}, { text: "Fill in", questions: [ "<p>Oslo is the capital of *Norway*.</p>" ], overallFeedback: [], userAnswers: [], // TODO This isn't in semantics? showSolutions: "Show solution", tryAgain: "Try again", checkAnswer: "Check", changeAnswer: "Change answer", notFilledOut: "Please fill in all blanks to view solution", answerIsCorrect: "':ans' is correct", answerIsWrong: "':ans' is wrong", answeredCorrectly: "Answered correctly", answeredIncorrectly: "Answered incorrectly", solutionLabel: "Correct answer:", inputLabel: "Blank input @num of @total", inputHasTipLabel: "Tip available", tipLabel: "Tip", scoreBarLabel: 'You got :num out of :total points', behaviour: { enableRetry: true, enableSolutionsButton: true, enableCheckButton: true, caseSensitive: true, showSolutionsRequiresInput: true, autoCheck: false, separateLines: false }, a11yCheck: 'Check the answers. The responses will be marked as correct, incorrect, or unanswered.', a11yShowSolution: 'Show the solution. The task will be marked with its correct solution.', a11yRetry: 'Retry the task. Reset all responses and start the task over again.', a11yHeader: 'Checking mode', submitAnswer: 'Submit', }, params); // Delete empty questions for (var i = this.params.questions.length - 1; i >= 0; i--) { if (!this.params.questions[i]) { this.params.questions.splice(i, 1); } } // Previous state this.contentData = contentData; if (this.contentData !== undefined && this.contentData.previousState !== undefined) { this.previousState = this.contentData.previousState; } // Clozes this.clozes = []; // Keep track tabbing forward or backwards this.shiftPressed = false; H5P.$body.keydown(function (event) { if (event.keyCode === 16) { self.shiftPressed = true; } }).keyup(function (event) { if (event.keyCode === 16) { self.shiftPressed = false; } }); } // Inheritance Blanks.prototype = Object.create(Question.prototype); Blanks.prototype.constructor = Blanks; /** * Registers this question type's DOM elements before they are attached. * Called from H5P.Question. */ Blanks.prototype.registerDomElements = function () { var self = this; // Check for task media var media = self.params.media; if (media && media.type && media.type.library) { media = media.type; var type = media.library.split(' ')[0]; if (type === 'H5P.Image') { if (media.params.file) { // Register task image self.setImage(media.params.file.path, { disableImageZooming: self.params.media.disableImageZooming || false, alt: media.params.alt, title: media.params.title }); } } else if (type === 'H5P.Video') { if (media.params.sources) { // Register task video self.setVideo(media); } } } // Using instructions as label for our text groups const labelId = 'h5p-blanks-instructions-' + Blanks.idCounter; // Register task introduction text self.setIntroduction('<div id="' + labelId + '">' + self.params.text + '</div>'); // Register task content area self.setContent(self.createQuestions(labelId), { 'class': self.params.behaviour.separateLines ? 'h5p-separate-lines' : '' }); // ... and buttons self.registerButtons(); // Restore previous state self.setH5PUserState(); }; /** * Create all the buttons for the task */ Blanks.prototype.registerButtons = function () { var self = this; var $content = $('[data-content-id="' + self.contentId + '"].h5p-content'); var $containerParents = $content.parents('.h5p-container'); // select find container to attach dialogs to var $container; if ($containerParents.length !== 0) { // use parent highest up if any $container = $containerParents.last(); } else if ($content.length !== 0) { $container = $content; } else { $container = $(document.body); } if (!self.params.behaviour.autoCheck && this.params.behaviour.enableCheckButton) { // Check answer button self.addButton('check-answer', self.params.checkAnswer, function () { // Move focus to top of content self.a11yHeader.innerHTML = self.params.a11yHeader; self.a11yHeader.focus(); self.toggleButtonVisibility(STATE_CHECKING); self.markResults(); self.showEvaluation(); self.triggerAnswered(); }, true, { 'aria-label': self.params.a11yCheck, }, { confirmationDialog: { enable: self.params.behaviour.confirmCheckDialog, l10n: self.params.confirmCheck, instance: self, $parentElement: $container }, textIfSubmitting: self.params.submitAnswer, contentData: self.contentData, }); } // Show solution button self.addButton('show-solution', self.params.showSolutions, function () { self.showCorrectAnswers(false); }, self.params.behaviour.enableSolutionsButton, { 'aria-label': self.params.a11yShowSolution, }); // Try again button if (self.params.behaviour.enableRetry === true) { self.addButton('try-again', self.params.tryAgain, function () { self.a11yHeader.innerHTML = ''; self.resetTask(); self.$questions.filter(':first').find('input:first').focus(); }, true, { 'aria-label': self.params.a11yRetry, }, { confirmationDialog: { enable: self.params.behaviour.confirmRetryDialog, l10n: self.params.confirmRetry, instance: self, $parentElement: $container } }); } self.toggleButtonVisibility(STATE_ONGOING); }; /** * Find blanks in a string and run a handler on those blanks * * @param {string} question * Question text containing blanks enclosed in asterisks. * @param {function} handler * Replaces the blanks text with an input field. * @returns {string} * The question with blanks replaced by the given handler. */ Blanks.prototype.handleBlanks = function (question, handler) { // Go through the text and run handler on all asterisk var clozeEnd, clozeStart = question.indexOf('*'); var self = this; while (clozeStart !== -1 && clozeEnd !== -1) { clozeStart++; clozeEnd = question.indexOf('*', clozeStart); if (clozeEnd === -1) { continue; // No end } var clozeContent = question.substring(clozeStart, clozeEnd); var replacer = ''; if (clozeContent.length) { replacer = handler(self.parseSolution(clozeContent)); clozeEnd++; } else { clozeStart += 1; } question = question.slice(0, clozeStart - 1) + replacer + question.slice(clozeEnd); clozeEnd -= clozeEnd - clozeStart - replacer.length; // Find the next cloze clozeStart = question.indexOf('*', clozeEnd); } return question; }; /** * Create questitons html for DOM */ Blanks.prototype.createQuestions = function (labelId) { var self = this; var html = ''; var clozeNumber = 0; for (var i = 0; i < self.params.questions.length; i++) { var question = self.params.questions[i]; // Go through the question text and replace all the asterisks with input fields question = self.handleBlanks(question, function (solution) { // Create new cloze clozeNumber += 1; var defaultUserAnswer = (self.params.userAnswers.length > self.clozes.length ? self.params.userAnswers[self.clozes.length] : null); var cloze = new Blanks.Cloze(solution, self.params.behaviour, defaultUserAnswer, { answeredCorrectly: self.params.answeredCorrectly, answeredIncorrectly: self.params.answeredIncorrectly, solutionLabel: self.params.solutionLabel, inputLabel: self.params.inputLabel, inputHasTipLabel: self.params.inputHasTipLabel, tipLabel: self.params.tipLabel }); self.clozes.push(cloze); return cloze; }); html += '<div role="group" aria-labelledby="' + labelId + '">' + question + '</div>'; } self.hasClozes = clozeNumber > 0; this.$questions = $(html); self.a11yHeader = document.createElement('div'); self.a11yHeader.classList.add('hidden-but-read'); self.a11yHeader.tabIndex = -1; self.$questions[0].insertBefore(self.a11yHeader, this.$questions[0].childNodes[0] || null); // Set input fields. this.$questions.find('input').each(function (i) { var afterCheck; if (self.params.behaviour.autoCheck) { afterCheck = function () { var answer = $("<div>").text(this.getUserAnswer()).html(); self.read((this.correct() ? self.params.answerIsCorrect : self.params.answerIsWrong).replace(':ans', answer)); if (self.done || self.allBlanksFilledOut()) { // All answers has been given. Show solutions button. self.toggleButtonVisibility(STATE_CHECKING); self.showEvaluation(); self.triggerAnswered(); self.done = true; } }; } self.clozes[i].setInput($(this), afterCheck, function () { self.toggleButtonVisibility(STATE_ONGOING); if (!self.params.behaviour.autoCheck) { self.hideEvaluation(); } }, i, self.clozes.length); }).keydown(function (event) { var $this = $(this); // Adjust width of text input field to match value self.autoGrowTextField($this); var $inputs, isLastInput; var enterPressed = (event.keyCode === 13); var tabPressedAutoCheck = (event.keyCode === 9 && self.params.behaviour.autoCheck); if (enterPressed || tabPressedAutoCheck) { // Figure out which inputs are left to answer $inputs = self.$questions.find('.h5p-input-wrapper:not(.h5p-correct) .h5p-text-input'); // Figure out if this is the last input isLastInput = $this.is($inputs[$inputs.length - 1]); } if ((tabPressedAutoCheck && isLastInput && !self.shiftPressed) || (enterPressed && isLastInput)) { // Focus first button on next tick setTimeout(function () { self.focusButton(); }, 10); } if (enterPressed) { if (isLastInput) { // Check answers $this.trigger('blur'); } else { // Find next input to focus $inputs.eq($inputs.index($this) + 1).focus(); } return false; // Prevent form submission on enter key } }).on('change', function () { self.answered = true; self.triggerXAPI('interacted'); }); self.on('resize', function () { self.resetGrowTextField(); }); return this.$questions; }; /** * */ Blanks.prototype.autoGrowTextField = function ($input) { // Do not set text field size when separate lines is enabled if (this.params.behaviour.separateLines) { return; } var self = this; var fontSize = parseInt($input.css('font-size'), 10); var minEm = 3; var minPx = fontSize * minEm; var rightPadEm = 3.25; var rightPadPx = fontSize * rightPadEm; var static_min_pad = 0.5 * fontSize; setTimeout(function () { var tmp = $('<div>', { 'text': $input.val() }); tmp.css({ 'position': 'absolute', 'white-space': 'nowrap', 'font-size': $input.css('font-size'), 'font-family': $input.css('font-family'), 'padding': $input.css('padding'), 'width': 'initial' }); $input.parent().append(tmp); var width = tmp.width(); var parentWidth = self.$questions.width(); tmp.remove(); if (width <= minPx) { // Apply min width $input.width(minPx + static_min_pad); } else if (width + rightPadPx >= parentWidth) { // Apply max width of parent $input.width(parentWidth - rightPadPx); } else { // Apply width that wraps input $input.width(width + static_min_pad); } }, 1); }; /** * Resize all text field growth to current size. */ Blanks.prototype.resetGrowTextField = function () { var self = this; this.$questions.find('input').each(function () { self.autoGrowTextField($(this)); }); }; /** * Toggle buttons dependent of state. * * Using CSS-rules to conditionally show/hide using the data-attribute [data-state] */ Blanks.prototype.toggleButtonVisibility = function (state) { // The show solutions button is hidden if all answers are correct var allCorrect = (this.getScore() === this.getMaxScore()); if (this.params.behaviour.autoCheck && allCorrect) { // We are viewing the solutions state = STATE_FINISHED; } if (this.params.behaviour.enableSolutionsButton) { if (state === STATE_CHECKING && !allCorrect) { this.showButton('show-solution'); } else { this.hideButton('show-solution'); } } if (this.params.behaviour.enableRetry) { if ((state === STATE_CHECKING && !allCorrect) || state === STATE_SHOWING_SOLUTION) { this.showButton('try-again'); } else { this.hideButton('try-again'); } } if (state === STATE_ONGOING) { this.showButton('check-answer'); } else { this.hideButton('check-answer'); } this.trigger('resize'); }; /** * Check if solution is allowed. Warn user if not */ Blanks.prototype.allowSolution = function () { if (this.params.behaviour.showSolutionsRequiresInput === true) { if (!this.allBlanksFilledOut()) { this.updateFeedbackContent(this.params.notFilledOut); this.read(this.params.notFilledOut); return false; } } return true; }; /** * Check if all blanks are filled out * * @method allBlanksFilledOut * @return {boolean} Returns true if all blanks are filled out. */ Blanks.prototype.allBlanksFilledOut = function () { return !this.clozes.some(function (cloze) { return !cloze.filledOut(); }); }; /** * Mark which answers are correct and which are wrong and disable fields if retry is off. */ Blanks.prototype.markResults = function () { var self = this; for (var i = 0; i < self.clozes.length; i++) { self.clozes[i].checkAnswer(); if (!self.params.behaviour.enableRetry) { self.clozes[i].disableInput(); } } this.trigger('resize'); }; /** * Removed marked results */ Blanks.prototype.removeMarkedResults = function () { this.$questions.find('.h5p-input-wrapper').removeClass('h5p-correct h5p-wrong'); this.$questions.find('.h5p-input-wrapper > input').attr('disabled', false); this.trigger('resize'); }; /** * Displays the correct answers * @param {boolean} [alwaysShowSolution] * Will always show solution if true */ Blanks.prototype.showCorrectAnswers = function (alwaysShowSolution) { if (!alwaysShowSolution && !this.allowSolution()) { return; } this.toggleButtonVisibility(STATE_SHOWING_SOLUTION); this.hideSolutions(); for (var i = 0; i < this.clozes.length; i++) { this.clozes[i].showSolution(); } this.trigger('resize'); }; /** * Toggle input allowed for all input fields * * @method function * @param {boolean} enabled True if fields should allow input, otherwise false */ Blanks.prototype.toggleAllInputs = function (enabled) { for (var i = 0; i < this.clozes.length; i++) { this.clozes[i].toggleInput(enabled); } }; /** * Display the correct solution for the input boxes. * * This is invoked from CP and QS - be carefull! */ Blanks.prototype.showSolutions = function () { this.params.behaviour.enableSolutionsButton = true; this.toggleButtonVisibility(STATE_FINISHED); this.markResults(); this.showEvaluation(); this.showCorrectAnswers(true); this.toggleAllInputs(false); //Hides all buttons in "show solution" mode. this.hideButtons(); }; /** * Resets the complete task. * Used in contracts. * @public */ Blanks.prototype.resetTask = function () { this.answered = false; this.hideEvaluation(); this.hideSolutions(); this.clearAnswers(); this.removeMarkedResults(); this.toggleButtonVisibility(STATE_ONGOING); this.resetGrowTextField(); this.toggleAllInputs(true); this.done = false; }; /** * Hides all buttons. * @public */ Blanks.prototype.hideButtons = function () { this.toggleButtonVisibility(STATE_FINISHED); }; /** * Trigger xAPI answered event */ Blanks.prototype.triggerAnswered = function () { this.answered = true; var xAPIEvent = this.createXAPIEventTemplate('answered'); this.addQuestionToXAPI(xAPIEvent); this.addResponseToXAPI(xAPIEvent); this.trigger(xAPIEvent); }; /** * Get xAPI data. * Contract used by report rendering engine. * * @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6} */ Blanks.prototype.getXAPIData = function () { var xAPIEvent = this.createXAPIEventTemplate('answered'); this.addQuestionToXAPI(xAPIEvent); this.addResponseToXAPI(xAPIEvent); return { statement: xAPIEvent.data.statement }; }; /** * Generate xAPI object definition used in xAPI statements. * @return {Object} */ Blanks.prototype.getxAPIDefinition = function () { var definition = {}; definition.description = { 'en-US': this.params.text }; definition.type = 'http://adlnet.gov/expapi/activities/cmi.interaction'; definition.interactionType = 'fill-in'; const clozeSolutions = []; let crp = ''; // xAPI forces us to create solution patterns for all possible solution combinations for (var i = 0; i < this.params.questions.length; i++) { var question = this.handleBlanks(this.params.questions[i], function (solution) { // Collect all solution combinations for the H5P Alternative extension clozeSolutions.push(solution.solutions); // Create a basic response pattern out of the first alternative for each blanks field crp += (!crp ? '' : '[,]') + solution.solutions[0]; // We replace the solutions in the question with a "blank" return '__________'; }); definition.description['en-US'] += question; } // Set the basic response pattern (not supporting multiple alternatives for blanks) definition.correctResponsesPattern = [ '{case_matters=' + this.params.behaviour.caseSensitive + '}' + crp, ]; // Add the H5P Alternative extension which provides all the combinations of different answers // Reporting software will need to support this extension for alternatives to work. definition.extensions = definition.extensions || {}; definition.extensions[XAPI_CASE_SENSITIVITY] = this.params.behaviour.caseSensitive; definition.extensions[XAPI_ALTERNATIVE_EXTENSION] = clozeSolutions; return definition; }; /** * Add the question itselt to the definition part of an xAPIEvent */ Blanks.prototype.addQuestionToXAPI = function (xAPIEvent) { var definition = xAPIEvent.getVerifiedStatementValue(['object', 'definition']); $.extend(true, definition, this.getxAPIDefinition()); // Set reporting module version if alternative extension is used if (this.hasAlternatives) { const context = xAPIEvent.getVerifiedStatementValue(['context']); context.extensions = context.extensions || {}; context.extensions[XAPI_REPORTING_VERSION_EXTENSION] = '1.1.0'; } }; /** * Parse the solution text (text between the asterisks) * * @param {string} solutionText * @returns {object} with the following properties * - tip: the tip text for this solution, undefined if no tip * - solutions: array of solution words */ Blanks.prototype.parseSolution = function (solutionText) { var tip, solution; var tipStart = solutionText.indexOf(':'); if (tipStart !== -1) { // Found tip, now extract tip = solutionText.slice(tipStart + 1); solution = solutionText.slice(0, tipStart); } else { solution = solutionText; } // Split up alternatives var solutions = solution.split('/'); this.hasAlternatives = this.hasAlternatives || solutions.length > 1; // Trim solutions for (var i = 0; i < solutions.length; i++) { solutions[i] = H5P.trim(solutions[i]); //decodes html entities var elem = document.createElement('textarea'); elem.innerHTML = solutions[i]; solutions[i] = elem.value; } return { tip: tip, solutions: solutions }; }; /** * Add the response part to an xAPI event * * @param {H5P.XAPIEvent} xAPIEvent * The xAPI event we will add a response to */ Blanks.prototype.addResponseToXAPI = function (xAPIEvent) { xAPIEvent.setScoredResult(this.getScore(), this.getMaxScore(), this); xAPIEvent.data.statement.result.response = this.getxAPIResponse(); }; /** * Generate xAPI user response, used in xAPI statements. * @return {string} User answers separated by the "[,]" pattern */ Blanks.prototype.getxAPIResponse = function () { var usersAnswers = this.getCurrentState(); return usersAnswers.join('[,]'); }; /** * Show evaluation widget, i.e: 'You got x of y blanks correct' */ Blanks.prototype.showEvaluation = function () { var maxScore = this.getMaxScore(); var score = this.getScore(); var scoreText = H5P.Question.determineOverallFeedback(this.params.overallFeedback, score / maxScore).replace('@score', score).replace('@total', maxScore); this.setFeedback(scoreText, score, maxScore, this.params.scoreBarLabel); if (score === maxScore) { this.toggleButtonVisibility(STATE_FINISHED); } }; /** * Hide the evaluation widget */ Blanks.prototype.hideEvaluation = function () { // Clear evaluation section. this.removeFeedback(); }; /** * Hide solutions. (/try again) */ Blanks.prototype.hideSolutions = function () { // Clean solution from quiz this.$questions.find('.h5p-correct-answer').remove(); }; /** * Get maximum number of correct answers. * * @returns {Number} Max points */ Blanks.prototype.getMaxScore = function () { var self = this; return self.clozes.length; }; /** * Count the number of correct answers. * * @returns {Number} Points */ Blanks.prototype.getScore = function () { var self = this; var correct = 0; for (var i = 0; i < self.clozes.length; i++) { if (self.clozes[i].correct()) { correct++; } self.params.userAnswers[i] = self.clozes[i].getUserAnswer(); } return correct; }; Blanks.prototype.getTitle = function () { return H5P.createTitle((this.contentData.metadata && this.contentData.metadata.title) ? this.contentData.metadata.title : 'Fill In'); }; /** * Clear the user's answers */ Blanks.prototype.clearAnswers = function () { this.clozes.forEach(function (cloze) { cloze.setUserInput(''); cloze.resetAriaLabel(); }); }; /** * Checks if all has been answered. * * @returns {Boolean} */ Blanks.prototype.getAnswerGiven = function () { return this.answered || !this.hasClozes; }; /** * Helps set focus the given input field. * @param {jQuery} $input */ Blanks.setFocus = function ($input) { setTimeout(function () { $input.focus(); }, 1); }; /** * Returns an object containing content of each cloze * * @returns {object} object containing content for each cloze */ Blanks.prototype.getCurrentState = function () { var clozesContent = []; // Get user input for every cloze this.clozes.forEach(function (cloze) { clozesContent.push(cloze.getUserAnswer()); }); return clozesContent; }; /** * Sets answers to current user state */ Blanks.prototype.setH5PUserState = function () { var self = this; var isValidState = (this.previousState !== undefined && this.previousState.length && this.previousState.length === this.clozes.length); // Check that stored user state is valid if (!isValidState) { return; } // Set input from user state var hasAllClozesFilled = true; this.previousState.forEach(function (clozeContent, ccIndex) { // Register that an answer has been given if (clozeContent.length) { self.answered = true; } var cloze = self.clozes[ccIndex]; cloze.setUserInput(clozeContent); // Handle instant feedback if (self.params.behaviour.autoCheck) { if (cloze.filledOut()) { cloze.checkAnswer(); } else { hasAllClozesFilled = false; } } }); if (self.params.behaviour.autoCheck && hasAllClozesFilled) { self.showEvaluation(); self.toggleButtonVisibility(STATE_CHECKING); } }; /** * Disables any active input. Useful for freezing the task and dis-allowing * modification of wrong answers. */ Blanks.prototype.disableInput = function () { this.$questions.find('input').attr('disabled', true); }; Blanks.idCounter = 0; return Blanks; })(H5P.jQuery, H5P.Question); /** * Static utility method for parsing H5P.Blanks qestion into a format useful * for creating reports. * * Example question: 'H5P content may be edited using a *browser/web-browser:something you use every day*.' * * Produces the following result: * [ * { * type: 'text', * content: 'H5P content may be edited using a ' * }, * { * type: 'answer', * correct: ['browser', 'web-browser'] * }, * { * type: 'text', * content: '.' * } * ] * * @param {string} question */ H5P.Blanks.parseText = function (question) { var blank = new H5P.Blanks({ question: question }); /** * Parses a text into an array where words starting and ending * with an asterisk are separated from other text. * e.g ["this", "*is*", " an ", "*example*"] * * @param {string} text * * @return {string[]} */ function tokenizeQuestionText(text) { return text.split(/(\*.*?\*)/).filter(function (str) { return str.length > 0; } ); } function startsAndEndsWithAnAsterisk(str) { return str.substr(0,1) === '*' && str.substr(-1) === '*'; } function replaceHtmlTags(str, value) { return str.replace(/<[^>]*>/g, value); } return tokenizeQuestionText(replaceHtmlTags(question, '')).map(function (part) { return startsAndEndsWithAnAsterisk(part) ? ({ type: 'answer', correct: blank.parseSolution(part.slice(1, -1)).solutions }) : ({ type: 'text', content: part }); }); }; ; (function ($, Blanks) { /** * Simple private class for keeping track of clozes. * * @class H5P.Blanks.Cloze * @param {string} answer * @param {Object} behaviour Behavioral settings for the task from semantics * @param {boolean} behaviour.acceptSpellingErrors - If true, answers will also count correct if they contain small spelling errors. * @param {string} defaultUserAnswer * @param {Object} l10n Localized texts * @param {string} l10n.solutionLabel Assistive technology label for cloze solution * @param {string} l10n.inputLabel Assistive technology label for cloze input * @param {string} l10n.inputHasTipLabel Assistive technology label for input with tip * @param {string} l10n.tipLabel Label for tip icon */ Blanks.Cloze = function (solution, behaviour, defaultUserAnswer, l10n) { var self = this; var $input, $wrapper; var answers = solution.solutions; var answer = answers.join('/'); var tip = solution.tip; var checkedAnswer = null; var inputLabel = l10n.inputLabel; if (behaviour.caseSensitive !== true) { // Convert possible solutions into lowercase for (var i = 0; i < answers.length; i++) { answers[i] = answers[i].toLowerCase(); } } /** * Check if the answer is correct. * * @private * @param {string} answered */ var correct = function (answered) { if (behaviour.caseSensitive !== true) { answered = answered.toLowerCase(); } for (var i = 0; i < answers.length; i++) { // Damerau-Levenshtein comparison if (behaviour.acceptSpellingErrors === true) { var levenshtein = H5P.TextUtilities.computeLevenshteinDistance(answered, answers[i], true); /* * The correctness is temporarily computed by word length and number of number of operations * required to change one word into the other (Damerau-Levenshtein). It's subject to * change, cmp. https://github.com/otacke/udacity-machine-learning-engineer/blob/master/submissions/capstone_proposals/h5p_fuzzy_blanks.md */ if ((answers[i].length > 9) && (levenshtein <= 2)) { return true; } else if ((answers[i].length > 3) && (levenshtein <= 1)) { return true; } } // regular comparison if (answered === answers[i]) { return true; } } return false; }; /** * Check if filled out. * * @param {boolean} */ this.filledOut = function () { var answered = this.getUserAnswer(); // Blank can be correct and is interpreted as filled out. return (answered !== '' || correct(answered)); }; /** * Check the cloze and mark it as wrong or correct. */ this.checkAnswer = function () { checkedAnswer = this.getUserAnswer(); var isCorrect = correct(checkedAnswer); if (isCorrect) { $wrapper.addClass('h5p-correct'); $input.attr('disabled', true) .attr('aria-label', inputLabel + '. ' + l10n.answeredCorrectly); } else { $wrapper.addClass('h5p-wrong'); $input.attr('aria-label', inputLabel + '. ' + l10n.answeredIncorrectly); } }; /** * Disables input. * @method disableInput */ this.disableInput = function () { this.toggleInput(false); }; /** * Enables input. * @method enableInput */ this.enableInput = function () { this.toggleInput(true); }; /** * Toggles input enable/disable * @method toggleInput * @param {boolean} enabled True if input should be enabled, otherwise false */ this.toggleInput = function (enabled) { $input.attr('disabled', !enabled); }; /** * Show the correct solution. */ this.showSolution = function () { if (correct(this.getUserAnswer())) { return; // Only for the wrong ones } $('<span>', { 'aria-hidden': true, 'class': 'h5p-correct-answer', text: answer, insertAfter: $wrapper }); $input.attr('disabled', true); var ariaLabel = inputLabel + '. ' + l10n.solutionLabel + ' ' + answer + '. ' + l10n.answeredIncorrectly; $input.attr('aria-label', ariaLabel); }; /** * @returns {boolean} */ this.correct = function () { return correct(this.getUserAnswer()); }; /** * Set input element. * * @param {H5P.jQuery} $element * @param {function} afterCheck * @param {function} afterFocus * @param {number} clozeIndex Index of cloze * @param {number} totalCloze Total amount of clozes in blanks */ this.setInput = function ($element, afterCheck, afterFocus, clozeIndex, totalCloze) { $input = $element; $wrapper = $element.parent(); inputLabel = inputLabel.replace('@num', (clozeIndex + 1)) .replace('@total', totalCloze); // Add tip if tip is set if(tip !== undefined && tip.trim().length > 0) { $wrapper.addClass('has-tip') .append(H5P.JoubelUI.createTip(tip, { tipLabel: l10n.tipLabel })); inputLabel += '. ' + l10n.inputHasTipLabel; } $input.attr('aria-label', inputLabel); if (afterCheck !== undefined) { $input.blur(function () { if (self.filledOut()) { // Check answers if (!behaviour.enableRetry) { self.disableInput(); } self.checkAnswer(); afterCheck.apply(self); } }); } $input.keyup(function () { if (checkedAnswer !== null && checkedAnswer !== self.getUserAnswer()) { // The Answer has changed since last check checkedAnswer = null; $wrapper.removeClass('h5p-wrong'); $input.attr('aria-label', inputLabel); if (afterFocus !== undefined) { afterFocus(); } } }); }; /** * @returns {string} Cloze html */ this.toString = function () { var extra = defaultUserAnswer ? ' value="' + defaultUserAnswer + '"' : ''; var result = '<span class="h5p-input-wrapper"><input type="text" class="h5p-text-input" autocomplete="off" autocapitalize="off" spellcheck="false"' + extra + '></span>'; self.length = result.length; return result; }; /** * @returns {string} Trimmed answer */ this.getUserAnswer = function () { return H5P.trim($input.val()); }; /** * @param {string} text New input text */ this.setUserInput = function (text) { $input.val(text); }; /** * Resets aria label of input field */ this.resetAriaLabel = function () { $input.attr('aria-label', inputLabel); }; }; })(H5P.jQuery, H5P.Blanks); ; !function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=6)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.jQuery=H5P.jQuery,t.EventDispatcher=H5P.EventDispatcher,t.JoubelUI=H5P.JoubelUI},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.stripHTML=t.addClickAndKeyboardListeners=t.keyCode=t.defaultValue=t.contains=t.isIOS=t.isIPad=t.kebabCase=t.isFunction=t.flattenArray=void 0;var i=n(0),r=(t.flattenArray=function(e){return e.concat.apply([],e)},t.isFunction=function(e){return"function"==typeof e},t.kebabCase=function(e){return e.replace(/[\W]/g,"-")},t.isIPad=null!==navigator.userAgent.match(/iPad/i),t.isIOS=null!==navigator.userAgent.match(/iPad|iPod|iPhone/i),t.contains=function(e,t){return-1!==e.indexOf(t)}),s=(t.defaultValue=function(e,t){return void 0!==e?e:t},t.keyCode={ENTER:13,ESC:27,SPACE:32}),o=(t.addClickAndKeyboardListeners=function(e,t,n){e.click(function(e){t.call(n||this,e)}),e.keydown(function(e){r([s.ENTER,s.SPACE],e.which)&&(e.preventDefault(),t.call(n||this,e))})},(0,i.jQuery)("<div>"));t.stripHTML=function(e){return o.html(e).text().trim()}},function(e,t,n){"use strict";function i(e,t){var n=this;s.call(n),n.children=[];var i=function(e){for(var t=e;t<n.children.length;t++)n.children[t].index=t};if(n.addChild=function(t,s){void 0===s&&(s=n.children.length);var o=new r(s,n);return s===n.children.length?n.children.push(o):(n.children.splice(s,0,o),i(s)),e.call(o,t),o},n.removeChild=function(e){n.children.splice(e,1),i(e)},n.moveChild=function(e,t){var r=n.children.splice(e,1)[0];n.children.splice(t,0,r),i(t<e?t:e)},t)for(var o=0;o<t.length;o++)n.addChild(t[o])}var r=n(15),s=H5P.EventDispatcher;i.prototype=Object.create(s.prototype),i.prototype.constructor=i,e.exports=i},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=n(19),a=n(4),l=n(20),d=(0,o.removeAttribute)("tabindex"),c=((0,a.forEach)(d),(0,o.setAttribute)("tabindex","0")),u=(0,o.setAttribute)("tabindex","-1"),p=(0,o.hasAttribute)("tabindex"),h=function(){function e(t){i(this,e),r(this,(0,l.Eventful)()),this.plugins=t||[],this.elements=[],this.negativeTabIndexAllowed=!1,this.on("nextElement",this.nextElement,this),this.on("previousElement",this.previousElement,this),this.on("firstElement",this.firstElement,this),this.on("lastElement",this.lastElement,this),this.initPlugins()}return s(e,[{key:"addElement",value:function(e){this.elements.push(e),this.firesEvent("addElement",e),1===this.elements.length&&this.setTabbable(e)}},{key:"insertElementAt",value:function(e,t){this.elements.splice(t,0,e),this.firesEvent("addElement",e),1===this.elements.length&&this.setTabbable(e)}},{key:"removeElement",value:function(e){this.elements=(0,a.without)([e],this.elements),p(e)&&(this.setUntabbable(e),this.elements[0]&&this.setTabbable(this.elements[0])),this.firesEvent("removeElement",e)}},{key:"count",value:function(){return this.elements.length}},{key:"firesEvent",value:function(e,t){var n=this.elements.indexOf(t);return this.fire(e,{element:t,index:n,elements:this.elements,oldElement:this.tabbableElement})}},{key:"nextElement",value:function(e){var t=e.index,n=t===this.elements.length-1,i=this.elements[n?0:t+1];this.setTabbable(i),i.focus()}},{key:"firstElement",value:function(){var e=this.elements[0];this.setTabbable(e),e.focus()}},{key:"lastElement",value:function(){var e=this.elements[this.elements.length-1];this.setTabbable(e),e.focus()}},{key:"setTabbableByIndex",value:function(e){var t=this.elements[e];t&&this.setTabbable(t)}},{key:"setTabbable",value:function(e){(0,a.forEach)(this.setUntabbable.bind(this),this.elements),c(e),this.tabbableElement=e}},{key:"setUntabbable",value:function(e){e!==document.activeElement&&(this.negativeTabIndexAllowed?u(e):d(e))}},{key:"previousElement",value:function(e){var t=e.index,n=0===t,i=this.elements[n?this.elements.length-1:t-1];this.setTabbable(i),i.focus()}},{key:"useNegativeTabIndex",value:function(){this.negativeTabIndexAllowed=!0,this.elements.forEach(function(e){e.hasAttribute("tabindex")||u(e)})}},{key:"initPlugins",value:function(){this.plugins.forEach(function(e){void 0!==e.init&&e.init(this)},this)}}]),e}();t.default=h},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=t.curry=function(e){var t=e.length;return function n(){var i=Array.prototype.slice.call(arguments,0);return i.length>=t?e.apply(null,i):function(){var e=Array.prototype.slice.call(arguments,0);return n.apply(null,i.concat(e))}}},r=(t.compose=function(){for(var e=arguments.length,t=Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.reduce(function(e,t){return function(){return e(t.apply(void 0,arguments))}})},t.forEach=i(function(e,t){t.forEach(e)}),t.map=i(function(e,t){return t.map(e)}),t.filter=i(function(e,t){return t.filter(e)})),s=(t.some=i(function(e,t){return t.some(e)}),t.contains=i(function(e,t){return-1!=t.indexOf(e)}));t.without=i(function(e,t){return r(function(t){return!s(t,e)},t)}),t.inverseBooleanString=function(e){return("true"!==e).toString()}},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),s=function(){function e(){i(this,e),this.selectability=!0}return r(e,[{key:"init",value:function(e){this.boundHandleKeyDown=this.handleKeyDown.bind(this),this.controls=e,this.controls.on("addElement",this.listenForKeyDown,this),this.controls.on("removeElement",this.removeKeyDownListener,this)}},{key:"listenForKeyDown",value:function(e){e.element.addEventListener("keydown",this.boundHandleKeyDown)}},{key:"removeKeyDownListener",value:function(e){e.element.removeEventListener("keydown",this.boundHandleKeyDown)}},{key:"handleKeyDown",value:function(e){switch(e.which){case 27:this.close(e.target),e.preventDefault(),e.stopPropagation();break;case 35:this.lastElement(e.target),e.preventDefault(),e.stopPropagation();break;case 36:this.firstElement(e.target),e.preventDefault(),e.stopPropagation();break;case 13:case 32:this.select(e.target),e.preventDefault(),e.stopPropagation();break;case 37:case 38:this.hasChromevoxModifiers(e)||(this.previousElement(e.target),e.preventDefault(),e.stopPropagation());break;case 39:case 40:this.hasChromevoxModifiers(e)||(this.nextElement(e.target),e.preventDefault(),e.stopPropagation())}}},{key:"hasChromevoxModifiers",value:function(e){return e.shiftKey||e.ctrlKey}},{key:"previousElement",value:function(e){!1!==this.controls.firesEvent("beforePreviousElement",e)&&(this.controls.firesEvent("previousElement",e),this.controls.firesEvent("afterPreviousElement",e))}},{key:"nextElement",value:function(e){!1!==this.controls.firesEvent("beforeNextElement",e)&&(this.controls.firesEvent("nextElement",e),this.controls.firesEvent("afterNextElement",e))}},{key:"select",value:function(e){this.selectability&&!1!==this.controls.firesEvent("before-select",e)&&(this.controls.firesEvent("select",e),this.controls.firesEvent("after-select",e))}},{key:"firstElement",value:function(e){!1!==this.controls.firesEvent("beforeFirstElement",e)&&(this.controls.firesEvent("firstElement",e),this.controls.firesEvent("afterFirstElement",e))}},{key:"lastElement",value:function(e){!1!==this.controls.firesEvent("beforeLastElement",e)&&(this.controls.firesEvent("lastElement",e),this.controls.firesEvent("afterLastElement",e))}},{key:"disableSelectability",value:function(){this.selectability=!1}},{key:"enableSelectability",value:function(){this.selectability=!0}},{key:"close",value:function(e){!1!==this.controls.firesEvent("before-close",e)&&(this.controls.firesEvent("close",e),this.controls.firesEvent("after-close",e))}}]),e}();t.default=s},function(e,t,n){"use strict";n(7),n(8),n(9),n(10),n(11),n(12),n(13);var i=n(14),r=function(e){return e&&e.__esModule?e:{default:e}}(i);H5P=H5P||{},H5P.CoursePresentation=r.default},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t){},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){var n=[],i=!0,r=!1,s=void 0;try{for(var o,a=e[Symbol.iterator]();!(i=(o=a.next()).done)&&(n.push(o.value),!t||n.length!==t);i=!0);}catch(e){r=!0,s=e}finally{try{!i&&a.return&&a.return()}finally{if(r)throw s}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=n(2),a=i(o),l=n(16),d=i(l),c=n(17),u=i(c),p=n(21),h=i(p),f=n(22),v=i(f),m=n(0),y=n(1),b=n(23),g=i(b),S=function(e,t,n){var i=this;this.presentation=e.presentation,this.slides=this.presentation.slides,this.contentId=t,this.elementInstances=[],this.elementsAttached=[],this.slidesWithSolutions=[],this.hasAnswerElements=!1,this.ignoreResize=!1,this.isTask=!1,n.cpEditor&&(this.editor=n.cpEditor),n&&(this.previousState=n.previousState),this.currentSlideIndex=this.previousState&&this.previousState.progress?this.previousState.progress:0,this.presentation.keywordListEnabled=void 0===e.presentation.keywordListEnabled||e.presentation.keywordListEnabled,this.l10n=m.jQuery.extend({slide:"Slide",score:"Score",yourScore:"Your score",maxScore:"Max score",total:"Total",totalScore:"Total Score",showSolutions:"Show solutions",summary:"summary",retry:"Retry",exportAnswers:"Export text",close:"Close",hideKeywords:"Hide sidebar navigation menu",showKeywords:"Show sidebar navigation menu",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen",prevSlide:"Previous slide",nextSlide:"Next slide",currentSlide:"Current slide",lastSlide:"Last slide",solutionModeTitle:"Exit solution mode",solutionModeText:"Solution Mode",summaryMultipleTaskText:"Multiple tasks",scoreMessage:"You achieved:",shareFacebook:"Share on Facebook",shareTwitter:"Share on Twitter",shareGoogle:"Share on Google+",goToSlide:"Go to slide :num",solutionsButtonTitle:"Show comments",printTitle:"Print",printIngress:"How would you like to print this presentation?",printAllSlides:"Print all slides",printCurrentSlide:"Print current slide",noTitle:"No title",accessibilitySlideNavigationExplanation:"Use left and right arrow to change slide in that direction whenever canvas is selected.",containsNotCompleted:"@slideName contains not completed interaction",containsCompleted:"@slideName contains completed interaction",slideCount:"Slide @index of @total",accessibilityCanvasLabel:"Presentation canvas. Use left and right arrow to move between slides.",containsOnlyCorrect:"@slideName only has correct answers",containsIncorrectAnswers:"@slideName has incorrect answers",shareResult:"Share Result",accessibilityTotalScore:"You got @score of @maxScore points in total",accessibilityEnteredFullscreen:"Entered fullscreen",accessibilityExitedFullscreen:"Exited fullscreen"},void 0!==e.l10n?e.l10n:{}),e.override&&(this.activeSurface=!!e.override.activeSurface,this.hideSummarySlide=!!e.override.hideSummarySlide,this.enablePrintButton=!!e.override.enablePrintButton,this.showSummarySlideSolutionButton=void 0===e.override.summarySlideSolutionButton||e.override.summarySlideSolutionButton,this.showSummarySlideRetryButton=void 0===e.override.summarySlideRetryButton||e.override.summarySlideRetryButton,e.override.social&&(this.enableTwitterShare=!!e.override.social.showTwitterShare,this.enableFacebookShare=!!e.override.social.showFacebookShare,this.enableGoogleShare=!!e.override.social.showGoogleShare,this.twitterShareStatement=e.override.social.twitterShare.statement,this.twitterShareHashtags=e.override.social.twitterShare.hashtags,this.twitterShareUrl=e.override.social.twitterShare.url,this.facebookShareUrl=e.override.social.facebookShare.url,this.facebookShareQuote=e.override.social.facebookShare.quote,this.googleShareUrl=e.override.social.googleShareUrl)),this.keywordMenu=new v.default({l10n:this.l10n,currentIndex:void 0!==this.previousState?this.previousState.progress:0}),this.setElementsOverride(e.override),a.default.call(this,g.default,e.presentation.slides),this.on("resize",this.resize,this),this.on("printing",function(e){i.ignoreResize=!e.data.finished,e.data.finished?i.resize():e.data.allSlides&&i.attachAllElements()})};S.prototype=Object.create(a.default.prototype),S.prototype.constructor=S,S.prototype.getCurrentState=function(){var e=this,t=this.previousState?this.previousState:{};t.progress=this.getCurrentSlideIndex(),t.answers||(t.answers=[]),t.answered=this.elementInstances.map(function(t,n){return e.slideHasAnsweredTask(n)});for(var n=0;n<this.elementInstances.length;n++)if(this.elementInstances[n])for(var i=0;i<this.elementInstances[n].length;i++){var r=this.elementInstances[n][i];(r.getCurrentState instanceof Function||"function"==typeof r.getCurrentState)&&(t.answers[n]||(t.answers[n]=[]),t.answers[n][i]=r.getCurrentState())}return t},S.prototype.slideHasAnsweredTask=function(e){return(this.slidesWithSolutions[e]||[]).filter(function(e){return(0,y.isFunction)(e.getAnswerGiven)}).some(function(e){return e.getAnswerGiven()})},S.prototype.attach=function(e){var t=this,n=this;void 0!==this.isRoot&&this.isRoot()&&this.setActivityStarted();var i='<div class="h5p-keymap-explanation hidden-but-read">'+this.l10n.accessibilitySlideNavigationExplanation+'</div><div class="h5p-fullscreen-announcer hidden-but-read" aria-live="polite"></div><div class="h5p-wrapper" tabindex="0" aria-label="'+this.l10n.accessibilityCanvasLabel+'"> <div class="h5p-current-slide-announcer hidden-but-read" aria-live="polite"></div> <div tabindex="-1"></div> <div class="h5p-box-wrapper"> <div class="h5p-presentation-wrapper"> <div class="h5p-keywords-wrapper"></div> <div class="h5p-slides-wrapper"></div> </div> </div> <nav class="h5p-cp-navigation"> <ol class="h5p-progressbar list-unstyled"></ol> </nav> <div class="h5p-footer"></div></div>';e.attr("role","application").addClass("h5p-course-presentation").html(i),this.$container=e,this.$slideAnnouncer=e.find(".h5p-current-slide-announcer"),this.$fullscreenAnnouncer=e.find(".h5p-fullscreen-announcer"),this.$slideTop=this.$slideAnnouncer.next(),this.$wrapper=e.children(".h5p-wrapper").focus(function(){n.initKeyEvents()}).blur(function(){void 0!==n.keydown&&(H5P.jQuery("body").unbind("keydown",n.keydown),delete n.keydown)}).click(function(e){var t=H5P.jQuery(e.target),i=n.belongsToTagName(e.target,["input","textarea","a","button"],e.currentTarget),r=-1!==e.target.tabIndex,s=t.closest(".h5p-popup-container"),o=0!==s.length;if(!i&&!r&&!n.editor)if(o){var a=t.closest("[tabindex]");1===a.closest(".h5p-popup-container").length?a.focus():s.find(".h5p-close-popup").focus()}else n.$wrapper.focus();n.presentation.keywordListEnabled&&!n.presentation.keywordListAlwaysShow&&n.presentation.keywordListAutoHide&&!t.is("textarea, .h5p-icon-pencil, span")&&n.hideKeywords()}),this.on("exitFullScreen",function(){t.$footer.removeClass("footer-full-screen"),t.$fullScreenButton.attr("title",t.l10n.fullscreen),t.$fullscreenAnnouncer.html(t.l10n.accessibilityExitedFullscreen)}),this.on("enterFullScreen",function(){t.$fullscreenAnnouncer.html(t.l10n.accessibilityEnteredFullscreen)});var r=parseInt(this.$wrapper.css("width"));this.width=0!==r?r:640;var s=parseInt(this.$wrapper.css("height"));this.height=0!==s?s:400,this.ratio=16/9,this.fontSize=16,this.$boxWrapper=this.$wrapper.children(".h5p-box-wrapper");var o=this.$boxWrapper.children(".h5p-presentation-wrapper");this.$slidesWrapper=o.children(".h5p-slides-wrapper"),this.$keywordsWrapper=o.children(".h5p-keywords-wrapper"),this.$progressbar=this.$wrapper.find(".h5p-progressbar"),this.$footer=this.$wrapper.children(".h5p-footer"),this.initKeywords=void 0===this.presentation.keywordListEnabled||!0===this.presentation.keywordListEnabled||void 0!==this.editor,this.activeSurface&&void 0===this.editor&&(this.initKeywords=!1,this.$boxWrapper.css("height","100%")),this.isSolutionMode=!1,this.createSlides(),this.elementsAttached[this.currentSlideIndex]=!0;var a;if(this.showSummarySlide=!1,this.hideSummarySlide?this.showSummarySlide=!this.hideSummarySlide:this.slidesWithSolutions.forEach(function(e){n.showSummarySlide=e.length}),void 0===this.editor&&(this.showSummarySlide||this.hasAnswerElements)){var l={elements:[],keywords:[]};this.slides.push(l),a=H5P.jQuery(g.default.createHTML(l)).appendTo(this.$slidesWrapper),a.addClass("h5p-summary-slide"),this.isCurrentSlide(this.slides.length-1)&&(this.$current=a)}var c=this.getKeywordMenuConfig();c.length>0||this.isEditor()?(this.keywordMenu.init(c),this.keywordMenu.on("select",function(e){return t.keywordClick(e.data.index)}),this.keywordMenu.on("close",function(){return t.hideKeywords()}),this.keywordMenu.on("select",function(){t.$currentKeyword=t.$keywords.children(".h5p-current")}),this.$keywords=(0,m.jQuery)(this.keywordMenu.getElement()).appendTo(this.$keywordsWrapper),this.$currentKeyword=this.$keywords.children(".h5p-current"),this.setKeywordsOpacity(void 0===this.presentation.keywordListOpacity?90:this.presentation.keywordListOpacity),this.presentation.keywordListAlwaysShow&&this.showKeywords()):(this.$keywordsWrapper.remove(),this.initKeywords=!1),void 0===this.editor&&this.activeSurface?(this.$progressbar.add(this.$footer).remove(),H5P.fullscreenSupported&&(this.$fullScreenButton=H5P.jQuery("<div/>",{class:"h5p-toggle-full-screen",title:this.l10n.fullscreen,role:"button",tabindex:0,appendTo:this.$wrapper}),(0,y.addClickAndKeyboardListeners)(this.$fullScreenButton,function(){return n.toggleFullScreen()}))):(this.initTouchEvents(),this.navigationLine=new u.default(this),this.previousState&&this.previousState.progress||this.setSlideNumberAnnouncer(0,!1),this.summarySlideObject=new d.default(this,a)),new h.default(this),this.previousState&&this.previousState.progress&&this.jumpToSlide(this.previousState.progress)},S.prototype.belongsToTagName=function(e,t,n){if(!e)return!1;n=n||document.body,"string"==typeof t&&(t=[t]),t=t.map(function(e){return e.toLowerCase()});var i=e.tagName.toLowerCase();return-1!==t.indexOf(i)||n!==e&&this.belongsToTagName(e.parentNode,t,n)},S.prototype.updateKeywordMenuFromSlides=function(){this.keywordMenu.removeAllMenuItemElements();var e=this.getKeywordMenuConfig();return(0,m.jQuery)(this.keywordMenu.init(e))},S.prototype.getKeywordMenuConfig=function(){var e=this;return this.slides.map(function(t,n){return{title:e.createSlideTitle(t),subtitle:e.l10n.slide+" "+(n+1),index:n}}).filter(function(e){return null!==e.title})},S.prototype.createSlideTitle=function(e){var t=this.isEditor()?this.l10n.noTitle:null;return this.hasKeywords(e)?e.keywords[0].main:t},S.prototype.isEditor=function(){return void 0!==this.editor},S.prototype.hasKeywords=function(e){return void 0!==e.keywords&&e.keywords.length>0},S.prototype.createSlides=function(){for(var e=this,t=0;t<e.children.length;t++){var n=t===e.currentSlideIndex;e.children[t].getElement().appendTo(e.$slidesWrapper),n&&e.children[t].setCurrent(),(e.isEditor()||0===t||1===t||n)&&e.children[t].appendElements()}},S.prototype.hasScoreData=function(e){return"undefined"!==(void 0===e?"undefined":s(e))&&"function"==typeof e.getScore&&"function"==typeof e.getMaxScore},S.prototype.getScore=function(){var e=this;return(0,y.flattenArray)(e.slidesWithSolutions).reduce(function(t,n){return t+(e.hasScoreData(n)?n.getScore():0)},0)},S.prototype.getMaxScore=function(){var e=this;return(0,y.flattenArray)(e.slidesWithSolutions).reduce(function(t,n){return t+(e.hasScoreData(n)?n.getMaxScore():0)},0)},S.prototype.setProgressBarFeedback=function(e){var t=this;void 0!==e&&e?e.forEach(function(e){var n=t.progressbarParts[e.slide-1].find(".h5p-progressbar-part-has-task");if(n.hasClass("h5p-answered")){var i=e.score>=e.maxScore;n.addClass(i?"h5p-is-correct":"h5p-is-wrong"),t.navigationLine.updateSlideTitle(e.slide-1)}}):this.progressbarParts.forEach(function(e){e.find(".h5p-progressbar-part-has-task").removeClass("h5p-is-correct").removeClass("h5p-is-wrong")})},S.prototype.toggleKeywords=function(){this[this.$keywordsWrapper.hasClass("h5p-open")?"hideKeywords":"showKeywords"]()},S.prototype.hideKeywords=function(){this.$keywordsWrapper.hasClass("h5p-open")&&(void 0!==this.$keywordsButton&&(this.$keywordsButton.attr("title",this.l10n.showKeywords),this.$keywordsButton.attr("aria-label",this.l10n.showKeywords),this.$keywordsButton.attr("aria-expanded","false"),this.$keywordsButton.focus()),this.$keywordsWrapper.removeClass("h5p-open"))},S.prototype.showKeywords=function(){this.$keywordsWrapper.hasClass("h5p-open")||(void 0!==this.$keywordsButton&&(this.$keywordsButton.attr("title",this.l10n.hideKeywords),this.$keywordsButton.attr("aria-label",this.l10n.hideKeywords),this.$keywordsButton.attr("aria-expanded","true")),this.$keywordsWrapper.addClass("h5p-open"),this.presentation.keywordListAlwaysShow||this.$keywordsWrapper.find('li[tabindex="0"]').focus())},S.prototype.setKeywordsOpacity=function(e){var t=this.$keywordsWrapper.css("background-color").split(/\(|\)|,/g),n=r(t,3),i=n[0],s=n[1],o=n[2];this.$keywordsWrapper.css("background-color","rgba("+i+", "+s+", "+o+", "+e/100+")")},S.prototype.fitCT=function(){void 0===this.editor&&this.$current.find(".h5p-ct").each(function(){for(var e=100,t=H5P.jQuery(this),n=t.parent().height();t.outerHeight()>n&&(e--,t.css({fontSize:e+"%",lineHeight:e+65+"%"}),!(e<0)););})},S.prototype.resize=function(){var e=this.$container.hasClass("h5p-fullscreen")||this.$container.hasClass("h5p-semi-fullscreen");if(!this.ignoreResize){this.$wrapper.css("width","auto");var t=this.$container.width(),n={};if(e){var i=this.$container.height();t/i>this.ratio&&(t=i*this.ratio,n.width=t+"px")}var r=t/this.width;n.height=t/this.ratio+"px",n.fontSize=this.fontSize*r+"px",void 0!==this.editor&&this.editor.setContainerEm(this.fontSize*r*.75),this.$wrapper.css(n),this.swipeThreshold=100*r;var s=this.elementInstances[this.$current.index()];if(void 0!==s)for(var o=this.slides[this.$current.index()].elements,a=0;a<s.length;a++){var l=s[a];void 0!==l.preventResize&&!1!==l.preventResize||void 0===l.$||o[a].displayAsButton||H5P.trigger(l,"resize")}this.fitCT()}},S.prototype.toggleFullScreen=function(){H5P.isFullscreen||this.$container.hasClass("h5p-fullscreen")||this.$container.hasClass("h5p-semi-fullscreen")?void 0!==H5P.exitFullScreen&&void 0!==H5P.fullScreenBrowserPrefix?H5P.exitFullScreen():void 0===H5P.fullScreenBrowserPrefix?H5P.jQuery(".h5p-disable-fullscreen").click():""===H5P.fullScreenBrowserPrefix?window.top.document.exitFullScreen():"ms"===H5P.fullScreenBrowserPrefix?window.top.document.msExitFullscreen():window.top.document[H5P.fullScreenBrowserPrefix+"CancelFullScreen"]():(this.$footer.addClass("footer-full-screen"),this.$fullScreenButton.attr("title",this.l10n.exitFullscreen),H5P.fullScreen(this.$container,this),void 0===H5P.fullScreenBrowserPrefix&&H5P.jQuery(".h5p-disable-fullscreen").hide())},S.prototype.focus=function(){this.$wrapper.focus()},S.prototype.keywordClick=function(e){this.shouldHideKeywordsAfterSelect()&&this.hideKeywords(),this.jumpToSlide(e,!0)},S.prototype.shouldHideKeywordsAfterSelect=function(){return this.presentation.keywordListEnabled&&!this.presentation.keywordListAlwaysShow&&this.presentation.keywordListAutoHide&&void 0===this.editor},S.prototype.setElementsOverride=function(e){this.elementsOverride={params:{}},e&&(this.elementsOverride.params.behaviour={},e.showSolutionButton&&(this.elementsOverride.params.behaviour.enableSolutionsButton="on"===e.showSolutionButton),e.retryButton&&(this.elementsOverride.params.behaviour.enableRetry="on"===e.retryButton))},S.prototype.attachElements=function(e,t){if(void 0===this.elementsAttached[t]){var n=this.slides[t],i=this.elementInstances[t];if(void 0!==n.elements)for(var r=0;r<n.elements.length;r++)this.attachElement(n.elements[r],i[r],e,t);this.trigger("domChanged",{$target:e,library:"CoursePresentation",key:"newSlide"},{bubbles:!0,external:!0}),this.elementsAttached[t]=!0}},S.prototype.attachElement=function(e,t,n,i){var r=void 0!==e.displayAsButton&&e.displayAsButton,s=void 0!==e.buttonSize?"h5p-element-button-"+e.buttonSize:"",o="h5p-element"+(r?" h5p-element-button-wrapper":"")+(s.length?" "+s:""),a=H5P.jQuery("<div>",{class:o}).css({left:e.x+"%",top:e.y+"%",width:e.width+"%",height:e.height+"%"}).appendTo(n),l=void 0===e.backgroundOpacity||0===e.backgroundOpacity;if(a.toggleClass("h5p-transparent",l),r){this.createInteractionButton(e,t).appendTo(a)}else{var d=e.action&&e.action.library,c=d?this.getLibraryTypePmz(e.action.library):"other",u=H5P.jQuery("<div>",{class:"h5p-element-outer "+c+"-outer-element"}).css({background:"rgba(255,255,255,"+(void 0===e.backgroundOpacity?0:e.backgroundOpacity/100)+")"}).appendTo(a),p=H5P.jQuery("<div>",{class:"h5p-element-inner"}).appendTo(u);if(t.on("set-size",function(e){for(var t in e.data)a.get(0).style[t]=e.data[t]}),t.attach(p),void 0!==e.action&&"H5P.InteractiveVideo"===e.action.library.substr(0,20)){var h=function(){t.$container.addClass("h5p-fullscreen"),t.controls.$fullscreen&&t.controls.$fullscreen.remove(),t.hasFullScreen=!0,t.controls.$play.hasClass("h5p-pause")?t.$controls.addClass("h5p-autohide"):t.enableAutoHide()};void 0!==t.controls?h():t.on("controls",h)}this.setOverflowTabIndex()}return void 0!==this.editor?this.editor.processElement(e,a,i,t):(e.solution&&this.addElementSolutionButton(e,t,a),this.hasAnswerElements=this.hasAnswerElements||void 0!==t.exportAnswers),a},S.prototype.disableTabIndexes=function(){var e=this.$container.find(".h5p-popup-container");this.$tabbables=this.$container.find("a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]").filter(function(){var t=(0,m.jQuery)(this),n=m.jQuery.contains(e.get(0),t.get(0));if(t.data("tabindex"))return!0;if(!n){var i=t.attr("tabindex");return t.data("tabindex",i),t.attr("tabindex","-1"),!0}return!1})},S.prototype.restoreTabIndexes=function(){this.$tabbables&&this.$tabbables.each(function(){var e=(0,m.jQuery)(this),t=e.data("tabindex");e.hasClass("ui-slider-handle")?(e.attr("tabindex",0),e.removeData("tabindex")):void 0!==t?(e.attr("tabindex",t),e.removeData("tabindex")):e.removeAttr("tabindex")})},S.prototype.createInteractionButton=function(e,t){var n=this,i=e.action.params&&e.action.params.cpAutoplay,r=e.action.metadata?e.action.metadata.title:"";""===r&&(r=e.action.params&&e.action.params.contentName||e.action.library.split(" ")[0].split(".")[1]);var s=this.getLibraryTypePmz(e.action.library),o=function(e){return function(){return e.attr("aria-expanded","false")}},a=(0,m.jQuery)("<div>",{role:"button",tabindex:0,"aria-label":r,"aria-popup":!0,"aria-expanded":!1,class:"h5p-element-button h5p-element-button-"+e.buttonSize+" "+s+"-button"}),l=(0,m.jQuery)('<div class="h5p-button-element"></div>');t.attach(l);var d="h5p-advancedtext"===s?{x:e.x,y:e.y}:null;return(0,y.addClickAndKeyboardListeners)(a,function(){a.attr("aria-expanded","true"),n.showInteractionPopup(t,a,l,s,i,o(a),d),n.disableTabIndexes()}),void 0!==e.action&&"H5P.InteractiveVideo"===e.action.library.substr(0,20)&&t.on("controls",function(){t.controls.$fullscreen&&t.controls.$fullscreen.remove()}),a},S.prototype.showInteractionPopup=function(e,t,n,i,r,s){var o=this,a=arguments.length>6&&void 0!==arguments[6]?arguments[6]:null,l=function(){e.trigger("resize")};if(!this.isEditor()){this.on("exitFullScreen",l),this.showPopup(n,t,a,function(){o.pauseMedia(e),n.detach(),o.off("exitFullScreen",l),s()},i),H5P.trigger(e,"resize"),"h5p-image"===i&&this.resizePopupImage(n);n.closest(".h5p-popup-container");setTimeout(function(){var e=n.find(":input").add(n.find("[tabindex]"));e.length?e[0].focus():(n.attr("tabindex",0),n.focus())},200),(0,y.isFunction)(e.setActivityStarted)&&(0,y.isFunction)(e.getScore)&&e.setActivityStarted(),r&&(0,y.isFunction)(e.play)&&e.play()}},S.prototype.getLibraryTypePmz=function(e){return(0,y.kebabCase)(e.split(" ")[0]).toLowerCase()},S.prototype.resizePopupImage=function(e){var t=Number(e.css("fontSize").replace("px","")),n=e.find("img"),i=function(n,i){if(!(i/t<18.5)){var r=n/i;i=18.5*t,e.css({width:i*r,height:i})}};n.height()?i(n.width(),n.height()):n.one("load",function(){i(this.width,this.height)})},S.prototype.addElementSolutionButton=function(e,t,n){var i=this;t.showCPComments=function(){if(0===n.children(".h5p-element-solution").length&&(0,y.stripHTML)(e.solution).length>0){var t=(0,m.jQuery)("<div/>",{role:"button",tabindex:0,title:i.l10n.solutionsButtonTitle,"aria-popup":!0,"aria-expanded":!1,class:"h5p-element-solution"}).append('<span class="joubel-icon-comment-normal"><span class="h5p-icon-shadow"></span><span class="h5p-icon-speech-bubble"></span><span class="h5p-icon-question"></span></span>').appendTo(n),r={x:e.x,y:e.y};e.displayAsButton||(r.x+=e.width-4,r.y+=e.height-12),(0,y.addClickAndKeyboardListeners)(t,function(){return i.showPopup(e.solution,t,r)})}},void 0!==e.alwaysDisplayComments&&e.alwaysDisplayComments&&t.showCPComments()},S.prototype.showPopup=function(e,t){var n,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,r=this,s=arguments[3],o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"h5p-popup-comment-field",a=this,l=function(e){if(n)return void(n=!1);void 0!==s&&setTimeout(function(){s(),a.restoreTabIndexes()},100),e.preventDefault(),d.addClass("h5p-animate"),d.find(".h5p-popup-container").addClass("h5p-animate"),setTimeout(function(){d.remove()},100),t.focus()},d=(0,m.jQuery)('<div class="h5p-popup-overlay '+o+'"><div class="h5p-popup-container" role="dialog"><div class="h5p-cp-dialog-titlebar"><div class="h5p-dialog-title"></div><div role="button" tabindex="0" class="h5p-close-popup" title="'+this.l10n.close+'"></div></div><div class="h5p-popup-wrapper" role="document"></div></div></div>'),c=d.find(".h5p-popup-wrapper");e instanceof H5P.jQuery?c.append(e):c.html(e);var u=d.find(".h5p-popup-container");return function(e,t,n){if(n){t.css({visibility:"hidden"}),e.prependTo(r.$wrapper);var i=t.height(),s=t.width(),o=e.height(),a=e.width(),l=s*(100/a),d=i*(100/o);if(l>50&&d>50)return void e.detach();l>d&&d<45&&(l=Math.sqrt(l*d),t.css({width:l+"%"}));var c=100-l-7.5,u=n.x;n.x>c?u=c:n.x<7.5&&(u=7.5),d=t.height()*(100/o);var p=100-d-10,h=n.y;n.y>p?h=p:n.y<10&&(h=10),e.detach(),t.css({left:u+"%",top:h+"%"})}}(d,u,i),d.addClass("h5p-animate"),u.css({visibility:""}).addClass("h5p-animate"),d.prependTo(this.$wrapper).focus().removeClass("h5p-animate").click(l).find(".h5p-popup-container").removeClass("h5p-animate").click(function(){n=!0}).keydown(function(e){e.which===y.keyCode.ESC&&l(e)}).end(),(0,y.addClickAndKeyboardListeners)(d.find(".h5p-close-popup"),function(e){return l(e)}),d},S.prototype.checkForSolutions=function(e){return void 0!==e.showSolutions||void 0!==e.showCPComments},S.prototype.initKeyEvents=function(){if(void 0===this.keydown&&!this.activeSurface){var e=this,t=!1;this.keydown=function(n){t||(37!==n.keyCode&&33!==n.keyCode||!e.previousSlide()?39!==n.keyCode&&34!==n.keyCode||!e.nextSlide()||(n.preventDefault(),t=!0):(n.preventDefault(),t=!0),t&&setTimeout(function(){t=!1},300))},H5P.jQuery("body").keydown(this.keydown)}},S.prototype.initTouchEvents=function(){var e,t,n,i,r,s,o=this,a=!1,l=!1,d=function(e){return{"-webkit-transform":e,"-moz-transform":e,"-ms-transform":e,transform:e}},c=d("");this.$slidesWrapper.bind("touchstart",function(d){l=!1,n=e=d.originalEvent.touches[0].pageX,t=d.originalEvent.touches[0].pageY;var c=o.$slidesWrapper.width();i=0===o.currentSlideIndex?0:-c,r=o.currentSlideIndex+1>=o.slides.length?0:c,s=null,a=!0}).bind("touchmove",function(c){var u=c.originalEvent.touches;a&&(o.$current.prev().addClass("h5p-touch-move"),o.$current.next().addClass("h5p-touch-move"),a=!1),n=u[0].pageX;var p=e-n;null===s&&(s=Math.abs(t-c.originalEvent.touches[0].pageY)>Math.abs(p)),1!==u.length||s||(c.preventDefault(),l||(p<0?o.$current.prev().css(d("translateX("+(i-p)+"px")):o.$current.next().css(d("translateX("+(r-p)+"px)")),o.$current.css(d("translateX("+-p+"px)"))))}).bind("touchend",function(){if(!s){var t=e-n;if(t>o.swipeThreshold&&o.nextSlide()||t<-o.swipeThreshold&&o.previousSlide())return}o.$slidesWrapper.children().css(c).removeClass("h5p-touch-move")})},S.prototype.updateTouchPopup=function(e,t,n,i){if(arguments.length<=0)return void(void 0!==this.touchPopup&&this.touchPopup.remove());var r="";if(void 0!==this.$keywords&&void 0!==this.$keywords.children(":eq("+t+")").find("span").html())r+=this.$keywords.children(":eq("+t+")").find("span").html();else{var s=t+1;r+=this.l10n.slide+" "+s}void 0===this.editor&&t>=this.slides.length-1&&(r=this.l10n.showSolutions),void 0===this.touchPopup?this.touchPopup=H5P.jQuery("<div/>",{class:"h5p-touch-popup"}).insertAfter(e):this.touchPopup.insertAfter(e),i-.15*e.parent().height()<0?i=0:i-=.15*e.parent().height(),this.touchPopup.css({"max-width":e.width()-n,left:n,top:i}),this.touchPopup.html(r)},S.prototype.previousSlide=function(e){var t=this.$current.prev();return!!t.length&&this.jumpToSlide(t.index(),e,!1)},S.prototype.nextSlide=function(e){var t=this.$current.next();return!!t.length&&this.jumpToSlide(t.index(),e,!1)},S.prototype.isCurrentSlide=function(e){return this.currentSlideIndex===e},S.prototype.getCurrentSlideIndex=function(){return this.currentSlideIndex},S.prototype.attachAllElements=function(){for(var e=this.$slidesWrapper.children(),t=0;t<this.slides.length;t++)this.attachElements(e.eq(t),t);void 0!==this.summarySlideObject&&this.summarySlideObject.updateSummarySlide(this.slides.length-1,!0)},S.prototype.jumpToSlide=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=this;if(void 0===this.editor&&this.contentId){var r=this.createXAPIEventTemplate("progressed");r.data.statement.object.definition.extensions["http://id.tincanapi.com/extension/ending-point"]=e+1,this.trigger(r)}if(!this.$current.hasClass("h5p-animate")){var s=this.$current.addClass("h5p-animate"),o=i.$slidesWrapper.children(),a=o.filter(":lt("+e+")");this.$current=o.eq(e).addClass("h5p-animate");var l=this.currentSlideIndex;this.currentSlideIndex=e,this.attachElements(this.$current,e);var d=this.$current.next();d.length&&this.attachElements(d,e+1),this.setOverflowTabIndex();var c=this.elementInstances[l];if(void 0!==c)for(var u=0;u<c.length;u++)this.slides[l].elements[u].displayAsButton||i.pauseMedia(c[u]);return setTimeout(function(){s.removeClass("h5p-current"),o.css({"-webkit-transform":"","-moz-transform":"","-ms-transform":"",transform:""}).removeClass("h5p-touch-move").removeClass("h5p-previous"),a.addClass("h5p-previous"),i.$current.addClass("h5p-current"),i.trigger("changedSlide",i.$current.index())},1),setTimeout(function(){if(i.$slidesWrapper.children().removeClass("h5p-animate"),void 0===i.editor){var e=i.elementInstances[i.currentSlideIndex],t=i.slides[i.currentSlideIndex].elements;if(void 0!==e)for(var n=0;n<e.length;n++)t[n]&&t[n].action&&t[n].action.params&&t[n].action.params.cpAutoplay&&!t[n].displayAsButton&&"function"==typeof e[n].play&&e[n].play(),t[n].displayAsButton||"function"!=typeof e[n].setActivityStarted||"function"!=typeof e[n].getScore||e[n].setActivityStarted()}},250),void 0!==this.$keywords&&(this.keywordMenu.setCurrentSlideIndex(e),this.$currentKeyword=this.$keywords.find(".h5p-current"),t||this.keywordMenu.scrollToKeywords(e)),i.presentation.keywordListEnabled&&i.presentation.keywordListAlwaysShow&&i.showKeywords(),i.navigationLine&&(i.navigationLine.updateProgressBar(e,l,this.isSolutionMode),i.navigationLine.updateFooter(e),this.setSlideNumberAnnouncer(e,n)),i.summarySlideObject&&i.summarySlideObject.updateSummarySlide(e,!0),void 0!==this.editor&&void 0!==this.editor.dnb&&(this.editor.dnb.setContainer(this.$current),this.editor.dnb.blurAll()),this.trigger("resize"),this.fitCT(),!0}},S.prototype.setOverflowTabIndex=function(){void 0!==this.$current&&this.$current.find(".h5p-element-inner").each(function(){var e=(0,m.jQuery)(this),t=void 0;this.classList.contains("h5p-table")&&(t=e.find(".h5p-table").outerHeight());var n=e.closest(".h5p-element-outer").innerHeight();void 0!==t&&null!==n&&t>n&&e.attr("tabindex",0)})},S.prototype.setSlideNumberAnnouncer=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n="";if(!this.navigationLine)return n;var i=this.slides[e];i.keywords&&i.keywords.length>0&&!this.navigationLine.isSummarySlide(e)&&(n+=this.l10n.slide+" "+(e+1)+": "),n+=this.navigationLine.createSlideTitle(e),this.$slideAnnouncer.html(n),t&&this.$slideTop.focus()},S.prototype.resetTask=function(){this.summarySlideObject.toggleSolutionMode(!1);for(var e=0;e<this.slidesWithSolutions.length;e++)if(void 0!==this.slidesWithSolutions[e])for(var t=0;t<this.slidesWithSolutions[e].length;t++){var n=this.slidesWithSolutions[e][t];n.resetTask&&n.resetTask()}this.navigationLine.updateProgressBar(0),this.jumpToSlide(0,!1),this.$container.find(".h5p-popup-overlay").remove()},S.prototype.showSolutions=function(){for(var e=!1,t=[],n=!1,i=0;i<this.slidesWithSolutions.length;i++)if(void 0!==this.slidesWithSolutions[i]){this.elementsAttached[i]||this.attachElements(this.$slidesWrapper.children(":eq("+i+")"),i),e||(this.jumpToSlide(i,!1),e=!0);for(var r=0,s=0,o=[],a=0;a<this.slidesWithSolutions[i].length;a++){var l=this.slidesWithSolutions[i][a];void 0!==l.addSolutionButton&&l.addSolutionButton(),l.showSolutions&&l.showSolutions(),l.showCPComments&&l.showCPComments(),void 0!==l.getMaxScore&&(s+=l.getMaxScore(),r+=l.getScore(),n=!0,o.push(l.coursePresentationIndexOnSlide))}t.push({indexes:o,slide:i+1,score:r,maxScore:s})}if(n)return t},S.prototype.getSlideScores=function(e){for(var t=!0===e,n=[],i=!1,r=0;r<this.slidesWithSolutions.length;r++)if(void 0!==this.slidesWithSolutions[r]){this.elementsAttached[r]||this.attachElements(this.$slidesWrapper.children(":eq("+r+")"),r),t||(this.jumpToSlide(r,!1),t=!0);for(var s=0,o=0,a=[],l=0;l<this.slidesWithSolutions[r].length;l++){var d=this.slidesWithSolutions[r][l];void 0!==d.getMaxScore&&(o+=d.getMaxScore(),s+=d.getScore(),i=!0,a.push(d.coursePresentationIndexOnSlide))}n.push({indexes:a,slide:r+1,score:s,maxScore:o})}if(i)return n},S.prototype.getCopyrights=function(){var e,t=new H5P.ContentCopyrights;if(this.presentation&&this.presentation.globalBackgroundSelector&&this.presentation.globalBackgroundSelector.imageGlobalBackground){var n=this.presentation.globalBackgroundSelector.imageGlobalBackground,i=new H5P.MediaCopyright(n.copyright);i.setThumbnail(new H5P.Thumbnail(H5P.getPath(n.path,this.contentId),n.width,n.height)),t.addMedia(i)}for(var r=0;r<this.slides.length;r++){var s=new H5P.ContentCopyrights;if(s.setLabel(this.l10n.slide+" "+(r+1)),this.slides[r]&&this.slides[r].slideBackgroundSelector&&this.slides[r].slideBackgroundSelector.imageSlideBackground){var o=this.slides[r].slideBackgroundSelector.imageSlideBackground,a=new H5P.MediaCopyright(o.copyright);a.setThumbnail(new H5P.Thumbnail(H5P.getPath(o.path,this.contentId),o.width,o.height)),s.addMedia(a)}if(void 0!==this.elementInstances[r])for(var l=0;l<this.elementInstances[r].length;l++){var d=this.elementInstances[r][l];if(this.slides[r].elements[l].action){var c=this.slides[r].elements[l].action.params,u=this.slides[r].elements[l].action.metadata;e=void 0,void 0!==d.getCopyrights&&(e=d.getCopyrights()),void 0===e&&(e=new H5P.ContentCopyrights,H5P.findCopyrights(e,c,this.contentId,{metadata:u,machineName:d.libraryInfo.machineName}));var p=l+1;void 0!==c.contentName?p+=": "+c.contentName:void 0!==d.getTitle?p+=": "+d.getTitle():c.l10n&&c.l10n.name&&(p+=": "+c.l10n.name),e.setLabel(p),s.addContent(e)}}t.addContent(s)}return t},S.prototype.pauseMedia=function(e){try{void 0!==e.pause&&(e.pause instanceof Function||"function"==typeof e.pause)?e.pause():void 0!==e.video&&void 0!==e.video.pause&&(e.video.pause instanceof Function||"function"==typeof e.video.pause)?e.video.pause():void 0!==e.stop&&(e.stop instanceof Function||"function"==typeof e.stop)&&e.stop()}catch(e){H5P.error(e)}},S.prototype.getXAPIData=function(){var e=this.createXAPIEventTemplate("answered"),t=e.getVerifiedStatementValue(["object","definition"]);H5P.jQuery.extend(t,{interactionType:"compound",type:"http://adlnet.gov/expapi/activities/cmi.interaction"});var n=this.getScore(),i=this.getMaxScore();e.setScoredResult(n,i,this,!0,n===i);var r=(0,y.flattenArray)(this.slidesWithSolutions).map(function(e){if(e&&e.getXAPIData)return e.getXAPIData()}).filter(function(e){return!!e});return{statement:e.data.statement,children:r}},t.default=S},function(e,t,n){"use strict";function i(e,t){var n=this;n.index=e,n.parent=t}e.exports=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=n(0),r=n(1),s=function(){function e(e,t){this.$summarySlide=t,this.cp=e}return e.prototype.updateSummarySlide=function(e,t){var n=this,r=void 0===this.cp.editor&&void 0!==this.$summarySlide&&e>=this.cp.slides.length-1,s=!this.cp.showSummarySlide&&this.cp.hasAnswerElements;if(r){n.cp.presentation.keywordListEnabled&&n.cp.presentation.keywordListAlwaysShow&&n.cp.hideKeywords(),this.$summarySlide.children().remove();var o=n.cp.getSlideScores(t),a=n.outputScoreStats(o);if((0,i.jQuery)(a).appendTo(n.$summarySlide),!s){var l=n.totalScores(o);if(!isNaN(l.totalPercentage)){var d=i.JoubelUI.createScoreBar(l.totalMaxScore,"","","");d.setScore(l.totalScore);var c=(0,i.jQuery)(".h5p-summary-total-score",n.$summarySlide);d.appendTo(c),setTimeout(function(){c.append((0,i.jQuery)("<div/>",{"aria-live":"polite",class:"hidden-but-read",html:n.cp.l10n.summary+". "+n.cp.l10n.accessibilityTotalScore.replace("@score",l.totalScore).replace("@maxScore",l.totalMaxScore)}))},100)}if(1==n.cp.enableTwitterShare){var u=(0,i.jQuery)(".h5p-summary-twitter-message",n.$summarySlide);this.addTwitterScoreLinkTo(u,l)}if(1==n.cp.enableFacebookShare){var p=(0,i.jQuery)(".h5p-summary-facebook-message",n.$summarySlide);this.addFacebookScoreLinkTo(p,l)}if(1==n.cp.enableGoogleShare){var h=(0,i.jQuery)(".h5p-summary-google-message",n.$summarySlide);this.addGoogleScoreLinkTo(h)}n.$summarySlide.find(".h5p-td > .h5p-slide-link").each(function(){var e=(0,i.jQuery)(this);e.click(function(t){n.cp.jumpToSlide(parseInt(e.data("slide"),10)-1),t.preventDefault()})})}var f=(0,i.jQuery)(".h5p-summary-footer",n.$summarySlide);this.cp.showSummarySlideSolutionButton&&i.JoubelUI.createButton({class:"h5p-show-solutions",html:n.cp.l10n.showSolutions,on:{click:function(){n.toggleSolutionMode(!0)}},appendTo:f}),this.cp.showSummarySlideRetryButton&&i.JoubelUI.createButton({class:"h5p-cp-retry-button",html:n.cp.l10n.retry,on:{click:function(){n.cp.resetTask()}},appendTo:f}),n.cp.hasAnswerElements&&i.JoubelUI.createButton({class:"h5p-eta-export",html:n.cp.l10n.exportAnswers,on:{click:function(){H5P.ExportableTextArea.Exporter.run(n.cp.slides,n.cp.elementInstances)}},appendTo:f})}},e.prototype.outputScoreStats=function(e){var t=this;if(void 0===e)return this.$summarySlide.addClass("h5p-summary-only-export"),'<div class="h5p-summary-footer"></div>';var n,i=this,r=0,s=0,o="",a=0,l="";for(n=0;n<e.length;n+=1)l=t.getSlideDescription(e[n]),a=Math.round(e[n].score/e[n].maxScore*100),isNaN(a)&&(a=0),o+='<tr><td class="h5p-td h5p-summary-task-title"><a href="#" class="h5p-slide-link" aria-label=" '+i.cp.l10n.slide+" "+e[n].slide+": "+l.replace(/(<([^>]+)>)/gi,"")+" "+a+'%" data-slide="'+e[n].slide+'">'+i.cp.l10n.slide+" "+e[n].slide+": "+l.replace(/(<([^>]+)>)/gi,"")+'</a></td><td class="h5p-td h5p-summary-score-bar"><p class="hidden-but-read">'+a+"%</p><p>"+e[n].score+"<span>/</span>"+e[n].maxScore+"</p></td></tr>",r+=e[n].score,s+=e[n].maxScore;i.cp.triggerXAPICompleted(r,s);var d=i.cp.enableTwitterShare||i.cp.enableFacebookShare||i.cp.enableGoogleShare?'<span class="h5p-show-results-text">'+i.cp.l10n.shareResult+"</span>":"",c=1==i.cp.enableTwitterShare?'<span class="h5p-summary-twitter-message" aria-label="'+i.cp.l10n.shareTwitter+'"></span>':"",u=1==i.cp.enableFacebookShare?'<span class="h5p-summary-facebook-message" aria-label="'+i.cp.l10n.shareFacebook+'"></span>':"",p=1==i.cp.enableGoogleShare?'<span class="h5p-summary-google-message" aria-label="'+i.cp.l10n.shareGoogle+'"></span>':"";return'<div class="h5p-summary-table-holder"><div class="h5p-summary-table-pages"><table class="h5p-score-table"><thead><tr><th class="h5p-summary-table-header slide">'+i.cp.l10n.slide+'</th><th class="h5p-summary-table-header score">'+i.cp.l10n.score+"<span>/</span>"+i.cp.l10n.total.toLowerCase()+"</th></tr></thead><tbody>"+o+'</tbody></table></div><div class="h5p-summary-total-table"><div class="h5p-summary-social">'+d+u+c+p+'</div><div class="h5p-summary-total-score"><p>'+i.cp.l10n.totalScore+'</p></div></div></div><div class="h5p-summary-footer"></div>'},e.prototype.getSlideDescription=function(e){var t,n,i=this,r=i.cp.slides[e.slide-1].elements;if(e.indexes.length>1)t=i.cp.l10n.summaryMultipleTaskText;else if(void 0!==r[e.indexes[0]]&&r[0])if(n=r[e.indexes[0]].action,"function"==typeof i.cp.elementInstances[e.slide-1][e.indexes[0]].getTitle)t=i.cp.elementInstances[e.slide-1][e.indexes[0]].getTitle();else if(void 0!==n.library&&n.library){var s=n.library.split(" ")[0].split(".")[1].split(/(?=[A-Z])/),o="";s.forEach(function(e,t){0!==t&&(e=e.toLowerCase()),o+=e,t<=s.length-1&&(o+=" ")}),t=o}return t},e.prototype.addTwitterScoreLinkTo=function(e,t){var n=this,i=n.cp.twitterShareStatement||"",s=n.cp.twitterShareHashtags||"",o=n.cp.twitterShareUrl||"";o=o.replace("@currentpageurl",window.location.href),i=i.replace("@score",t.totalScore).replace("@maxScore",t.totalMaxScore).replace("@percentage",t.totalPercentage+"%").replace("@currentpageurl",window.location.href),s=s.trim().replace(" ",""),i=encodeURIComponent(i),s=encodeURIComponent(s),o=encodeURIComponent(o);var a="https://twitter.com/intent/tweet?";a+=i.length>0?"text="+i+"&":"",a+=o.length>0?"url="+o+"&":"",a+=s.length>0?"hashtags="+s:"";var l=window.innerWidth/2,d=window.innerHeight/2;e.attr("tabindex","0").attr("role","button"),(0,r.addClickAndKeyboardListeners)(e,function(){return window.open(a,n.cp.l10n.shareTwitter,"width=800,height=300,left="+l+",top="+d),!1})},e.prototype.addFacebookScoreLinkTo=function(e,t){var n=this,i=n.cp.facebookShareUrl||"",s=n.cp.facebookShareQuote||"";i=i.replace("@currentpageurl",window.location.href),s=s.replace("@currentpageurl",window.location.href).replace("@percentage",t.totalPercentage+"%").replace("@score",t.totalScore).replace("@maxScore",t.totalMaxScore),i=encodeURIComponent(i),s=encodeURIComponent(s);var o="https://www.facebook.com/sharer/sharer.php?";o+=i.length>0?"u="+i+"&":"",o+=s.length>0?"quote="+s:"";var a=window.innerWidth/2,l=window.innerHeight/2;e.attr("tabindex","0").attr("role","button"),(0,r.addClickAndKeyboardListeners)(e,function(){return window.open(o,n.cp.l10n.shareFacebook,"width=800,height=300,left="+a+",top="+l),!1})},e.prototype.addGoogleScoreLinkTo=function(e){var t=this,n=t.cp.googleShareUrl||"";n=n.replace("@currentpageurl",window.location.href),n=encodeURIComponent(n);var i="https://plus.google.com/share?";i+=n.length>0?"url="+n:"";var s=window.innerWidth/2,o=window.innerHeight/2;e.attr("tabindex","0").attr("role","button"),(0,r.addClickAndKeyboardListeners)(e,function(){return window.open(i,t.cp.l10n.shareGoogle,"width=401,height=437,left="+s+",top="+o),!1})},e.prototype.totalScores=function(e){if(void 0===e)return{totalScore:0,totalMaxScore:0,totalPercentage:0};var t,n=0,i=0;for(t=0;t<e.length;t+=1)n+=e[t].score,i+=e[t].maxScore;var r=Math.round(n/i*100);return isNaN(r)&&(r=0),{totalScore:n,totalMaxScore:i,totalPercentage:r}},e.prototype.toggleSolutionMode=function(e){var t=this;if(this.cp.isSolutionMode=e,e){var n=t.cp.showSolutions();this.cp.setProgressBarFeedback(n),this.cp.$footer.addClass("h5p-footer-solution-mode"),this.setFooterSolutionModeText(this.cp.l10n.solutionModeText)}else this.cp.$footer.removeClass("h5p-footer-solution-mode"),this.setFooterSolutionModeText(),this.cp.setProgressBarFeedback()},e.prototype.setFooterSolutionModeText=function(e){void 0!==e&&e?this.cp.$exitSolutionModeText.html(e):this.cp.$exitSolutionModeText&&this.cp.$exitSolutionModeText.html("")},e}();t.default=s},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0});var s=n(18),o=i(s),a=n(3),l=i(a),d=n(5),c=i(d),u=n(1),p={NO_INTERACTIONS:"none",NOT_ANSWERED:"not-answered",ANSWERED:"answered",CORRECT:"has-only-correct",INCORRECT:"has-incorrect"},h=function(e){function t(e){var t;this.cp=e,this.answeredLabels=(t={},r(t,p.NOT_ANSWERED,this.cp.l10n.containsNotCompleted),r(t,p.ANSWERED,this.cp.l10n.containsCompleted),r(t,p.CORRECT,this.cp.l10n.containsOnlyCorrect),r(t,p.INCORRECT,this.cp.l10n.containsIncorrectAnswers),r(t,p.NO_INTERACTIONS,"@slideName"),t),this.initProgressbar(this.cp.slidesWithSolutions),this.initFooter(),this.initTaskAnsweredListener(),this.toggleNextAndPreviousButtonDisabled(this.cp.getCurrentSlideIndex())}return t.prototype.initTaskAnsweredListener=function(){var e=this;this.cp.elementInstances.forEach(function(t,n){t.filter(function(e){return(0,u.isFunction)(e.on)}).forEach(function(t){t.on("xAPI",function(t){var i=t.getVerb();if((0,u.contains)(["interacted","answered","attempted"],i)){var r=e.cp.slideHasAnsweredTask(n);e.setTaskAnswered(n,r)}else"completed"===i&&t.setVerb("answered");void 0===t.data.statement.context.extensions&&(t.data.statement.context.extensions={}),t.data.statement.context.extensions["http://id.tincanapi.com/extension/ending-point"]=n+1})})})},t.prototype.initProgressbar=function(t){var n=this,i=this,r=i.cp.previousState&&i.cp.previousState.progress||0;this.progresbarKeyboardControls=new l.default([new c.default]),this.progresbarKeyboardControls.negativeTabIndexAllowed=!0,this.progresbarKeyboardControls.on("select",function(t){i.displaySlide(e(t.element).data("slideNumber"))}),this.progresbarKeyboardControls.on("beforeNextElement",function(e){return e.index!==e.elements.length-1}),this.progresbarKeyboardControls.on("beforePreviousElement",function(e){return 0!==e.index}),void 0!==this.cp.progressbarParts&&this.cp.progressbarParts&&this.cp.progressbarParts.forEach(function(e){i.progresbarKeyboardControls.removeElement(e.children("a").get(0)),e.remove()}),i.cp.progressbarParts=[];for(var s=function(t){t.preventDefault();var n=e(this).data("slideNumber");i.progresbarKeyboardControls.setTabbableByIndex(n),i.displaySlide(n),i.cp.focus()},o=0;o<this.cp.slides.length;o+=1){var a=this.cp.slides[o],d=this.createSlideTitle(o),p=e("<li>",{class:"h5p-progressbar-part"}).appendTo(i.cp.$progressbar),h=e("<a>",{href:"#",html:'<span class="h5p-progressbar-part-title hidden-but-read">'+d+"</span>",tabindex:"-1"}).data("slideNumber",o).click(s).appendTo(p);if(this.progresbarKeyboardControls.addElement(h.get(0)),u.isIOS||function(){var t=e("<div/>",{class:"h5p-progressbar-popup",html:d,"aria-hidden":"true"}).appendTo(p);p.mouseenter(function(){return n.ensurePopupVisible(t)})}(),this.isSummarySlide(o)&&p.addClass("progressbar-part-summary-slide"),0===o&&p.addClass("h5p-progressbar-part-show"),o===r&&p.addClass("h5p-progressbar-part-selected"),i.cp.progressbarParts.push(p),this.updateSlideTitle(o),this.cp.slides.length<=60&&a.elements&&a.elements.length>0){var f=t[o]&&t[o].length>0,v=!!(i.cp.previousState&&i.cp.previousState.answered&&i.cp.previousState.answered[o]);f&&(e("<div>",{class:"h5p-progressbar-part-has-task"}).appendTo(h),this.setTaskAnswered(o,v))}}},t.prototype.ensurePopupVisible=function(e){var t=this.cp.$container.width(),n=e.outerWidth(),i=e.offset().left;i<0?(e.css("left",0),e.css("transform","translateX(0)")):i+n>t&&(e.css("left","auto"),e.css("right",0),e.css("transform","translateX(0)"))},t.prototype.displaySlide=function(e){var t=this.cp.getCurrentSlideIndex();this.updateSlideTitle(e,{isCurrent:!0}),this.updateSlideTitle(t,{isCurrent:!1}),this.cp.jumpToSlide(e),this.toggleNextAndPreviousButtonDisabled(e)},t.prototype.createSlideTitle=function(e){var t=this.cp.slides[e];return t.keywords&&t.keywords.length>0?t.keywords[0].main:this.isSummarySlide(e)?this.cp.l10n.summary:this.cp.l10n.slide+" "+(e+1)},t.prototype.isSummarySlide=function(e){return!(void 0!==this.cp.editor||e!==this.cp.slides.length-1||!this.cp.showSummarySlide)},t.prototype.initFooter=function(){var t=this,n=this,i=this.cp.$footer,r=e("<div/>",{class:"h5p-footer-left-adjusted"}).appendTo(i),s=e("<div/>",{class:"h5p-footer-center-adjusted"}).appendTo(i),a=e("<div/>",{role:"toolbar",class:"h5p-footer-right-adjusted"}).appendTo(i);this.cp.$keywordsButton=e("<div/>",{class:"h5p-footer-button h5p-footer-toggle-keywords","aria-expanded":"false","aria-label":this.cp.l10n.showKeywords,title:this.cp.l10n.showKeywords,role:"button",tabindex:"0",html:'<span class="h5p-icon-menu"></span><span class="current-slide-title"></span>'}).appendTo(r),(0,u.addClickAndKeyboardListeners)(this.cp.$keywordsButton,function(e){n.cp.presentation.keywordListAlwaysShow||(n.cp.toggleKeywords(),e.stopPropagation())}),!this.cp.presentation.keywordListAlwaysShow&&this.cp.initKeywords||this.cp.$keywordsButton.hide(),this.cp.presentation.keywordListEnabled||this.cp.$keywordsWrapper.add(this.$keywordsButton).hide(),this.updateFooterKeyword(0),this.cp.$prevSlideButton=e("<div/>",{class:"h5p-footer-button h5p-footer-previous-slide","aria-label":this.cp.l10n.prevSlide,title:this.cp.l10n.prevSlide,role:"button",tabindex:"-1","aria-disabled":"true"}).appendTo(s),(0,u.addClickAndKeyboardListeners)(this.cp.$prevSlideButton,function(){return t.cp.previousSlide()});var l=e("<div/>",{class:"h5p-footer-slide-count"}).appendTo(s);this.cp.$footerCurrentSlide=e("<div/>",{html:"1",class:"h5p-footer-slide-count-current",title:this.cp.l10n.currentSlide,"aria-hidden":"true"}).appendTo(l),this.cp.$footerCounter=e("<div/>",{class:"hidden-but-read",html:this.cp.l10n.slideCount.replace("@index","1").replace("@total",this.cp.slides.length.toString())}).appendTo(s),e("<div/>",{html:"/",class:"h5p-footer-slide-count-delimiter","aria-hidden":"true"}).appendTo(l),this.cp.$footerMaxSlide=e("<div/>",{html:this.cp.slides.length,class:"h5p-footer-slide-count-max",title:this.cp.l10n.lastSlide,"aria-hidden":"true"}).appendTo(l),this.cp.$nextSlideButton=e("<div/>",{class:"h5p-footer-button h5p-footer-next-slide","aria-label":this.cp.l10n.nextSlide,title:this.cp.l10n.nextSlide,role:"button",tabindex:"0"}).appendTo(s),(0,u.addClickAndKeyboardListeners)(this.cp.$nextSlideButton,function(){return t.cp.nextSlide()}),void 0===this.cp.editor&&(this.cp.$exitSolutionModeButton=e("<div/>",{role:"button",class:"h5p-footer-exit-solution-mode","aria-label":this.cp.l10n.solutionModeTitle,title:this.cp.l10n.solutionModeTitle,tabindex:"0"}).appendTo(a),(0,u.addClickAndKeyboardListeners)(this.cp.$exitSolutionModeButton,function(){return n.cp.jumpToSlide(n.cp.slides.length-1)}),this.cp.enablePrintButton&&o.default.supported()&&(this.cp.$printButton=e("<div/>",{class:"h5p-footer-button h5p-footer-print","aria-label":this.cp.l10n.printTitle,title:this.cp.l10n.printTitle,role:"button",tabindex:"0"}).appendTo(a),(0,u.addClickAndKeyboardListeners)(this.cp.$printButton,function(){return n.openPrintDialog()})),H5P.fullscreenSupported&&(this.cp.$fullScreenButton=e("<div/>",{class:"h5p-footer-button h5p-footer-toggle-full-screen","aria-label":this.cp.l10n.fullscreen,title:this.cp.l10n.fullscreen,role:"button",tabindex:"0"}),(0,u.addClickAndKeyboardListeners)(this.cp.$fullScreenButton,function(){return n.cp.toggleFullScreen()}),this.cp.$fullScreenButton.appendTo(a))),this.cp.$exitSolutionModeText=e("<div/>",{html:"",class:"h5p-footer-exit-solution-mode-text"}).appendTo(this.cp.$exitSolutionModeButton)},t.prototype.openPrintDialog=function(){var t=this,n=e(".h5p-wrapper");o.default.showDialog(this.cp.l10n,n,function(e){o.default.print(t.cp,n,e)}).children('[role="dialog"]').focus()},t.prototype.updateProgressBar=function(e,t,n){var i,r=this;for(i=0;i<r.cp.progressbarParts.length;i+=1)e+1>i?r.cp.progressbarParts[i].addClass("h5p-progressbar-part-show"):r.cp.progressbarParts[i].removeClass("h5p-progressbar-part-show");if(r.progresbarKeyboardControls.setTabbableByIndex(e),r.cp.progressbarParts[e].addClass("h5p-progressbar-part-selected").siblings().removeClass("h5p-progressbar-part-selected"),void 0===t)return void r.cp.progressbarParts.forEach(function(e,t){r.setTaskAnswered(t,!1)});n||r.cp.editor},t.prototype.setTaskAnswered=function(e,t){this.cp.progressbarParts[e].find(".h5p-progressbar-part-has-task").toggleClass("h5p-answered",t),this.updateSlideTitle(e,{state:t?p.ANSWERED:p.NOT_ANSWERED})},t.prototype.updateSlideTitle=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.state,i=t.isCurrent;this.setSlideTitle(e,{state:(0,u.defaultValue)(n,this.getAnsweredState(e)),isCurrent:(0,u.defaultValue)(i,this.cp.isCurrentSlide(e))})},t.prototype.setSlideTitle=function(e,t){var n=t.state,i=void 0===n?p.NO_INTERACTIONS:n,r=t.isCurrent,s=void 0!==r&&r,o=this.cp.slides.length,a=this.cp.progressbarParts[e],l=a.find(".h5p-progressbar-part-title"),d=this.cp.l10n.slideCount.replace("@index",e+1).replace("@total",o),c=this.answeredLabels[i].replace("@slideName",this.createSlideTitle(e)),u=s?this.cp.l10n.currentSlide:"";l.html(d+": "+c+". "+u)},t.prototype.getAnsweredState=function(e){var t=this.cp.progressbarParts[e],n=this.slideHasInteraction(e),i=this.cp.slideHasAnsweredTask(e);return n?t.find(".h5p-is-correct").length>0?p.CORRECT:t.find(".h5p-is-wrong").length>0?p.INCORRECT:i?p.ANSWERED:p.NOT_ANSWERED:p.NO_INTERACTIONS},t.prototype.slideHasInteraction=function(e){return this.cp.progressbarParts[e].find(".h5p-progressbar-part-has-task").length>0},t.prototype.updateFooter=function(e){this.cp.$footerCurrentSlide.html(e+1),this.cp.$footerMaxSlide.html(this.cp.slides.length),this.cp.$footerCounter.html(this.cp.l10n.slideCount.replace("@index",(e+1).toString()).replace("@total",this.cp.slides.length.toString())),this.cp.isSolutionMode&&e===this.cp.slides.length-1?this.cp.$footer.addClass("summary-slide"):this.cp.$footer.removeClass("summary-slide"),this.toggleNextAndPreviousButtonDisabled(e),this.updateFooterKeyword(e)},t.prototype.toggleNextAndPreviousButtonDisabled=function(e){var t=this.cp.slides.length-1;this.cp.$prevSlideButton.attr("aria-disabled",(0===e).toString()),this.cp.$nextSlideButton.attr("aria-disabled",(e===t).toString()),this.cp.$prevSlideButton.attr("tabindex",0===e?"-1":"0"),this.cp.$nextSlideButton.attr("tabindex",e===t?"-1":"0")},t.prototype.updateFooterKeyword=function(e){var t=this.cp.slides[e],n="";t&&t.keywords&&t.keywords[0]&&(n=t.keywords[0].main),!this.cp.isEditor()&&this.cp.showSummarySlide&&e>=this.cp.slides.length-1&&(n=this.cp.l10n.summary),this.cp.$keywordsButton.children(".current-slide-title").html((0,u.defaultValue)(n,""))},t}(H5P.jQuery);t.default=h},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),r=function(e){function t(){}var n=0;return t.supported=function(){return"function"==typeof window.print},t.print=function(t,n,i){t.trigger("printing",{finished:!1,allSlides:i});var r=e(".h5p-slide.h5p-current"),s=r.height(),o=r.width(),a=o/670,l=e(".h5p-slide");l.css({height:s/a+"px",width:"670px",fontSize:Math.floor(100/a)+"%"});var d=n.height();n.css("height","auto"),l.toggleClass("doprint",!0===i),r.addClass("doprint"),setTimeout(function(){window.print(),l.css({height:"",width:"",fontSize:""}),n.css("height",d+"px"),t.trigger("printing",{finished:!0})},500)},t.showDialog=function(t,r,s){var o=this,a=n++,l="h5p-cp-print-dialog-"+a+"-title",d="h5p-cp-print-dialog-"+a+"-ingress",c=e('<div class="h5p-popup-dialog h5p-print-dialog">\n <div role="dialog" aria-labelledby="'+l+'" aria-describedby="'+d+'" tabindex="-1" class="h5p-inner">\n <h2 id="'+l+'">'+t.printTitle+'</h2>\n <div class="h5p-scroll-content"></div>\n <div class="h5p-close" role="button" tabindex="0" title="'+H5P.t("close")+'">\n </div>\n </div>').insertAfter(r).click(function(){o.close()}).children(".h5p-inner").click(function(){return!1}).end();(0,i.addClickAndKeyboardListeners)(c.find(".h5p-close"),function(){return o.close()});var u=c.find(".h5p-scroll-content");return u.append(e("<div>",{class:"h5p-cp-print-ingress",id:d,html:t.printIngress})),H5P.JoubelUI.createButton({html:t.printAllSlides,class:"h5p-cp-print-all-slides",click:function(){o.close(),s(!0)}}).appendTo(u),H5P.JoubelUI.createButton({html:t.printCurrentSlide,class:"h5p-cp-print-current-slide",click:function(){o.close(),s(!1)}}).appendTo(u),this.open=function(){setTimeout(function(){c.addClass("h5p-open"),H5P.jQuery(o).trigger("dialog-opened",[c])},1)},this.close=function(){c.removeClass("h5p-open"),setTimeout(function(){c.remove()},200)},this.open(),c},t}(H5P.jQuery);t.default=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createElement=t.toggleClass=t.toggleVisibility=t.show=t.hide=t.removeClass=t.addClass=t.classListContains=t.removeChild=t.querySelectorAll=t.nodeListToArray=t.querySelector=t.appendChild=t.toggleAttribute=t.attributeEquals=t.hasAttribute=t.removeAttribute=t.setAttribute=t.getAttribute=void 0;var i=n(4),r=t.getAttribute=(0,i.curry)(function(e,t){return t.getAttribute(e)}),s=t.setAttribute=(0,i.curry)(function(e,t,n){return n.setAttribute(e,t)}),o=(t.removeAttribute=(0,i.curry)(function(e,t){return t.removeAttribute(e)}),t.hasAttribute=(0,i.curry)(function(e,t){return t.hasAttribute(e)}),t.attributeEquals=(0,i.curry)(function(e,t,n){return n.getAttribute(e)===t}),t.toggleAttribute=(0,i.curry)(function(e,t){var n=r(e,t);s(e,(0,i.inverseBooleanString)(n),t)}),t.appendChild=(0,i.curry)(function(e,t){return e.appendChild(t)}),t.querySelector=(0,i.curry)(function(e,t){return t.querySelector(e)}),t.nodeListToArray=function(e){return Array.prototype.slice.call(e)}),a=(t.querySelectorAll=(0,i.curry)(function(e,t){return o(t.querySelectorAll(e))}),t.removeChild=(0,i.curry)(function(e,t){return e.removeChild(t)}),t.classListContains=(0,i.curry)(function(e,t){return t.classList.contains(e)}),t.addClass=(0,i.curry)(function(e,t){return t.classList.add(e)})),l=t.removeClass=(0,i.curry)(function(e,t){return t.classList.remove(e)}),d=t.hide=a("hidden"),c=t.show=l("hidden");t.toggleVisibility=(0,i.curry)(function(e,t){return(e?c:d)(t)}),t.toggleClass=(0,i.curry)(function(e,t,n){n.classList[t?"add":"remove"](e)}),t.createElement=function(e){var t=e.tag,n=e.id,i=e.classes,r=e.attributes,s=document.createElement(t);return n&&(s.id=n),i&&i.forEach(function(e){s.classList.add(e)}),r&&Object.keys(r).forEach(function(e){s.setAttribute(e,r[e])}),s}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.Eventful=function(){return{listeners:{},on:function(e,t,n){var i={listener:t,scope:n};return this.listeners[e]=this.listeners[e]||[],this.listeners[e].push(i),this},fire:function(e,t){return(this.listeners[e]||[]).every(function(e){return!1!==e.listener.call(e.scope||this,t)})},propagate:function(e,t){var n=this;e.forEach(function(e){return t.on(e,function(t){return n.fire(e,t)})})}}}},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),s=function e(t){i(this,e);var n=t.presentation;n=r.jQuery.extend(!0,{globalBackgroundSelector:{fillGlobalBackground:"",imageGlobalBackground:{}},slides:[{slideBackgroundSelector:{fillSlideBackground:"",imageSlideBackground:{}}}]},n);var s=function(e,n,i){var r=t.$slidesWrapper.children().filter(":not(.h5p-summary-slide)");void 0!==i&&(r=r.eq(i)),e&&""!==e?r.addClass("has-background").css("background-image","").css("background-color",e):n&&n.path&&r.addClass("has-background").css("background-color","").css("background-image","url("+H5P.getPath(n.path,t.contentId)+")")};!function(){var e=n.globalBackgroundSelector;s(e.fillGlobalBackground,e.imageGlobalBackground)}(),function(){n.slides.forEach(function(e,t){var n=e.slideBackgroundSelector;n&&s(n.fillSlideBackground,n.imageSlideBackground,t)})}()};t.default=s},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=n(3),a=i(o),l=n(5),d=i(l),c=n(1),u=n(0),p=function(e){return parseInt(e.dataset.index)},h=function(){function e(t){var n=this,i=t.l10n,s=t.currentIndex;r(this,e),this.l10n=i,this.state={currentIndex:(0,c.defaultValue)(s,0)},this.eventDispatcher=new u.EventDispatcher,this.controls=new a.default([new d.default]),this.controls.on("select",function(e){n.onMenuItemSelect(p(e.element))}),this.controls.on("close",function(){return n.eventDispatcher.trigger("close")}),this.menuElement=this.createMenuElement(),this.currentSlideMarkerElement=this.createCurrentSlideMarkerElement()}return s(e,[{key:"init",value:function(e){var t=this;return this.menuItemElements=e.map(function(e){return t.createMenuItemElement(e)}),this.menuItemElements.forEach(function(e){return t.menuElement.appendChild(e)}),this.menuItemElements.forEach(function(e){return t.controls.addElement(e)}),this.setCurrentSlideIndex(this.state.currentIndex),this.menuItemElements}},{key:"on",value:function(e,t){this.eventDispatcher.on(e,t)}},{key:"getElement",value:function(){return this.menuElement}},{key:"removeAllMenuItemElements",value:function(){var e=this;this.menuItemElements.forEach(function(t){e.controls.removeElement(t),e.menuElement.removeChild(t)}),this.menuItemElements=[]}},{key:"createMenuElement",value:function(){var e=this.menuElement=document.createElement("ol");return e.setAttribute("role","menu"),e.classList.add("list-unstyled"),e}},{key:"createMenuItemElement",value:function(e){var t=this,n=document.createElement("li");return n.setAttribute("role","menuitem"),n.addEventListener("click",function(e){t.onMenuItemSelect(p(e.currentTarget))}),this.applyConfigToMenuItemElement(n,e),n}},{key:"applyConfigToMenuItemElement",value:function(e,t){e.innerHTML='<div class="h5p-keyword-subtitle">'+t.subtitle+'</div><span class="h5p-keyword-title">'+t.title+"</span>",e.dataset.index=t.index}},{key:"onMenuItemSelect",value:function(e){this.setCurrentSlideIndex(e),this.eventDispatcher.trigger("select",{index:e})}},{key:"setCurrentSlideIndex",value:function(e){var t=this.getElementByIndex(this.menuItemElements,e);t&&(this.state.currentIndex=e,this.updateCurrentlySelected(this.menuItemElements,this.state),this.controls.setTabbable(t))}},{key:"updateCurrentlySelected",value:function(e,t){var n=this;e.forEach(function(e){var i=t.currentIndex===p(e);e.classList.toggle("h5p-current",i),i&&e.appendChild(n.currentSlideMarkerElement)})}},{key:"scrollToKeywords",value:function(e){var t=this.getFirstElementAfter(e);if(t){var n=(0,u.jQuery)(this.menuElement),i=n.scrollTop()+(0,u.jQuery)(t).position().top-8;c.isIPad?n.scrollTop(i):n.stop().animate({scrollTop:i},250)}}},{key:"getFirstElementAfter",value:function(e){return this.menuItemElements.filter(function(t){return p(t)>=e})[0]}},{key:"getElementByIndex",value:function(e,t){return e.filter(function(e){return p(e)===t})[0]}},{key:"createCurrentSlideMarkerElement",value:function(){var e=document.createElement("div");return e.classList.add("hidden-but-read"),e.innerHTML=this.l10n.currentSlide,e}}]),e}();t.default=h},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=this;l.default.call(t,o.default,e.elements);var n=void 0;t.getElement=function(){return n||(n=H5P.jQuery(r.createHTML(e))),n},t.setCurrent=function(){this.parent.$current=n.addClass("h5p-current")},t.appendElements=function(){for(var i=0;i<t.children.length;i++)t.parent.attachElement(e.elements[i],t.children[i].instance,n,t.index);t.parent.elementsAttached[t.index]=!0,t.parent.trigger("domChanged",{$target:n,library:"CoursePresentation",key:"newSlide"},{bubbles:!0,external:!0})}}Object.defineProperty(t,"__esModule",{value:!0});var s=n(24),o=i(s),a=n(2),l=i(a);r.createHTML=function(e){return'<div role="document" class="h5p-slide"'+(void 0!==e.background?' style="background:'+e.background+'"':"")+"></div>"},t.default=r},function(e,t,n){"use strict";function i(e){var t=this;if(void 0===e.action)t.instance=new s.default(e,{l10n:t.parent.parent.l10n,currentIndex:t.parent.index}),t.parent.parent.isEditor()||t.instance.on("navigate",function(e){var n=e.data;t.parent.parent.jumpToSlide(n)});else{var n;n=t.parent.parent.isEditor()?H5P.jQuery.extend(!0,{},e.action,t.parent.parent.elementsOverride):H5P.jQuery.extend(!0,e.action,t.parent.parent.elementsOverride),n.params.autoplay?(n.params.autoplay=!1,n.params.cpAutoplay=!0):n.params.playback&&n.params.playback.autoplay?(n.params.playback.autoplay=!1,n.params.cpAutoplay=!0):n.params.media&&n.params.media.params&&n.params.media.params.playback&&n.params.media.params.playback.autoplay?(n.params.media.params.playback.autoplay=!1,n.params.cpAutoplay=!0):n.params.media&&n.params.media.params&&n.params.media.params.autoplay?(n.params.media.params.autoplay=!1,n.params.cpAutoplay=!0):n.params.override&&n.params.override.autoplay&&(n.params.override.autoplay=!1,n.params.cpAutoplay=!0);var i=t.parent.parent.elementInstances[t.parent.index]?t.parent.parent.elementInstances[t.parent.index].length:0;t.parent.parent.previousState&&t.parent.parent.previousState.answers&&t.parent.parent.previousState.answers[t.parent.index]&&t.parent.parent.previousState.answers[t.parent.index][i]&&(n.userDatas={state:t.parent.parent.previousState.answers[t.parent.index][i]}),n.params=n.params||{},t.instance=H5P.newRunnable(n,t.parent.parent.contentId,void 0,!0,{parent:t.parent.parent}),void 0!==t.instance.preventResize&&(t.instance.preventResize=!0)}void 0===t.parent.parent.elementInstances[t.parent.index]?t.parent.parent.elementInstances[t.parent.index]=[t.instance]:t.parent.parent.elementInstances[t.parent.index].push(t.instance),(void 0!==t.instance.showCPComments||t.instance.isTask||void 0===t.instance.isTask&&void 0!==t.instance.showSolutions)&&(t.instance.coursePresentationIndexOnSlide=t.parent.parent.elementInstances[t.parent.index].length-1,void 0===t.parent.parent.slidesWithSolutions[t.parent.index]&&(t.parent.parent.slidesWithSolutions[t.parent.index]=[]),t.parent.parent.slidesWithSolutions[t.parent.index].push(t.instance)),void 0!==t.instance.exportAnswers&&t.instance.exportAnswers&&(t.parent.parent.hasAnswerElements=!0),t.parent.parent.isTask||t.parent.parent.hideSummarySlide||(t.instance.isTask||void 0===t.instance.isTask&&void 0!==t.instance.showSolutions)&&(t.parent.parent.isTask=!0)}Object.defineProperty(t,"__esModule",{value:!0});var r=n(25),s=function(e){return e&&e.__esModule?e:{default:e}}(r);t.default=i},function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),s=n(1),o=n(0),a={SPECIFIED:"specified",NEXT:"next",PREVIOUS:"previous"},l=function(){function e(t,n){var r=this,l=t.title,d=t.goToSlide,c=void 0===d?1:d,u=t.invisible,p=t.goToSlideType,h=void 0===p?a.SPECIFIED:p,f=n.l10n,v=n.currentIndex;i(this,e),this.eventDispatcher=new o.EventDispatcher;var m="h5p-press-to-go",y=0;if(u)l=void 0,y=-1;else{if(!l)switch(h){case a.SPECIFIED:l=f.goToSlide.replace(":num",c.toString());break;case a.NEXT:l=f.goToSlide.replace(":num",f.nextSlide);break;case a.PREVIOUS:l=f.goToSlide.replace(":num",f.prevSlide)}m+=" h5p-visible"}var b=c-1;h===a.NEXT?b=v+1:h===a.PREVIOUS&&(b=v-1),this.$element=(0,o.jQuery)("<a/>",{href:"#",class:m,tabindex:y,title:l}),(0,s.addClickAndKeyboardListeners)(this.$element,function(e){r.eventDispatcher.trigger("navigate",b),e.preventDefault()})}return r(e,[{key:"attach",value:function(e){e.html("").addClass("h5p-go-to-slide").append(this.$element)}},{key:"on",value:function(e,t){this.eventDispatcher.on(e,t)}}]),e}();t.default=l}]);;