import { uuid } from './utils/index'; import { removeClasses, height, width, outerHeight } from './utils/css'; import { isString, isFunction } from './utils/type'; import EventEmitter from './utils/eventEmitter'; import Autoplay from './components/autoplay'; import Breakpoint from './components/breakpoint'; import Infinite from './components/infinite'; import Loop from './components/loop'; import Navigation from './components/navigation'; import Pagination from './components/pagination'; import Swipe from './components/swipe'; import Transitioner from './components/transitioner'; import defaultOptions from './defaultOptions'; import template from './templates'; import templateItem from './templates/item'; export default class bulmaCarousel extends EventEmitter { constructor(selector, options = {}) { super(); this.element = isString(selector) ? document.querySelector(selector) : selector; // An invalid selector or non-DOM node has been provided. if (!this.element) { throw new Error('An invalid selector or non-DOM node has been provided.'); } this._clickEvents = ['click', 'touch']; // Use Element dataset values to override options const elementConfig = this.element.dataset ? Object.keys(this.element.dataset) .filter(key => Object.keys(defaultOptions).includes(key)) .reduce((obj, key) => { return { ...obj, [key]: this.element.dataset[key] }; }, {}) : {}; // Set default options - dataset attributes are master this.options = { ...defaultOptions, ...options, ...elementConfig }; this._id = uuid('slider'); this.onShow = this.onShow.bind(this); // Initiate plugin this._init(); } /** * Initiate all DOM element containing datePicker class * @method * @return {Array} Array of all datePicker instances */ static attach(selector = '.slider', options = {}) { let instances = new Array(); const elements = isString(selector) ? document.querySelectorAll(selector) : Array.isArray(selector) ? selector : [selector]; [].forEach.call(elements, element => { if (typeof element[this.constructor.name] === 'undefined') { const instance = new bulmaCarousel(element, options); element[this.constructor.name] = instance; instances.push(instance); } else { instances.push(element[this.constructor.name]); } }); return instances; } /**************************************************** * * * PRIVATE FUNCTIONS * * * ****************************************************/ /** * Initiate plugin instance * @method _init * @return {Slider} Current plugin instance */ _init() { this._items = Array.from(this.element.children); // Load plugins this._breakpoint = new Breakpoint(this); this._autoplay = new Autoplay(this); this._navigation = new Navigation(this); this._pagination = new Pagination(this); this._infinite = new Infinite(this); this._loop = new Loop(this); this._swipe = new Swipe(this); this._build(); if (isFunction(this.options.onReady)) { this.options.onReady(this); } return this; } /** * Build Slider HTML component and append it to the DOM * @method _build */ _build() { // Generate HTML Fragment of template this.node = document.createRange().createContextualFragment(template(this.id)); // Save pointers to template parts this._ui = { wrapper: this.node.firstChild, container: this.node.querySelector('.slider-container') } // Add slider to DOM this.element.appendChild(this.node); this._ui.wrapper.classList.add('is-loading'); this._ui.container.style.opacity = 0; this._transitioner = new Transitioner(this); // Wrap all items by slide element this._slides = this._items.map((item, index) => { return this._createSlide(item, index); }); this.reset(); this._bindEvents(); this._ui.container.style.opacity = 1; this._ui.wrapper.classList.remove('is-loading'); } /** * Bind all events * @method _bindEvents * @return {void} */ _bindEvents() { this.on('show', this.onShow); } _unbindEvents() { this.off('show', this.onShow); } _createSlide(item, index) { const slide = document.createRange().createContextualFragment(templateItem()).firstChild; slide.dataset.sliderIndex = index; slide.appendChild(item); return slide; } /** * Calculate slider dimensions */ _setDimensions() { if (!this.options.vertical) { if (this.options.centerMode) { this._ui.wrapper.style.padding = '0px ' + this.options.centerPadding; } } else { this._ui.wrapper.style.height = outerHeight(this._slides[0]) * this.slidesToShow; if (this.options.centerMode) { this._ui.wrapper.style.padding = this.options.centerPadding + ' 0px'; } } this._wrapperWidth = width(this._ui.wrapper); this._wrapperHeight = outerHeight(this._ui.wrapper); if (!this.options.vertical) { this._slideWidth = Math.ceil(this._wrapperWidth / this.slidesToShow); this._containerWidth = Math.ceil(this._slideWidth * this._slides.length); this._ui.container.style.width = this._containerWidth + 'px'; } else { this._slideWidth = Math.ceil(this._wrapperWidth); this._containerHeight = Math.ceil((outerHeight(this._slides[0]) * this._slides.length)); this._ui.container.style.height = this._containerHeight + 'px'; } this._slides.forEach(slide => { slide.style.width = this._slideWidth + 'px'; }); } _setHeight() { if (this.options.effect !== 'translate') { this._ui.container.style.height = outerHeight(this._slides[this.state.index]) + 'px'; } } // Update slides classes _setClasses() { this._slides.forEach(slide => { removeClasses(slide, 'is-active is-current is-slide-previous is-slide-next'); if (Math.abs((this.state.index - 1) % this.state.length) === parseInt(slide.dataset.sliderIndex, 10)) { slide.classList.add('is-slide-previous'); } if (Math.abs(this.state.index % this.state.length) === parseInt(slide.dataset.sliderIndex, 10)) { slide.classList.add('is-current'); } if (Math.abs((this.state.index + 1) % this.state.length) === parseInt(slide.dataset.sliderIndex, 10)) { slide.classList.add('is-slide-next'); } }); } /**************************************************** * * * GETTERS and SETTERS * * * ****************************************************/ /** * Get id of current datePicker */ get id() { return this._id; } set index(index) { this._index = index; } get index() { return this._index; } set length(length) { this._length = length; } get length() { return this._length; } get slides() { return this._slides; } set slides(slides) { this._slides = slides; } get slidesToScroll() { return this.options.effect === 'translate' ? this._breakpoint.getSlidesToScroll() : 1; } get slidesToShow() { return this.options.effect === 'translate' ? this._breakpoint.getSlidesToShow() : 1; } get direction() { return (this.element.dir.toLowerCase() === 'rtl' || this.element.style.direction === 'rtl') ? 'rtl' : 'ltr'; } get wrapper() { return this._ui.wrapper; } get wrapperWidth() { return this._wrapperWidth || 0; } get container() { return this._ui.container; } get containerWidth() { return this._containerWidth || 0; } get slideWidth() { return this._slideWidth || 0; } get transitioner() { return this._transitioner; } /**************************************************** * * * EVENTS FUNCTIONS * * * ****************************************************/ onShow(e) { this._navigation.refresh(); this._pagination.refresh(); this._setClasses(); } /**************************************************** * * * PUBLIC FUNCTIONS * * * ****************************************************/ next() { if (!this.options.loop && !this.options.infinite && this.state.index + this.slidesToScroll > this.state.length - this.slidesToShow && !this.options.centerMode) { this.state.next = this.state.index; } else { this.state.next = this.state.index + this.slidesToScroll; } this.show(); } previous() { if (!this.options.loop && !this.options.infinite && this.state.index === 0) { this.state.next = this.state.index; } else { this.state.next = this.state.index - this.slidesToScroll; } this.show(); } start() { this._autoplay.start(); } pause() { this._autoplay.pause(); } stop() { this._autoplay.stop(); } show(index, force = false) { // If all slides are already visible then return if (!this.state.length || this.state.length <= this.slidesToShow) { return; } if (typeof index === 'number') { this.state.next = index; } if (this.options.loop) { this._loop.apply(); } if (this.options.infinite) { this._infinite.apply(); } // If new slide is already the current one then return if (this.state.index === this.state.next) { return; } this.emit('before:show', this.state); this._transitioner.apply(force, this._setHeight.bind(this)); this.emit('after:show', this.state); this.emit('show', this); } reset() { this.state = { length: this._items.length, index: Math.abs(this.options.initialSlide), next: Math.abs(this.options.initialSlide), prev: undefined }; // Fix options if (this.options.loop && this.options.infinite) { this.options.loop = false; } if (this.options.slidesToScroll > this.options.slidesToShow) { this.options.slidesToScroll = this.slidesToShow; } this._breakpoint.init(); if (this.state.index >= this.state.length && this.state.index !== 0) { this.state.index = this.state.index - this.slidesToScroll; } if (this.state.length <= this.slidesToShow) { this.state.index = 0; } this._ui.wrapper.appendChild(this._navigation.init().render()); this._ui.wrapper.appendChild(this._pagination.init().render()); if (this.options.navigationSwipe) { this._swipe.bindEvents(); } else { this._swipe._bindEvents(); } this._breakpoint.apply(); // Move all created slides into slider this._slides.forEach(slide => this._ui.container.appendChild(slide)); this._transitioner.init().apply(true, this._setHeight.bind(this)); if (this.options.autoplay) { this._autoplay.init().start(); } } /** * Destroy Slider * @method destroy */ destroy() { this._unbindEvents(); this._items.forEach(item => { this.element.appendChild(item); }); this.node.remove(); } }