ultimenu-8.x-2.x-dev/js/src/ultimenu.base.js

js/src/ultimenu.base.js
/**
 * @file
 * Provides mobile toggler for the the Ultimenu blocks.
 */

(function ($, Drupal, drupalSettings, _win, _doc, $HTML) {

  'use strict';

  var NAME = 'ultimenu';
  var ACTIVE = 'active';
  var EXPANDED = 'expanded';
  var IS_NAME = 'is-' + NAME;
  var IS_ULTI = 'is-ulti';
  var IS_ROOT_DESKTOP = IS_ULTI + 'desktop';
  var IS_ROOT_MOBILE = IS_ULTI + 'mobile';
  var IS_ROOT_STICKY = IS_ULTI + 'sticky';
  var IS_ROOT_EXPANDED = IS_NAME + '--' + EXPANDED;
  var IS_ROOT_ACTIVE = IS_NAME + '--' + ACTIVE;
  var IS_ROOT_HOVER = IS_NAME + '--hover';
  var IS_ROOT_HIDING = IS_NAME + '--hiding';
  var IS_ITEM_EXPANDED = 'is-uitem-' + EXPANDED;
  var IS_LINK_ACTIVE = 'is-ulink-' + ACTIVE;
  var IS_HAMBURGER_ACTIVE = 'is-ubtn-' + ACTIVE;
  var IS_AJAX_HIT = 'data-ultiajax-hit';
  var IS_HOVER = false;
  var IS_STICKY = false;
  var C_HIDDEN = 'hidden';
  var C_FLYOUT = 'flyout';
  var IS_FLYOUT = 'is-' + C_FLYOUT;
  var IS_FLYOUT_EXPANDED = IS_FLYOUT + '-' + EXPANDED;
  var C_LINK = NAME + '__link';
  var S_LINK = '.' + C_LINK;
  var S_AJAX_LINK = '.' + NAME + '__ajax';
  var AJAX_TRIGGER = 'data-ultiajax-trigger';
  var C_ULTIMENU_FLYOUT = NAME + '__' + C_FLYOUT;
  var C_OFFCANVAS = IS_NAME + '__canvas-off';
  var C_ONCANVAS = IS_NAME + '__canvas-on';
  var C_BACKDROP = IS_NAME + '__backdrop';
  var C_CARET = NAME + '__caret';
  var S_CARET = '.' + C_CARET;
  var S_BACKDROP = '.' + C_BACKDROP;
  var S_HAMBURGER = '.button--ultiburger';
  var TIMER_HIDING = void 0;
  var TIMER_WAITING = void 0;
  var ADDCLASS = 'addClass';
  var REMOVECLASS = 'removeClass';
  var E_DBLCLICK = 'dblclick';
  var C_TOUCH = 'touchevents';
  var SETTINGS = drupalSettings.ultimenu || {};
  var WIN_SIZE = {};

  // Adds [no-]touchevents HTML classes.
  if ('touchOrNot' in $) {
    $.touchOrNot();
  }

  Drupal.ultimenu = {
    $backdrop: null,
    $hamburger: null,
    $offCanvas: null,
    isInvalid: function (e, selector) {
      return !$.is(e.target, selector) || e.type === E_DBLCLICK;
    },

    viewport: function () {
      WIN_SIZE = $.windowSize();
      var vh = WIN_SIZE.height / 100;

      $HTML.style.setProperty('--vh', vh + 'px');
    },

    breakpoints: function () {
      var me = this;
      var removeClasses = function () {
        $.removeClass($HTML, [
          IS_ROOT_ACTIVE,
          IS_ROOT_HOVER,
          IS_ROOT_EXPANDED,
          IS_ROOT_STICKY
        ]);
        me.closeFlyout();
      };

      var touchClasses = function () {
        $.addClass($HTML, [IS_ROOT_MOBILE, IS_ROOT_ACTIVE]);
        $.removeClass($HTML, IS_ROOT_DESKTOP);
        $.trigger(_win, 'ultimenu:touch');
      };

      var desktopClasses = function () {
        // If hamburger is hidden, meaning hover.
        if (IS_HOVER || me.isHidden($.find(_doc, S_HAMBURGER) || me.$hamburger)) {
          $.addClass($HTML, IS_ROOT_HOVER);

          if (IS_STICKY) {
            $.addClass($HTML, IS_ROOT_STICKY);
            if (me.$offCanvas) {
              setTimeout(function () {
                var vh = me.$offCanvas.offsetHeight;

                $HTML.style.setProperty('--ultiheader', vh + 'px');
              }, 101);
            }
          }
        }
        else {
          $.addClass($HTML, IS_ROOT_ACTIVE);
        }

        $.addClass($HTML, IS_ROOT_DESKTOP);
        $.removeClass($HTML, IS_ROOT_MOBILE);

        $.trigger(_win, 'ultimenu:desktop');
      };

      var toggleClasses = function (e) {
        setTimeout(function () {
          me.viewport();
          removeClasses();

          if ((e && e.matches) || e === C_TOUCH) {
            touchClasses();
          }
          else {
            desktopClasses();
          }
        });
      };

      var isMobile = $.isTouch(toggleClasses);
      toggleClasses(isMobile ? C_TOUCH : null);
    },

    onClickHamburger: function (e) {
      var me = this;

      e.preventDefault();
      e.stopPropagation();

      var $button = e.target;
      var expanded = $.hasClass($HTML, IS_ROOT_EXPANDED);

      if (me.isInvalid(e, S_HAMBURGER)) {
        return false;
      }

      _win.setTimeout(function () {
        $[expanded ? REMOVECLASS : ADDCLASS]($HTML, IS_ROOT_EXPANDED);
        $[expanded ? REMOVECLASS : ADDCLASS]($button, IS_HAMBURGER_ACTIVE);

        me.closeFlyout();
      }, 30);

      // Cannot use transitionend as can be jumpy affected by child transitions.
      if (expanded) {
        _win.clearTimeout(TIMER_HIDING);
        $.addClass($HTML, IS_ROOT_HIDING);

        TIMER_HIDING = _win.setTimeout(function () {
          $.removeClass($HTML, IS_ROOT_HIDING);
        }, 500);
      }

      // Scroll to top in case the current viewport is far below the fold.
      /*
      if (me.$backdrop && !expanded) {
        _win.scroll({
          top: me.$backdrop.offsetTop,
          behavior: 'smooth'
        });
      }
      */
    },

    onResize: function () {
      var me = this;

      me.viewport();
    },

    hamburger: function () {
      var me = this;

      if ($.find(_doc, 'body > ' + S_HAMBURGER) === null) {
        var hamburger = $.find(_doc, S_HAMBURGER);
        if (hamburger) {
          _doc.body.appendChild(hamburger);
        }
      }

      me.$hamburger = $.find(_doc, S_HAMBURGER);

      // Reacts on clicking Ultimenu hamburger button.
      $.on(me.$hamburger, 'click.' + NAME, me.onClickHamburger.bind(me));
    },

    offcanvas: function () {
      var me = this;

      if (!me.$hamburger) {
        return;
      }

      // @todo remove BC.
      var header = $.find(_doc, 'is-ultimenu-canvas-off');
      if (header) {
        $.addClass(header, C_OFFCANVAS);
      }

      // @todo remove BC.
      var siblings = $.findAll(_doc, 'is-ultimenu-canvas-on');
      if (siblings.length) {
        $.addClass(siblings, C_ONCANVAS);
      }

      me.$offCanvas = $.find(_doc, '.' + C_OFFCANVAS);

      if (SETTINGS.canvasOff && SETTINGS.canvasOn) {
        if (!me.$offCanvas) {
          me.$offCanvas = $.find(_doc, SETTINGS.canvasOff);
          $.addClass(me.$offCanvas, C_OFFCANVAS);
        }

        var $onCanvas = $.find(_doc, '.' + C_ONCANVAS);
        if (!$onCanvas) {
          var $onCanvases = $.findAll(_doc, SETTINGS.canvasOn);
          $.addClass($onCanvases, C_ONCANVAS);
        }
      }
    },

    backdrop: function () {
      var me = this;

      if (!me.$offCanvas) {
        return;
      }

      // @todo remove BC.
      var backdrop = $.find(_doc, 'is-ultimenu-canvas-backdrop');
      if (backdrop) {
        $.addClass(backdrop, C_BACKDROP);
      }

      me.$backdrop = $.find(_doc, S_BACKDROP);

      if (!me.$backdrop) {
        var $parent = me.$offCanvas.parentNode;
        var el = _doc.createElement('div');
        el.className = C_BACKDROP;
        $parent.insertBefore(el, $parent.firstElementChild || null);

        me.$backdrop = el;
      }

      $.on(me.$backdrop, 'click.' + NAME, me.onClickBackdrop.bind(me));
    },

    slideToggle: function (el, className, hidden) {
      if (el) {
        $[hidden ? ADDCLASS : REMOVECLASS](el, className);
      }
    },

    executeAjax: function (el) {
      var $li = $.closest(el, 'li');
      var $ajax = $.find($li, S_AJAX_LINK);

      var cleanUp = function () {
        // Removes attribute to prevent this event from firing again.
        el.removeAttribute(AJAX_TRIGGER);
      };

      // The AJAX link will be gone on successful AJAX request.
      if ($ajax) {
        // Hover event can fire many times, prevents from too many clicks.
        if (!$ajax.hasAttribute(IS_AJAX_HIT)) {
          $ajax.click();

          $ajax.setAttribute(IS_AJAX_HIT, 1);
          $.addClass($ajax, C_HIDDEN);
        }

        // This is the last resort while the user is hovering over menu link.
        // If the AJAX link is still there, an error likely stops it, or
        // the AJAX is taking longer time than 1.5 seconds. In such a case,
        // TIMER_WAITING will re-fire the click event, yet on interval now.
        // At any rate, Drupal.Ajax.ajaxing manages the AJAX requests.
        _win.clearTimeout(TIMER_WAITING);
        TIMER_WAITING = _win.setTimeout(function () {
          $ajax = $.find($li, S_AJAX_LINK);
          if ($ajax) {
            $.removeClass($ajax, C_HIDDEN);
            $ajax.click();
          }
          else {
            cleanUp();
          }

          var onces = $.findAll($li, '[data-once]');
          if (onces.length) {
            $.each(onces, function (elOnce) {
              Drupal.attachBehaviors(elOnce);
            });
          }
        }, 1500);
      }
      else {
        cleanUp();
      }
    },

    triggerAjax: function (e) {
      var me = this;
      var target = e.target;

      e.stopPropagation();

      var link = $.hasClass(target, C_CARET) ?
        $.closest(target, S_LINK) : target;

      if ($.hasAttr(link, AJAX_TRIGGER)) {
        me.executeAjax(link);
      }
    },

    onClickBackdrop: function (e) {
      var me = this;

      e.preventDefault();

      if (me.isInvalid(e, S_BACKDROP)) {
        return false;
      }

      if (me.$hamburger) {
        me.$hamburger.click();
      }
    },

    closeFlyout: function (base) {
      base = base || _doc;

      var expands = $.findAll(base, '.' + IS_ITEM_EXPANDED);
      $.removeClass(expands, IS_ITEM_EXPANDED);

      // @todo remove for just above.
      var actives = $.findAll(base, '.' + IS_LINK_ACTIVE);
      var flyouts = $.findAll(base, '.' + IS_FLYOUT_EXPANDED);
      $.removeClass(actives, IS_LINK_ACTIVE);
      $.removeClass(flyouts, IS_FLYOUT_EXPANDED);
    },

    isHidden: function (el) {
      if (el) {
        if (el.offsetParent === null || el.clientHeight === 0) {
          return true;
        }
        var style = _win.getComputedStyle(el);
        return style.display === 'none' || style.visibility === 'hidden';
      }
      return false;
    },

    onClickCaret: function (e) {
      var me = this;
      var $caret = e.target;

      e.preventDefault();
      e.stopPropagation();

      if (me.isInvalid(e, S_CARET)) {
        return false;
      }

      var $link = $.closest($caret, S_LINK);
      var $li = $.closest($link, 'li');
      var $flyout = $link.nextElementSibling;
      var $container = $.closest($li, '.ultimenusub');
      var $submenu = $container ? $.closest($li, '.menu') : null;

      if (!$flyout) {
        return false;
      }

      // If hoverable for desktop, one at a time click should hide flyouts.
      // We let regular mobile toggle not affected, to avoid jumping accordion.
      var hidden = me.isHidden($flyout);
      // Note! Intentionally multi-conditions, not merged, else collapses.
      if ($submenu) {
        if (hidden) {
          me.closeFlyout($submenu);
        }
      }
      else {
        // Only for when desktop hover displaying caret via Use caret option.
        // Flyouts are already being displayed when being hovered, so useless
        // unless Use caret option enabled.
        // To support carets anywhere, do not condition it, so that always
        // one flyout being displayed at a time.
        // if (me.isHidden(me.$hamburger)) {
        me.closeFlyout();
        // }
      }

      // Toggle the current flyout.
      // @todo remove strict checks after another check.
      var isFlyout = $.hasClass($flyout, C_ULTIMENU_FLYOUT);
      var isMenu = $.hasClass($flyout, 'menu') || $.is($flyout, 'ul');
      if (isFlyout || isMenu) {
        $[hidden ? ADDCLASS : REMOVECLASS]($li, IS_ITEM_EXPANDED);
        $[hidden ? ADDCLASS : REMOVECLASS]($link, IS_LINK_ACTIVE);

        me.slideToggle($flyout, IS_FLYOUT_EXPANDED, hidden);
      }
    },

    prepare: function () {
      var me = this;

      IS_HOVER = $.hasClass($HTML, IS_ROOT_HOVER);
      IS_STICKY = $.hasClass($HTML, IS_ROOT_STICKY);

      // Checks viewport height based to set var(--vh).
      $.resize(me.onResize.bind(me))();

      // Moves the hamburger button to the end of the body.
      me.hamburger();

      // Allows hard-coded CSS classes to not use this.
      me.offcanvas();

      // Prepends our backdrop before the main off-canvas element.
      me.backdrop();

      // Reacts on resizing, and breakpoint changes.
      me.breakpoints();
    }

  };

})(dBlazy, Drupal, drupalSettings, this, this.document, this.document.documentElement);

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

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