A lot of web sites and web applications get their dynamic
content from either databases or structured data files/objects
such as plain text files,
XML
(Extensible Markup Language)
or JSON
(JavaScript Object Notation) files.
Although XML
is still used in many applications today,
JSON is more popular
with JavaScript applications. In this lesson we'll look at
some simple applications you can create with dynamic data that
is read in from JSON
files. If you're interested, there's also a lesson on
how to create dynamic content from a
MySQL database, also.
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. You should also have read through
the EJS Templates lesson.
Recall that in the menu.json file, there is a single object that contains
7 properties: Sunday, Monday, Tuesday, Wednesday, Thursday, and Friday.
Each property contains its own object with 3 of its own properties (Breakfast,
Lunch, and Dinner"). Our previous project used a URL Parameter to capture
a day name (which ideally should correspond to one of the 7 object property
names e.g. localhost:3000/mvc/Tuesday). We then passed that value to our
controller function that rendered the index page. The controller function
passed to the index page both the day of the week from the URL parameter,
and the object for that day. In the index page, we displayed the day of the
week and also displayed at least one of the day-object's 3 properties (you
might have displayed all three, Breakfast, Lunch, and Dinner if you were
exploring the example).
In the Embedded JavaScript lesson, you learned
how to use scriptlets to include Javascript selections and loops in your
.ejs/html pages. This means we could actually display our menu in
a much nicer way!
Start a new project and copy over your original menu project (or copy
my version from the link up above). Open the /views/index.ejs file
in the editor.
Proposed output for our MVC program
Delete the contents of the <main> element. We're going to
add a for-in loop (for..in allows you to iterate object properties,
which you can't do with a for..of loop)
to iterate through the 3 properties inside the
object we passed into the index page:
<% for (let m in menuInput) { %>
<h2><%=m%></h2>
<p><%=menuInput[m]%></p>
<% } %>
Don't forget to initialize your application and install express and ejs.
Now run your program and try each day of the week (make sure
you use title case). You should see the menu, similar to the earlier
screen shot.
What happens if you don't enter a valid day of the week?
How do you think you might fix this?
One easy way is to make the day URL parameter
optional by adding a question mark (?) to the end of the
parameter name in the request URL in the app.js file:
In our handler in the controller, we can simply assume
that if the day was not included in the URL, there is no
menu for that day and pass a null value instead of an
actual day value and menu object:
exports.renderIndex = (req, res) => {
let menuInput = null; // assume empty
let dayInput = null; // assume empty
// if there is a day parameter:
if (req.params.day) {
dayInput = req.params.day;
menuInput = menu[req.params.day];
}
res.render("index", {
dayInput: dayInput,
menuInput: menuInput
});
};
In the index.ejs file, we can then use scriptlets to decide what
to display if the day was passed in or if null was passed in:
<header>
<h1>MVC: Index Page</h1>
<%if (dayInput) {%>
<h2>Menu for <%=dayInput%></h2>
<%} else {%>
<h2>Menu Not Available</h2>
<%}%>
</header>
The loop doesn't need to be modified at all: it will only iterate
if there are properties, and a "null" object has no properties.
A second option could be to create a 404 error page when the
URL doesn't contain the extra parameter segment, and this would
also be useful to deal with an issue when the segment is not
one of the 7 days of the week in title case. That will be
covered in an upcoming lesson.
Another cool thing you can do by reading a JSON file
is to create dynamic HTML elements such as menus,
articles, etc. For example, say we wanted to add a simple
menu to the top of our header, and we want the menu information
to come from a JSON file:
This file contains an array of objects, and each object has 2
properties:
path - this will be the path to the file, relative
to the /views directory of the project.
Each file is a static file except for the third object
with the path "page2", which is an ejs file.
You can spot the static files because they all have
file extensions.
I've also assumed that each file's path is coming off
of the main application path. For example, if the path is
/dynamic, then all paths are off the /dyanamic directory
e.g. /dynamic/staff/index.html
text - the link text for the link
So, for example, the first menu item would appear as
<a href="visitors.html">Visitor Information</a> and
when clicked, it will load /public/visitors.html file. Similarly, the
last link will appear as
<a href="about/staff/index.html">Our Staff</a> and
when clicked, it will load /public/about/staff/index.html file.
Copy the JSON code up above and paste it into links.json
in your project's /models directory.
First, let's create the actual links. Then we'll get them working.
Creating the Links
This is fairly simple: in the controller, add the statement to
import the links.json file:
Add a request handler renderIndex() to exports: make sure you pass the links array
to your index.ejs file (I passed mine is as the local variable "links"):
Lastly, update your index.ejs file to adda for..of loop
to your <nav> element:
<header>
<nav>
<% for (let link of links) {%>
<a href="<%=link.path%>"><%=link.text%></a>
<%}%>
</nav>
....
Notice that for the link's href attribute, I used
<%=link.path%> - This places the path
property value of each link object into the href
attribute. I also set the link text to the object's
text property value.
Add the app.js file to your project and add the necessary
code to add express, set the view engine to EJS, set up
a middleware to log all requests, and
start the server:
Notice that the last middleware uses "/dynamic/page2" as
the URL pattern. That's because the path
property for that object in the links array is set to "page2".
You should also recall that all the rest of the objects in the
links array are static files and will be handled by the
express.static() middleware.
GET-requests to /dynamic will all load the index.ejs file.
You might have noticed in all the static HTML files provided,
there is a paragraph with a link written as <a href="/dynamic">Home</a>
That will make a request to /dynamic, which is hanled by
the same middleware that loads the index page.
Also make note of the path used to the main.css file
in each static HTML file: visitors.html and hiring.html
use "./css/main.css", which causes a request to
/dynamic/css/main.css (express.static() ensures that
this loads the css/main.css file in the /public directory).
The about/index.html uses the path "../css/main.css".
If we used "./css/main.css", then the request would be
"/dynamic/about/css/main.css" which is incorrect, so we
add use "../" (previous directory) instead of "./" (current
directory) so that the request becomes "/dynamic/css/main.css".
This is also why the about/staff/index.html path to the
css file is "../../css/main.css" (it has to go up 2
directories to get to the correct location of the /css
directory).
If you haven't already, init your program and install
express, then run the project:
Load up localhost:3000/dynamic in your
browser: you should see the links at the
top of the header. Inspect the page DOM and make sure it
renders similar to the screen shot below:
DOM for the navigation on the page
Also check each sub page and make sure the CSS is loading
for each one and that the "Home" on each one brings you
back to the main page.
Further Modifications
What if you wanted the same menu on each page of the
project? To do this, each page would have to be a dynamic
page, since the menu is written in EJS code. Feel free
to modify your project. Is there a way you can easily
load all those "previously static" EJS files with one
single middleware function? This is something you should
try!