Instantly share code, notes, and snippets.

Embed
What would you like to do?
Multi-tiered Dynamic JS Menu

Multi-tiered Dynamic JS Menu

This was made because I disliked the amount of scrolling needed when viewing menu items. The goal was to make an easy and efficient way for the user to view simple data without excessive scrolling.

  • Solution uses only vanilla JavaScript

See a live demo.

This was exported by Jeffrey on CodePen.

License.

// UI CONTROLLER
const UIController = (function() {
const menuHeaders = document.querySelectorAll('.menu-header');
const menuNavItems = document.querySelectorAll('.menu-nav-item');
const menuTables = document.querySelectorAll('.table-wrapper');
function resetNavItems() {
menuNavItems.forEach(function(item) {
item.classList.remove('active');
item.classList.add('hidden');
});
}
function hideTables() {
menuTables.forEach(function(item) {
item.classList.add('hidden');
});
}
return {
changeMenu: function() {
menuHeaders.forEach(function(check) {
check.addEventListener('click', menuClicked);
});
function menuClicked(event) {
let clicked;
clicked = Array.from(menuHeaders).indexOf(event.target);
menuHeaders.forEach(function(item) {
item.classList.remove('active');
menuHeaders[clicked].classList.add('active');
if (clicked === 0) {
resetNavItems();
hideTables();
menuTables[clicked].classList.remove('hidden');
menuNavItems[clicked].classList.add('active');
menuNavItems[clicked].classList.remove('hidden');
menuNavItems.item(1).classList.remove('hidden');
menuNavItems.item(2).classList.remove('hidden');
menuNavItems.item(3).classList.remove('hidden');
} else {
resetNavItems();
hideTables();
menuNavItems[clicked+3].classList.add('active');
menuNavItems[clicked+3].classList.remove('hidden');
menuTables[clicked+3].classList.remove('hidden');
menuNavItems.item(5).classList.remove('hidden');
menuNavItems.item(6).classList.remove('hidden');
menuNavItems.item(7).classList.remove('hidden');
menuNavItems.item(8).classList.remove('hidden');
}
});
}
},
changeSubMenu: function() {
menuNavItems.forEach(function(check) {
check.addEventListener('click', menuNavClicked);
});
function menuNavClicked(event) {
let clicked; //track node index of clicked btn
clicked = Array.from(menuNavItems).indexOf(event.target);
//remove 'active' class from all nav items
menuNavItems.forEach(function(item) {
item.classList.remove('active');
});
//readd 'active' class to clicked nav item
menuNavItems[clicked].classList.add('active');
hideTables();
menuTables[clicked].classList.remove('hidden');
}
}
};
})();
// GLOBAL CONTROLLER
const controller = (function(UICtrl) {
const setupEventListeners = function() {
UICtrl.changeMenu();
UICtrl.changeSubMenu();
};
return {
init: function() {
console.log('App has started.');
setupEventListeners();
}
};
})(UIController);
controller.init();
<section class="two">
<div class="inner">
<a class="active menu-header-0 menu-header">Food</a>
<a class="menu-header-1 menu-header">Drink</a>
<div class="menu-nav">
<a class="menu-nav-item active">Little Bites</a>
<a class="menu-nav-item">Flatbreads</a>
<a class="menu-nav-item">Sandwiches</a>
<a class="menu-nav-item">Salads</a>
<a class="menu-nav-item hidden">Sparkling</a>
<a class="menu-nav-item hidden">Reds</a>
<a class="menu-nav-item hidden">Whites</a>
<a class="menu-nav-item hidden">Draft Beer</a>
<a class="menu-nav-item hidden">Bottle/Can Beer</a>
<a class="menu-nav-item hidden">Beverages</a>
</div>
<div class="menu">
<div class="table-wrapper">
<h5 class="h-alt">Little Bites</h5>
<table>
<tbody>
<tr>
<td>Pretzel Bites with warm Spinach &amp; Fresh Jalapeno Dip</td>
<td>7</td>
</tr>
<tr>
<td>Pretzel Bites with House-made Mustard</td>
<td>6</td>
</tr>
<tr>
<td>Charcuterie Board with Cured Meats, Cheeses &amp; more</td>
<td>12</td>
</tr>
<tr>
<td>Avocado Hummus served with Warm Bread &amp; Veggies</td>
<td>9</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<h5 class="h-alt">Flatbreads</h5>
<table>
<tbody>
<tr>
<td>Proscuitto, Fig Butter, Gorgonzola &amp; Arugula Flatbread</td>
<td>10</td>
</tr>
<tr>
<td>Spanish Chorizo Flatbread</td>
<td>10</td>
</tr>
<tr>
<td>Triple Cheese Flatbread</td>
<td>9</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">Add a Salad (Mixed Greens/Arugula) to any Flatbread for 3.50</td>
</tr>
</tfoot>
</table>
</div>
<div class="table-wrapper hidden">
<h5 class="h-alt">Sandwiches</h5>
<table>
<tbody>
<tr>
<td>Italian Sub with Salami, Sopresetta, Provolone, Greens, Pepperoncini &amp; Italian Dressing</td>
<td>9</td>
</tr>
<tr>
<td>Turkey Pesto Melt with Sundried Tomatoes</td>
<td>9</td>
</tr>
<tr>
<td>Grilled Cheese (Good ole American or Cheddar)</td>
<td>8</td>
</tr>
</tbody>
<tfoot>
<td colspan="1">
Sandwiches served with Gourmet Chips. Add avocado for 2.50 or salad for 3.50
</td>
</tfoot>
</table>
</div>
<div class="table-wrapper hidden">
<h5 class="h-alt">Salads</h5>
<table>
<tbody>
<tr>
<td>Arugula, Fennel &amp; Pear Salad with Avocado, Walnuts &amp; Lemon Basil Dressing</td>
<td>10</td>
</tr>
<tr>
<td>Burrata Salad with Mixed Dark Greens, Olive Oil, Balasmic</td>
<td>12</td>
</tr>
</tbody>
<tfoot>
<td colspan="1">
Salads Gluten free and Vegan
</td>
</tfoot>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Sparkling</h5>
</th>
<th>Glass</th>
<th>Bottle</th>
</tr>
</thead>
<tbody>
<tr>
<td>Opera Prima Brut &nbsp;</td>
<td>7</td>
<td>21</td>
</tr>
<tr>
<td>Chandon Split Brut &nbsp;</td>
<td>8</td>
<td>&ndash;</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Reds</h5>
</th>
<th>Glass</th>
<th>Bottle</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bee's Box Pinot Noir
<br>
<span>(2016) Geyserville, CA</span>
</td>
<td>12.50</td>
<td>42</td>
</tr>
<tr>
<td>Erath Pinot Noir
<br>
<span>(2015) Willamette Valley, OR</span>
</td>
<td>10</td>
<td>35</td>
</tr>
<tr>
<td>Esser Cabernet Sauvignon
<br>
<span>(2014) Monterey, CA</span>
</td>
<td>11.50</td>
<td>39</td>
</tr>
<tr>
<td>Line 39 Excursion Red Blend
<br>
<span>(2016) Napa Valley, Lodi, CA</span>
</td>
<td>7</td>
<td>24</td>
</tr>
<tr>
<td>Newton Cabernet Sauvignon
<br>
<span>(2015) Napa Valley, CA</span>
</td>
<td>13</td>
<td>42</td>
</tr>
<tr>
<td>Shannon Ridge Wrangler Red
<br>
<span>(2015) Lake County, CA</span>
</td>
<td>10</td>
<td>35</td>
</tr>
<tr>
<td>Barnard Griffin Rose' Sangiovese
<br>
<span>(2016) Richland, WA</span>
</td>
<td>12</td>
<td>40</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Whites/Rose</h5>
</th>
<th>Glass</th>
<th>Bottle</th>
</tr>
</thead>
<tbody>
<tr>
<td>Smoke Tree Chardonnay
<br>
<span>(2015) Sonoma, CA</span>
</td>
<td>8</td>
<td>26</td>
</tr>
<tr>
<td>Josh Cellars Chardonnay
<br>
<span>(2016) Napa Valley, CA</span>
</td>
<td>8.50</td>
<td>28</td>
</tr>
<tr>
<td>Bianchi Chardonnay
<br>
<span>(2016) Paso Robles, CA</span>
</td>
<td>14.50</td>
<td>49</td>
</tr>
<tr>
<td>Esser Sauvignon Blanc
<br>
<span>(2014) Monterey, CA</span>
</td>
<td>10</td>
<td>33</td>
</tr>
<tr>
<td>Dry Creek Sauvignon Blanc
<br>
<span>(2017) Sonoma, CA</span>
</td>
<td>11</td>
<td>39</td>
</tr>
<tr>
<td>Caposaldo Pinot Grigio
<br>
<span>(2016) Geyserville, CA</span>
</td>
<td>9</td>
<td>30</td>
</tr>
<tr>
<td>Barnard Griffin Rose' Sangiovese
<br>
<span>(2016) Richland, WA</span>
</td>
<td>12</td>
<td>40</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Beer-Craft &amp; Draft</h5>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Guinness</td>
<td>8</td>
</tr>
<tr>
<td>4 Sons Sour Cherry K</td>
<td>7</td>
</tr>
<tr>
<td>4 Sons Surf City Pale Ale</td>
<td>7</td>
</tr>
<tr>
<td>Barley Forge Patsy</td>
<td>7</td>
</tr>
<tr>
<td>Elysian Space Dust</td>
<td>7</td>
</tr>
<tr>
<td>Firestone Luponic</td>
<td>7</td>
</tr>
<tr>
<td>Golden Road Mango Cart</td>
<td>7</td>
</tr>
<tr>
<td>Golden State Mighty Dry Cider</td>
<td>7</td>
</tr>
<tr>
<td>Kona Long Board</td>
<td>7</td>
</tr>
<tr>
<td>Stella</td>
<td>7</td>
</tr>
<tr>
<td>Coors Light</td>
<td>5</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Beer-Bottle/Can</h5>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Budweiser</td>
<td>4.50</td>
</tr>
<tr>
<td>Bud Light</td>
<td>4.50</td>
</tr>
<tr>
<td>Coors Light</td>
<td>4.50</td>
</tr>
<tr>
<td>Michelob Ultra Light</td>
<td>4.50</td>
</tr>
<tr>
<td>M Pabst Blue Ribbon (Can)</td>
<td>4</td>
</tr>
<tr>
<td>Heineken</td>
<td>5.50</td>
</tr>
<tr>
<td>Corona</td>
<td>5.50</td>
</tr>
<tr>
<td>Modelo</td>
<td>5.50</td>
</tr>
<tr>
<td>Sam Adams</td>
<td>5.50</td>
</tr>
<tr>
<td>Angry Orchard Rose</td>
<td>5.50</td>
</tr>
</tbody>
</table>
</div>
<div class="table-wrapper hidden">
<table>
<thead>
<tr>
<th>
<h5 class="h-alt">Beverages</h5>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ice Tea</td>
<td>3</td>
</tr>
<tr>
<td>Lemonade</td>
<td>3</td>
</tr>
<tr>
<td>Coke</td>
<td>3</td>
</tr>
<tr>
<td>Diet Coke</td>
<td>3</td>
</tr>
<tr>
<td>Sprite</td>
<td>3</td>
</tr>
<tr>
<td>Benedetto Sparkling Water</td>
<td>4</td>
</tr>
<tr>
<td>Bottled Water</td>
<td>1</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- menu -->
</div>
</section>
// variables
//font & fallback
$font-serif: 'IBM Plex Serif', 'Laila', Georgia, serif
$font-sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Roboto', 'Segoe UI', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans','Helvetica Neue', Arial, sans-serif
//colors
$gold: #d2ac67
$yellow: #DFCF56
$dark: #1A1E1A
$green: #006741
$green-light: #2D8D55
$orange: #B05A2E
$brown: #302A23
$bkg-alt: #f9f4df
$darkish: #707875
$gray: #CACFCC
$bkg: #F3F4F1
$bkg2: #fdfbfb
//nav
$nav-font-color: $darkish
// reset
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, section, summary, time, mark, audio, video, .row
margin: 0
padding: 0
border: 0
vertical-align: baseline
article, footer, header, nav, section
display: block
// box & basic
*, *:before, *:after
box-sizing: border-box
-moz-box-sizing: border-box
-webkit-box-sizing: border-box
html
width: 100%
height: 100%
body
background-color: $bkg
margin: 0 auto
padding-top: 1rem
letter-spacing: 0.005rem
font-size: 1rem
font-feature-settings: kern
-ms-font-feature-settings: kern
-o-font-feature-settings: kern
font-kerning: normal
text-rendering: optimizeLegibility
-webkit-font-smoothing: antialiased
-osx-font-smoothing: grayscale
-webkit-text-size-adjust: 100%
scroll-behavior: smooth
html, body
min-width: 320px
font-size: 15px
body, input, select, textarea
font-family: $font-sans
color: $dark
font-weight: 400
line-height: 1.5
a, a:visited
-moz-transition: color 0.2s ease-in-out, border-style 0.2s ease-in-out
-webkit-transition: color 0.2s ease-in-out, border-color 0.2s ease-in-out
-ms-transition: color 0.2s ease-in-out, border-style 0.2s ease-in-out
transition: color 0.2s ease-in-out, border-style 0.2s ease-in-out
border-bottom: 1px dotted $darkish
color: transparentize($dark, 0.3)
text-decoration: none
&:hover
color: transparentize($dark, 0.15)
text-decoration: none
border-bottom-style: solid
strong, b
color: $dark
font-weight: 600
em
font-style: italic
p
margin-top: 1rem
margin-bottom: 1rem
color: transparentize($dark, 0.3)
max-width: 40rem
&:first-of-type
margin-top: 0
h1, h2, h3, h4, h5, h6
color: transparentize($dark, 0.2)
font-family: $font-serif
font-weight: 700
letter-spacing: 0.055rem
margin-left: auto
margin-right: auto
max-width: 40rem
h1
font-size: 2rem
margin-top: 2rem
margin-bottom: 2rem
letter-spacing: 0.01rem
h2
font-size: 1.4rem
//margin-left: 0
margin-top: 1.35rem
margin-bottom: 1.35rem
h3
font-size: 1.15rem
margin-top: 1rem
margin-bottom: 1rem
&.h-alt
font-size: 1.1rem
font-family: $font-sans
color: $green
font-weight: 500
letter-spacing: 0.5px
margin-bottom: 1.5rem
text-align: center
h4, h5, h6
font-size: 1.05rem
//letter-spacing: 0.025rem
margin-bottom: 1rem
font-weight: 500
font-family: $font-serif
&.h-alt
font-size: 1rem
font-family: $font-sans
color: $green
font-weight: 500
letter-spacing: 0.025rem
margin-bottom: 1.5rem
text-align: center
h4
font-size: 1.1rem
&.h-alt
display: inline-block
font-size: 1.15rem
color: transparentize($brown, 0.2)
font-weight: 600
text-align: center
h6
font-size: 0.9rem
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a
color: inherit
border-bottom: 0
//reusables
.inner
max-width: 69rem
margin: 0 auto
//margin: 1rem auto
background: transparent
padding: 1rem 1.5rem
@media screen and (min-width: 736px)
.h-alt
margin-bottom: 3rem
.inner
padding: 2.5rem
//table
.table-wrapper
overflow-x: auto
-webkit-overflow-scrolling: touch
table
margin: 0 0 1.5rem 0
width: 100%
display: block
border-collapse: separate
border-spacing: 0
& tbody tr
border: solid 1px transparentize($dark, 0.75)
border-left: 0
border-right: 0
&:nth-child(2n + 1)
background-color: transparentize($dark, 0.95)
& td
padding: 1rem
& th
color: transparentize($dark, 0.2)
font-size: 0.95rem
font-weight: 600
text-align: left
padding: 0 1rem
& thead
border-bottom: solid 2px transparentize($dark, 0.75)
& tfoot
border-top: solid 2px transparentize($dark, 0.75)
&.alt
border-collapse: separate
border-spacing: 0
& tbody tr
border: solid 1px transparentize($dark, 0.75)
& td
border-left: solid 1px transparentize($dark, 0.75)
border-bottom: solid 1px transparentize($dark, 0.75)
&:last-child
border-right: solid 1px transparentize($dark, 0.75)
&:first-child td
border-top: solid 1px transparentize($dark, 0.75)
&:last-child td
border-bottom: solid 1px transparentize($dark, 0.75)
& thead
border-bottom: 0
& tfoot
border-top: 0
//page
.two
padding: 1rem 0
margin: 2rem auto
max-width: 80rem
background: lighten($gray, 10%)
text-align: center
position: relative
//menu
.events
text-align: left
.menu
width: 100%
text-align: center
margin: 1.5rem auto 0 auto
display: flex
flex-direction: column
justify-content: center
&:last-of-type
margin-top: 0
> .table-wrapper
max-width: 35rem
text-align: left
margin-right: auto
margin-left: auto
overflow: hidden
max-height: 50rem
transition: max-height 0.5s ease-in-out
tfoot
td
padding-top: 0.5rem
font-size: 0.9rem
color: transparentize($dark, 0.3)
&.hidden
max-height: 0
transition: max-height 0.3s ease-in-out
&.events
.table-wrapper
max-width: unset
td
max-width: 40rem
&:first-of-type
font-weight: 500
//menu nav
.menu-nav
margin-bottom: 1.5rem
.menu-header-0
margin-right: 1.5rem
.menu-header-1
margin-left: 1.5rem
.menu-header
border-bottom: 2px solid transparent
cursor: pointer
transition: all 0.2s ease-in-out
display: inline-block
font-size: 1.15rem
color: transparentize($brown, 0.2)
font-weight: 600
text-align: center
margin-bottom: 1rem
&.active, &:hover
border-bottom-color: transparentize($brown, 0.2)
.menu-nav-item
display: inline-block
margin-right: 0.75rem
margin-bottom: 0.5rem
border-bottom: 1.1px solid transparent
font-size: 1.05rem
cursor: pointer
transition: all 0.2s ease-in-out
&.hidden
display: none
&:last-of-type
margin-right: 0
&.active, &:hover
color: $dark
border-bottom-color: transparentize($dark, 0.1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment