bs_base-8.x-1.x-dev/js/navbar.js

js/navbar.js
/**
 * @file
 *
 * Navigation bar improvements and tweaks.
 *
 * The biggest addition is support for 3 levels of depth for small screens.
 */

(function ($, Drupal, drupalSettings) {

  'use strict';

  // Lets expose some usefull methods to the public.
  Drupal.bs_base = Drupal.bs_base ? Drupal.bs_base : {};
  Drupal.bs_base.navbar = Drupal.bs_base.navbar ? Drupal.bs_base.navbar : {};

  /**
   * Toggle all dropdown menu items in active path.
   *
   * @param $navbar
   *   Navigation bar jQuery element.
   * @param state
   *   Pass true or false to specifically set the aria-expanded state. If not
   *   used aria-expanded state value is toggled.
   */
  var toggleActivePath = function($navbar, state) {
    $navbar.find('.dropdown > a.is-active').each(function () {
      toggleDropdown($(this), state);
    });
  };

  /**
   * Toggle dropdown for a given dropdown toggle and it parent.
   *
   * @param $dropdownToggle
   * @param state
   *   Pass true or false to specifically set the aria-expanded state. If not
   *   used aria-expanded state value is toggled.
   */
  var toggleDropdown = function($dropdownToggle, state) {
    $($dropdownToggle.parents('.dropdown').get(0)).toggleClass('show');
    // If state is passed use it, otherwise toggle the state.
    var newState = state !== null && typeof(state) !== 'undefined' ? state : ($dropdownToggle.attr('aria-expanded') === 'false' ? 'true' : 'false');
    $dropdownToggle.attr('aria-expanded', newState);
    $dropdownToggle.siblings('.dropdown-menu').toggleClass('show');
  };

  /**
   * Find open dropdown items and close them in a given dropdown parent.
   *
   * @param $dropdownParent
   */
  var closeDropdowns = function($dropdownParent) {
    $dropdownParent.find('> .dropdown.show').each(function () {
      var $this = $(this);
      $this.removeClass('show');
      $this.find('> a').attr('aria-expanded', 'false');
      $this.find('> .dropdown-menu').removeClass('show');
    });
  };

  /**
   * Check is navbar shown.
   *
   * @param $element
   *   Element that belongs to a navbar.
   * @return {boolean}
   *   True if navbar collapse is shown, false if not.
   */
  Drupal.bs_base.navbar.isNavbarCollapseShow = function($element) {
    var $navbarCollapse;
    if ($element.hasClass('navbar-collapse')) {
      $navbarCollapse = $element;
    }
    else {
      $navbarCollapse = $element.parents('.navbar-collapse');
    }

    // If parent .navbar-collapse has show class this means that we are
    // in small view where responsive navbar is visible.
    // canvas-slid is used for off canvas navigation
    // @todo can we maybe normalize this so show class is used in both cases?
    if ($navbarCollapse.hasClass('show') || $navbarCollapse.hasClass('canvas-slid')) {
      return true;
    }
    return false;
  };

  /**
   * Follow the link.
   *
   * @param $link
   *   jQuery element with href and optional target attribute.
   */
  Drupal.bs_base.navbar.followLink = function($link) {
    // Load a page in browser and disable propagation of event.
    if ($link.attr('target')) {
      window.open($link.attr('href'), $link.attr('target'));
    }
    else {
      window.location = $link.attr('href');
    }
  };

  /**
   * Open sub-navigation items on hover.
   */
  function subNavigationOnHover($navbar) {
    var $navbarWraper = $navbar.parents('.navbar-wrapper');
    // Stores all top level navbar nav menu items.
    var menuItems = [];
    // Stores initial top level active menu item.
    var initialActiveMenu = null;
    // Does menu item already have second-level-expanded CSS class.
    let hasSecondLevelExpanded = false;

    // Activate menu item.
    function activateMenuItem(item) {
      if (item.active) {
        return;
      }

      item.active = true;
      // We are attaching show CSS class because we are using it later to detect
      // a case of clicking on menu item which already have open submenu - the
      // case when we are following the link.
      item.item.addClass('active show');
      item.itemLink.addClass('active is-active').attr('aria-expanded', true);

      if ($navbarWraper.hasClass('second-level-expanded')) {
        hasSecondLevelExpanded = true;
      }
      else {
        $navbarWraper.addClass('second-level-expanded');
      }
    }

    // Deactivate menu item.
    function deactivateMenuItem(item) {
      if (!item.active) {
        return;
      }

      item.active = false;
      item.item.removeClass('active show');
      item.itemLink.removeClass('active is-active').attr('aria-expanded', false);

      if (!hasSecondLevelExpanded)  {
        $navbarWraper.removeClass('second-level-expanded');
      }
    }

    // Deactivate all menu items.
    function deactivateMenuItems(skipMenuItem) {
      // Remove active class from all other dropdown items.
      for (var i = 0; i < menuItems.length; ++i) {
        if ((!skipMenuItem || i !== skipMenuItem.index) && menuItems[i].dropdown && menuItems[i].active) {
          deactivateMenuItem(menuItems[i]);
        }
      }
    }

    // Get menu item from passed DOM element. Support second level menu items
    // also.
    function getItem(node) {
      var link = node;
      if (node.nodeName == 'SPAN') {
        // If target is menu item span link child then lets switch to parent.
        link = node.parentElement;
      }
      var itemIndex = Array.prototype.indexOf.call($navbar[0].children, link.parentElement);
      if (itemIndex == -1) {
        // Check is this second level and if yes find a parent item.
        var parentDropdown = $(node).parents('.nav-item.dropdown').get(0);
        if (parentDropdown) {
          itemIndex = Array.prototype.indexOf.call($navbar[0].children, parentDropdown);
        }
      }
      return itemIndex != -1 ? menuItems[itemIndex] : null;
    }

    // Initialize top level menu items data.
    $navbar.find('> .nav-item').each(function (index) {
      var $item = $(this);
      var menuItem = {
        index: index,
        item: $item,
        itemLink: $item.find('> a'),
        active: $item.hasClass('active'),
        dropdown: $item.hasClass('dropdown'),
      };
      if (menuItem.active) {
        menuItem.initiallyActive = true;
        initialActiveMenu = menuItem;
      }
      menuItems.push(menuItem);
    });

    // Hover in.
    var hoverIn = function (e) {
      var menuItem = getItem(e.target);
      if (menuItem) {
        deactivateMenuItems(menuItem);
        if (menuItem.dropdown && !menuItem.active) {
          activateMenuItem(menuItem);
        }
      }
    };

    // Hover out.
    var hoverOut = function (e) {
      var menuItem = getItem(e.target);
      if (!menuItem) {
        return;
      }

      // We process hover out only when we are leaving navbar. In that case
      // we will activate initially active menu item.
      var parent = $(e.relatedTarget).parents('.navbar-nav');
      if (!parent.length) {
        if (menuItem.active && !menuItem.initiallyActive) {
          deactivateMenuItem(menuItem);
        }
        if (initialActiveMenu && !initialActiveMenu.active) {
          deactivateMenuItems();
          activateMenuItem(initialActiveMenu);
        }
      }
    };

    // Attach hover processing to all top level menu items.
    for (var i = 0; i < menuItems.length; ++i) {
      // Use jQuery.hoverIntent plugin if it exist.
      if ($.fn.hoverIntent) {
        // Attach hover intent over for switching between navbar items.
        // We want fast 100ms for switching between items because we don't
        // want user to wait when hovering over navbar items.
        menuItems[i].item.hoverIntent({
          over: hoverIn,
          out: $.noop,
          timeout: 100
        });

        // Attach hover intent out for moving outside of navbar.
        // Slow 2000ms hover out from navbar completely because we want to
        // give user a chance to return.
        menuItems[i].item.hoverIntent({
          over: $.noop,
          out: hoverOut,
          timeout: 2000
        });
      }
      else {
        menuItems[i].item.hover(hoverIn, hoverOut);
      }
    }
  }

  $(function () {
    if (drupalSettings.bs_base.navbar_type) {
      $('.navbar-nav').each(function () {
        var $navbar = $(this);
        var $navbarCollapse = $navbar.parents('.navbar-collapse');

        // Visit navbar open dropdown click behavior - click on parent open
        // submenu link will visit a parent link.
        if (drupalSettings.bs_base.navbar_opened_submenu_behavior === 'visit') {
          $navbar.find('.dropdown').each(function () {
            var $dropdown = $(this);

            var $link = $dropdown.find('> a');
            $link.click(function (e) {
              // If menu dropdown is shown (expanded) follow the link.
              // This allows us to use expanded dropdown toggle links as regular
              // navigation elements.
              if ($dropdown.hasClass('show') ||
                // Or if we are on the first level dropdown menu and second
                // level horizontal option is on, and we are not on responsive
                // menu we should follow the menu and stop processing of a
                // click, so we don't show second level in dropdown menu.
                (this.dataset.menuLevel === '0' && drupalSettings.bs_base.navbar_type === 'second-level-horizontal' && !Drupal.bs_base.navbar.isNavbarCollapseShow($navbarCollapse)) ||
                // Or if we are on second level and navbar type is second level
                // dropdown and not in responsive menu then make sure we always
                // follow the click on a link. With this we are sure we also
                // cover cases when second level has third level presented in
                // which case click on second level will not work because it
                // will open third level which we do not show.
                // Problem here is that this will not work with close dropdown
                // option, but this is an edge case and generally this whole
                // case can be avoided if you do not expand third level menus
                // or just remove them.
                (this.dataset.menuLevel === '1' && drupalSettings.bs_base.navbar_type === 'second-level-dropdown' && !Drupal.bs_base.navbar.isNavbarCollapseShow($navbarCollapse))) {
                Drupal.bs_base.navbar.followLink($link);
                return false;
              }
            });
          });
        }

        // Second level horizontal with show on hover option.
        if (drupalSettings.bs_base.navbar_type === 'second-level-horizontal' && drupalSettings.bs_base.navbar_onhover) {
          // Initialize hover event (open sub navigation) on menu items.
          // Do not initialize hover events when responsive menu is active. This
          // will avoid various problems like that on touch display the tap
          // effect will cast first hover and then click event.
          // Detecting actually are we on responsive menu or not is tricky. One
          // solution is to check is navbar visible - if yes we assume that we
          // are on not on responsive menu, and we can init hover effects.
          if ($navbarCollapse.is(':visible')) {
            subNavigationOnHover($navbar);
          }

          // On big screens, we need to stop any click activity for the first
          // level. It can happen that if the user does a very fast click after
          // entering the dropdown link then BS dropdown will be activated. In
          // this case, we will show the second level on hover, but when hover
          // is done user will then see BS dropdown menu which got activated
          // from BS js dropdown code.
          $navbar.find('> .dropdown > a').click(function () {
            if (!Drupal.bs_base.navbar.isNavbarCollapseShow($navbarCollapse)) {
            return false;
            }
          });
        }
      });
    }

    // Toggle active path on first sidebar show.
    // @TODO - note that this can still fail if we have multiple toggler buttons
    // on page and user use multiple of them on same page. But this should not
    // happen hopefully or we need to refactor this more with additional state
    // var.
    $('.navbar-toggler').each(function () {
      var $navbar = $(this.dataset.target).find('.navbar-nav');
      this.addEventListener('click', function () {
        toggleActivePath($navbar, true);
      }, {once: true});
    });

    if (drupalSettings.bs_base.navbar_offcanvas_type) {
      // Small screen third level support for dropdown.
      // Taken and fixed from http://stackoverflow.com/a/18682698.
      // @todo - we should support second level the same way as we support third
      // level, but Bootstrap dropdown.js is making problems here (stealing the
      // click and reloading the page probably). This is not ideal and should be
      // improved.
      $('.navbar-nav').each(function () {
        var $navbar = $(this);
        $navbar.find('.dropdown .dropdown > a').each(function () {
          var $this = $(this);
          //var $parent = $($this.parents('.dropdown-menu').get(0));

          $this.on('click', function (event) {
            // If we are on big screen then don't continue.
            if (!Drupal.bs_base.navbar.isNavbarCollapseShow($navbar)) {
              return;
            }

            // Avoid following the href location when clicking.
            event.preventDefault();

            // Avoid having the menu to close when clicking.
            event.stopPropagation();

            // Close first any open dropdown in the same parent.
            // @todo - this is commented for now to avoid user focus losing when
            // we have open long menus and closing them will trigger viewport
            // vertical scrolling.
            //closeDropdowns($parent);

            // Open the one we clicked on.
            toggleDropdown($this);
          });
        });
      });

      // Offcanvas close link support.
      var $offcanvas = $('.offcanvas');
      $('.offcanvas-close-link').click(function () {
        $offcanvas.offcanvas('hide');
        // Our close link has hash for href. Clicking on hash link will scroll
        // page to the top. We disable propagation of click event to stop this.
        return false;
      });

      // On page unload we will hide offcanvas navigation if it's open. This
      // will prevent Firefox and maybe Safari to show offcanvas navigation when
      // using browser history back button.
      var pageUnloadCalled = false;
      var pageUnload = function (e) {
        // Lets not call this twice for browsers that are not iOS.
        if (pageUnloadCalled) {
          return;
        }
        pageUnloadCalled = true;
        var offcanvasData = $offcanvas.data('bs.offcanvas');
        if (offcanvasData && offcanvasData.state === 'slid') {
          $offcanvas.offcanvas('hide');
        }
      };
      // Although beforeunload is supported on iOS it will not be called there.
      // Apple documentation suggest using pagehide to support back and forward
      // browser buttons.
      window.addEventListener('beforeunload', pageUnload);
      window.addEventListener('pagehide', pageUnload);
    }
  });

  $(document).on('hide.bs.dropdown', function (e) {
    // Don't hide dropdown navigation menus which are in sidebar navigation.
    // This will prevent hiding open submenus in sidebar navigation when we want
    // to open a new sub navigation - the rest will stay open.
    if (e.target.classList.contains('nav-item') && Drupal.bs_base.navbar.isNavbarCollapseShow($(e.target))) {
      e.preventDefault();
    }
  });

})(jQuery, Drupal, drupalSettings);

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

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