Skip to content

3.1 - Templating

🎯 Objectives

  • Separate presentation code from logic code using a templating engine.
  • Control the data that is displayed using conditionals and loops inside the template files.
  • Nest templates within one another to reuse presentation code.

🔨 Setup

  1. Navigate to the template repository for this exercise and follow these directions to fork it.
  2. Assuming Docker is started, in VS Code, hit CMD/CTRL + SHIFT + P, search + run dev container: open folder in container, and select the downloaded folder.
  3. In the terminal of VS Code, hit the + icon to open a new terminal instance. Run ls to make sure you’re in the root directory of the exercise and that you see package.json.
  4. Run npm install to install all the dependencies.
  5. Run npm run server inside a JavaScript debug terminal to start the server.

🔍 Context

We’ve done the M and C of MVC, which means we only have the V left! The View is the part of the application where the presentation code lives - the pretty part! 🤩 Views are files that contain the HTML/CSS/JS that the user will see and interact with. The main goal of templating is to separate logic from presentation.

The template will have placeholders of where data should be populated. Instead of having dedicated pages for Home and About:

home.html
<body>
<h1>Home</h1>
</body>
about.html
<body>
<h1>About</h1>
</body>

We can now make one template and pass in the value dynamically:

page.hbs
<body>
<h1>{{heading}}</h1>
</body>

To populate the heading variable, we’re going to make use of a templating engine. Specifically, we’re going to be using the Handlebars templating engine.

🚦 Let’s Go

Part 1: Simple Template

  1. Inside src/page.hbs, write a simple HTML page. Simply type ! and use the autocomplete to generate the boilerplate HTML for you. Even though this file is not an .html file, we can still write regular HTML syntax inside. The <body> of the page should have:

    1. An <h1> tag that contains a Handlebars expression tag which will output the value of {{ heading }}. See the context section above if you’re not sure what that looks like.
    2. An <img> that contains a Handlebars expression tag which will output the value of image for its src attribute: <img src="http://localhost:3000/{{ image }}">. As you can see, you can use the Handlebars expression tag anywhere inside of HTML syntax.
  2. Make an images/ folder in the root of the project, i.e. not inside src/. This is where we’ll store all the images we want to display on our website.

  3. Download an image from the web to display on our website.

    • It can be a Pokemon related image, or if you decided to use your own resource like books or movies, find an image related to that.
    • Make sure to download it into the images/ folder.
  4. Inside src/controller.ts, let’s replace the JSON response inside getHome with an HTML response.

    1. The status code will remain the same (200).

    2. The Content-Type header should be changed from application/json to text/html to tell the client that we’ll now be sending back HTML data.

    3. Finally, instead of outputing JSON in res.end(), we now need to render the template to output the HTML we need.

      controller.ts
      res.end(
      await renderTemplate("src/page.hbs", {
      heading: "Homepage!",
      image: "images/image-from-previous-step.png",
      })
      );
      • We give it the name of the template file, src/page.hbs
      • We also give it the data that the template will need. The heading and image properties of this object map to the same ones we defined inside of page.hbs.
  5. Start the server by running npm run server.

  6. In a browser, navigate to http://localhost:3000 and you should see a web page with the “Homepage!” and your image.

  7. Right-click and select “view page source” to see the HTML that was sent to the browser. You should see the HTML that you wrote in page.hbs, but with the {{ heading }} and {{ image }} placeholders replaced with the values you passed in.

Part 2: List View

  1. Inside page.hbs, add a <ul> tag that contains a Handlebars each tag. This will loop through all the objects in the database array from model.ts, much like a forEach loop, and will put each value inside of a <li> tag:

    page.hbs
    <ul>
    {{#each pokemon}}
    <li>{{name}} is a {{type}} type Pokemon.</li>
    {{/each}}
    </ul>
  2. In the getAllPokemon controller method, make the same changes you made to the getHome method from the previous section. When calling res.end(), you’ll pass in

    • heading: “All Pokemon” (or a title of your choice)
    • image: The same image as the homepage, or a new image if you’d like to display a different one for this page.
    • pokemon: The database imported from model.ts.
    controller.ts
    res.end(
    await renderTemplate("src/page.hbs", {
    heading: "All Pokemon!",
    image: "images/your-image.png",
    pokemon: pokemon,
    })
    );
  3. In a browser, navigate to http://localhost:3000/pokemon and you should see a web page with all the Pokemon (or your entities) listed.

Part 3: Partials

A more intermediate use of templates is splitting up “common” parts of the UI into their own, smaller, templates. The goal is to define all the UI components once and try to minimize any repeated code.

  1. Create views/partials/Header.hbs and views/partials/Footer.hbs. Cut everything in page.hbs from the top of the file up to and including the opening of the <body> tag, and paste it into Header.hbs.

    Header.hbs
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>Pokedex</title>
    </head>
    <body>
  2. Cut everything from the bottom of page.hbs up to and including the close </body> tag and paste it into Footer.hbs. To make it more interesting, we’ll add a footer with a copyright message:

    Footer.hbs
    <footer>&copy; Copyright 2024 Your Name</footer>
    </body>
    </html>
  3. Inside page.hbs we can use Handlebar’s partial tag to include the content from Header.hbs and Footer.hbs:

    page.hbs
    {{> Header }}
    <!-- h1, image, and list -->
    {{> Footer }}
  4. To get this to work, we need to register the partial template with Handlebars. I’ve provided a function for this called registerPartialTemplate() that takes the name of the partial and the path to where the template file lives:

    server.ts
    const hostname = "127.0.0.1";
    const port = 3000;
    registerPartialTemplate("Header", "src/views/partials/Header.hbs");
    registerPartialTemplate("Footer", "src/views/partials/Footer.hbs");
  5. In your browser, navigate to all the routes to make sure they all still work.

Bonus: Single View

If you want an extra challenge, see if you can implement the getOne controller function to serve an HTML page which displays one Pokemon and their image depending on the ID from the request URL.

  1. Download an image per Pokemon (or resource of your choice) into the images folder.
  2. Add an image attribute to each Pokemon object inside model.ts. The value of this attribute should be the path to the image (ex. images/pikachu.png).
  3. In the controller method, use the attributes from the requested Pokemon to set:
    1. heading as the name of the Pokemon
    2. image as the Pokemon’s image

If done correctly, you should see a different Pokemon when you navigate to /pokemon/1, /pokemon/2, etc.

📥 Submission

Take a screenshot of your list page and submit it on Moodle.


If there’s one thing to remember from this exercise, it’s that templates/views exist to separate presentation code from logic code! Any developer can make the application work; What separates the novice from the experts is the organization and cleanliness of your code. 🧼

Comic