lory-8.x-1.x-dev/js/src/lory.extend.js
js/src/lory.extend.js
/**
* @file
* Provides Lory extended feature.
*/
(function (drupalSettings, _db, window, document) {
'use strict';
var _lory = lory;
/**
* Overiddes lory constructor to allow both partial, and full replacement.
*
* @param {HTMLElement} el
* The lory HTML element.
* @param {Object} options
* The given options.
*
* @return {Object}
* The original lory contructor.
*/
lory = function (el, options) {
var me = this;
var settings = drupalSettings.lory || {};
me.$slider = el;
me.options = _db.extend({}, me.defaults, settings, options);
// Set elements, properties, custom events.
me.setElements();
me.setProps();
me.attachEvents();
// Call the real function, passing along all the arguments.
var ret = _lory.apply(this, arguments);
// If a non-`null` object returned, use it instead of `this`, else `this`.
if (!ret || typeof ret !== 'object') {
ret = me;
}
// Merge methods and properties.
_db.extend(me, ret);
// Runs own initialization.
me.init();
return ret;
};
// Inherits from original lory prototype.
lory.prototype = Object.create(_lory.prototype);
// Cache the prototype once.
var _l = lory.prototype;
/**
* Adds default objects.
*/
_l.defaults = {
asNavFor: '',
classes: false,
transition: false,
animation: false,
responsive: null,
effect: null,
shadow: false,
useTransform: true
};
/**
* Adds extensible lory effect objects.
*/
_l.effects = {
// Do nothing to satisfy eslint, coder.
};
/**
* Runs init if needed.
*/
_l.init = function () {
var me = this;
var o = me.options;
if (me.count > 1) {
// @todo: Use .setCss() when indices are correct.
if (o.initialSlide > 0 && !o.asNavFor) {
me.slideTo(o.initialSlide);
}
}
};
/**
* Runs after.lory.init.
*
* @param {Object.Event} e
* The event triggered by a `lory.after.init` event.
*/
_l.afterInit = function (e) {
var me = this;
var o = me.options;
if (me.count > 1) {
o.focusOnSelect = !o.asNavFor ? o.focusOnSelect : false;
// Excludes asNavFor which should move both nav and main slides.
if (o.focusOnSelect === true) {
_db.on(me.$slider, 'click', '.slide', me.focusOnSelect.bind(me));
}
if (o.arrows) {
me.$arrows.classList.remove('visually-hidden');
me.updateArrows(e);
}
// Runs optional transition when all is ready.
me.doEffect();
}
};
/**
* Execute effect if supported.
*/
_l.doEffect = function () {
var me = this;
var trans = me.options.effect;
if (trans) {
_db.forEach(trans, function (name) {
if (typeof me.effects[name] === 'function') {
var fx = new me.effects[name](me);
if (!fx.support) {
me.$slider.classList.remove('lory--' + name);
return;
}
fx.run();
}
});
}
};
/**
* Adapts the slider height.
*
* @param {Object.Event} e
* The event triggered by various `custom` events.
*/
_l.adaptiveHeight = function (e) {
var me = this;
var o = me.options;
if (o.adaptiveHeight && me.$curr !== null) {
var ht = me.$curr.offsetHeight;
me.$slider.style.minHeight = o.dots ? ht + 40 + 'px' : ht + 'px';
me.$track.style.minHeight = ht + 'px';
}
};
/**
* Update relevant states.
*
* Triggered at after.lory.init, before.lory.slide, after.lory.slide.
*
* @param {Object.Event} e
* The event triggered by various `custom` events.
*/
_l.update = function (e) {
var me = this;
// @todo: Use me.currentSlide when corrected.
me.$curr = me.$slider.querySelector(me.currSel);
// If transform disabled, empty transform.
// @todo: Respect initialSlide option.
if (!me.options.useTransform) {
me.setCss(0, true, true);
}
me.adaptiveHeight(e);
};
/**
* Fixes for Firefox draggable issue.
*
* @param {Object.Event} e
* The event triggered by a `dragstart` event.
*/
_l.preventDefault = function (e) {
e.preventDefault();
};
/**
* Fetches all events.
*
* @return {Object}
* The merged event objects.
*/
_l.getEvents = function () {
var me = this;
var events = {
'before.lory.init': me.onEvents,
'after.lory.init': me.onEvents,
'before.lory.slide': me.onBeforeSlide,
'after.lory.slide': me.onAfterSlide,
'on.lory.resize': me.onEvents,
'dragstart': me.preventDefault
};
return _db.extend({}, me.events, events);
};
/**
* Handles various events.
*
* @param {Object.Event} e
* The event triggered by various `custom` events.
*
* @todo: The nextSlide and currentSlide seem incorrect, readjust once fixed.
* @see https://github.com/meandmax/lory/issues/520
*/
_l.onEvents = function (e) {
var me = this;
var o = me.options;
var slides = me.$track;
var i;
if (e.type === 'before.lory.init') {
if (o.randomize) {
for (i = me.count; i >= 0; i--) {
slides.appendChild(slides.children[Math.random() * i | 0]);
}
}
}
if (e.type === 'on.lory.resize') {
me.winWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
// Seems lory.reset() causes broken lory, so reinit here.
// Fixed for TypeError: slides[(index + infinite)] is undefined.
// @todo: Remove me.setup() when core library fixes this.
me.setup();
me.update(e);
}
if (e.type === 'after.lory.init') {
me.direction = 0;
me.currentSlide = o.initialSlide;
if (o.infinite) {
me.$clones = slides.querySelectorAll(me.slide);
me.countClones = slides.children.length;
}
me.$curr = me.$clones[me.currentSlide];
// Fixed for failing Chrome focusOnSelect.
if (o.infinite > 1) {
me.$track.style.minWidth = me.$list.offsetWidth + 'px';
}
me.afterInit(e);
me.updateClasses();
me.update(e);
// Do not mix transition vs. animation.
if (me.movingItem) {
for (i = 0; i < me.countClones; i++) {
me.$clones[i].addEventListener(o.animation ? 'animationend' : 'transitionend', me.onMoveEnd.bind(me), false);
}
}
}
};
/**
* Handles `before.lory.slide` event.
*
* @param {Object.Event} e
* The event triggered by a `before.lory.slide` event.
*/
_l.onBeforeSlide = function (e) {
var me = this;
me.currentSlide = e.detail.index;
var nextSlide = e.detail.nextSlide;
me.$curr = me.$clones[nextSlide];
me.direction = me.currentSlide > nextSlide ? -1 : 1;
e.detail.direction = me.direction;
if (me.lazy) {
me.doBlazy(e);
}
me.updateClasses(nextSlide);
me.$slider.classList.add('is-animating');
me.$slider.classList.remove('is-animated');
me.update(e);
};
/**
* Handles `after.lory.slide` event.
*
* @param {Object.Event} e
* The event triggered by a `after.lory.slide` event.
*/
_l.onAfterSlide = function (e) {
var me = this;
var el = me.$slider;
var o = me.options;
me.currentSlide = e.detail.currentSlide;
me.direction = 0;
me.$curr = me.$clones[me.currentSlide];
if (el.classList.contains('is-interrupted') || el.classList.contains('is-paused')) {
el.classList.remove('is-interrupted', 'is-paused');
var $playing = el.querySelector('.is-playing');
if ($playing !== null) {
$playing.classList.remove('is-playing');
}
}
if (o.arrows) {
me.updateArrows(e);
}
me.update(e);
};
/**
* Adds click events on each slide HTML element to move slide on click.
*
* @param {Object.Event} e
* The event triggered by various `click` events.
*/
_l.focusOnSelect = function (e) {
var me = this;
var $slide = _db.closest(e.target, '.slide');
var clicked = $slide !== null;
e.preventDefault();
if (_db.matches(e.target, '.media__icon')) {
clicked = false;
}
if (clicked && $slide.classList.contains(me.currClass) && !me.options.asNavFor) {
clicked = false;
}
if (clicked) {
var lid = parseInt($slide.getAttribute('data-lid'));
// @todo: Revisit when https://github.com/meandmax/lory/issues/520 fixed.
// me.slideTo(me.options.asNavFor ? (lid - 1) : lid);
me.slideTo(lid);
me.update(e);
clicked = false;
}
};
/**
* Attaches all events on the container element.
*/
_l.attachEvents = function () {
var me = this;
var el = me.$slider;
me.$curr = me.$curr === null ? el.querySelector('.slide.is-current') : me.$curr;
// Add event listeners.
_db.forEach(me.getEvents(), function (fn, eventName) {
el.addEventListener(eventName, fn.bind(me), false);
});
if (!me.movingItem) {
el.addEventListener('transitionend', me.onMoveEnd.bind(me), false);
}
if (el.querySelector('.media__icon') !== null) {
_db.on(el, 'click', '.media__icon', me.onClickMediaIcon.bind(me));
}
};
/**
* Reacts on transitionend xor animationend event, but not both.
*
* @param {Object.Event} e
* The event triggered by a `transitionend` or `animationend` event.
*/
_l.onMoveEnd = function (e) {
var me = this;
var t = me.movingItem ? me.currSel : me.track;
me.direction = 2;
if (typeof e === 'object' && _db.matches(e.target, t)) {
me.updateClasses();
me.$curr.classList.add('is-focus');
me.$slider.classList.remove('is-animating');
me.$slider.classList.add('is-animated');
me.update(e);
}
};
/**
* Adds classes to the neighbors of current element.
*
* @param {int} nextSlide
* The nest slide index.
*/
_l.updateClasses = function (nextSlide) {
var me = this;
var pos = (typeof nextSlide !== 'undefined') ? nextSlide : me.currentSlide;
var curr = me.$clones[pos];
var $prev = curr.previousElementSibling;
var $next = curr.nextElementSibling;
var o = me.options;
var durationProp = o.animation ? 'animationDuration' : 'transitionDuration';
var item;
// Remove classes.
for (var i = 0; i < me.countClones; i++) {
item = me.$clones[i];
item.classList.remove('is-before', 'is-after', 'is-focus');
if (me.movingItem) {
item.style[durationProp] = '';
}
}
// Add classes.
if ($prev !== null) {
$prev.classList.add('is-before');
}
if ($next !== null) {
$next.classList.add('is-after');
}
if (me.movingItem) {
curr.style[durationProp] = o.slideSpeed + 'ms';
}
};
/**
* Reacts on clicking media icons.
*
* @param {Object.Event} e
* The event triggered by a `click` event.
*/
_l.onClickMediaIcon = function (e) {
var me = this;
var el = me.$slider;
if (_db.matches(e.target, '.media__icon--play')) {
el.classList.add('is-paused');
}
else if (_db.matches(e.target, '.media__icon--close')) {
el.classList.remove('is-paused', 'is-interrupted');
}
};
/**
* Sets CSS for custom needs.
*
* This is currently used by centerMode, fade, or vertical.
*
* @param {int} pos
* The current element position.
* @param {bool} reset
* The flag whether to remove a particular CSS prop.
* @param {bool} all
* The flag to remove all styles.
*/
_l.setCss = function (pos, reset, all) {
var me = this;
var x;
var y;
var el = me.$track;
if (!reset) {
x = me.positionProp === 'left' ? Math.ceil(pos) + 'px' : '0px';
y = me.positionProp === 'top' ? Math.ceil(pos) + 'px' : '0px';
}
if (all) {
el.style = '';
}
else {
if (!me.isTransition) {
el.style[me.transformProp] = reset ? '' : 'translate(' + x + ', ' + y + ')';
}
else {
el.style[me.transformProp] = reset ? '' : 'translate3d(' + x + ', ' + y + ', 0px)';
}
}
};
/**
* Set elements.
*/
_l.setElements = function () {
var me = this;
var el = me.$slider;
me.track = '.lory__track';
me.slide = '.lory__slide';
me.currSel = '.slide.is-current';
me.currClass = 'is-current';
me.$wrap = el.parentNode;
me.$track = el.querySelector(me.track);
me.$list = el.querySelector('.lory__frame');
me.$slides = el.querySelectorAll(me.slide);
me.$clones = me.$slides;
me.$arrows = el.querySelector('.lory__arrows');
me.$prev = me.$arrows.querySelector('.lory__prev');
me.$next = me.$arrows.querySelector('.lory__next');
me.$curr = me.$slides[me.currentSlide];
};
/**
* Set properties.
*/
_l.setProps = function () {
var me = this;
var style = document.body.style;
var $container = me.$slider;
var $list = me.$list;
var o = me.options;
me.count = me.$track.children.length;
me.countClones = me.count;
me.currPos = {};
me.currentSlide = o.initialSlide || 0;
me.events = {};
me.direction = 0;
me.down = false;
me.fWidth = $list.offsetWidth;
me.fHeight = $list.offsetHeight;
me.lazy = $container.classList.contains('blazy');
me.infinite = o.infinite ? o.infinite : false;
me.isTransition = 'transition' in style;
me.lastSlide = me.count - 1;
me.movingItem = o.animation || o.transition;
me.positionProp = o.vertical ? 'top' : 'left';
me.transformProp = 'transform';
me.slideWidth = $container.offsetWidth;
me.slideHeight = $container.offsetHeight;
me.winWidth = window.innerWidth;
// Only enforced frame dimensions if required.
var ow = o.width;
if (ow && ow.indexOf('x') > 0) {
var dim = ow.split('x');
me.fWidth = dim[0].trim();
me.fHeight = dim[1].trim();
var ratio = me.fWidth / me.fHeight;
if (me.fWidth > me.winWidth) {
var extra = me.fWidth - me.winWidth;
me.fWidth = me.fWidth - (extra + 32);
}
me.fHeight = me.fWidth / ratio;
$list.style.width = me.fWidth + 'px';
$list.style.height = me.fHeight + 'px';
}
};
lory.prototype.constructor = lory;
}(drupalSettings, dBlazy, this, this.document));
