var H5P = H5P || {}; /** * Constructor. * * @param {Object} params Options for this library. * @param {Number} id Content identifier * @returns {undefined} */ (function ($) { H5P.Image = function (params, id) { H5P.EventDispatcher.call(this); if (params.file === undefined || !(params.file instanceof Object)) { this.placeholder = true; } else { this.source = H5P.getPath(params.file.path, id); this.width = params.file.width; this.height = params.file.height; // Use new copyright information if available. Fallback to old. if (params.file.copyright !== undefined) { this.copyright = params.file.copyright; } else if (params.copyright !== undefined) { this.copyright = params.copyright; } } this.alt = params.alt !== undefined ? params.alt : 'New image'; if (params.title !== undefined) { this.title = params.title; } }; H5P.Image.prototype = Object.create(H5P.EventDispatcher.prototype); H5P.Image.prototype.constructor = H5P.Image; /** * Wipe out the content of the wrapper and put our HTML in it. * * @param {jQuery} $wrapper * @returns {undefined} */ H5P.Image.prototype.attach = function ($wrapper) { var self = this; var source = this.source; if (self.$img === undefined) { if(self.placeholder) { self.$img = $('
', { width: '100%', height: '100%', class: 'h5p-placeholder', title: this.title === undefined ? '' : this.title, load: function () { self.trigger('loaded'); } }); } else { self.$img = $('', { width: '100%', height: '100%', src: source, alt: this.alt, title: this.title === undefined ? '' : this.title, load: function () { self.trigger('loaded'); } }); } } $wrapper.addClass('h5p-image').html(self.$img); }; /** * Gather copyright information for the current content. * * @returns {H5P.ContentCopyright} */ H5P.Image.prototype.getCopyrights = function () { if (this.copyright === undefined) { return; } var info = new H5P.ContentCopyrights(); var image = new H5P.MediaCopyright(this.copyright); image.setThumbnail(new H5P.Thumbnail(this.source, this.width, this.height)); info.addMedia(image); return info; }; return H5P.Image; }(H5P.jQuery)); ; var H5P = H5P || {}; H5P.ImageSlide = (function ($) { /** * Constructor function. */ function C(options, contentId, extras) { var self = this; this.$ = $(this); H5P.EventDispatcher.call(this); this.aspectRatio = this.originalAspectRatio = extras.aspectRatio; // Extend defaults with provided options this.options = $.extend(true, {}, { image: null, }, options); // Keep provided id. this.contentId = contentId; this.image = H5P.newRunnable(this.options.image, this.contentId); this.image.on('loaded', function() { self.trigger('loaded'); self.trigger('resize'); }); } C.prototype = Object.create(H5P.EventDispatcher.prototype); C.prototype.constructor = C; /** * Attach function called by H5P framework to insert H5P content into * page * * @param {jQuery} $container */ C.prototype.attach = function ($container) { this.$container = $container; // Set class on container to identify it as a greeting card // container. Allows for styling later. $container.addClass("h5p-image-slide"); this.$imageHolder = $('
', { class: 'h5p-image-slide-image-holder', }); $container.append(this.$imageHolder); // Add image this.image.attach(this.$imageHolder); this.adjustSize(); }; /** * Set the ascpect ratio for this slide * * @param {Integer} newAspectRatio the aspect ratio */ C.prototype.setAspectRatio = function(newAspectRatio) { this.aspectRatio = newAspectRatio; // Adjust size if image has been attached if (this.$imageHolder) { this.adjustSize(); } }; /** * Reset the aspect ratio to the previously set aspect ratio * * Typically used when exiting fullscreen mode */ C.prototype.resetAspectRatio = function() { this.aspectRatio = this.originalAspectRatio; // Adjust size if image has been attached if (this.$imageHolder) { this.adjustSize(); } }; /** * Update the size of the slide * * Typically used when the screen resizes, goes to fullscreen or similar */ C.prototype.adjustSize = function() { var imageHeight = this.options.image.params.file.height; var imageWidth = this.options.image.params.file.width; var imageAspectRatio = imageWidth / imageHeight; if (this.aspectRatio >= imageAspectRatio) { // image too tall - Make it smaller and center it var widthInPercent = imageAspectRatio / this.aspectRatio * 100; var borderSize = (100 - widthInPercent) / 2 + '%'; this.$imageHolder.css({ height: '100%', width: imageAspectRatio / this.aspectRatio * 100 + '%', paddingLeft: borderSize, paddingRight: borderSize, paddingTop: 0, paddingBottom: 0 }); } else if (this.aspectRatio < imageAspectRatio) { // image too wide var heightInPercent = this.aspectRatio / imageAspectRatio * 100; // Note: divide by aspect ratio since padding top/bottom is relative to width var borderSize = (100 - heightInPercent) / 2 / this.aspectRatio + '%'; this.$imageHolder.css({ width: '100%', height: heightInPercent + '%', paddingTop: borderSize, paddingBottom: borderSize, paddingLeft: 0, paddingRight: 0 }); } else if (this.aspectRatio === undefined) { this.$imageHolder.css({ width: '100%', height: '', paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0 }); } }; return C; })(H5P.jQuery); ; var H5P = H5P || {}; H5P.ImageSlider = (function ($) { /** * Constructor function. */ function C(options, id) { this.$ = $(this); var self = this; H5P.EventDispatcher.call(this); // Extend defaults with provided options this.options = $.extend(true, {}, { imageSlides: [ { imageSlide: null } ], a11y: { nextSlide: 'Next Image', prevSlide: 'Previous Image', gotoSlide: 'Go to image %slide' }, aspectRatioMode: 'auto', aspectRatio: { aspectWidth: 4, aspectHeight: 3 } }, options); // Keep provided id. this.id = id; this.currentSlideId = 0; this.imageSlides = []; this.imageSlideHolders = []; this.determineAspectRatio(); for (var i = 0; i < this.options.imageSlides.length; i++) { this.imageSlides[i] = H5P.newRunnable(this.options.imageSlides[i], this.id, undefined, undefined, { aspectRatio: this.aspectRatio }); this.imageSlides[i].on('loaded', function() { self.trigger('resize'); }); this.imageSlideHolders[i] = false; } this.on('enterFullScreen', function() { self.enterFullScreen(); }); this.on('exitFullScreen', function(){ self.exitFullScreen(); }); this.on('resize', function() { var fullScreenOn = self.$container.hasClass('h5p-fullscreen') || self.$container.hasClass('h5p-semi-fullscreen'); if (fullScreenOn) { self.$slides.css('height', ''); var newAspectRatio = window.innerWidth / (window.innerHeight - self.$progressBar.outerHeight()); for (var i = 0; i < self.imageSlides.length; i++) { self.imageSlides[i].setAspectRatio(newAspectRatio); } } else { if (self.aspectRatio && self.$slides) { self.$slides.height(self.$slides.width() / self.aspectRatio); } } self.updateNavButtons(); self.updateProgressBar(); }); } C.prototype = Object.create(H5P.EventDispatcher.prototype); C.prototype.constructor = C; /** * Set the aspect ratio for this image-slider */ C.prototype.determineAspectRatio = function() { // Set aspectRatio to default this.aspectRatio = 4/3; // Try to identify aspectRatio according to settings switch (this.options.aspectRatioMode) { case 'auto': var imageRatios = []; for (var i = 0; i < this.options.imageSlides.length; i++) { var imageFile = this.options.imageSlides[i].params.image.params.file; imageRatios[i] = imageFile.width / imageFile.height; } imageRatios.sort(function (a, b) { return a - b; }); // Get the median image ratio this.aspectRatio = imageRatios[Math.round(imageRatios.length / 2) - 1]; break; case 'custom': if (this.options.aspectRatio.aspectWidth && this.options.aspectRatio.aspectHeight) { this.aspectRatio = this.options.aspectRatio.aspectWidth / this.options.aspectRatio.aspectHeight; } break; case 'notFixed': this.aspectRatio = undefined; break; } }; /** * Attach function called by H5P framework to insert H5P content into * page * * @param {jQuery} $container */ C.prototype.attach = function ($container) { this.$container = $container; // Set class on container to identify it as a greeting card // container. Allows for styling later. $container.addClass("h5p-image-slider").addClass('h5p-image-slider-using-mouse'); $container.bind('keydown', function(e) { var keyboardNavKeys = [32, 13, 9]; if (keyboardNavKeys.indexOf(e.which) !== -1) { $container.removeClass('h5p-image-slider-using-mouse'); } }); $container.bind('mousedown', function() { $container.addClass('h5p-image-slider-using-mouse'); }); this.$slidesHolder = $('
', { class: 'h5p-image-slider-slides-holder' }).appendTo($container); this.$slides = $('
', { class: 'h5p-image-slider-slides' }).appendTo(this.$slidesHolder); this.loadImageSlides(); this.$currentSlide = this.imageSlideHolders[0].addClass('h5p-image-slider-current'); this.attachControls(); }; /** * Update layout when entering fullscreen. * * Many layout changes are handled on resize. */ C.prototype.enterFullScreen = function() { this.updateNavButtons(); this.updateProgressBar(); }; /** * Update layout when entering fullscreen. * * Many layout changes are handled on resize. */ C.prototype.exitFullScreen = function() { for (var i = 0; i < this.imageSlides.length; i++) { this.imageSlides[i].resetAspectRatio(); } this.updateNavButtons(); this.updateProgressBar(); }; /** * Adds the HTML for the next three slides to the DOM */ C.prototype.loadImageSlides = function() { // Load next three imageSlides (not all for performance reasons) for (var i = this.currentSlideId; i < this.imageSlides.length && i < this.currentSlideId + 3; i++) { if (this.imageSlideHolders[i] === false) { this.imageSlideHolders[i] = $('
', { 'class': 'h5p-image-slide-holder' }); if (i > 0) { this.imageSlideHolders[i].attr('aria-hidden', true); } if (i > this.currentSlideId) { this.imageSlideHolders[i].addClass('h5p-image-slider-future'); } this.imageSlides[i].attach(this.imageSlideHolders[i]); this.$slides.append(this.imageSlideHolders[i]); } } }; /** * Attaches controls to the DOM */ C.prototype.attachControls = function() { var self = this; this.$leftButton = this.createControlButton(this.options.a11y.prevSlide, 'left'); this.$rightButton = this.createControlButton(this.options.a11y.nextSlide, 'right'); C.handleButtonClick(this.$leftButton, function () { if (!self.dragging) { self.gotoSlide(self.currentSlideId - 1); } }); C.handleButtonClick(this.$rightButton, function() { if (!self.dragging) { self.gotoSlide(self.currentSlideId + 1); } }); this.$slidesHolder.append(this.$leftButton); this.$slidesHolder.append(this.$rightButton); this.updateNavButtons(); this.attachProgressBar(); this.initDragging(); this.initKeyEvents(); }; /** * Attaches the progress bar to the DOM */ C.prototype.attachProgressBar = function() { this.$progressBar = $('
    ', { class: 'h5p-image-slider-progress' }); for (var i = 0; i < this.imageSlides.length; i++) { this.$progressBar.append(this.createProgressBarElement(i)); } this.$slidesHolder.append(this.$progressBar); }; /** * Creates a progress bar button * * @param {Integer} index - slide index the progress bare element corresponds to * @return {jQuery} - progress bar button */ C.prototype.createProgressBarElement = function(index) { var self = this; var $progressBarElement = $('
  • ', { class: 'h5p-image-slider-progress-element', role: 'button', "aria-label": self.options.a11y.gotoSlide.replace('%slide', index + 1), tabindex: 0, }); C.handleButtonClick($progressBarElement, function() { self.gotoSlide(index); }); if (index === 0) { $progressBarElement.addClass('h5p-image-slider-current-progress-element'); } return $progressBarElement; }; /** * Creates a next or previous button * * @param {string} text - label for the button * @param {string} dir - next or prev * @return {jQuery} control button */ C.prototype.createControlButton = function(text, dir) { var $controlButton = $('
    ', { class: 'h5p-image-slider-button ' + 'h5p-image-slider-' + dir + '-button', }); var $controlBg = $('
    ', { class: 'h5p-image-slider-button-background' }); $controlButton.append($controlBg); var $controlText = $('
    ', { class: 'h5p-image-slider-button-text', 'aria-label': text, 'role': 'button', 'tabindex': 0 }); $controlButton.append($controlText); return $controlButton; }; /** * Go to a specific slide * * @param {Integer} slideId the index of the slide we want to go to * @return {Boolean} false if failed(typically the slide didn't exist), true if not */ C.prototype.gotoSlide = function(slideId) { if (slideId < 0 || slideId >= this.imageSlideHolders.length) { return false; } $('.h5p-image-slider-removing', this.$container).removeClass('.h5p-image-slider-removing'); var nextSlideDirection = (this.currentSlideId < slideId) ? 'future' : 'past'; var prevSlideDirection = nextSlideDirection === 'past' ? 'future' : 'past'; this.currentSlideId = slideId; this.loadImageSlides(); var $prevSlide = this.$currentSlide; var $nextSlide = (this.imageSlideHolders[slideId]); if (!this.dragging) { this.prepareNextSlideForAnimation($nextSlide, nextSlideDirection); } setTimeout(function() { $nextSlide.removeClass('h5p-image-slider-no-transition'); $prevSlide.removeClass('h5p-image-slider-current') .addClass('h5p-image-slider-removing') .removeClass('h5p-image-slider-' + nextSlideDirection) .addClass('h5p-image-slider-' + prevSlideDirection) .attr('aria-hidden', true); $nextSlide.removeClass('h5p-image-slider-past') .removeClass('h5p-image-slider-future') .addClass('h5p-image-slider-current') .removeAttr('aria-hidden'); }, 1); this.$currentSlide = $nextSlide; this.updateNavButtons(); this.updateProgressBar(); return true; }; /** * Position the next slide correctly so that it is ready to be aimated in * * @param {jQuery} $nextSlide the slide to be prepared * @param {String} direction prev or next */ C.prototype.prepareNextSlideForAnimation = function($nextSlide, direction) { $nextSlide.removeClass('h5p-image-slider-past') .removeClass('h5p-image-slider-future') .addClass('h5p-image-slider-no-transition') .addClass('h5p-image-slider-' + direction); }; /** * Updates all navigation buttons, typically toggling and positioning */ C.prototype.updateNavButtons = function() { if (this.currentSlideId >= this.imageSlides.length - 1) { this.$rightButton.hide(); } else { this.$rightButton.show(); } if (this.currentSlideId <= 0) { this.$leftButton.hide(); } else { this.$leftButton.show(); } var heightInPercent = 100; var fullScreenOn = this.$container.hasClass('h5p-fullscreen') || this.$container.hasClass('h5p-semi-fullscreen'); if (!fullScreenOn) { heightInPercent = this.$currentSlide.height() / this.$slides.height() * 100; } this.$leftButton.css('height', heightInPercent + '%'); this.$rightButton.css('height', heightInPercent + '%'); }; /** * Update the progress bar * * Highlights the element in the progress bar corresponding to the current slide * and reposition the progress bar */ C.prototype.updateProgressBar = function () { $('.h5p-image-slider-current-progress-element', this.$container).removeClass('h5p-image-slider-current-progress-element'); $('.h5p-image-slider-progress-element', this.$container).eq(this.currentSlideId).addClass('h5p-image-slider-current-progress-element'); var heightInPercent = this.$currentSlide.height() / this.$slides.height() * 100; $('.h5p-image-slider-progress', this.$container).css('top', heightInPercent + '%'); }; /** * Make a slide draggable */ C.prototype.initDragging = function () { var self = this; this.$slidesHolder.on('touchstart', function(event) { self.dragging = true; self.dragStartX = event.originalEvent.touches[0].pageX; self.$currentSlide.addClass('h5p-image-slider-dragging'); if (self.isButton(event.target)) { event.preventDefault(); event.stopPropagation(); var d = new Date(); self.dragStartTime = d.getTime(); } }); this.$slidesHolder.on('touchmove', function(event) { event.preventDefault(); self.dragActionUpdate(event.originalEvent.touches[0].pageX); }); this.$slidesHolder.on('touchend', function(event) { self.finishDragAction(); if (self.dragStartTime !== false && self.isButton(event.target)) { // This was possibly a click var d = new Date(); if (d.getTime() - self.dragStartTime < 300) { if (self.isRightButton(event.target)) { self.gotoSlide(self.currentSlideId + 1); } else { self.gotoSlide(self.currentSlideId - 1); } } } self.dragStartTime = false; }); this.$slidesHolder.on('touchcancel', function() { self.finishDragAction(); self.dragStartTime = false; }); }; /** * Initialize key press events. * * @returns {undefined} Nothing. */ C.prototype.initKeyEvents = function () { if (this.keydown !== undefined) { return; } var self = this; this.keydown = function (event) { // Left if (event.which === 37) { self.gotoSlide(self.currentSlideId - 1); } // Right else if (event.which === 39) { self.gotoSlide(self.currentSlideId + 1); } }; H5P.jQuery('body').keydown(this.keydown); }; /** * Is the domElement a button? * * @param {DOMElement} domElement the element to be checked * @return {Boolean} whether or not the element is a button */ C.prototype.isButton = function (domElement) { var $target = $(domElement); return $target.hasClass('h5p-image-slider-button-background') || $target.hasClass('h5p-image-slider-button-text') || $target.hasClass('h5p-image-slider-button'); }; /** * Is the element the right/next button? * * @param {DOMElement} domElement the DOM element to be checked * @return {Boolean} Whether or not the element is the right button */ C.prototype.isRightButton = function (domElement) { var $target = $(domElement); return $target.hasClass('h5p-image-slider-right-button') || $target.parent().hasClass('h5p-image-slider-right-button'); }; /** * Update the current and next slide in response to a drag action */ C.prototype.dragActionUpdate = function(x) { this.dragXMovement = x - this.dragStartX; this.$currentSlide.css('transform', 'translateX(' + this.dragXMovement + 'px)'); if (this.currentSlideId > 0) { var $prevSlide = this.imageSlideHolders[this.currentSlideId - 1].addClass('h5p-image-slider-dragging'); if (this.dragXMovement < 0) { $prevSlide.css('transform', 'translateX(-100.001%)'); } else { $prevSlide.css('transform', 'translateX(' + (this.dragXMovement - $prevSlide.width()) + 'px)'); } } if (this.currentSlideId < this.imageSlideHolders.length - 1) { var $nextSlide = this.imageSlideHolders[this.currentSlideId + 1].addClass('h5p-image-slider-dragging'); if (this.dragXMovement > 0) { $nextSlide.css('transform', 'translateX(100.001%)'); } else { $nextSlide.css('transform', 'translateX(' +(this.dragXMovement + $nextSlide.width()) + 'px)'); } } }; /** * Actions to be done when a drag action is finished * * Will either go back to the current slide or finish the transition to the next slide */ C.prototype.finishDragAction = function() { $('.h5p-image-slider-dragging', this.$container).removeClass('h5p-image-slider-dragging').each(function() { this.style.removeProperty('transform'); }); this.dragStartX = undefined; var xInPercent = this.dragXMovement / this.$currentSlide.width(); if (xInPercent < -0.3) { if (this.currentSlideId < this.imageSlideHolders.length - 1) { this.gotoSlide(this.currentSlideId + 1); } else { this.$currentSlide.css('transform', 'translateX(0%)'); } } else if (xInPercent > 0.3) { if (this.currentSlideId > 0) { this.gotoSlide(this.currentSlideId - 1); } else { this.$currentSlide.css('transform', 'translateX(0%)'); } } this.dragging = false; this.dragXMovement = 0; }; /** * Make a non-button element behave as a button. I.e handle enter and space * keydowns as click * * @param {H5P.jQuery} $element The "button" element * @param {Function} callback */ C.handleButtonClick = function ($element, callback) { $element.click(function (event) { callback.call(this, event); }); $element.keydown(function (event) { // 32 - space, 13 - enter if ([32, 13].indexOf(event.which) !== -1) { event.preventDefault(); callback.call(this, event); } }); }; return C; })(H5P.jQuery); ;