nortmas
9/27/2017 - 4:40 PM

JS pattern

/**
 * @file
 * Alpha menu js.
 */

var alphaMenu = alphaMenu || {};

/**
 * Register Alpha menu behavior so it can act on DOM.
 */
Drupal.behaviors.alphaMenu = {
  attach: function (context, settings) {
    alphaMenu.init(context, settings);
  }
}; // Drupal.behaviors.alphaMenu


(function ($, Drupal) {

  /**
   * Global menu dom element
   */
  alphaMenu.menuDom = null;

  /**
   * Global variable to check if current device is touchable based on class generated by modernizr.js
   */
  alphaMenu.tuouchDevice = $('html').hasClass('touchevents');

  /**
   * Global timer to detect if user left menu
   */
  alphaMenu.timerBackToMenu = null;

  /**
   * Global variable to detect last touched menu item
   */
  alphaMenu.lastTouched = null;

  /**
   * Global breakpoints for different device types
   * Keep this up-to-date with src/scss/utils/variables/_breakpoints.scss
   */
  alphaMenu.breakpoints = {
    bp1: 1367,
    bp2: 992,
    bp3: 544
  };

  /**
   * Media queries required for enquire.js library
   */
  alphaMenu.mediaQueries = {
    // Breakpoint 1 (XL): 1367+
    q1: '(min-width: ' + (alphaMenu.breakpoints.bp1) + 'px)',
    // Breakpoint 2 (L): 992-1366
    q2: '(min-width: ' + alphaMenu.breakpoints.bp2 + 'px) and (max-width: ' + (alphaMenu.breakpoints.bp1 - 1) + 'px)',
    // Breakpoint 3 (M): 544-991
    q3: '(min-width: ' + alphaMenu.breakpoints.bp3 + 'px) and (max-width: ' + (alphaMenu.breakpoints.bp2 - 1) + 'px)',
    // Breakpoint 4 (S): <544
    q4: '(max-width: ' + (alphaMenu.breakpoints.bp3 - 1) + 'px)'
  };

  /**
   * Attach handler.
   * Will be called on any DOM changes triggered by Drupal system.
   */
  alphaMenu.init = function (context, settings) {

    alphaMenu.menuDom = $('.block-alpha-menu-block', context);

    var $firstMenuLevel = $('> ul > li', alphaMenu.menuDom),
        $secondMenuLevel = $('.second-level > ul > li', $firstMenuLevel);

    alphaMenu.mobileMenuInit(context);
    alphaMenu.enquiresInit(context);
    alphaMenu.menuButtonsInit(context);
    alphaMenu.addIndexes([$firstMenuLevel, $secondMenuLevel]);

    if (alphaMenu.tuouchDevice) {
      alphaMenu.applyTouchEvent();
      alphaMenu.hideMenuTouch();
    }
    else {
      alphaMenu.applyHoverLevel($firstMenuLevel);
      alphaMenu.applyHoverLevel($secondMenuLevel);
      alphaMenu.hideMenuHover();
    }

  }; // alphaMenu.init

  /**
   * Initialize enquires - see enquire.js library
   */
  alphaMenu.enquiresInit = function (context) {

    $(context).find('html').once('alpha-menu-enquire').each(function() {
      enquire.register(alphaMenu.mediaQueries.q3, {match: alphaMenu.implementQ3});
      enquire.register(alphaMenu.mediaQueries.q4, {match: alphaMenu.implementQ4});
    });

  }; // alphaMenu.enquiresInit

  /**
   * Implementation of media query 3
   */
  alphaMenu.implementQ3 = function () {
    // Hide active block in hidden section
    $('.alpha-menu-buttons-list li').not('.search-button').find('.active').trigger('click');
    window.setTimeout(alphaMenu.mobileMenuApplyMargin, 250);
  };

  /**
   *  Implementation of media query 4
   */
  alphaMenu.implementQ4 = function () {
    window.setTimeout(alphaMenu.mobileMenuApplyMargin, 250);
  };

  /**
   * Add top margin to make header visible
   */
  alphaMenu.mobileMenuApplyMargin = function () {
    var height = $('#toolbar-bar').height() +
                 $('#header').height();
    $('#mobile-menu').css({'margin-top' : height + 'px', 'height' : 'calc(100% - ' + height + 'px)'});

  }; // alphaMenu.enquiresInit

  /**
   *  Set all the menu buttons.
   */
  alphaMenu.getMenuButtons = function (context) {

    var $menuButtons = $('.alpha-menu-buttons-list', context),
        $hiddenSection = $('#hidden-section', context);

    return {
      'locale': {
        btn: $('.locale-button a', $menuButtons),
        block: $('.block-locale-switcher-block', $hiddenSection)
      },
      'login': {
        btn : $('.login-button a', $menuButtons),
        block : $('.block-alpha-menu-login-block', $hiddenSection)
      },
      'search': {
        btn : $('.search-button a', $menuButtons),
        block : $('.block-gse-search', $hiddenSection)
      }
    };

  };  // alphaMenu.getMenuButtons

  /**
   * Initialize menu buttons and block sections
   */
  alphaMenu.menuButtonsInit = function (context) {

    var menuButtons = alphaMenu.getMenuButtons(context);

    $.each(menuButtons, function(type, el) {

      el.block.prepend('<a href="#" class="close">' + Drupal.t('Close') + '</a>');

      // Show block if button initially active
      if (el.btn.hasClass('active')) {
        el.block.show().addClass('expanded');
      }

      $('.close', el.block).add(el.btn).on('click', function (e) {
        if (el.block.hasClass('expanded')) {
          alphaMenu.sectionAnimate('slideUp', el.block, el.btn);
        }
        else {
          alphaMenu.sectionAnimate('slideDown', el.block, el.btn);
          // Close mobile menu so that only one item was opened.
          if (type == 'search') {
            if ($('html').hasClass('mm-opened')) {
              $('#mobile-menu').data('mmenu').close();
              $('.mobile-menu-toggle', context).removeClass('opened');
            }
          }
          // Check if there is another opened block, if so - close it.
          $.each(menuButtons, function(name, item) {
            if (item.block.hasClass('expanded')) {
              alphaMenu.sectionAnimate('slideUp', item.block, item.btn);
            }
          });
        }
        e.preventDefault();
      });
    });

  }; // alphaMenu.menuButtonsInit

  /**
   * Animate hidden blocks
   */
  alphaMenu.sectionAnimate = function (slide, block, btn) {
    var classApply = (slide == 'slideDown') ? 'addClass' : 'removeClass';
    var opacity = (slide == 'slideDown') ? 1 : 0;

    block.animate({'opacity' : opacity}, 100);

    block[slide](200, function () {
      btn[classApply]('active');
      block[classApply]('expanded');
    });
  }; // alphaMenu.sectionAnimate

  /**
   * Add indexes for all menu items and save classes "collapsed"
   */
  alphaMenu.addIndexes = function (menuLevels) {
    menuLevels.forEach(function (level) {
      $(level).each(function(index, element) {
        $(element).data('id', index);
        if ($(element).hasClass('collapsed')) {
          $(element).attr('data-collapsed', 'collapsed');
        }
      });
    });
  }; // alphaMenu.addIndexes

  /**
   * Remove all "collapsed" classes from menu
   */
  alphaMenu.removeAllCollapsed = function (currentElement) {
    if ($(currentElement).closest('.collapsed').length == 0) {
      $('li.collapsed', alphaMenu.menuDom).removeClass('collapsed');
    }
  }; // alphaMenu.removeAllCollapsed

  /**
   * Hide menu if user left it and restore all collapsed items
   */
  alphaMenu.hideMenuHover = function () {
    alphaMenu.menuDom.on("mouseleave", function(e) {
      alphaMenu.timerBackToMenu = setTimeout(function() {
        $('li.active-trail', alphaMenu.menuDom).removeClass('active-trail');
        $('li[data-collapsed]', alphaMenu.menuDom).addClass('collapsed');
        }, 500);
    });
  }; // alphaMenu.hideMenuHover

  /**
   * Hide menu if user left it and restore all collapsed items
   */
  alphaMenu.hideMenuTouch = function () {
    $('html').on('touchstart', function(e) {
      if ($(e.target).closest('ul.menu').length == 0) {
        alphaMenu.lastTouched = null;
        $('li.active-trail', alphaMenu.menuDom).removeClass('active-trail');
        $('li[data-collapsed]', alphaMenu.menuDom).addClass('collapsed');
      }
    });
  }; // alphaMenu.hideMenuTouch

  /**
   * Processing desktop menu levels for touch devices
   */
  alphaMenu.applyTouchEvent = function () {

    $('li.drop-down', alphaMenu.menuDom).find('> a, .nolink').on("touchstart", function (e) {

      var $link = $(this),
          $linkParent = $link.parent();

      if (alphaMenu.lastTouched == null) {
        alphaMenu.lastTouched = $linkParent;
        alphaMenu.lastTouched.addClass('active-trail');
        alphaMenu.removeAllCollapsed(e.currentTarget);
        e.preventDefault();
        return false; //extra, and to make sure the function has consistent return points
      }
      else if ($link.is('.nolink') && $link.closest('li.active-trail').length != 0) {
        $linkParent.addClass('active-trail');
        $linkParent.siblings().removeClass("active-trail");
      }
      else if ($linkParent.data('id') !== alphaMenu.lastTouched.data('id')) {
        var $previousItem = alphaMenu.lastTouched;
        alphaMenu.lastTouched = $linkParent;
        alphaMenu.removeAllCollapsed(e.currentTarget);
        alphaMenu.lastTouched.addClass('active-trail');
        $previousItem.find('li.active-trail').andSelf().removeClass('active-trail');
        e.preventDefault();
        return false; //extra, and to make sure the function has consistent return points
      }
      else if (alphaMenu.lastTouched.hasClass('active-trail')) {
        return true;
      }

    });

  };// alphaMenu.applyTouchEvent

  /**
   * Processing desktop menu levels.
   */
  alphaMenu.applyHoverLevel = function (selector) {

    var $previousItem = null,
        $currentItem = null,
        delayHappened = false,
        timer;

    alphaMenu.menuDom.on("mouseleave", function(e) {
      delayHappened = false;
    });

    $(selector).hover(
      function(e) {
        // Don't hide the menu if user got back to it
        if (alphaMenu.timerBackToMenu) {
          clearTimeout(alphaMenu.timerBackToMenu);
        }
        // First time we are over an item
        if (delayHappened === false) {
          timer = setTimeout(function() {
            // Remove all collapsed classes
            alphaMenu.removeAllCollapsed(e.currentTarget);
            // Update delay and current item
            delayHappened = true;
            $currentItem = $(e.currentTarget);
            $currentItem.addClass('active-trail');
          }, 200);
        }
        else {
          // Changing between items
          timer = setTimeout(function() {
            if ($(e.currentTarget).data('id') !== $currentItem.data('id')) {
              // Update current and previous items
              $previousItem = $currentItem;
              $currentItem = $(e.currentTarget);
              $currentItem.addClass('active-trail');
              // Remove all collapsed classes
              alphaMenu.removeAllCollapsed(e.currentTarget);
              // Remove classes from previous item and all internal
              $previousItem.find('li.active-trail').andSelf().removeClass('active-trail');
            }
          }, 300);
        }
      },
      function() {
        clearTimeout(timer);
      }
    );
  }; // alphaMenu.applyHoverLevel

  /**
   * Initialize mobile menu
   */
  alphaMenu.mobileMenuInit = function (context) {

    var $mobileMenu = alphaMenu.prepareMobileMenuTree(context);

    if ($mobileMenu == null) return;

    $mobileMenu.mmenu({
      extensions: ['fullscreen', 'multiline', 'popup'],
      counters: false,
      navbar: {
        title: Drupal.t('Menu'),
        titleLink: 'anchor'
      }
    });

    var mobileMenuApi = $mobileMenu.data('mmenu');

    alphaMenu.mobileMenuButton(context, mobileMenuApi);

  }; // alphaMenu.mobileMenuInit

  /**
   * Prepare tree for mobile menu
   */
  alphaMenu.prepareMobileMenuTree = function (context) {

    var $mobileMenu = null,
        linkTitle = Drupal.t('Overview'),
        $menuList = $('> ul.menu', alphaMenu.menuDom).clone(),
        $listItemSeparator = $('<li class="separator"></li>');

    var $hiddenSection = $('#hidden-section', context),
        $switcherList = $('.locale-switcher-list', $hiddenSection).clone(),
        $loginList = $('.alpha-menu-login-list', $hiddenSection).clone();

    var $menuButtons = $('.alpha-menu-buttons-list', context),
        $localeBtn = $('.locale-button', $menuButtons).clone(),
        $loginBtn = $('.login-button', $menuButtons).clone();

    var $localeBtnLink = $('a', $localeBtn),
        $loginBtnLink = $('a', $loginBtn);

    var localeBtnTitle = $localeBtnLink.text(),
        loginBtnTitle = $loginBtnLink.text();

    alphaMenu.mobileMenuReplaceLink($localeBtnLink, localeBtnTitle);
    alphaMenu.mobileMenuReplaceLink($loginBtnLink, loginBtnTitle);

    $localeBtn.append($switcherList);
    $loginBtn.append($loginList);

    if ($menuList.length) {

      $menuList.append($listItemSeparator).append($loginBtn).append($localeBtn);

      $mobileMenu = $('<nav id="mobile-menu"></nav>').html($menuList);

      $('li.drop-down', $mobileMenu).each(function() {

        var $levelWrapper = $('> div', this),
            $listItem = $('<li class="overview-link menu-item"></li>');

        if ($levelWrapper.hasClass('second-level')) {
          var $sectionLink = $('> a', this),
              $sectionLinkClone = $sectionLink.clone().text(linkTitle),
              title = $sectionLink.text();

          $listItem.prepend($sectionLinkClone);
          $('> ul', $levelWrapper).prepend($listItem);
          alphaMenu.mobileMenuReplaceLink($sectionLink, title);
        }

        if ($levelWrapper.hasClass('third-level')) {
          var $titleLink = $('.title-link', $levelWrapper).text(linkTitle);

          $listItem.prepend($titleLink);
          $('ul', $levelWrapper).prepend($listItem);
        }

      });

    }
    return $mobileMenu;
  }; // alphaMenu.prepareMobileMenuTree

  /**
   * Replaces link to span in order to fix mobile menu behaviour
   */
  alphaMenu.mobileMenuReplaceLink = function (obj, title) {
    obj.replaceWith('<span class="nolink">' + title + '</span>');
  }; // alphaMenu.mobileMenuReplaceLink

  /**
   * Mobile menu toggle button events.
   */
  alphaMenu.mobileMenuButton = function (context, mobileMenuApi) {

    var $toggleListItem = $('.mobile-menu-toggle', context),
        menuButtons = alphaMenu.getMenuButtons(context);

    $('a', $toggleListItem).click(function (e) {
      alphaMenu.mobileMenuApplyMargin();
      if ($('html').hasClass('mm-opened')) {
        mobileMenuApi.close();
        $toggleListItem.removeClass('opened');
      }
      else {
        mobileMenuApi.open();
        $toggleListItem.addClass('opened');
        alphaMenu.sectionAnimate('slideUp', menuButtons.search.block, menuButtons.search.btn);
      }
      e.preventDefault();
    });

    $(window).resize(function () {
      if ($(window).width() >= alphaMenu.breakpoints.bp2) {
        mobileMenuApi.close();
        $toggleListItem.removeClass('opened');
      }
    });

  }; // alphaMenu.mobileMenuButton

})(jQuery, Drupal);