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
- Navigate to the template repository for this exercise and follow these directions to fork it.
- Assuming Docker is started, in VS Code, hit
CMD/CTRL + SHIFT + P, search + rundev container: open folder in container, and select the downloaded folder. - In the terminal of VS Code, hit the
+icon to open a new terminal instance. Runlsto make sure you’re in the root directory of the exercise and that you seepackage.json. - Run
npm installto install all the dependencies. - Run
npm run serverinside 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:
<body> <h1>Home</h1></body><body> <h1>About</h1></body>We can now make one template and pass in the value dynamically:
<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
-
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.htmlfile, we can still write regular HTML syntax inside. The<body>of the page should have:- 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. - An
<img>that contains a Handlebars expression tag which will output the value ofimagefor itssrcattribute:<img src="http://localhost:3000/{{ image }}">. As you can see, you can use the Handlebars expression tag anywhere inside of HTML syntax.
- An
-
Make an
images/folder in the root of the project, i.e. not insidesrc/. This is where we’ll store all the images we want to display on our website. -
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.
-
Inside
src/controller.ts, let’s replace the JSON response insidegetHomewith an HTML response.-
The status code will remain the same (200).
-
The
Content-Typeheader should be changed fromapplication/jsontotext/htmlto tell the client that we’ll now be sending back HTML data. -
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
headingandimageproperties of this object map to the same ones we defined inside ofpage.hbs.
- We give it the name of the template file,
-
-
Start the server by running
npm run server. -
In a browser, navigate to
http://localhost:3000and you should see a web page with the “Homepage!” and your image. -
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
-
Inside
page.hbs, add a<ul>tag that contains a Handlebarseachtag. This will loop through all the objects in thedatabasearray frommodel.ts, much like aforEachloop, 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> -
In the
getAllPokemoncontroller method, make the same changes you made to thegetHomemethod from the previous section. When callingres.end(), you’ll pass inheading: “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 frommodel.ts.
controller.ts res.end(await renderTemplate("src/page.hbs", {heading: "All Pokemon!",image: "images/your-image.png",pokemon: pokemon,})); -
In a browser, navigate to
http://localhost:3000/pokemonand 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.
-
Create
views/partials/Header.hbsandviews/partials/Footer.hbs. Cut everything inpage.hbsfrom the top of the file up to and including the opening of the<body>tag, and paste it intoHeader.hbs.Header.hbs <!DOCTYPE html><html lang="en"><head><title>Pokedex</title></head><body> -
Cut everything from the bottom of
page.hbsup to and including the close</body>tag and paste it intoFooter.hbs. To make it more interesting, we’ll add a footer with a copyright message:Footer.hbs <footer>© Copyright 2024 Your Name</footer></body></html> -
Inside
page.hbswe can use Handlebar’s partial tag to include the content fromHeader.hbsandFooter.hbs:page.hbs {{> Header }}<!-- h1, image, and list -->{{> Footer }} -
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"); -
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.
- Download an image per Pokemon (or resource of your choice) into the
imagesfolder. - Add an
imageattribute to eachPokemonobject insidemodel.ts. The value of this attribute should be the path to the image (ex.images/pikachu.png). - In the controller method, use the attributes from the requested Pokemon to set:
headingas the name of the Pokemonimageas 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. 🧼
