5th Oct '22
/
3 comments

How to add a resizable desktop navbar in Bricks

How to add a resizable desktop navbar in Bricks

This tutorial will show you how to add a “More” menu item when the navbar overflows on desktop.

Table of Contents

Introduction

The UI/UX literature recommends having as less menu items as possible. But if you’re a web professional, you certainly know that some client insists to have a bunch of items inside their navbar and it ends up being a nightmare to style on different breakpoints in order to avoid the overflow.

Well, there is an alternative. Instead of using tons of media queries, you could just integrate this small script. As soon as your menu overflows on resize, the script will automatically create a “More” item at the end of your navbar, and list the overflown items as sub-menu items. Let’s see how to implement it.

The DOM Structure

Follow the following DOM tree:

Make sure to add the menu-wrapper, menu-inner and menu-crop classes to the correct elements as shown above.

The menu-wrapper element needs to have a width of 100%. If you want your navbar to be boxed, set the max-width as well.

The menu-inner block needs to use flexbox, and use flex-direction: row:

JavaScript

Copy/Paste the following JavaScript code where it needs to be applied. As an example, if you’re menu is global and runs on all pages, you could paste the code in Bricks Settings -> Custom Code tab -> Body (footer) scripts.

window.addEventListener('load', () => {

   // Variables
   const wrapper = document.querySelector('.menu-wrapper');
   if (!wrapper) {
      return console.log('Resizable Desktop Menu - There is no menu-wrapper class on this page. Please double check you correctly assigned the classes to each element');
   }
   const inner = wrapper.querySelector('.menu-inner');
   if (!inner) {
      return console.log('Resizable Desktop Menu - There is no menu-inner class on this page. Please double check you correctly assigned the classes to each element');
   }
   const menu = inner.querySelector('.menu-crop');
   if (!menu) {
      return console.log('Resizable Desktop Menu - There is no menu-crop class on this page. Please double check you correctly assigned the classes to each element');
   }
   const menuUl = menu.querySelector('ul.bricks-nav-menu');
   const menuItems = menu.querySelectorAll("ul.bricks-nav-menu > li.menu-item").length;
   let hiddenItems = [];
   let moreItem;
   let moreIsVisible = false;

   // debounce so calculation doesn't happen every millisecond when resizing
   const debounce = (fn, threshold) => {
      var timeout;
      threshold = threshold || 200;
      return function debounced() {
         clearTimeout(timeout);
         var args = arguments;
         var _this = this;

         function delayed() {
            fn.apply(_this, args);
         }
         timeout = setTimeout(delayed, threshold);
      };
   };

   // Check if overflow
   const isOverflown = (element) => {
      return element.scrollWidth > element.clientWidth;
   }

   // Create elements function
   const createElement = (el, options, append) => {
      let element = document.createElement(el);

      if (!options) return element;
      let entries = Object.entries(options)
      let data = entries.map(([key, val] = entry) => {
         element.setAttribute(key, val);
      });

      if (!append) return element
      return append.appendChild(element);
   }

   // Create More Item
   const createMoreMenu = (menuUl) => {

      // Create Elemets
      const tag = createElement('li', {
         'id': 'moreItem',
         'class': 'bricks-menu-item'
      }, menuUl);
      const active = createElement('a', {}, tag);
      const text = document.createTextNode("More");
      active.appendChild(text);
      const qty = createElement('span', {
         'id': 'moreQty'
      }, active);
      const ulTag = createElement('ul', {
         'id': 'moreSubMenu',
         'role': 'menu',
         'class': 'sub-menu'
      }, tag);

      moreIsVisible = true;
   }

   // Remove Item
   const removeItem = () => {
      // Stop the function is NOT overflown
      if (!isOverflown(inner) || hiddenItems.length == menuItems) return;

      // Declare Variables
      let menuLi = menu.querySelectorAll("ul.bricks-nav-menu > li.menu-item");
      let lastItem = menuLi[menuLi.length - 1];

      // Add node to hidden array
      hiddenItems.push(lastItem);

      // Remove node from the DOM
      if (lastItem) menuUl.removeChild(lastItem);

      // Insert the More Button
      if (!moreIsVisible) createMoreMenu(menuUl);

      // Append the item to the sub menu
      let subMenu = menu.querySelector('#moreSubMenu');
      if (subMenu) subMenu.insertBefore(lastItem, subMenu.firstChild);

      // Update the Qty Number
      let subMenuQty = menu.querySelector('#moreQty');
      subMenuQty.innerHTML = hiddenItems.length;

      // Keep removing items if the inner container is overflown
      removeItem();
   }

   // Add Item
   const addItem = () => {
      if (hiddenItems.length < 1) return;

      // Remove the More Item
      if (moreIsVisible) {
         let moreItem = menuUl.querySelector("#moreItem");
         moreItem.remove();
         moreIsVisible = false;
      }

      // Readd all hidden items as visible
      if (hiddenItems.length > 0) {
         hiddenItems.slice().reverse().forEach(item => {
            menuUl.insertBefore(item, moreItem);
         });

         // reset the hidden array
         hiddenItems = [];
      }

      // Remove item is the inner container is overflown
      if (isOverflown(inner)) removeItem();
   }

   // Init
   if (isOverflown(inner)) removeItem();

   // Resize
   window.addEventListener('resize', debounce(() => {
      isOverflown(inner) ? removeItem() : addItem();
   }, 0))

})

CSS

Paste the following CSS snippet where the script is running. As an example, if you’re menu is global and runs on all pages, you could paste the code in Bricks Settings -> Custom Code tab -> Custom CSS.

.menu-inner > * {
   white-space: nowrap;
}

#moreItem a {
   display: flex;
   align-items: flex-start;
   flex-wrap: nowrap;
   flex-direction: row;
}

span#moreQty {
   margin-left: 0.3rem;
   background-color: var(--bricks-color-primary, blue);
   padding: 0.3rem 0.45rem;
   font-size: 9px;
   color: #fff;
   border-radius: 50%;
   vertical-align: super;
   font-weight: 800;
   width: 15px;
   height: 15px;
   display: flex;
   align-items: center;
   justify-content: center;
}

Resources

debounce function

https://www.freecodecamp.org/news/javascript-debounce-example/

keys and values in a JS object

https://javascript.info/keys-values-entries

Understanding offsetWidth, clientWidth, scrollWidth

https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively

Get access to all 625 Bricks code tutorials with BricksLabs Pro

3 comments

  • The "More" button doesn't seem to work

  • Aarón Blanco Tejedor

    Hello Maxime ????

    I am about to use this method in my second website, but this time the header doesn't have a button on it, and when not using a button, as soon as there is no space available, all the elements collapse under the "more" element. Do you think it would be possible to make this work without the button element?

    Love ????

  • Stephen Revere

    Wow. Glad I got the pro version early!

Leave your comment

 

Related Tutorials..

Pro
Bricks Query Loop Cards with Hover Image

Bricks Query Loop Cards with Hover Image

How images uploaded to posts can appear when the featured images in a Bricks posts query loop are hovered.
Categories:
How to add SVG icon as list item bullets

How to add SVG icon as list item bullets

Add this sample CSS: Replace brxe-list with the class of your ul element. Here's how you can generate the value of background-image property: Copy your…
Categories:
Tags:
Pro
Inserting Random Ad Between Posts in Bricks

Inserting Random Ad Between Posts in Bricks

How to insert random ads (post type) in between regular posts on the blog page.
Categories:
Floating Element in Bricks

Floating Element in Bricks

How to create a floating element in Bricks that appears across the entire website.
Categories:
Tags:
Pro
Oxygen’s Focus Section in Bricks

Oxygen’s Focus Section in Bricks

These are the steps to implement Focus Section, an Oxygen’s composite element, in Bricks.
Categories:
Width and Height of Mobile Hamburger Toggle in Bricks

Width and Height of Mobile Hamburger Toggle in Bricks

Looking to change the size of Bricks hamburger toggle in the mobile menu? The default width value of 20px can be changed by selecting the…
Categories:
Pro
“Pro” Category Ribbon for Posts in Bricks

“Pro” Category Ribbon for Posts in Bricks

This Pro tutorial provides the steps to show a "Pro" ribbon for posts that are categorized under the "Pro" category when using Bricks builder. Step…
Categories:
Pro
How to Combine Multiple Menus into Bricks’ Mobile Menu

How to Combine Multiple Menus into Bricks’ Mobile Menu

Bricks' native 'Nav Menu' element comes with a built-in mobile menu, which shrinks the menu down to a menu toggle button to open an offcanvas…
Categories: