DEV Community

loading...

Why HTML template engines are the nuts 🥜

kaxmoglan profile image Maximilian ・9 min read
const tldr = Allows you to write DRY code and incorporate functions into HTML, which is a game changer for maintainability and debugging.
Enter fullscreen mode Exit fullscreen mode

In this article I will show you how I wrote dynamic markup for a mid-sized mobile menu using Pug (formerly Jade). You can see the menu in action here!

The main goals for the mobile navigation are as follows:

  1. Display all 18 pages in a user friendly way, by splitting them up into 4 categories: Design, Marketing, Events and More
  2. When the user opens the menu, it opens on the category they’re currently in, with the page they’re on highlighted
  3. As the user browses other categories, the page they’re currently on remains highlighted

With our goals in mind, before we get into the code, have a think about how you would go about building something like this.

Pug and HTML templating — an explanation

If you’re not sure what an HTML templating language is, this bit is for you.

Think of an HTML Templating language as a ‘pre-processor’ to HTML. This is to say, you write code in the templating language of your choice (in our case, Pug), and when you’re done, you compile it (or output it) to good old fashioned HTML files.

Now, why the hell would you want to do this?

const tldr = because we can write DRY code, cleaner code, less code, dynamic code and thus, more maintainable code. And its fun.
Enter fullscreen mode Exit fullscreen mode

Well, now would be a good time to remember that HTML is just markup. It doesn’t really do anything. HTML Template languages allow us to write dynamic code that can be broken up into smaller chunks and files. Aside from the added benefits of being able to write functions and conditional statements in HTML (there will be examples later), the main benefit I’ve found is being able to write DRY code. (Don’t Repeat Yourself).

For maintainability, DRY code is essential to any project, even for the smallest static website. Let me explain:

Say you’re building a small portfolio site with 5 pages and, of course, you have a navigation menu. In plain HTML, that navigation menu exists on all 5 pages. That’s 5 times you have to write the menu. Want to update it? You have to update it in 5 places. Made a spelling mistake? You have to fix the mistake in 5 places. It’s also prone to mistakes that appear in random places as you have the ability to make mistakes in multiple locations, so error checking can be difficult.

Using an HTML templating language allows you to write less HTML, but more excitingly, fun HTML! The syntax is also, subjectively, simpler and clearer.

How to run a templating language

In order to use a templating language, we need a way of compiling our files to HTML. For this project, I used a Node.js environment and Gulp. My favourite templating language is Pug (formerly Jade), but you should experiment with others and see what you like. To my knowledge, they’re all pretty much capable of the same things. Other popular languages in the Node.js environment are EJS and Handlebars, so I would start there if you’re at all unfamiliar.

Let’s Build Our Menu

My plan and the pseudo code for this menu is as follows:

mobileMenu(mainMenu, subMenu)
  div class = “mobileMenuContainer”
    div class = “mobileMenuCategories”
      p Design class=(mainMenu == “design” ? “active” : “”)
     p Marketing class=(mainMenu == “marketing” ? “active” : “”)
     p Events class=(mainMenu == “events” ? “active” : “”)
     p More class=(mainMenu == “more” ? “active” : “”)

    div mobileMenuSubMenu
      div DesignMenu
        [Design Pages] class=(subMenu == *page* ? “Active” : “”) 
      div MarketingMenu
        [Marketing Pages] class=(subMenu == *page* ? “Active” : “”)
      div EventsMenu
        [Events Pages] class=(subMenu == *page* ? “Active” : “”)
      div MoreMenu
        [More Pages] class=(subMenu == *page* ? “Active” : “”)
Enter fullscreen mode Exit fullscreen mode

The idea here is to make the whole mobile menu a re-usable function that takes in 2 arguments - mainMenu and subMenu. The mainMenu argument will serve as a way of using standard CSS class names to dynamically select which category is currently active, and the subMenu argument will serve as a way of dynamically selecting which page is currently active. (e.g. on our design category list item: “does mainMenu == "design"? If so, add the class name active, otherwise, don’t.")

This means that on the “Website Design” page, we would simply include our mobile menu in the following way:

+mobileMenu(design, website design)
Enter fullscreen mode Exit fullscreen mode

That’s it! That’s the markup for our whole menu, dynamically generated on our Website Design page with custom class names specific to that page.

It follows then, that if we were on the Freelance page, which is under the ‘marketing’ section of the site, we would use the following code on our page:

+mobileMenu(marketing, freelance)
Enter fullscreen mode Exit fullscreen mode

Cool huh?

Ok, now on to some actual code (although with Pug, we’re not far off):

In Pug, reusable blocks of code that have the option to take in arguments (like a function) are called ‘Mixins’.

There are three things to quickly note if you’re unfamiliar with pug:

  1. To define a <div> with a classname, we simply write .this-is-the-class-name which will output the HTML <div class=“this-is-the-class-name”></div>
  2. To give an HTML element an id, we do the same thing as above, but with #. E.g. #this-is-the-id will output <div id=“this-is-the-id”></div>.
  3. Pug relies on indentation to place elements inside other elements. e.g.
.mobile-menu__menu
    nav.mobilemenu__main-nav
Enter fullscreen mode Exit fullscreen mode

Will output:

<div class=“mobile-menu__menu”>
    <nav class=“mobilemenu__main-nav”>
    </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

I hope you’re starting to see how much cleaner Pug is to read and write!

In our mobile menu file, let’s define our Mixin:

mixin mobileMenu(main, sub)
Enter fullscreen mode Exit fullscreen mode

Inside our Mixin, we’ll start making our code block with a single div which will contain everything, starting with the main navigation element which will house an unordered list and some list items.

mixin mobileMenu(main, sub)
  .mobile-menu__menu
    nav.mobile-menu__main-nav
      ul
        li.main-nav__list-item#main-menu__design
        li.main-nav__list-item#main-menu__marketing
        li.main-nav__list-item#main-menu__events
        li.main-nav__list-item#main-menu__more
Enter fullscreen mode Exit fullscreen mode

Just to make sure you’re on track with what the above code means, this should output the following HTML. I won’t reference the HTML again, as it should be self explanatory from this point on. Bear in mind, we’re not making use of our Mixin parameters yet.

<div class=“mobile-menu__menu”>
  <nav class=“mobile-menu__main-nav>
    <ul>
      <li class=“main-nav__list-item id=“main-menu__design>
      </li>
      <li class=“main-nav__list-item id=“main-menu__marketing>
      </li>
      <li class=“main-nav__list-item id=“main-menu__events>
      </li>
      <li class=“main-nav__list-item id=“main-menu__more>
      </li>
    </ul>
  </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, we’ll add the contents of each list item and (finally) make use of our first parameter:

mixin mobileMenu(main, sub)
  .mobile-menu__menu
    nav.mobile-menu__main-nav
      ul
        li.main-nav__list-item#main-menu__design(class = main == design ? active : “”)
        p Design
        li.main-nav__list-item#main-menu__marketing(class = main == marketing ? active : “”)
        p Marketing
        li.main-nav__list-item#main-menu__events(class = main == events ? active : “”)
        p Events
        li.main-nav__list-item#mani-menu__more(class = main == more ? active : “”)
        p More
Enter fullscreen mode Exit fullscreen mode

For each list item, we’re checking the value of main whenever our mobileMenu Mixin is called, and applying the active class name if it matches using the shorthand Javascript if statement:

main == more ? active : “”
Enter fullscreen mode Exit fullscreen mode

Which is equivalent to:

if (main == more) {
  active"
} else {
  “”
}
Enter fullscreen mode Exit fullscreen mode

The contents of each list item just includes a <p> tag with the title of each category name.

Now we’ll move onto our Sub Menu

It’s the same concept as the main menu above, so the code should start to feel familiar to you. We are now also making use of our second Mixin parameter.

nav.mobile-menu__secondary-nav
  ul(id=events-list class = main == events ? secondary-nav__list : secondary-nav__list remove fade-out)
    li
      a(href=./events class = sub == "events" ? "active" : "") Events
    li
      a(href=./event-management class = sub == "event management" ? "active" : "") Event Management
    li
      a(href=./markets class = sub == "markets" ? "active" : "") Markets
Enter fullscreen mode Exit fullscreen mode

The class names on the <ul> elements may not make sense at the moment, but the idea is to apply the class name remove (which will apply display: none; using CSS) and fade-out which we’ll use to apply a CSS animation when the sub-menu is changed.

Each <li> element contains an <a> tag linked to each page and includes a conditional CSS class name, exactly like we did for the main menu. E.g. For the Markets page, we check if sub == "markets"? If so, add the class name active, otherwise, don't!

Rinse and Repeat

Now we just repeat the code above for each sub menu and put it all together for our finished Mixin.

mixin mobileMenu(main, sub)
  .mobile-menu__menu

    nav.mobile-menu__main-nav
      ul
        li.main-nav__list-item#main-menu__design(class = main == "design" ? "active" : "")
          p Design
        li.main-nav__list-item#main-menu__marketing(class = main == "marketing" ? "active" : "")
          p Marketing
        li.main-nav__list-item#main-menu__events(class = main == "events" ? "active" : "")
          p Events
        li.main-nav__list-item#main-menu__more(class = main == "more" ? "active" : "") 
          p More

    nav.mobile-menu__secondary-nav

      ul(id="events-list" class = main == "events" ? "secondary-nav__list" : "secondary-nav__list remove fade-out")
        li
          a(href="./events" class = sub == "events" ? "active" : "") Events
        li 
          a(href="./event-management" class = sub == "event management" ? "active" : "") Event Management
        li 
          a(href="./markets" class = sub == "markets" ? "active" : "") Markets


      ul(id="design-list", class = main == "design" ? "secondary-nav__list" : "secondary-nav__list remove fade-out" )
        li 
          a(href="./graphic-design" class = sub == "design" ? "active" : "") Design
        li
          a(href="./website-design" class = sub == "website design" ? "active" : "") Website Design
        li 
          a(href="./design-for-print" class = sub == "design for print" ? "active" : "") Design for Print
        li 
          a(href="./logo-design" class = sub == "logo design" ? "active" : "") Logo Design


      ul(id="marketing-list", class = main == "marketing" ? "secondary-nav__list" : "secondary-nav__list remove fade-out" )
        li 
          a(href="./marketing" class = sub == "marketing" ? "active" : "") Marketing
        li
          a(href="./workshops" class = sub == "workshops" ? "active" : "") Workshops
        li 
          a(href="./freelance-marketing" class = sub == "freelance" ? "active" : "") Freelance
        li 
          a(href="./social-media" class = sub == "social media" ? "active" : "") Social Media
        li 
          a(href="./copywriting-services" class = sub == "copywriting" ? "active" : "") Copywriting
        li 
          a(href="./consultancy" class = sub == "consultancy" ? "active" : "") Consultancy


      ul(id="more-list", class = main == "more" ? "secondary-nav__list" : "secondary-nav__list remove fade-out" )
        li 
          a(href="./pricing" class = sub == "pricing" ? "active" : "") Pricing
        li 
          a(href="./privacy" class = sub == "privacy" ? "active" : "") Privacy
        li 
          a(href="./contact-us" class = sub == "contact" ? "active" : "") Contact
        li 
          a(href="./sitemap" class = sub == "sitemap" ? "active" : "") Site Map
        li 
          a(href="./testimonials" class = sub == "testimonials" ? "active" : "") Testimonials

Enter fullscreen mode Exit fullscreen mode

Include the Mixins

On each page of our site, we can now call our Mixin with the two parameters we’ve defined, and the HTML markup will be included in the page with the appropriate class names defined by the arguments we pass it. If we want to edit the menu in any way, we have one place in which to edit it. Although I already covered it in pseudo-code, the actual code to include our markup in our other Pug files is as follows:

On the website design page:

+mobileMenu(design, website design)
Enter fullscreen mode Exit fullscreen mode

On the freelance page:

+mobileMenu(marketing, freelance)
Enter fullscreen mode Exit fullscreen mode

CONCLUSION

That’s the end of our time discussing Pug and HTML Templating. I hope I’ve proved the case for it already, but if I haven’t, I’d like to end on a quick story:

The client I built this mobile menu for (and their entire site) decided after I had designed and built the entire app that they wanted to change their name and logo.

Even though I made a song and dance about what a MONUMENTAL TASK this was, it actually took me about 5 minutes, but only thanks to templating.

Because everything only existed in one place, I only had a couple of things to change! Even the SVG logo was added as an ‘include’ to every file it was needed, so when I changed the SVG file, BAM — it was everywhere! And for the remaining text, VS Code took over and I used the ‘replace all’ feature inside the Find tool to replace their old name with their new name and…. Done!

What about the CSS and Javascript?

The JS and CSS was beyond the scope of this article BUT…. If you use your imagination, I’m sure you can guess what I did with these. I’ll summarise below, but if anyone actually reads this and is interested, let me know and I’ll cover them in a future post.

The Javascript
The JS gets all the elements in the menu and applies event listeners to the category links. When the user clicks a category, it adds and removes the respective class names which are then styled in CSS to make the appropriate sub menu appear and disappear. I also utilise timeouts to allow for animation times.

The CSS
I used Sass to make it all look pretty. Nothing crazy - just some transform: translateX(), opacity, and display: none kinda stuff.

Discussion (0)

Forem Open with the Forem app