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));

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc