Lesson Overview
So far in previous lessons we've started making basic
MVC (Model View Controller)
applications with Node.js and Express. We used JSON objects and also a JavaScript class
as the model, we used some basic HTML and very basic EJS for our views, and we used a controller with
all the behind-the-scenes processing. In this lesson we'll take a
more detailed look at EJS
and see what else you can do with it to create pages with more
dynamic content.
Pre-Requisites
Before doing this lesson, make sure you've done the
Express MVC lesson and that you've
done the demonstration programs and exercises, as you'll continue
building those in this lesson.
Intro to EJS Templates
Templates are views that contain placeholders. When a template
page is going to be sent in a response, the placeholders are
filled with actual data. In an Express application, this is
done with the help of a template engine.
A template engine looks for placeholders using its preferred
syntax, and helps your code replace the placeholders with
actual data.
Express supports a few different template engines.
I'm only going to talk about the EJS
(Embedded JavaScript) template
engine, but you might be interested in learning about
some of the others that are popular:
EJS is very simple
and easy to learn: it's just JavaScript enclosed within
<% and %> symbols. You just code your
regular HTML, but place the code inside files with the
.ejs
extension and store them in the project's
/views directory. You can then add any JavaScript code inside the
special EJS tags,
as described below.
EJS commands are enclosed inside special tags defined with
the <% %>
symbols as indicated below:
There are some other tags you can read about in the
EJS
documentation, but you won't be using them in this lesson.
Recall from the previous lesson that the
EJS render engine needs to be
set up in your app.js file by including the
app.set("view engine", "ejs");
statement. Also recall that the dependency will need to
be added to a project as part of the npm install
command.
Output Tags (Expressions)
As you saw in the Express MVC lesson,
the EJS output tag
<%= %>
will simply output whatever value its
expression evaluates to; the output tag is a basic placeholder, so
it will be replaced with the expression's value. The expression can
be a single variable (in which case the tag is replaced with the variable's
value) or it can be any valid JavaScript expression (in which case the
tag is replaced with the final result of the expression). For example,
all of these are valid because each is an expression that yields a single
value:
<%=dayInput%>
<%=(Math.PI * req.params.day ** 2).toFixed(2)%>
<%=(radius <= 0) ? radius : 'invalid, default of 1 used'))%>
Scriptlets: If Statements
Scriptlets allow you to include any JavaScript inside your
.ejs files. For example, you can include any if statements
or a switch statement inside your code.
Let's do a very basic example: set up a new project (I'm
calling mine /ejs_ifs and it will have a base application path
of "/esj1"). Add the /models, /views, and /controllers directories,
plus the app.js file.
Add the usual code inside your app.js: strict mode, require
express, create the express app, require the home controller,
set the port # to 3000, set the view engine, and start the server.
Add a handler for GET requests to /ejs1 that include two
parameters: one for user name and one for a rating number for
the user's current mood from 1 to 3. These requests should
be routed to a function in your main controller:
app.get("/ejs1/:user/:rating", homeController.renderIndex);
Add the rendering function to your controller to render the
index.ejs page, and pass in the two URL parameter values:
exports.renderIndex = (req, res) => {
res.render("index", {
user: req.params.user,
rating: req.params.rating
});
}
Now add the index.ejs file to your /views directory and add the
minimal HTML, plus a header and footer. Between the header and
footer, add a <main> element.
Let's add a simple if statement: if the user name and rating are
not empty/falsy, we'll display a level-2 heading with the user name.
Otherwise we won't display anything:
<% if (user && rating) { %>
Welcome, <%=user%>!
<%}%>
Notice how this code was written? We put the start of the If-block inside
a scriptlet tag - the scriptlet was closed right after the if's opening
brace. Not only is this permitted, but encouraged! Scriptlets can't
display output, so to display the heading, we have to close the scriptlet.
We can finish the if-block (just it's closing brace is all that's needed)
with the last scriptlet below the level-2 heading. When you have a lot
of complex code, this can definitely make your code much harder to read
and debug - that's why many beginners who start off with
EJS eventually decide to learn
an alternative such as PUG: PUG is not the same syntax as JavaScript, so
it would be another new language to learn, but it's not difficult and it
doesn't have all the annoying <% and %> everywhere, so it's easier to read.
Try your program out so far: don't forget to iniitalize your application
with npm init
and don't forget to install the express and ejs
dependencies.
We can use the same kind of syntax to perform nested selections.
For example, I'd like to add a switch statement that displays a text
emoji (e.g. :) ) depending on the rating that was input via the URL
parameter. I only want to do this if both the user name and rating
are not empty. Be careful coding this because it's very easy to make
mistakes!
<% if (user && rating) {
let output = ""; %>
Welcome, <%=user%>!
<% switch (parseInt(rating)) {
case 1:
output = ":)";
break;
case 2:
output = ":|";
break;
case 3:
output = ":(";
break;
default:
output = "o.O";
}%>
<%=output%>
<%}%>
Note that because the values are coming in as strings, any
URL parameters you want to calculate or compare with as numbers should be
converted to integer (parseInt()) or float (parseFloat()).
This opens up many new possibilities for dynamic content: if you're
familiar with the JavaScript selection structures, you can use them
easily in your .ejs files! Of course, this also means you can use
iteration structures, too.
Scriptlets: Loops
You'll probably use a lot of loops when creating dynamic content.
For example, you might create a data table with some values that
were read in from a JSON file or database. You might have a blog
whose articles and comments are displayed iteratively based on
cloud data.
Let's start off easily by creating some content with random
numbers. The user can specify the minimum and maximum values,
plus the number of values to generate, using
URL parameters.
Set up a new project (I'll call this one /ejs_loops with
a base application URL of /ejs2) and set
it up with the same structure as the previous one.
In your app.js, add a GET request handler to /ejs2 that
includes 3 parameters: miminum value, starting value, and number of values. The
last parameter is optional (you should have learned how to do
this in the exercises of the previous
lesson). Add the function to the main controller that
renders the index page and passes it the 3 parameter values.
However, if the number of values parameter is empty or <= 0,
give it a default value of 10. EJS templates don't play well
with null values (not easily, anyway).
Let's add a counted for-loop to our .ejs file that prints
the random numbers in a paragraph. Don't forget to parse each
of the numeric values to an integer, otherwise your calculations
will be off.
<p>
<% min = parseInt(min);
max = parseInt(max);
num = parseInt(num);
for (let i = 1; i <= num; i++) {
let n = Math.floor(Math.random() * (max - min + 1) + min); %>
<%=n%>
<%}%>
</p>
Note once again that you have to close the scriptlet inside the
for-loop (after the let n ... statement) so that you can output
the value of n using an output tag. The scriptlet is re-opened at
the end to close the for-loop brace.