░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/datadrive/moodledata/filedir/9f/04
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: 9f0458ef069077b05fa5a98d039901ff4884d0d6
/** @namespace H5P */ var H5P = H5P || {}; H5P.PersonalityQuiz = (function ($, EventDispatcher) { /** A personality quiz. @memberof PersonalityQuiz @param {Object} params @param {number} id @constructor */ function PersonalityQuiz(params, id) { var self = this; self.classPrefix = 'h5p-personality-quiz-'; self.resultAnimation = params.resultScreen.animation; self.resultTitle = params.resultScreen.displayTitle; self.resultDescription = params.resultScreen.displayDescription; self.resultImagePosition = params.resultScreen.imagePosition; self.progressText = params.progressText; self.personalities = params.personalities; self.numQuestions = params.questions.length; self.slidePercentage = 100 / self.numQuestions; var loadingImages = []; // NOTE (Emil): These constants are defined in pixels. var responsiveColumnThreshold = 450; var minimumHeight = 400; var canvas = { classname: classes('wheel'), width: 300, height: 300, }; var $content = $('.h5p-content'); var body = document.querySelector('body'); var animation = ((body.style.animationName !== undefined) && params.animation); var resizeEventHandler = null; EventDispatcher.call(self); /** Wrapper around H5P.getPath so as not to need id everywhere. @param {string} path @return {string} Full path for the resource. */ function _getPath(path) { return H5P.getPath(path, id); } /** Prefix a classname with self.classPrefix. @param {string} className @param {boolean} addDot If true adds '.' to the start of the start of the class name. @return {string} */ function prefix(className, addDot) { var prefixed = self.classPrefix + className; if (addDot) { prefixed = '.' + prefixed; } return prefixed; } /** Prefixes all arguments with self.classPrefix. @return {string} A string with all prefixed class names passed to the function separated by spaces. */ function classes() { var args = Array.prototype.slice.call(arguments); var classNames = 'h5p-personality-quiz'; args.forEach(function (argument) { classNames += ' ' + prefix(argument); }); return classNames; } /** Interpolates a string, looping over the properties of variables and replaces instances of '@' + property name with the value of the property. @param {string} str @param {Object} variables @return {string} */ function interpolate(str, variables) { var keys = Object.keys(variables); keys.forEach(function (key) { str = str.replace('@' + key, variables[key]); }); return str; } /** Creates an element of 'type' and adds the attributes in the object 'attributes'. In addition some general styles are added to the element. @param {string} type - name of an element type. @param {Object} attributes @returns {jQuery} - The new button element. */ function createButton(type, attributes) { var $button = $('<' + type + '>', attributes); $button.css({ 'border-left': '5px solid #' + params.buttonColor, 'background': 'linear-gradient(to right, #' + params.buttonColor + ' 50%, rgb(233, 239, 247) 50%)', 'background-size': '200% 100%', 'background-position': '100%' }); return $button; } /** Takes a callback and creates a listener for on the button for the click event. The logic for calling the callback is determined by the avilability of animation. @param {jQuery} $element @param {listenerCallback} */ function addButtonListener($element, callback) { if (animation) { $element.click(function () { $(this).addClass(prefix('button-animate')); }); $element.on('animationend', function () { $(this).removeClass(prefix('button-animate')); callback(); }); } else { $element.click(function () { callback(); }); } } /** Creates a canvas element and returns a canvas element. @return {jQuery} */ function createCanvas() { var $wrapper = $('<div>', { 'class': classes('wheel-container slide') }); var $canvas = $('<canvas>', { 'class': canvas.classname, }); self.$canvas = $canvas; $wrapper.append($canvas); return $wrapper; } /** Entry point for creating the entire personality quiz ui. @param {PersonalityQuiz} quiz A personality quiz instance @param {Object[]} data The params received from H5P @return {jQuery} */ function createQuiz(quiz, data) { var $container, $slides, $bar, $title, $question, $canvas, $result; $container = $('<div>', { 'class': classes('container') }); $slides = $('<div>', { 'class': classes('slides') }); $bar = createProgressbar(); if (!data.titleScreen.skip) { $title = createTitleCard(quiz, data.titleScreen, data.startText); $slides.append($title); } data.questions.forEach(function (question) { $question = createQuestion(quiz, question); $slides.append($question); }); if (animation && self.resultAnimation === 'wheel') { $canvas = createCanvas(); $slides.append($canvas); } $result = createResult(quiz, data.resultScreen, data.retakeText); $slides.append($result); $container.append($bar, $slides); quiz.$progressbar = $bar; if (data.titleScreen.skip) { quiz.$progressbar.show(); } quiz.$progressText = $bar.children(prefix('progress-text', true)); quiz.$wrapper = $container; quiz.$slides = $slides.children(); quiz.$result = $result; return $container; } /** Create a progress bar for the quiz @return {jQuery} */ function createProgressbar() { var $bar, $text, text; $bar = $('<div>', { 'class': classes('progressbar') }); $bar.css({ 'background': 'linear-gradient(to right, #' + params.progressbarColor + ' 50%, rgb(60, 62, 64) 50%)', 'background-size': '200% 100%', 'background-position': '100%' }); $bar.hide(); $text = $('<p>', { 'class': classes('progress-text') }); text = interpolate(self.progressText, { 'question': self.answered + 1, 'total': self.numQuestions }); $text.html(text); if (animation) { $bar.css('transition', 'background-position 1s'); } $bar.append($text); return $bar; } /** Create the title screen. @param {PersonalityQuiz} quiz A personality quiz instance @param {Object} data The params received from H5P @param {string} startText The UI text for the start button @return {jQuery} */ function createTitleCard(quiz, data, startText) { var $card, $content, $title, $wrapper, $startButton, path, hasImage; hasImage = data.image.file ? true : false; $card = $('<div>', { 'class': classes('title-card', 'slide', 'background') }); $content = $('<div>', { 'class': classes('title-card-wrapper') }); $title = $('<h2>', { html: data.title.text, 'class': classes('title') }); if (hasImage) { path = _getPath(data.image.file.path); $card.css('background-image', 'url(' + path + ')'); } $wrapper = $('<div>', { 'class': classes('start-button-wrapper') }); $startButton = createButton('button', { 'class': classes('start-button', 'button'), 'html': startText, 'type': 'button' }); addButtonListener($startButton, function () { quiz.trigger('personality-quiz-start'); }); $wrapper.append($startButton); $content.append($title, $wrapper); $card.append($content); return $card; } /** Creates a question for the quiz. @param {PersonalityQuiz} quiz @param {Object} question A question instance from params @return {jQuery} */ function createQuestion(quiz, question) { var $slide, $text, $image, $answer; var path, deferred, images, createAnswerButton; $slide = $('<div>', { 'class': classes('question', 'slide') }); $text = $('<h2>', { 'class': classes('question-text'), 'html': question.text }); if (question.image.file) { path = _getPath(question.image.file.path); $image = $('<img>', { 'class': classes('question-image'), 'src': path }); deferred = $.Deferred(); $image.on('load', function () { deferred.resolve(); }); loadingImages.push(deferred.promise()); $slide.append($image); } $slide.append($text); // NOTE (Emil): We only make the answers show images if all the alternatives have images. images = true; question.answers.forEach(function (answer) { images = images && answer.image.file !== undefined; }); createAnswerButton = images ? createImageAnswer : createAnswer; $answer = createAnswerButton(question.answers, quiz.answerListener); $slide.append($answer); return $slide; } /** Get the number of columns per row for image answers. @return {number} The number of columns based on $container width and the responseColumnThreshold. */ function getNumColumns() { return (self.$container.width() < responsiveColumnThreshold) ? 2 : 3; } /** Add or remove rows if there are respectively too few or too many rows. @param {jQuery} $container @param {jQuery} $rows @param {number} rowCount */ function checkRows($container, $rows, rowCount) { var $row, $extra; $extra = $rows.slice(rowCount); $extra.remove(); while ($rows.length < rowCount) { $row = createRow(); $container.append($row); $rows = $container.children(); } } /** Attaches $elements to the $rows in the $container. @param {jQuery} $container @param {jQuery} $elements @param {number} columns decides how many elements go in each row. @param {number} [height] An optional height value for the row. */ function attachRows($container, $elements, columns) { var $rows = $container.children(prefix('row', true)); $rows.each(function (index) { var $row = $(this); var start = (index) * columns; var end = start + columns; $row.append($elements.slice(start, end)); }); } /** Creates a list element with the 'h5p-personality-quiz-row' class. @return {jQuery} */ function createRow() { return $('<li>', { 'class': classes('row') }); } /** Creates an answer with an image attached. @param {Object} answer @param {listenerCallback} listener @return {jQuery} */ function createImageAnswer(answers, listener) { var $wrapper, $answers, $row, $elements; var columns = getNumColumns(); var rowCount = answers.length / columns; var row = 0; $wrapper = $('<div>', { 'class': classes('answers-wrapper') }); $answers = $('<ul>', { 'class': classes('image-answers') }); $elements = answers.map(function (answer) { var $answer, $button, $image; var path = _getPath(answer.image.file.path); $answer = $('<div>', { 'class': classes('column', 'columns-' + String(columns)), 'data-personality': answer.personality }); $button = createButton('div', { 'class': classes('button', 'image-answer-button'), 'html': answer.text }); $image = $('<div>', { 'class': classes('image-answer-image') }); $image.css('background-image', 'url(' + path + ')'); $answer.append($image, $button); $answer.click(listener); return $answer; }); for (row = 0; row < rowCount; row++) { $row = createRow(); $answers.append($row); } attachRows($answers, $elements, columns); $wrapper.append($answers); return $wrapper; } /** Creates a button for the answer element. @param {Object} answer @param {listenerCallback} listener @return {jQuery} */ function createAnswer(answers, listener) { var $wrapper = $('<div>', { 'class': classes('answers-wrapper') }); var $answers = $('<ul>', { 'class': classes('answers') }); $answers.click(listener); answers.forEach(function (answer) { var $answer = createButton('li', { 'data-personality': answer.personality, 'class': classes('button', 'answer'), 'html': answer.text }); $answers.append($answer); }); $wrapper.append($answers); return $wrapper; } /** Creates the slide for showing the result at the end of the quiz. @param {PersonalityQuiz} quiz A PersonalityQuiz instance @param {Object} data The params received from H5P @param {string} retakeText The UI text for the button to retake the quiz @return {jQuery} */ function createResult(quiz, data, retakeText) { var $result = $('<div>', { 'class': classes('result', 'slide') }); var $wrapper = $('<div>', { 'class': classes('personality-wrapper') }); var $container = $('<div>', { 'class': classes('retake-button-wrapper') }); var $button = createButton('button', { 'html': retakeText, 'class': classes('button', 'retake-button'), 'type': 'button' }); addButtonListener($button, function () { quiz.trigger('personality-quiz-restart'); }); $container.append($button); quiz.$resultWrapper = $wrapper; $result.append($wrapper); $result.append($container); return $result; } /** Sets the background image of the personality slide the the image associated with the personality. @param {jQuery} $result The result slide to set the background on @param {Object} personality */ function setPersonalityBackgroundImage($result, $personality, personality) { var path = _getPath(personality.image.file.path); var classNames = [ prefix('background'), prefix('center-personality-wrapper'), ]; $result.css('background-image', 'url(' + path + ')'); $result.addClass(classNames.join(' ')); $personality.addClass(prefix('center-personality')); } /** Create an element only if the passed expression evaluates to 'true'. @param {boolean} expression @param {string} element The tag for the element to be created. @param {Object} attributes Attributes to set on the created element. @return {jQuery} */ function createIf(expression, element, attributes) { var $element = null; if (expression) { $element = $(element, attributes); } return $element; } /** Sets the height of the inline personality image. @param {jQuery} $wrapper @param {jQuery} $personality @param {jQuery} $image */ function setInlineImageHeight ($image, $personality, $resultWrapper) { var height; if (!$image) { return; } $image.hide(); height = $resultWrapper.outerHeight() - $personality.outerHeight(true); $image.css({'height': 'calc(' + height + 'px - 4 * 1em)'}); $image.show(); } /** Appends the personality information to the result slide. @param {PeronalityQuiz} quiz @param {Object} personality @param {boolean} hasTitle @param {boolean} hasImage @param {boolean} hasDescription */ function appendPersonality(quiz, personality, hasTitle, hasImage, hasDescription) { var $personality, $title, $description, $image; $title = createIf(hasTitle, '<h2>', { 'html': personality.name }); if (personality.image.file) { $image = createIf(hasImage, '<img>', { 'class': classes('result-image'), 'src': _getPath(personality.image.file.path), 'alt': personality.image.alt }); } $description = createIf(hasDescription, '<p>', { 'html': personality.description }); // NOTE (Emil): We only create $personality element if it has at least // one child element. if (hasTitle || hasImage || hasDescription) { $personality = $('<div>', { 'class': classes('personality') }); $personality.append($title); $personality.append($image); $personality.append($description); } quiz.$resultWrapper.append($personality); setInlineImageHeight($image, $personality, quiz.$resultWrapper); return $personality; } /** The click event listener if animations are enabled. @param {jQuery} $button @param {Object[]} personalities The list of personalities associated with the $button */ function animatedButtonListener($button, personalities) { var animationClass = prefix('button-animate'); $button.addClass(animationClass); $button.on('animationend', function () { $(this).removeClass(animationClass); $(this).off('animationend'); self.trigger('personality-quiz-answer', personalities); }); } /** Click event handler for disabled animation option. @param {jQuery} @param {Object[]} personalities The personalities associated with the $button */ function nonAnimatedButtonListener($button, personalities) { self.trigger('personality-quiz-answer', personalities); } /** Resize the questions with image answers. */ function resizeColumns($quiz) { var rowCount, columns; var $answers, $rows; columns = getNumColumns(); $answers = $quiz.find(prefix('image-answers', true)); $answers.each(function () { var $answer, $slide, $alternatives; $answer = $(this); $rows = $answer.children(prefix('row', true)); $alternatives = $rows.children(prefix('column', true)); // NOTE (Emil): Remove the answer-images from the DOM so we can // calculate the new column width. $alternatives = $alternatives.detach(); rowCount = Math.ceil($alternatives.length / columns); checkRows($answer, $rows, rowCount); if (!$alternatives.hasClass(prefix('columns-' + columns))) { $alternatives.toggleClass(classes('columns-2', 'columns-3')); } attachRows($answer, $alternatives, columns); // NOTE (Emil): Update the selection after changes. $rows = $answer.children(prefix('row', true)); $slide = $answer.parent().parent(); var titleHeight = $slide.children(prefix('question-text', true)).outerHeight(true) || 0; var imageHeight = $slide.children(prefix('question-image', true)).outerHeight(true) || 0; var height = $slide.height() - (titleHeight + imageHeight); setAnswerImageHeight($rows, height / $rows.length); }); } /** Resize the result screen. */ function resizeResult($quiz) { var $wrapper, $personality, $image; $wrapper = self.$resultWrapper; $personality = $quiz.find(prefix('personality')); $image = $quiz.find(prefix('result-image')); setInlineImageHeight($image, $personality, self.$resultWrapper); } /** Resize event handler. @param {Object} event The resize event object. */ function resize() { resizeColumns(self.$wrapper); resizeResult(self.$wrapper); } /** Calculate and set the height of the slides in the quiz. @param {Object} self The quiz object @param {jQuery} $quiz The container for the entire quiz */ function setQuizHeight(self, $quiz) { var height = 0; self.$slides.each(function (index, element) { var $slide, $image; var h = 0; $slide = $(element); $image = $slide.children(prefix('question-image', true)); $image.hide(); if (this.clientHeight > height) { h = $(this).height(); if ($image) { h = h * 1.3; } height = h; } $image.show(); }); height = Math.max(height, minimumHeight); $quiz.height(height); self.height = height; } /** Set the height of all images attached to answer alternatives. @param {jQuery} $quiz The root of the quiz */ function setAnswerImageHeight($rows, maxRowHeight) { var ratio = 9.0 / 16.0; var numColumns = getNumColumns(); $rows.each(function () { var imageHeight, heights, width; var $row, $columns, $buttons, $images; $row = $(this); $columns = $row.children(); $buttons = $columns.children(prefix('image-answer-button', true)); $images = $columns.children(prefix('image-answer-image', true)); width = (self.$wrapper.width() / numColumns); imageHeight = Math.floor(width * ratio); heights = $buttons.map(function (i, e) { var $e = $(e); $e.css('height', ''); // NOTE (Emil): Unset previous height calculations. return $e.height(); }); $buttons.height(Math.max.apply(null, heights)); $images.height(imageHeight); // If the size of the containing box is larger than the limit set by // maxRowHeight we subtract the difference from the height of the image. if (maxRowHeight && $columns.outerHeight() > maxRowHeight) { imageHeight -= ($columns.outerHeight() - maxRowHeight); $images.height(imageHeight); } }); } /** Internal attach function. Creates the quiz, calculates the height the quiz needs to be and starts canvas rendering if the wheel of fortune animation is enabled. @param {jQuery} $container */ function attach($container) { loadingImages = []; self.reset(); var $quiz = createQuiz(self, params); $container.append($quiz); // NOTE (Emil): We only want to do the work for a resize event once. // Only the resize event call that survives 100 ms is called. $(window).resize(function () { clearTimeout(resizeEventHandler); resizeEventHandler = setTimeout(resize, 100); }); // NOTE (Emil): Wait for images to load, if there are any. // If there aren't any images to wait for this function is called immediately. $.when.apply(null, loadingImages).done(function () { setAnswerImageHeight($quiz.find(prefix('row', true))); setQuizHeight(self, $quiz); if (animation && params.resultScreen.animation === 'wheel') { canvas.width = $container.width() * 0.8; canvas.height = $container.height(); self.wheel = new PersonalityQuiz.WheelAnimation( self, self.personalities, canvas.width, canvas.height, _getPath ); } self.trigger('resize'); }); } /** Required function for interacting with H5P. @param {jQuery} $container The parent element for the entire quiz. */ self.attach = function ($container) { if (self.$container === undefined) { self.$container = $container; attach(self.$container); } }; /** Sets the result of the personality quiz. Creates the missing elements for the result screen and sets the result on the wheel of fortune animation if it is enabled. @param {Object} personality */ self.setResult = function (personality) { var $personality; var backgroundImage = (personality.image.file) && self.resultImagePosition === 'background'; var inlineImage = (personality.image.file) && self.resultImagePosition === 'inline'; if (self.$canvas) { self.wheel.attach(self.$canvas[0]); self.wheel.setTarget(personality); self.wheel.animate(); } $personality = appendPersonality( self, personality, self.resultTitle, inlineImage, self.resultDescription ); if (backgroundImage) { setPersonalityBackgroundImage(self.$result, $personality, personality); } }; /** Searches the personalities for the one with the highest 'count' property. In the case of a tie the first element with the shared highest 'count' is selected. @return {Object} The result personality of the quiz */ self.calculatePersonality = function () { var max = self.personalities[0].count; var index = 0; self.personalities.forEach(function (personality, i) { if (max < personality.count) { max = personality.count; index = i; } }); return self.personalities[index]; }; /** Updates the progressbar. Moves the background gradient based on the number of questions answered and updates the text with the current question number and the question total. */ self.updateProgress = function () { var percentage = 100 - (self.answered) * self.slidePercentage; var text = interpolate(self.progressText, { 'question': self.answered + 1, 'total': self.numQuestions }); self.$progressbar.css('background-position', String(percentage) + '%'); self.$progressText.html(text); }; /** Moves to the next slide. Toggles visiblity of slides and triggers 'personality-quiz-completed' event upon completion. */ self.next = function () { var answeredAllQuestions = (self.answered === self.numQuestions); var $prev = self.$slides.eq(self.index); var $curr = self.$slides.eq(self.index + 1); $prev.hide(); $curr.show(); self.index = self.index + 1; if ($curr.hasClass(prefix('question'))) { self.updateProgress(self.index); } if (!self.completed && answeredAllQuestions) { self.trigger('personality-quiz-completed'); } }; /** The click event listener used for all buttons associated with an answer to a question in the personality quiz. @param {Object} event */ self.answerListener = function (event) { var $target, $button; var isImage, isButton, buttonListener, personalities; $target = $(event.target); $button = $target; isImage = $target.hasClass(prefix('image-answer-image')); isButton = $target.hasClass(prefix('image-answer-button')); buttonListener = animatedButtonListener; $button = isImage ? $target.siblings().eq(0) : $button; $target = (isButton || isImage) ? $target.parent() : $target; personalities = $target.attr('data-personality'); if (personalities) { buttonListener = animation ? animatedButtonListener : nonAnimatedButtonListener; buttonListener($button, personalities); $target.parent(prefix('answers')).off('click'); } }; /** Zeros out all personality quiz state variables. */ self.reset = function () { self.personalities.map(function (e) { e.count = 0; }); self.index = 0; self.answered = 0; self.completed = false; }; /** Event handler for the personality quiz start event. Makes the progressbar visible and goes to the next slide. */ self.on('personality-quiz-start', function () { self.$progressbar.show(); self.next(); }); /** Event handler for the personality quiz answer event. Counts up all personalities in the answer matching the given personalities. */ self.on('personality-quiz-answer', function (event) { var answers; if (event !== undefined && event.data !== undefined) { answers = event.data.split(', '); answers.forEach(function (answer) { self.personalities.forEach(function (personality) { if (personality.name === answer) { personality.count++; } }); }); self.answered += 1; } self.next(); }); /** Event handler for the personality quiz completed event. Hides the progressbar, since it is no longer needed. Sets the quiz as completed, calculates the personality and sets the result. */ self.on('personality-quiz-completed', function () { var personality = self.calculatePersonality(); self.$progressbar.hide(); self.completed = true; self.setResult(personality); if (animation && self.resultAnimation === 'fade-in') { self.$result.addClass(prefix('fade-in')); } }); /** Event handler for the animation end event for the wheel of fortune animation. Sets a fade-out animation and moves the quiz on to the next slide. */ self.on('wheel-animation-end', function () { setTimeout(function () { self.$canvas.addClass(prefix('fade-out')); }, 500); self.$canvas.on('animationend', self.next); }); /** Event handler for the quiz restart event. Empties the root container for the quiz and rebuilds it. */ self.on('personality-quiz-restart', function () { self.$container.empty(); attach(self.$container); }); } PersonalityQuiz.prototype = Object.create(EventDispatcher); PersonalityQuiz.prototype.constructor = PersonalityQuiz; return PersonalityQuiz; })(H5P.jQuery, H5P.EventDispatcher);
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 9f0458ef069077b05fa5a98d039901ff4884d0d6
?
29.5 KB
2023-08-03 13:29
EDIT