outlayer-8.x-1.4/js/src/outlayer.isotope.js

js/src/outlayer.isotope.js
/**
 * @file
 * Provides Outlayer Isotope loader.
 */

(function ($, Drupal, Isotope, _win, _doc) {

  'use strict';

  var _id = 'outlayer';
  var _plugin = 'isotope';
  var _idOnce = _id + '-' + _plugin;
  var _mounted = 'is-' + _plugin + '-on';
  var _selector = '.' + _id + '--' + _plugin + ':not(.' + _mounted + ')';
  var _emptySel = _id + '--empty';
  var _isActive = 'is-active';

  /**
   * Outlayer utility functions.
   *
   * @namespace
   */
  $.outLayer.isotope = $.extend({}, $.outLayer || {}, {
    $empty: null,
    $items: [],
    $slicks: [],
    $splides: [],
    $filter: null,
    $sorter: null,
    $search: null,
    $input: null,
    isIsotope: false,
    useGridStack: true,
    dataFilter: null,
    searchString: null,
    activeFilters: [],
    activeSorters: [],
    gridHeight: 210 + 'px',
    onClass: _plugin,

    /**
     * Initializes the Outlayer instance.
     *
     * Isotope is not inited during page load until one of the filter buttons
     * is hit. Unless when using Grid Custom (ungridstack) which disables
     * GridStack layout for non-responsive Grid Custom which simply means you
     * are on your own, need to layout the grid and make it responsive manually.
     *
     * @param {HTMLElement} el
     *   The .outlayer--isotope HTML element.
     */
    load: function (el) {
      var me = this;

      // Only initialize it if not already, or a force for ugridstack.
      if (me.$instance === null) {
        me.isIsotope = true;
        me.$instance = new Isotope(el, me.options);

        me.$instance.on('arrangeComplete', function (filteredItems) {
          // Add hints about empty results.
          if ($.isElm(me.$empty)) {
            var empty = filteredItems.length === 0;
            el.classList[empty ? 'add' : 'remove'](_emptySel);
            me.$empty.textContent = empty ? Drupal.t('No data found.') : '';
          }

          // Prevents annoying collapsed/ layout reflow during filtering.
          el.style.minHeight = me.gridHeight;

          if (!me.isEnabled(el)) {
            me.resetInline(el, true);
          }
        });
      }
    },

    onResizeEnd: function (e, el) {
      el.style.minHeight = '';
    },

    resetInline: function (el, reset) {
      var me = this;
      var gh = me.isNativeGrid ? '' : me.gridHeight;

      el = el || me.$el;
      reset = reset || true;

      me.rePosition(el, reset);
      el.style.height = gh;
      el.style.minHeight = reset ? '' : gh;
    },

    backToGridStack: function (el) {
      var me = this;

      me.$instance.destroy();

      // Reset to defaults.
      me.isIsotope = false;
      $.removeClass(el, _emptySel);

      // We have no onDestroy event, and destroy is unfortunately unclean.
      // This causes unwanted transition, but at least not breaking layout.
      // Isotope is NOT immediately destroyed till transitions ends.
      var reset = function () {
        me.resetInline(el, true);
      };
      _win.setTimeout(reset, 1100);
    },

    destroy: function (el) {
      var me = this;

      el = el || me.$el;

      // Only destroy it using gridstack to avoid breaking gridstack layouts.
      if (me.$instance && !$.isUnd(me.$instance)) {
        // Only destroy if using gridstack layout.
        // The layout is managed by GridStack, not Isotope.
        if (me.useGridStack) {
          me.backToGridStack(el);
        }
        else {
          // Reset filter in case one of its results empty.
          // The layout is managed by Isotope, not GridStack.
          me.$instance.arrange({filter: '', sortBy: ''});
        }
      }

      // Switch js-driven layouts to native CSS Grid if so required.
      $.removeClass(el, 'is-ol-native');
      if (me.isNativeGrid) {
        $.removeClass(el, 'is-gs-layout');
      }

      me.revalidate();

      // Slicks might be borked after being hidden during filtering, refresh.
      if (me.$slicks.length) {
        $.each(me.$slicks, function (el) {
          el.slick.refresh();
        });
      }
      if (me.$splides.length) {
        $.each(me.$splides, function (el) {
          el.splide.refresh();
        });
      }

    },

    /**
     * Revalidate items.
     */
    revalidate: function () {
      var me = this;
      var blazy = '.b-lazy:not(.b-loaded)';
      var $elms = $.findAll(me.$el, blazy);

      // Revalidate blazy.
      if ($elms.length && me.isBlazy && Drupal.blazy && Drupal.blazy.init) {
        Drupal.blazy.init.load($elms);
      }
    }

  });

  /**
   * Outlayer utility functions.
   *
   * @param {HTMLElement} root
   *   The Outlayer HTML element.
   */
  function process(root) {
    var me = $.outLayer.isotope;
    var id = $.attr(root, 'data-instance-id');

    // Pass data to $.outLayer.isotope for easy reference.
    me.$el = root;
    me.options = $.parse($.attr(root, 'data-outlayer-isotope')) || {};
    me.style = _win.getComputedStyle(root);

    me.prepare(root);

    var height = parseInt(me.style.getPropertyValue('height'), 0);

    me.gridHeight = height + 'px';
    me.$filter = $.find(_doc, '.outlayer-list--filter[data-instance-id="' + id + '"]');
    me.$search = $.find(_doc, '.outlayer-list--search[data-instance-id="' + id + '"]');
    me.$sorter = $.find(_doc, '.outlayer-list--sorter[data-instance-id="' + id + '"]');
    me.$slicks = $.findAll(root, '.slick__slider');
    me.$splides = $.findAll(root, '.splide');
    me.$empty = $.find(root, '.outlayer__empty');

    /**
     * Filter elements on a button click.
     *
     * @param {Event} e
     *   The event triggering the filter.
     */
    function doFilter(e) {
      var btn = e.target;
      var $active = $.find(me.$filter, '.is-active');

      me.dataFilter = $.attr(btn, 'data-filter');

      // Only initialize it once on an event to not mess up with gridstack.
      me.init();

      // Toggle the current active button class.
      toggleButton($active, btn);

      // Filter items.
      me.$instance.arrange({
        filter: me.dataFilter
      });
    }

    /**
     * Filter elements on a button click.
     *
     * @param {Event} e
     *   The event triggering the filter.
     */
    function doSearch(e) {
      var $target = e.target;
      var searchText = $target.value;

      me.$input = $target;
      if (me.$input.value === '') {
        doDestroy();

        return;
      }

      // Only initialize it once on an event to not destroy gridstack.
      me.init();

      me.searchString = Drupal.checkPlain(searchText.toLowerCase());

      me.$instance.arrange({
        filter: function () {
          var input = this;
          return input.textContent.toLowerCase().indexOf(me.searchString) !== -1 ? true : false;
        }
      });
    }

    /**
     * Sorter elements on a button click.
     *
     * @param {Event} e
     *   The event triggering the filter.
     */
    function doSorter(e) {
      var btn = e.target;
      var value = $.attr(btn, 'data-sort-by');
      var $active = $.find(me.$sorter, '.is-active');

      // Only initialize it once on an event to not mess up with gridstack.
      me.init();

      // Toggle the current active button class.
      toggleButton($active, btn);

      me.$instance.arrange({sortBy: value});
    }

    /**
     * Toggle button states.
     *
     * @param {HTMLElement} $active
     *   The active button, if any.
     * @param {HTMLElement} $btn
     *   The current clicked button
     */
    function toggleButton($active, $btn) {
      // Toggle the current active button class.
      if ($.isElm($active)) {
        $.removeClass($active, _isActive);
      }

      $.addClass($btn, _isActive);

      // Adjusts the class for absolute positioning required by Isotope.
      $.addClass(me.$el, 'is-gs-layout');

      // If using native Grid, disable absolute positioning required by Isotope.
      // The .is-gs-layout basically means disabling native temporarily during
      // filtering to allow Isotope take over the layout to arrange items.
      // Once a .button--reset hit, let native Grid take over its layout again.
      // The best of both worlds: native Grid and dynamic filtering.
      // The first requires relative positioning, the latter absolute.
      if (me.isNativeGrid) {
        $.addClass(me.$el, 'is-ol-native');

        if ($.hasClass($btn, 'button--reset')) {
          $.removeClass(me.$el, 'is-gs-layout');
        }
      }
    }

    /**
     * Destroy instance on a button click.
     */
    function doDestroy() {
      me.destroy();

      if ($.isElm(me.$input)) {
        me.$input.value = '';
      }
    }

    /**
     * Layout elements.
     */
    function outlay() {
      // Outlayer only triggered on events, not on init.
      if ($.isElm(me.$filter)) {
        $.on(me.$filter, 'click', '.button--filter', doFilter);
      }

      if ($.isElm(me.$search)) {
        var onSearch = Drupal.debounce(doSearch, 250);
        $.on(me.$search, 'keyup', '.form-text--search', onSearch);
      }

      if ($.isElm(me.$sorter)) {
        me.activeSorters = $.parse($.attr(me.$sorter, 'data-sorters')) || {};
        $.on(me.$sorter, 'click', '.button--sorter', doSorter);
      }

      $.each([me.$filter, me.$search, me.$sorter], function (cn) {
        if ($.isElm(cn)) {
          $.on(cn, 'click', '.button--reset', doDestroy);
        }
      });

      // Only initialize Isotope if not using gridstack grids.
      // If not using gridstack, or using native, layout items manually.
      if (me.isNativeGrid || me.isUnGridStack) {
        me.forceHeight = me.isNativeGrid;
      }

      // Prevents extra height calc if the sizer is not removed.
      if (me.isValid(me.$sizer)) {
        me.$sizer.classList.add('box--stamp');
      }

      // Only init if really ungridstack, not using GridStack layout. But
      // using the non-responsive Grid Custom option. So far Isotope is never
      // initialized till a filter, sorter or search button is hit.
      // If ungridstack, normal Isotope layout below is triggered.
      if (me.isUnGridStack) {
        me.useGridStack = false;

        me.init(root);

        // Runs update(), including on resize event, as nobody helps here.
        me.buildOut(root);
      }

      me.cleanUp(root);
    }

    outlay();
  }

  /**
   * Attaches Outlayer behavior to HTML element.
   *
   * @type {Drupal~behavior}
   */
  Drupal.behaviors.outLayerIsotope = {
    attach: function (context) {
      $.once(process, _idOnce, _selector, context);
    },
    detach: function (context, settings, trigger) {
      if (trigger === 'unload') {
        $.once.removeSafely(_idOnce, _selector, context);
      }
    }
  };

}(dBlazy, Drupal, Isotope, this, this.document));

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

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