0
(0)

Squarespace is a tricky site-builder, because even though there are a ton of facilities for a non-techie client, those pre-made components are not always as the client desires.

Let’s be clear, I won’t use the abbreviation SS for Squarespace, because it resembles with something from the 2nd world war(Protective Echelon for the guy who’s name starts with H), so I won’t do it. Secondly, yes Squarespace does not support only 2 level navigation. The first level are the folders in the editor and the second are the pages.

But what if we would want another level, the 3rd level because the 2nd level got really crowdy? Well then you could try a new prayer or a new site-builder. Or actually you could try my trick with Javascript and CSS. This is what you will get:

Desktop 3 level navigation
Mobile 3 level navigation

Are you with me? Then let me explain:

The basic idea is to create an HTML in the Injections panel in Sitecore, more exactly a <div> tag with links inside and then my javascript snippet will do the rest. Below you can see in the Injections Footer panel on top the 3rd level menu construction, that is HTML.

Each <div> tag represents a 3rd level submenu and each link, the submenu items. The data-connection attribute will have to match with the 2nd level menu’s link url, where we actually want to add the submenu. In this case the first submenu will be connected to the team link and the second to the financials link.

Now the “humongous” Javascript code follows in the Footer panel in script tags, I am just showing a part of it:

Basically what we will do is iterate through each 2nd level navigation item check if the item’s link url matches one of the submenus data-connection value. After we have a match we will move that div with the links near the top navigation link and we will do this also for the separate mobile menu. But let’s just demystify this code in segments.

First I will explain it in little parts and in the end I will post the entire code for you.

The HTML structure for the 3rd level menus

As I mentioned before the data-connection is crucial here to be able to identify where does this submenu belongs to. The class “header-nav-folder-subitem” will be important for the CSS.

Submenu items getting cloned/dupicated

level2Items will store an array of the navigation items from desktop main navigation and mobile main navigation. Now level3Items is more trickier, because we need the submenus duplicated since we are using them in desktop navigation and mobile navigation so we will need to do some serious cloning to do in the near future.

After we mapped each item in level2Items to be cloned or deep-cloned(true is for cloning with child content), then we will create a new array and spread the cloned and the original submenu items inside of it.

We create the leve2ItemNr to have the number of the items instead of adding it in a loop, where on each iteration it will count the length of the total items.

First loop for iterating though the 2nd level navigation

So we go through the 2nd level navigation items. We used for instead of forEach because we need to break the loop a couple of times and forEach is actually unstoppable, you can not break out of it, but you can with for.

I like to store selectors in variables because it is best practice if you use it more then 1 time. Now level3ItemNr is the tricky variable. First we take the length of the level3Items array, then we divide it with 2 because … well because it has the cloned items and those should not be counted and the Math.round is for the case where we divide 1 with 2 and we don’t want decimals.

The condition which translated is level3Items.length === 0, breaks the loop, stops the iteration because there is no more match checking remaining.

The inner loop iterating through the sub navigations that are not yet placed into the top menu

In this step the matching process is in scope.

mainItemLink.pathname – this is the 2nd level navigation item’s url

item.dataset.connection – this is how we get the data-connection attribute from the submenu item

After we made the matching we remove that object from the level3Items, so that array is getting smaller and smaller.

The next 4 rows actually are related to the small icons added on mobile version, a right arrow next to the 2nd level navigation that suddenly became a “parent”, meaning it got a child sub-navigation item(or at least in the next step it will get).

The 4th row is for adding a close button for the 3rd level navigation popup, because this is how the 3rd level will look like, this is how we will close it, because we rock, we do things right and visually striking. That’s why. But you will see later, what I am talking about.

The click event handler for opening 3rd level navigation in mobile device

We append the item(3rd level menu) into the mainItem(2nd level menu). The condition checks if we are on the mobile navigation(Squarespace put’s different class on menu items for mobile and desktop);

Now we need to handle the click on the 2nd level navigation item, to not go to another page as it was used to (ev.preventDefault()) but instead do the following:

Add the class ‘opened-nav’ to the menu item, that will open in a spectacular fashion the 3rd level menu from CSS. Then we add to the popup the height of the document minus the sticky header height(if you have one). Finally we use that forEach to check if we are on the page of one of the 3rd level submenu links, than that link should have the active class on it(to style it with an underline or bold or as you wish).

close button and burger menu click event handlers

Eventually we add some event listeners to the close button of the popup, when clicking on it to close the popup and one for the burger menu to clear the opened 3rd level navigation popup if you click on burger menu and revisit that submenu.

The only thing remaining is the CSS:

css for mobile/tablet view for the submenus

This is for mobile version, but of course you can style it as you wish. Also when you open the navigation by adding the opened-nav class, you should add also this style in the custom CSS panel:

css code how should the popup open

The pointer events actually initially was set to none, because the popup was there but it was invisible, fully transparent. When opening we restore the pointer-events to default value.

Conclusion: This trick involves a lengthy code and some CSS and also some HTML but if you think the juice worth the squeeze, then use it, implement it and style it in your own way.

The entire code is down below:

<div class="header-nav-folder-subitem" data-connection="whatweoffer">
    <a href="/vision"><span>Our vision</span></a>
    <a href="/legacy"><span>Our legacy</span></a>
 </div>
<div class="header-nav-folder-subitem" data-connection="financials">
    <a href="/money"><span>Money</span></a>
 </div>

<script>
 const level2Items = [...document.querySelectorAll('.header-display-desktop .header-nav-folder-item, .header-menu--folder-list .header-menu-nav-item')];
 let level3Items = [...document.querySelectorAll('.header-nav-folder-subitem')]; 
 const level3ClonedItems = level3Items.map(item => {
 	return item.cloneNode(true);
 });  
 level3Items = [...level3ClonedItems, ...level3Items];
 const level2ItemsNr = level2Items.length;


 for(let n = 0; n < level2ItemsNr; n++){ 
     const mainItem = level2Items[n];
     const mainItemLink = mainItem.querySelector('a');
     const level3ItemsNr = Math.round(level3Items.length / 2) ;
     if(!level3ItemsNr) {
        break;
     }
     for(let i = 0; i < level3ItemsNr; i++){   
         const item = level3Items[i]; 
         if(mainItemLink.pathname === '/' + item.dataset.connection) {  
           level3Items.splice(i,1);  
           
           if(mainItem.classList.contains('header-menu-nav-item')) {
               mainItemLink.insertAdjacentHTML('beforeEnd', '<span class="chevron chevron--right"></span>')
           }
           const closeButton = item.insertAdjacentHTML('beforeEnd','<span class="close"></span>');
           mainItem.append(item);
            if(mainItem.classList.contains('header-menu-nav-item')) {
               mainItemLink.addEventListener("click", ev => {
                    ev.preventDefault();
                    const docH = document.documentElement.offsetHeight;
     				const headerH = document.getElementById('header').offsetHeight;
                    item.classList.add('opened-nav');
                    item.style.height = docH - headerH;
                    item.querySelectorAll('a').forEach(submenuLink => {
                    	if(window.location.pathname === submenuLink.pathname) {
                        	submenuLink.classList.add('active');
                        } 
                    });       
               });  
              
               item.querySelector('.close').addEventListener('click', ev => {
                   item.classList.remove('opened-nav'); 
               });
               document.querySelectorAll('.header-burger')[1].addEventListener('click', burger => {
                	item.classList.remove('opened-nav');                 
               });  
            }   
           break;
         }     
     }    
  };	   
 </script>
.header-nav {
    .header-nav-folder-item {
        position: relative;
        &:hover {
            .header-nav-folder-subitem {
                display: block;
            }    
        } 
    }

    .header-nav-folder-subitem {
        position: absolute;
        top: -1px;
        left: calc(100% + 15px);
        background: #000;
        color: #fff;
    }
}

.header-nav-folder-subitem {
    display: none;
    min-width: 200px;
    a {
        padding: 10px 0 8px!important;
        border-top: 2px solid black;
        line-height: 1.5rem;
        &:first-child {
            border: none;
        }
    }
}
body:not(.header--menu-open) .header-nav-folder-item--active .header-nav-folder-item-content {
    background: none;
}
@media (max-width: 1024px) {
    .header-nav-folder-subitem {
      background: @gray!important;
      display: flex!important;
      justify-content: center;
      flex-direction: column;
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      margin: 0 auto;
      pointer-events: none;
      transform: scale(2);
      opacity: 0;
      transition: transform .6s, opacity .3s;
      z-index: 10000;
      a {
        border: 0;
        &.active {
          text-decoration: underline;
        }
      }
      .close {
        font-size: 30px;
        position: absolute;
        right: 6vw;
        top: 10px;
        cursor: pointer;
        min-width: 32px;
        min-height: 32px;
        &:before, &:after {
            position: absolute;
            left: 15px;
            content: ' ';
            height: 33px;
            width: 2px;
            background-color: #333;
        }
        &:before {
            transform: rotate(45deg);
        }
        &:after {
            transform: rotate(-45deg);
        }
      }
    }
    .opened-nav {
      opacity: 1;
      transform: scale(1) translateX(0);
      pointer-events: auto;
    }
}

In case you’re having trouble with keeping the 3rd level navigation opened then use this:

.header-nav {
    .header-nav-folder-item {
        position: relative;
        &:hover {
            .header-nav-folder-subitem {
                display: block;
            }    
        } 
    }

    .header-nav-folder-subitem {
        position: absolute;
        top: -1px;
        left: 100%;
        background: #000;
        color: #fff;
    }
}

.header-nav-folder-subitem {
    display: none;
    min-width: 200px;
    a {
        padding: 10px 0 8px!important;
        border-top: 2px solid black;
        line-height: 1.5rem;
        &:first-child {
            border: none;
        }
    }
}
body:not(.header--menu-open) .header-nav-folder-item--active .header-nav-folder-item-content {
    background: none;
}
@media (max-width: 1024px) {
    .header-nav-folder-subitem {
      background: @gray!important;
      display: flex!important;
      justify-content: center;
      flex-direction: column;
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      margin: 0 auto;
      pointer-events: none;
      transform: scale(2);
      opacity: 0;
      transition: transform .6s, opacity .3s;
      z-index: 10000;
      a {
        border: 0;
        &.active {
          text-decoration: underline;
        }
      }
      .close {
        font-size: 30px;
        position: absolute;
        right: 6vw;
        top: 10px;
        cursor: pointer;
        min-width: 32px;
        min-height: 32px;
        &:before, &:after {
            position: absolute;
            left: 15px;
            content: ' ';
            height: 33px;
            width: 2px;
            background-color: #333;
        }
        &:before {
            transform: rotate(45deg);
        }
        &:after {
            transform: rotate(-45deg);
        }
      }
    }
    .opened-nav {
      opacity: 1;
      transform: scale(1) translateX(0);
      pointer-events: auto;
    }
}

For further explanations watch the video below:

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.