patternfly-functions-vertical-nav.js

591 lines | 21.863 kB Blame History Raw Download
// Util: PatternFly Vertical Navigation
// Must have navbar-toggle in navbar-pf-vertical for expand/collapse
(function ($) {
  'use strict';

  $.fn.setupVerticalNavigation = function (handleItemSelections, ignoreDrawer) {

    var navElement = $('.nav-pf-vertical'),
      bodyContentElement = $('.container-pf-nav-pf-vertical'),
      toggleNavBarButton = $('.navbar-toggle'),
      handleResize = true,
      explicitCollapse = false,
      subDesktop = false,
      hoverDelay = 500,
      hideDelay = hoverDelay + 200,

      inMobileState = function () {
        return bodyContentElement.hasClass('hidden-nav');
      },

      forceResize = function (delay) {
        setTimeout(function () {
          $(window).trigger('resize');
        }, delay);
      },

      showSecondaryMenu = function () {
        if (inMobileState() || !subDesktop) {
          navElement.addClass('secondary-visible-pf');
          bodyContentElement.addClass('secondary-visible-pf');
        }

        // Dispatch a resize event when showing the secondary menu in non-subdesktop state to
        // allow content to adjust to the secondary menu sizing
        if (!subDesktop) {
          forceResize(100);
        }
      },

      hideSecondaryMenu = function () {
        navElement.removeClass('secondary-visible-pf');
        bodyContentElement.removeClass('secondary-visible-pf');

        if (navElement.find('.secondary-nav-item-pf.is-hover').length <= 1) {
          navElement.removeClass('hover-secondary-nav-pf');
        }

        navElement.find('.mobile-nav-item-pf').each(function (index, item) {
          $(item).removeClass('mobile-nav-item-pf');
        });

        navElement.find('.is-hover').each(function (index, item) {
          $(item).removeClass('is-hover');
        });
      },

      hideTertiaryMenu = function () {
        navElement.removeClass('tertiary-visible-pf');
        bodyContentElement.removeClass('tertiary-visible-pf');

        if (navElement.find('.tertiary-nav-item-pf.is-hover').length <= 1) {
          navElement.removeClass('hover-tertiary-nav-pf');
        }

        navElement.find('.mobile-nav-item-pf').each(function (index, item) {
          $(item).removeClass('mobile-nav-item-pf');
        });

        navElement.find('.is-hover').each(function (index, item) {
          $(item).removeClass('is-hover');
        });
      },

      setActiveItem = function (item) {
        // remove all .active
        $('.nav-pf-vertical .list-group-item.active').removeClass('active');

        // add .active to item and its parents
        item.addClass('active').parents('.list-group-item').addClass('active');
      },

      updateSecondaryMenuDisplayAfterSelection = function () {
        if (inMobileState()) {
          navElement.removeClass('show-mobile-nav');
          hideSecondaryMenu();
          navElement.find('.mobile-nav-item-pf').each(function (index, item) {
            $(item).removeClass('mobile-nav-item-pf');
          });
        } else {
          showSecondaryMenu();
        }
      },

      updateSecondaryCollapsedState = function (setCollapsed, collapsedItem) {
        if (setCollapsed) {
          collapsedItem.addClass('collapsed');
          navElement.addClass('collapsed-secondary-nav-pf');
          bodyContentElement.addClass('collapsed-secondary-nav-pf');
        } else {
          if (collapsedItem) {
            collapsedItem.removeClass('collapsed');
          } else {
            // Remove any collapsed secondary menus
            navElement.find('[data-toggle="collapse-secondary-nav"]').each(function (index, element) {
              var $e = $(element);
              $e.removeClass('collapsed');
            });
          }
          navElement.removeClass('collapsed-secondary-nav-pf');
          bodyContentElement.removeClass('collapsed-secondary-nav-pf');
        }
      },

      updateTertiaryCollapsedState = function (setCollapsed, collapsedItem) {
        if (setCollapsed) {
          collapsedItem.addClass('collapsed');
          navElement.addClass('collapsed-tertiary-nav-pf');
          bodyContentElement.addClass('collapsed-tertiary-nav-pf');
          updateSecondaryCollapsedState(false);
        } else {
          if (collapsedItem) {
            collapsedItem.removeClass('collapsed');
          } else {
            // Remove any collapsed tertiary menus
            navElement.find('[data-toggle="collapse-tertiary-nav"]').each(function (index, element) {
              var $e = $(element);
              $e.removeClass('collapsed');
            });
          }
          navElement.removeClass('collapsed-tertiary-nav-pf');
          bodyContentElement.removeClass('collapsed-tertiary-nav-pf');
        }
      },

      updateMobileMenu = function (selected, secondaryItem) {
        $(document).find('.list-group-item.mobile-nav-item-pf').each(function (index, item) {
          $(item).removeClass('mobile-nav-item-pf');
        });
        $(document).find('.list-group-item.mobile-secondary-item-pf').each(function (index, item) {
          $(item).removeClass('mobile-secondary-item-pf');
        });
        if (selected) {
          selected.addClass('mobile-nav-item-pf');
          if (secondaryItem) {
            secondaryItem.addClass('mobile-secondary-item-pf');
            navElement.removeClass('show-mobile-secondary');
            navElement.addClass('show-mobile-tertiary');
          } else {
            navElement.addClass('show-mobile-secondary');
            navElement.removeClass('show-mobile-tertiary');
          }
        } else {
          navElement.removeClass('show-mobile-secondary');
          navElement.removeClass('show-mobile-tertiary');
        }
      },

      enterMobileState = function () {
        if (!navElement.hasClass('hidden')) {
          //Set the nav to being hidden
          navElement.addClass('hidden');
          navElement.removeClass('collapsed');

          //Set the body class to the correct state
          bodyContentElement.removeClass('collapsed-nav');
          bodyContentElement.addClass('hidden-nav');

          // Reset the collapsed states
          updateSecondaryCollapsedState(false);
          updateTertiaryCollapsedState(false);

          explicitCollapse = false;
        }
      },

      exitMobileState = function () {
        // Always remove the hidden & peek class
        navElement.removeClass('hidden show-mobile-nav');

        // Set the body class back to the default
        bodyContentElement.removeClass('hidden-nav');
      },

      checkNavState = function () {
        var width = $(window).width(), makeSecondaryVisible;
        if (!handleResize) {
          return;
        }
        // Check to see if we need to enter/exit the mobile state
        if (width < $.pfBreakpoints.tablet && !explicitCollapse) {
          enterMobileState();
        } else if (navElement.hasClass('hidden')) {
          exitMobileState();
        }

        // Check to see if we need to enter/exit the sub desktop state
        if (width < $.pfBreakpoints.desktop) {
          if (!subDesktop) {
            // Collapse the navigation bars when entering sub desktop mode
            navElement.addClass('collapsed');
            bodyContentElement.addClass('collapsed-nav');
          }
          if (width >= $.pfBreakpoints.tablet) {
            hideSecondaryMenu();
          }
          subDesktop = true;
        } else {
          makeSecondaryVisible = subDesktop && (navElement.find('.secondary-nav-item-pf.active').length > 0);
          subDesktop = false;
          if (makeSecondaryVisible) {

            showSecondaryMenu();
          }
        }

        if (explicitCollapse) {
          navElement.addClass('collapsed');
          bodyContentElement.addClass('collapsed-nav');
        } else {
          navElement.removeClass('collapsed');
          bodyContentElement.removeClass('collapsed-nav');
        }
      },

      collapseMenu = function () {
        //Make sure this is expanded
        navElement.addClass('collapsed');
        //Set the body class to the correct state
        bodyContentElement.addClass('collapsed-nav');

        if (subDesktop) {
          hideSecondaryMenu();
        }

        explicitCollapse = true;
      },

      enableTransitions = function () {
        // enable transitions only when toggleNavBarButton is clicked or window is resized
        $('html').addClass('transitions');
      },

      expandMenu = function () {
        //Make sure this is expanded
        navElement.removeClass('collapsed');
        //Set the body class to the correct state
        bodyContentElement.removeClass('collapsed-nav');

        explicitCollapse = false;

        // Dispatch a resize event when showing the expanding then menu to
        // allow content to adjust to the menu sizing
        if (!subDesktop) {
          forceResize(100);
        }
      },

      bindMenuBehavior = function () {
        toggleNavBarButton.on('click', function (e) {
          var $drawer;

          enableTransitions();

          if (inMobileState()) {
            // Toggle the mobile nav
            if (navElement.hasClass('show-mobile-nav')) {
              navElement.removeClass('show-mobile-nav');
            } else {
              // Always start at the primary menu
              updateMobileMenu();
              navElement.addClass('show-mobile-nav');

              // If the notification drawer is shown, hide it
              if (!ignoreDrawer) {
                $drawer = $('.drawer-pf');
                if ($drawer.length) {
                  $('.drawer-pf-trigger').removeClass('open');
                  $drawer.addClass('hide');
                }
              }
            }
          } else if (navElement.hasClass('collapsed')) {
            window.localStorage.setItem('patternfly-navigation-primary', 'expanded');
            expandMenu();
          } else {
            window.localStorage.setItem('patternfly-navigation-primary', 'collapsed');
            collapseMenu();
          }
        });
      },

      forceHideSecondaryMenu = function () {
        navElement.addClass('force-hide-secondary-nav-pf');
        setTimeout(function () {
          navElement.removeClass('force-hide-secondary-nav-pf');
        }, 500);
      },

      bindMenuItemsBehavior = function (handleSelection) {
        $(document).find('.nav-pf-vertical .list-group-item').each(function (index, item) {
          var onClickFn,
            $item = $(item),
            $nav = $item.closest('[class*="nav-pf-"]');

          if ($nav.hasClass('nav-pf-vertical')) {
            // Set main nav active item on click or show secondary nav if it has a secondary nav bar and we are in the mobile state
            onClickFn = function (event) {
              var $this = $(this), $secondaryItem, $tertiaryItem, $activeItem;

              if (!$this.hasClass('secondary-nav-item-pf')) {
                hideSecondaryMenu();
                if (inMobileState()) {
                  updateMobileMenu();
                  navElement.removeClass('show-mobile-nav');
                }
                if (handleSelection) {
                  setActiveItem($this);
                  // Don't process the click on the item
                  event.stopImmediatePropagation();
                }
              } else if (inMobileState()) {
                updateMobileMenu($this);
              } else if (handleSelection) {
                $activeItem = $secondaryItem = $item.find('.nav-pf-secondary-nav > .list-group > .list-group-item').eq(0);

                if ($secondaryItem.hasClass('tertiary-nav-item-pf')) {
                  $activeItem = $secondaryItem.find('.nav-pf-tertiary-nav > .list-group > .list-group-item').eq(0);
                }

                setActiveItem($activeItem);
                event.stopImmediatePropagation();
              }
            };

          } else if ($nav.hasClass('nav-pf-secondary-nav')) {
            // Set secondary nav active item on click or show tertiary nav if it has a tertiary nav bar and we are in the mobile state
            onClickFn = function (event) {
              var $this = $(this), $tertiaryItem, $primaryItem;
              if (!$this.hasClass('tertiary-nav-item-pf')) {
                if (inMobileState()) {
                  updateMobileMenu();
                  navElement.removeClass('show-mobile-nav');
                }
                updateSecondaryMenuDisplayAfterSelection();
                if (handleSelection) {
                  setActiveItem($item);
                  hideSecondaryMenu();
                  event.stopImmediatePropagation();
                }
              } else if (inMobileState()) {
                $primaryItem = $item.parents('.list-group-item');
                updateMobileMenu($this, $primaryItem);
                event.stopImmediatePropagation();
              } else if (handleSelection) {
                $tertiaryItem = $item.find('.nav-pf-tertiary-nav > .list-group > .list-group-item').eq(0);
                setActiveItem($tertiaryItem);
                event.stopImmediatePropagation();
              }
            };

          } else if ($nav.hasClass('nav-pf-tertiary-nav')) {
            // Set tertiary nav active item on click
            onClickFn = function (event) {
              if (inMobileState()) {
                updateMobileMenu();
                navElement.removeClass('show-mobile-nav');
              }
              updateSecondaryMenuDisplayAfterSelection();
              if (handleSelection) {
                setActiveItem($item);
                hideTertiaryMenu();
                hideSecondaryMenu();
                event.stopImmediatePropagation();
              }
            };
          }

          // register event handler
          $item.on('click.pf.secondarynav.data-api', onClickFn);
        });

        $(document).find('.secondary-nav-item-pf').each(function (index, secondaryItem) {
          var $secondaryItem = $(secondaryItem);

          // Collapse the secondary nav bar when the toggle is clicked
          $secondaryItem.on('click.pf.secondarynav.data-api', '[data-toggle="collapse-secondary-nav"]', function (e) {
            var $this = $(this);
            if (inMobileState()) {
              updateMobileMenu();
              e.stopImmediatePropagation();
            } else {
              if ($this.hasClass('collapsed')) {
                window.localStorage.setItem('patternfly-navigation-secondary', 'expanded');
                window.localStorage.setItem('patternfly-navigation-tertiary', 'expanded');
                updateSecondaryCollapsedState(false, $this);
                forceHideSecondaryMenu();
              } else {
                window.localStorage.setItem('patternfly-navigation-secondary', 'collapsed');
                updateSecondaryCollapsedState(true, $this);
              }
            }
            navElement.removeClass('hover-secondary-nav-pf');
            if (handleSelection) {
              // Don't process the click on the parent item
              e.stopImmediatePropagation();
            }
          });

          $secondaryItem.find('.tertiary-nav-item-pf').each(function (index, primaryItem) {
            var $primaryItem = $(primaryItem);
            // Collapse the tertiary nav bar when the toggle is clicked
            $primaryItem.on('click.pf.tertiarynav.data-api', '[data-toggle="collapse-tertiary-nav"]', function (e) {
              var $this = $(this);
              if (inMobileState()) {
                updateMobileMenu($secondaryItem);
                e.stopImmediatePropagation();
              } else {
                if ($this.hasClass('collapsed')) {
                  window.localStorage.setItem('patternfly-navigation-secondary', 'expanded');
                  window.localStorage.setItem('patternfly-navigation-tertiary', 'expanded');
                  updateTertiaryCollapsedState(false, $this);
                  forceHideSecondaryMenu();
                } else {
                  window.localStorage.setItem('patternfly-navigation-tertiary', 'collapsed');
                  updateTertiaryCollapsedState(true, $this);
                }
              }
              navElement.removeClass('hover-secondary-nav-pf');
              navElement.removeClass('hover-tertiary-nav-pf');
              if (handleSelection) {
                // Don't process the click on the parent item
                e.stopImmediatePropagation();
              }
            });
          });
        });

        // Show secondary nav bar on hover of secondary nav items
        $(document).on('mouseenter.pf.tertiarynav.data-api', '.secondary-nav-item-pf', function (e) {
          var $this = $(this);
          if (!inMobileState()) {
            if ($this[0].navUnHoverTimeout !== undefined) {
              clearTimeout($this[0].navUnHoverTimeout);
              $this[0].navUnHoverTimeout = undefined;
            } else if ($this[0].navHoverTimeout === undefined) {
              $this[0].navHoverTimeout = setTimeout(function () {
                navElement.addClass('hover-secondary-nav-pf');
                $this.addClass('is-hover');
                $this[0].navHoverTimeout = undefined;
              }, hoverDelay);
            }
          }
        });

        $(document).on('mouseleave.pf.tertiarynav.data-api', '.secondary-nav-item-pf', function (e) {
          var $this = $(this);
          if ($this[0].navHoverTimeout !== undefined) {
            clearTimeout($this[0].navHoverTimeout);
            $this[0].navHoverTimeout = undefined;
          } else if ($this[0].navUnHoverTimeout === undefined &&
              navElement.find('.secondary-nav-item-pf.is-hover').length > 0) {
            $this[0].navUnHoverTimeout = setTimeout(function () {
              if (navElement.find('.secondary-nav-item-pf.is-hover').length <= 1) {
                navElement.removeClass('hover-secondary-nav-pf');
              }
              $this.removeClass('is-hover');
              $this[0].navUnHoverTimeout = undefined;
            }, hideDelay);
          }
        });

        // Show tertiary nav bar on hover of secondary nav items
        $(document).on('mouseover.pf.tertiarynav.data-api', '.tertiary-nav-item-pf', function (e) {
          var $this = $(this);
          if (!inMobileState()) {
            if ($this[0].navUnHoverTimeout !== undefined) {
              clearTimeout($this[0].navUnHoverTimeout);
              $this[0].navUnHoverTimeout = undefined;
            } else if ($this[0].navHoverTimeout === undefined) {
              $this[0].navHoverTimeout = setTimeout(function () {
                navElement.addClass('hover-tertiary-nav-pf');
                $this.addClass('is-hover');
                $this[0].navHoverTimeout = undefined;
              }, hoverDelay);
            }
          }
        });
        $(document).on('mouseout.pf.tertiarynav.data-api', '.tertiary-nav-item-pf', function (e) {
          var $this = $(this);
          if ($this[0].navHoverTimeout !== undefined) {
            clearTimeout($this[0].navHoverTimeout);
            $this[0].navHoverTimeout = undefined;
          } else if ($this[0].navUnHoverTimeout === undefined) {
            $this[0].navUnHoverTimeout = setTimeout(function () {
              if (navElement.find('.tertiary-nav-item-pf.is-hover').length <= 1) {
                navElement.removeClass('hover-tertiary-nav-pf');
              }
              $this.removeClass('is-hover');
              $this[0].navUnHoverTimeout = undefined;
            }, hideDelay);
          }
        });
      },

      loadFromLocalStorage = function () {
        if (inMobileState()) {
          return;
        }

        if (window.localStorage.getItem('patternfly-navigation-primary') === 'collapsed') {
          collapseMenu();
        }

        if ($('.nav-pf-vertical.nav-pf-vertical-collapsible-menus').length > 0) {
          if (window.localStorage.getItem('patternfly-navigation-secondary') === 'collapsed') {
            updateSecondaryCollapsedState(true, $('.secondary-nav-item-pf.active [data-toggle=collapse-secondary-nav]'));
          }

          if (window.localStorage.getItem('patternfly-navigation-tertiary') === 'collapsed') {
            updateTertiaryCollapsedState(true, $('.tertiary-nav-item-pf.active [data-toggle=collapse-tertiary-nav]'));
          }
        }
      },

      setTooltips = function () {
        var tooltipOptions = {
          container: 'body',
          placement: 'bottom',
          delay: { 'show': '500', 'hide': '200' },
          template: '<div class="nav-pf-vertical-tooltip tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
        };
        $('.nav-pf-vertical [data-toggle="tooltip"]').tooltip(tooltipOptions);

        $('.nav-pf-vertical').on("show.bs.tooltip", function (e) {
          return $(this).hasClass("collapsed");
        });
      },

      init = function (handleItemSelections) {
        // Hide the nav menus during initialization
        navElement.addClass('hide-nav-pf');
        bodyContentElement.addClass('hide-nav-pf');

        //Set correct state on load
        checkNavState();

        // Bind Top level hamburger menu with menu behavior;
        bindMenuBehavior();

        // Bind menu items
        bindMenuItemsBehavior(handleItemSelections);

        //Set tooltips
        setTooltips();

        loadFromLocalStorage();

        // Show the nav menus
        navElement.removeClass('hide-nav-pf');
        bodyContentElement.removeClass('hide-nav-pf');
        forceResize(250);
      },

      self = {
        hideMenu: function () {
          handleResize = false;
          enterMobileState();
        },
        showMenu: function () {
          handleResize = true;
          exitMobileState();
        },
        isVisible: function () {
          return handleResize;
        }
      };

    if (!$.fn.setupVerticalNavigation.self) {
      $.fn.setupVerticalNavigation.self = self;
      //Listen for the window resize event and collapse/hide as needed
      $(window).on('resize', function () {
        checkNavState();
        enableTransitions();
      });

      init(handleItemSelections);
    }
    return $.fn.setupVerticalNavigation.self;
  };
}(jQuery));