H5P.Collage = (function ($, EventDispatcher) { /** * Create a new collage. * * @class H5P.Collage * @extends H5P.EventDispatcher * @param {Object} parameters * @param {number} contentId */ function Collage(parameters, contentId) { var self = this; // Initialize event inheritance EventDispatcher.call(self); // Content defaults setDefaults(parameters, { collage: { template: '2-1', options: { heightRatio: 0.75, spacing: 0.5, frame: true }, clips: [] } }); var content = parameters.collage; var $wrapper; // Create new template for adding clips to var template = new Collage.Template(content.options.spacing); // Add clips to columns template.on('columnAdded', function (event) { var $col = event.data; var clipIndex = self.clips.length; // Set default if (!content.clips[clipIndex]) { content.clips[clipIndex] = {}; } // Add new clip var clip = new Collage.Clip($col, content.clips[clipIndex], contentId); self.clips.push(clip); self.trigger('clipAdded', clip); clip.load(); }); /** * Creates the HTML the first time the collage is attaced. * * @private */ var createHtml = function () { // Create collage wrapper var wrapperOptions = { 'class': 'h5p-collage-wrapper', css: {} }; if (content.options.frame) { wrapperOptions.css.borderWidth = content.options.spacing + 'em'; } $wrapper = $('
', wrapperOptions); // Add template template.appendTo($wrapper); // Render template self.setLayout(content.template); }; /** * Attach the collage to the given container. * * @param {H5P.jQuery} $container */ self.attach = function ($container) { this.triggerConsumed(); if ($wrapper === undefined) { createHtml(); var $parent = $container.parent(); if (!$parent.hasClass('h5p-frame')) { $parent.css('backgroundColor', 'transparent'); } } // Add to DOM $container.addClass('h5p-collage').html('').append($wrapper); }; /** * Trigger the 'consumed' xAPI event when this commences * * (Will be more sophisticated in future version) */ self.triggerConsumed = function () { var xAPIEvent = this.createXAPIEventTemplate({ id: 'http://activitystrea.ms/schema/1.0/consume', display: { 'en-US': 'consumed' } }, { result: { completion: true } }); this.trigger(xAPIEvent); }; /** * Set a new collage layout. * * @param {string} newLayout */ self.setLayout = function (newLayout) { self.clips = []; template.setLayout(newLayout); }; /** * Set the spacing between the collage clips. * * @param {number} newSpacing */ self.setSpacing = function (newSpacing) { template.setSpacing(newSpacing); }; /** * Set the frame around the collage. * * @param {number} newFrameWidth */ self.setFrame = function (newFrameWidth) { $wrapper.css('borderWidth', newFrameWidth + 'em'); }; /** * Set the height / aspect ratio of the collage. * * @param {number} newHeight */ self.setHeight = function (newHeight) { // Update template var wrapperSize = $wrapper[0].getBoundingClientRect(); $wrapper.css('height', (wrapperSize.width * newHeight) + 'px'); }; /** * Handle resize events */ self.on('resize', function () { if ($wrapper === undefined) { return; } // Get outer width without rounding var width = $wrapper[0].getBoundingClientRect().width; $wrapper.css({ fontSize: ((width / 480) * 16) + 'px', height: (content.options.heightRatio * width) + 'px' }); // Position clips for (let i = 0; i < self.clips.length; i++) { if (!self.clips[i].isPositioned()) { self.clips[i].positionImage() } } }); } // Extends the event dispatcher Collage.prototype = Object.create(EventDispatcher.prototype); Collage.prototype.constructor = Collage; /** * Simple recusive function the helps set default values without * destroying object references. * * @param {object} params values * @param {object} values default values */ var setDefaults = function (params, values) { for (var prop in values) { if (values.hasOwnProperty(prop)) { if (params[prop] === undefined) { params[prop] = values[prop]; } else if (params[prop] instanceof Object && !(params[prop] instanceof Array)) { setDefaults(params[prop], values[prop]); } } } }; return Collage; })(H5P.jQuery, H5P.EventDispatcher); ; (function ($, EventDispatcher, Collage) { /** * Collage Template * * @class H5P.Collage.Template * @extends H5P.EventDispatcher * @param {number} spacing * @param {string} layout */ Collage.Template = function (spacing, layout) { var self = this; // Initialize event inheritance EventDispatcher.call(self); // Create template wrapper var $wrapper; // Half the spacing spacing /= 2; // Keep track of our rows var table = []; /** * Add columns to row. * * @private * @param {H5P.jQuery} $row * @param {number} num */ var addCols = function ($row, num) { var cols = []; for (var i = 0; i < num; i++) { // Add column to row var $col = $('', { 'class': 'h5p-collage-col', css: { width: (100 / num) + '%', borderLeftWidth: (i === 0 ? 0 : spacing + 'em'), borderRightWidth: (i === num - 1 ? 0 : spacing + 'em') }, appendTo: $row }); self.trigger('columnAdded', $col); cols.push($col); } return cols; }; /** * Add rows to wrapper. * * @private * @param {Array} rows */ var addRows = function (rows) { for (var i = 0; i < rows.length; i++) { // Add row to wrapper var $row = $('', { 'class': 'h5p-collage-row', css: { height: (100 / rows.length) + '%', borderTopWidth: (i === 0 ? 0 : spacing + 'em'), borderBottomWidth: (i === rows.length - 1 ? 0 : spacing + 'em') }, appendTo: $wrapper }); // Add row columns table.push({ $row: $row, cols: addCols($row, Number(rows[i])) }); } }; /** * Append template to given container. * * @param {H5P.jQuery} $container */ self.appendTo = function ($container) { // Create wrapper $wrapper = $('', { 'class': 'h5p-collage-template' }); // Initialize right away if we have a layout if (layout) { self.setLayout(layout); } // Insert our wrapper into the given container $wrapper.appendTo($container); }; /** * Set a new layout for the template. * * @param {string} newLayout */ self.setLayout = function (newLayout) { $wrapper.html(''); addRows(newLayout.split('-')); }; /** * Set the spacing between the clips. * * @param {number} newSpacing */ self.setSpacing = function (newSpacing) { spacing = newSpacing / 2; // Update table styling for (var i = 0; i < table.length; i++) { var row = table[i]; row.$row.css({ borderTopWidth: (i === 0 ? 0 : spacing + 'em'), borderBottomWidth: (i === table.length - 1 ? 0 : spacing + 'em') }); for (var j = 0; j < row.cols.length; j++) { row.cols[j].css({ borderLeftWidth: (j === 0 ? 0 : spacing + 'em'), borderRightWidth: (j === row.cols.length - 1 ? 0 : spacing + 'em') }); } } }; }; // Extends the event dispatcher Collage.Template.prototype = Object.create(EventDispatcher.prototype); Collage.Template.prototype.constructor = Collage.Template; })(H5P.jQuery, H5P.EventDispatcher, H5P.Collage); ; (function ($, Collage, EventDispatcher) { /** * Collage Clip * * @class H5P.Collage.Clip * @extends H5P.EventDispatcher * @param {H5P.jQuery} $container * @param {Object} content * @param {number} contentId */ Collage.Clip = function ($container, content, contentId) { var self = this; // Initialize event inheritance EventDispatcher.call(self); // Photo wrapper self.$wrapper = $('', { 'class': 'h5p-collage-photo', appendTo: $container }); // Clip resource var $img; // Always available self.content = content; // Keep track of image has been positioned let isPositioned = false; /** * Position the clip image according to params. */ self.positionImage = function () { if (self.$wrapper[0].offsetParent === null || isPositioned || !$img || !$img.length) { return; // Not visible, position will not be correct } // Determine image ratio const imageRatio = $img[0].width ? ($img[0].width / $img[0].height) : (content.image.width && content.image.height ? content.image.width / content.image.height : null); if (imageRatio === null) { return; // Skip } isPositioned = true; // Find container raioratios var containerSize = window.getComputedStyle(self.$wrapper[0]); var containerRatio = (parseFloat(containerSize.width) / parseFloat(containerSize.height)); // Make sure image covers the whole container if (isNaN(containerRatio) || imageRatio > containerRatio) { self.prop = 'height'; } else { self.prop = 'width'; } $img.css(self.prop, (content.scale * 100) + '%'); // Pan image $img.css('margin', content.offset.top + '% 0 0 ' + content.offset.left + '%'); }; /** * Triggers the loading of the image. */ self.load = function () { if (self.empty()) { self.$wrapper.addClass('h5p-collage-empty'); return; // No image set } else { self.$wrapper.removeClass('h5p-collage-empty'); } // Create image $img = $('', { 'class': 'h5p-collage-image', alt: content.alt, title: content.title, src: H5P.getPath(content.image.path, contentId), prependTo: self.$wrapper, on: { load: function () { // Make sure it's in the correct position self.positionImage(); } } }); setTimeout(function () { // Wait for next tick to make sure everything is visible self.positionImage(); }, 0); self.trigger('change', $img); }; /** * Check if the current clip is empty or set. * * @returns {boolean} */ self.empty = function () { return !content.image; }; /** * Check if the current clip is positioned yet. * * @returns {boolean} */ self.isPositioned = function () { return isPositioned; }; }; // Extends the event dispatcher Collage.Clip.prototype = Object.create(EventDispatcher.prototype); Collage.Clip.prototype.constructor = Collage.Clip; })(H5P.jQuery, H5P.Collage, H5P.EventDispatcher); ;