Video Course

Build an Interactive JavaScript Food Menu: Lesson 6 of 14

Creating the Menu

Up Next

Creating the Footer

Autoplaying in 7 seconds!

Cancel

Now we will get to the meat of things. Here we will add the menu items to the page. However, we want this to be able to be dynamic. We aren't just going to hardcode sixteen items onto the page. Where's the fun in that?

First we need to add our menu to the page. You know where this is heading. Add this to the app component.

import { div, addId } from '../builders';
import navbar from './navbar';
import hero from './hero';
import menu from './menu';

export default function app(items) {
  const navbarEle = navbar();
  const heroEle = hero();
  const menuEle = menu(items);
  const appEle = addId(div(navbarEle, heroEle, menuEle), 'app-container');

  return appEle;
}

Next let's create our menu module. The menu element consists of a left side and a right side. Each holds two categories of foods. We will account for this in our structure as you will see. We want the structure to look something like this.

<div class="container" id="menu">
    <section class="columns">
        <div class="column is-6">
            <div class="collection">
                <h1 class="title">Appetizers</h1>
                <article class="media menu_item">
                    <div class="media-content">
                        <h3 class="name">Fried Asparagus</h3><span class="price is-pulled-right">$4.50</span>
                        <p class="desc">Fresh asparagus lightly battered and quick-fried. Topped with crumbled queso fresco, chopped cilantro &amp; a drizzle of chimichurri sauce. Served with roasted garlic aioli.</p>
                        <button class="button is-pulled-right add-to-cart">Add to Cart</button>
                    </div>
                </article>
            </div>
            <div class="collection">
                <h1 class="title">Burgers</h1>
                <article class="media menu_item">
                    <div class="media-content">
                        <h3 class="name">Sweet &amp; Smoky Burger</h3><span class="price is-pulled-right">$8.75</span>
                        <p class="desc">Topped with melted pepper Jack cheese, applewood smoked bacon,spiced panko onion rings, fresh leaf lettuce, tomato,mango-infused BBQ sauce &amp; Chili's Signature sauce. Served with a side of mango-infused BBQ sauce.</p>
                        <button class="button is-pulled-right add-to-cart">Add to Cart</button>
                    </div>
                </article>
            </div>
        </div>
        <div class="column is-6">
            <div class="collection">
                <h1 class="title">Soups and Salads</h1>
                <article class="media menu_item">
                    <div class="media-content">
                        <h3 class="name">House Salad</h3><span class="price is-pulled-right">$3.50</span>
                        <p class="desc">Freshly diced tomatoes, red onion, shredded carrots, cucumbers, 3-cheese blend &amp; garlic croutons. Served with your choice of dressing.</p>
                        <button class="button is-pulled-right add-to-cart">Add to Cart</button>
                    </div>
                </article>
            </div>
            <div class="collection">
                <h1 class="title">Desserts</h1>
                <article class="media menu_item">
                    <div class="media-content">
                        <h3 class="name">Triple Berry Crumble Cake</h3><span class="price is-pulled-right">$5.65</span>
                        <p class="desc">Warm blackberries &amp; blueberries baked with rich buttercake topped with streusel and finished with vanilla ice cream, strawberry sauce &amp; cinnamon-sugar.</p>
                        <button class="button is-pulled-right add-to-cart">Add to Cart</button>
                    </div>
                </article>
            </div>
        </div>
    </section>
</div>

Add the following to a file at src/js/components/menu.js.

import { addClass, addId, div, section } from '../builders';
import leftMenu from './leftMenu';
import rightMenu from './rightMenu';

export default function menu(items = []) {
    const leftSide = leftMenu(items);
    const rightSide = rightMenu(items);
    const columns = addClass(section(leftSide, rightSide), 'columns');
    const menuEle = addId(addClass(div(columns), 'container'), 'menu');

    return menuEle;
}

Here were are bringing in some builders (notice we have already made all these!) and also leftMenu and rightMenu modules. Since much of the structure is similar, we can abstract this out to make as much stuff reusable while also making it manageable to read and make changes to. Also notice we are using a new ES2015 feature of having true default values. We set the item parameter's default to an empty array. This is because items will be passed down in an array of objects. Let's see what the leftMenu looks like. Create a file at src/js/components/leftMenu.js and input the following into it.

import { addClass, div } from '../builders';
import { filterByType } from '../helpers';
import menuList from './menuList';

export default function leftMenu(items = []) {
  const appetizers = menuList('Appetizers', filterByType(items, 'appetizer'));
  const burgers = menuList('Burgers', filterByType(items, 'burger'));

  return addClass(div(appetizers, burgers), 'column', 'is-6');
}

Here we are not doing anything except we delegate the creation of each food type to a module called menuList. It takes a type and a list of items. We are filtering the array of items by type to make sure only certain items are passed down to the menuList. Let's create the rightMenu now too. Create a file at src/js/components/rightMenu.js and input the following into it.

import { addClass, div } from '../builders';
import { filterByType } from '../helpers';
import menuList from './menuList';

export default function rightMenu(items = []) {
  const soupSalad = menuList('Soups and Salads', filterByType(items, 'soup_salad'));
  const desserts = menuList('Desserts', filterByType(items, 'dessert'));

  return addClass(div(soupSalad, desserts), 'column', 'is-6');
}

This will look identical to the leftMenu except it creates menuLists for different food types. Next, let's create the helpers file and add that helper. Create a file at src/js/helpers.js and input the following code.

export function filterByType(items, type) {
  return items.filter(item => item.type === type);
}

This simply filters the array by checking if the type of each item is equal to the type we passed in. Now let's create the menuList module that the rightMenu and leftMenu both use. Put the following code in a file at src/js/components/menuList.js.

import { addClass, div, h1, text } from '../builders';
import menuItem from './menuItem';

export default function menuList(headline, items = []) {
  const menuItems = items.map(menuItem);

  const title = addClass(h1(text(headline)), 'title');

  return addClass(div(title, ...menuItems), 'collection');
}

This creates a new array of menuItem elements by mapping over the items array and passing in the menuItem factory function. This is all then added to an element much like we have done with previous elements. We also have a new builder to create so let's create that. Add this to the builders.js file.

export function h1(...children) {
  return createElement('h1', ...children);
}

Now, let's create the menuItem module. Add the following to a file at src/js/components/menuItem.js.

import { addClass, article, button, div, h3, p, span, text } from '../builders';
import { formatPrice } from '../helpers';

export default function menuItem(itemData = {}) {
  const name = addClass(h3(text(itemData.name)), 'name');
  const price = addClass(span(text(`$${formatPrice(itemData.price)}`)), 'price', 'is-pulled-right');
  const description = addClass(p(text(itemData.description)), 'desc');
  const addToCart = addClass(button(text('Add to Cart')), 'button', 'is-pulled-right', 'add-to-cart');

  const mediaContent = addClass(div(name, price, description, addToCart), 'media-content');

  const item = addClass(article(mediaContent), 'media', 'menu_item');
  item.dataset.key = itemData.id;

  return item;
}

This is similar to past modules. It takes in data and uses that data to construct some HTML and then returns it. You may notice we have a new helper and a few new builders. Let's add the builders since those are easy.

export function article(...children) {
  return createElement('article', ...children);
}

export function h3(...children) {
  return createElement('h3', ...children);
}

export function button(...children) {
  return createElement('button', ...children);
}

Next let's add the formatPrice helper to our helpers.js.

export function formatPrice(price) {
  return parseFloat(price).toFixed(2);
}

This is a simple function that gives us a floating point decimal then creates a fixed string with two decimal places.

Things are going great. We have a sweet pipeline for creating menu items. However, we haven't actually provided any items to our code yet. If you view things in the browser, you will notice that still nothing shows up. Luckily you grabbed that food.json file earlier. We are going to use the new fetch API to grab this data and give it to our application. Notice when we added the menu to our application earlier, we added an item parameter to the app function and then passed that parameter to the menu function when we created it. This means everything is already set up on that end. Change the index.js to look like the following code.

import app from './components/app';

fetch('food.json')
  .then(res => res.json())
  .then(resBody => {
    const body = document.querySelector('body');
    body.insertBefore(resBody, body.childNodes[0]);
  });

We call fetch, passing it the path to the file we want to get. Fetch will return a promise. We call then on the promise. The method we pass to then will be called once the fetch has resolved. We want it to simply return the JSON form of the response. The reason we do it this way is because it makes sense to read but also because res.json() returns a promise. Once it resolves, it passes that JSON to our second then callback. We have put our app creation code inside this callback since we don't want to run our application before we get our data. Then we simply pass the JSON to our application. That's it!

View the page in the browser and you should see all the items populating on the page. Congratulations! In the next one, we will quickly create the footer.

Chris Sevilleja

161 posts

Co-founder of Scotch.io. Slapping the keyboard until something good happens.