diff --git a/src/full.js b/src/full.js index ad71d64..20009bf 100644 --- a/src/full.js +++ b/src/full.js @@ -1,3 +1,6 @@ import WebSlides from './modules/webslides'; +import Scroll from './plugins/scroll'; + +WebSlides.registerPlugin('Scroll', Scroll); window.WebSlides = WebSlides; diff --git a/src/modules/webslides.js b/src/modules/webslides.js index 4e31f55..44d87f6 100644 --- a/src/modules/webslides.js +++ b/src/modules/webslides.js @@ -7,6 +7,7 @@ const CLASSES = { VERTICAL: 'vertical' }; +// Default plugins const PLUGINS = { 'nav': Plugins.Navigation, 'hash': Plugins.Hash, @@ -15,27 +16,68 @@ const PLUGINS = { export default class WebSlides { constructor() { + /** + * WebSlide element. + * @type {Element} + */ this.el = document.getElementById('webslides'); + /** + * Moving flag. + * @type {boolean} + */ this.isMoving = false; + /** + * Slide's array. + * @type {?Array} + */ this.slides = null; + /** + * Current slide's index. + * @type {number} + * @private + */ this.currentSlideI_ = -1; + /** + * Current slide reference. + * @type {?Slide} + * @private + */ this.currentSlide_ = null; + /** + * Max slide index. + * @type {number} + * @private + */ this.maxSlide_ = 0; + /** + * Whether the layout is going to be vertical or horizontal. + * @type {boolean} + */ this.isVertical = this.el.classList.contains(CLASSES.VERTICAL); + /** + * Plugin's dictionary. + * @type {Object} + */ this.plugins = {}; if (!this.el) { throw new Error('Couldn\'t find the webslides container!'); } + // Bootstrapping this.removeChildren_(); this.grabSlides_(); this.createPlugins_(); this.initSlides_(); - + // Finished this.onInit_(); } + /** + * Removes all children elements inside of the main container that are not + * eligible to be a Slide Element. + * @private + */ removeChildren_() { const nodes = this.el.childNodes; let i = nodes.length; @@ -49,6 +91,11 @@ export default class WebSlides { } } + /** + * Creates all the registered plugins and store the instances inside of the + * the webslide instance. + * @private + */ createPlugins_() { Object.keys(PLUGINS).forEach(pluginName => { const pluginCto = PLUGINS[pluginName]; @@ -56,10 +103,19 @@ export default class WebSlides { }); } + /** + * Called once the WebSlide instance has finished initialising. + * @private + * @fires WebSlide#ws:init + */ onInit_() { DOM.fireEvent(this.el, 'ws:init'); } + /** + * Grabs the slides from the DOM and creates all the Slides modules. + * @private + */ grabSlides_() { this.slides = Array.from(this.el.childNodes) .map((slide, i) => new Slide(slide, i)); @@ -67,6 +123,13 @@ export default class WebSlides { this.maxSlide_ = this.slides.length; } + /** + * Goes to a given slide. + * @param {!number} slideI The slide index. + * @param {?boolean} forward Whether we're forcing moving forward/backwards. + * This parameter is used only from the goNext, goPrev functions to adjust the + * scroll animations. + */ goToSlide(slideI, forward = null) { if (this.isValidIndexSlide_(slideI) && !this.isMoving) { this.isMoving = true; @@ -81,17 +144,28 @@ export default class WebSlides { } const nextSlide = this.slides[slideI]; - if (this.currentSlide_ !== null) { - this.animateToSlide_(isMovingForward, nextSlide, this.onSlideChange_); + if (this.currentSlide_ !== null && !this.isVertical) { + this.scrollTransitionToSlide_( + isMovingForward, nextSlide, this.onSlideChange_); } else { this.transitionToSlide_( isMovingForward, nextSlide, this.onSlideChange_); - nextSlide.moveBeforeFirst(); } } } - animateToSlide_(isMovingForward, nextSlide, callback) { + /** + * Transitions to a slide, doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is an + * async function so it'll happen once the scroll animation finishes. + * @private + * @see DOM.lockScroll + * @see DOM.unlockScroll + * @see ScrollHelper.scrollTo + */ + scrollTransitionToSlide_(isMovingForward, nextSlide, callback) { DOM.lockScroll(); if (!isMovingForward) { @@ -114,6 +188,14 @@ export default class WebSlides { }); } + /** + * Transitions to a slide, without doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is a + * sync function so it'll happen on run time. + * @private + */ transitionToSlide_(isMovingForward, nextSlide, callback) { ScrollHelper.scrollTo(0, 0); @@ -130,6 +212,14 @@ export default class WebSlides { callback.call(this, nextSlide); } + /** + * Whenever a slide is changed, this function gets called. It updates the + * references to the current slide, disables the moving flag and fires + * a custom event. + * @param {Slide} slide The slide we're transitioning to. + * @fires WebSlide#ws:slide-change + * @private + */ onSlideChange_(slide) { this.currentSlide_ = slide; this.currentSlideI_ = slide.i; @@ -142,6 +232,9 @@ export default class WebSlides { }); } + /** + * Goes to the next slide. + */ goNext() { let nextIndex = this.currentSlideI_ + 1; @@ -152,6 +245,9 @@ export default class WebSlides { this.goToSlide(nextIndex, true); } + /** + * Goes to the previous slide. + */ goPrev() { let prevIndex = this.currentSlideI_ - 1; @@ -162,10 +258,22 @@ export default class WebSlides { this.goToSlide(prevIndex, false); } + /** + * Check if the given number is a valid index to go to. + * @param {number} i The index to check. + * @return {boolean} Whether you can move to that slide or not. + * @private + */ isValidIndexSlide_(i) { return i >= 0 && i < this.maxSlide_; } + /** + * Init the shown slide on load. It'll fetch it from the Hash if present + * and, otherwise, it'll default to the first one. + * @private + * @see Hash.getSlideNumber + */ initSlides_() { let slideNumber = this.plugins.hash.constructor.getSlideNumber(); @@ -186,4 +294,19 @@ export default class WebSlides { this.goToSlide(slideNumber); } + + /** + * Registers a plugin to be loaded when the instance is created. It allows + * (on purpose) to replace default plugins. + * Those being: + * - Navigation + * - Hash + * - Keyboard + * @param {!string} key They key under which it'll be stored inside of the + * instance, inside the plugins dict. + * @param {!Function} cto Plugin constructor. + */ + static registerPlugin(key, cto) { + PLUGINS[key] = cto; + } } diff --git a/src/plugins/keyboard.js b/src/plugins/keyboard.js index d231e33..20f473d 100644 --- a/src/plugins/keyboard.js +++ b/src/plugins/keyboard.js @@ -2,6 +2,7 @@ import Keys from '../utils/keys'; export default class Keyboard { /** + * Keyboard interaction plugin. * @param {WebSlides} wsInstance The WebSlides instance */ constructor(wsInstance) { @@ -11,9 +12,15 @@ export default class Keyboard { */ this.ws_ = wsInstance; - document.addEventListener("keydown", this.onKeyPress_.bind(this), false); + document.addEventListener('keydown', this.onKeyPress_.bind(this), false); } + /** + * Reacts to the keydown event. It reacts to the arrows and space key + * depending on the layout of the page. + * @param {KeyboardEvent} event The key event. + * @private + */ onKeyPress_(event) { let method; diff --git a/src/plugins/scroll.js b/src/plugins/scroll.js new file mode 100644 index 0000000..e69de29