Home Reference Source Test Repository

src/components/navigation/navigation_component.js

import { Component } from "../../core/bane";
import Overlay from "../overlay";
import Notification from "../notification/notification";
import waitForTransition from "../../core/utils/waitForTransition";
import NavigationActions from "./navigation_actions";
import NavigationState from "./navigation_state";
import subscribe from "../../core/decorators/subscribe";
import matchMedia from "../../core/utils/matchMedia";

let userPanelTemplate = require("./user_panel.hbs"),
    userAvatarTemplate = require("./user_avatar.hbs"),
    userLinkTemplate = require("./user_link.hbs");

class NavigationComponent extends Component {

  initialize() {

    this.state = NavigationState.getState();
    this.overlay = new Overlay();

    let notificationLabel = this.state.cartItemCount === 1 ? "item" : "items";

    this.notification = new Notification({
      target: this.$el.find(".js-cart-notification"),
      content: this.state.cartItemCount,
      className: "notification-badge--shop",
      label: `${this.state.cartItemCount} ${notificationLabel} in your cart`
    });

    matchMedia("(min-width: 720px)", (query) => {
      if (query.matches) {
        this.notification.$el.find(".js-notification-badge")
          .removeClass("notification-badge--shop-inline")
          .addClass("notification-badge--shop");
      } else {
        this.notification.$el.find(".js-notification-badge")
          .removeClass("notification-badge--shop")
          .addClass("notification-badge--shop-inline")
          .text("");
      }
    });

    this.name = "navigation";
    this.$mobileNavigation = this.$el.find(".mobile-navigation").detach();
    this.$mobileNavigation.on("click", ".js-close", this._clickNav.bind(this));
    this.$mobileNavigation.on("click", ".js-nav-item", this._handleClick.bind(this));

    this.$el.on("touchstart", ".js-nav-item", this._handleClick.bind(this));

    // SubNavigation hover
    this.handleHover();

    // Events
    this.listenTo(NavigationState, "changed:nav", this.toggleNav);
    this.listenTo(this.overlay, "click", this._clickNav);

    this.subscribe();
  }
  /**
   * Set up hover events to trigger the sub menu's opening and closing.
   * Use event delegation here because the user login is dynamically added.
   * @return {[type]} [description]
   */

  _handleClick(e) {
    let $target = $(e.currentTarget);

    $target.hasClass("navigation__item") ? this._handleSubNav($target[0]) : this._handleMobileSubNav($target[0]); 
    
    if (
      $target.find(".mobile-sub-navigation").length &&
      !$(e.target).hasClass("sub-navigation__link")
    ) {
      e.preventDefault();
    }
  }

  _handleMobileSubNav(el) {
    let $navItem = $(el).find(".mobile-sub-navigation");

    if ( $(".is-expanded").length && !$navItem.hasClass("is-expanded") ) {
      this.$mobileNavigation.find(".mobile-sub-navigation").removeClass("is-expanded");
      this.$mobileNavigation.find(".js-nav-item").removeClass("clicked");
    }

    $(el).toggleClass("clicked");
    $navItem.toggleClass("is-expanded");
  }

  _handleSubNav(el) {
    if ($(el).find(".sub-navigation").hasClass("sub-navigation--visible")) {
      this._closeSubNav(el);
    } else {
      this._openSubNav(el);
    }
  }

  _openSubNav(el) {
    clearTimeout(this.hideTimer);

    // Always clear the currently active one
    this.$el.find(".sub-navigation").removeClass("sub-navigation--visible");

    this.showTimer = setTimeout(() => {
      $(el).find(".sub-navigation").addClass("sub-navigation--visible");
    }, 0);
  }

  _closeSubNav(el) {
    clearTimeout(this.showTimer);

    this.hideTimer = setTimeout(() => {
      $(el).find(".sub-navigation").removeClass("sub-navigation--visible");
    }, 100);
  }

  handleHover() {
    this.$el.on("mouseenter", ".js-nav-item", (e) => this._openSubNav(e.currentTarget));
    this.$el.on("mouseleave", ".js-nav-item", (e) => this._closeSubNav(e.currentTarget));
  }

  toggleNav() {
    if(this.state.isNavOpen) {
      this.show();
    } else {
      this.hide();
    }
  }

  show() {
    if(!this.state.isNavOpen){
      return Promise.all([]);
    }

    if(this.$mobileNavigation.parents().length === 0) {
      this.$mobileNavigation.appendTo(document.body);
    }

    this.overlay.show();

    setTimeout(() => {
      this.$mobileNavigation.addClass("mobile-navigation--visible");
    }, 20);

    return waitForTransition(this.$mobileNavigation, { fallbackTime: 2000 });
  }

  hide() {
    if(this.state.isNavOpen) {
      return Promise.all([]);
    }

    this.$mobileNavigation.removeClass("mobile-navigation--visible");

    this.overlay.hide();

    return waitForTransition(this.$mobileNavigation, { fallbackTime: 2000 })
      .then(() => {
        this.$mobileNavigation.detach();
      });
  }

  _clickNav() {
    NavigationActions.clickNav();
  }

  @subscribe("user.status.update")
  userStatusUpdate(user) {
    let $li = this.$el.find(".navigation__item--user"),
        $liMobile = this.$mobileNavigation.find(".mobile-navigation__item--user");

    if (!user.id) {
      return;
    }

    $li.html(userAvatarTemplate({
      user
    })).append(userPanelTemplate({
      className: "sub-navigation",
      user
    }));

    $liMobile.html(userLinkTemplate({
      user
    })).append(userPanelTemplate({
      className: "mobile-sub-navigation",
      user
    }));
  }
  @subscribe("user.notifications.update")
  userNotificationUpdate(user) {
    this.userStatusUpdate(user);
  }
}

export default NavigationComponent;