So far in all our MVC programs we've gotten our inputs using
URL parameters. However, most of the time you'll want to gather
your inputs from a user with HTML forms. This allows for more
advanced functionality: gathering multiple inputs, getting
different kinds of inputs such as a selection from a list, performing
client-side validation, etc.
In this lesson we'll learn how to process HTML forms on the
server side, allowing you to create even more dynamic content.
Let's try an example with a form. In a previous lesson
you wrote a program that displayed the Chinese Zodiac
animal for a specific year. Let's rewrite this as an MVC
program using Express.
Add a new project. I'm calling mine express_zodiac.
Add the /views and /controllers directory. We don't need
a model for this project.
Our form is going to ask for a user's name and the
year. So we'll need to handle a request to load the
index page.
When the user submits the form, we want to see the following
output:
Once the user submits the form with the two inputs,
we'll need that request to be handled as a POST
request. This request will have a few steps:
Get the request body as url-encoded data.
Retrieve the animal for the user-entered year.
Render the result.ejs with the user's name and the
zodiac year.
Each of these will be its own middleware function.
For this project, we should also separate our code into
two controllers. One homeController, which manages the rendering
of the EJS templates, and one animalController, which
uses the third-party chinese-year package to get the zodiac
animal for a specific year. This allows us to re-use the
animalController in other programs!
Add the two controllers to the /controllers directory:
homeController.js and animalController.js
or call them whatever makes sense to you (although I do thing the
name "animalController" is kind of funny :D )
Animal Controller
The animalController will have one exposed function that
does pretty much what we had in our previous zodiac program:
get the year, make sure if the year is empty that we assign
the current year, then use the year in the getAnimal() endpoint
of the chinese-year package.
To make the animal available to the next middleware function,
we can add it as a parameter in the request body's
query string: that's
a common way of passing data from one middleware function
to another, and it's totally fine: we'll just add a new
item to the request body for the animal we found.
Also remember that in this case, it's from a POST
request, so the query string is accessed via
req.body, not req.query.
"use strict";
// /controllers/animalController.js
const chineseYear = require("chinese-year");
exports.getAnimal = (req, res, next) => {
// make sure the year input has an actual year in it
if (!req.body.year) {
req.body.year = new Date().getFullYear();
}
// get the animal for the year
const animal = chineseYear.getAnimal(req.body.year);
// add the animal to the query string
req.body.animal = animal;
// next middleware
next();
}
Home Controller
Our homeController handles the rendering of the index
page and also the results page when the form has been submitted.
So this controller needs two functions exposed: one to render the
index page and one to render the results page.
The index page function is simple, since it has no
placeholders:
Now we can put it all together in the app.js file. Add the app.js
to your project root if you haven't already done so.
app.js
Add the code to app.js to:
Use strict mode
import express, the animalController, and homeController
set the port number environment variable
set the view engine to "ejs"
start the server
"use strict";
const express = require("express"),
app = express(),
animalController = require("./controllers/animalController"),
homeController = require("./controllers/homeController");
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");
// rest of code will go here
app.listen(app.get("port"), () => {
console.log(`Server running on http://localhost:${app.get("port")}`);
});
Recall that we need a function to handle requests to
the index page. Let's use /zodiac/index, but feel
free to be a bit more creative (e.g. with or without .html)
// route get requests to index page
app.get("/zodiac/index", homeController.renderIndex);
As we discussed, we need three middleware functions to
handle the POST request for form submission:
First, populate req.body with the query string
in the request body:
When the user clicks the submit button, the
page should reload with the circle information in
the first <section> element.
Add the circle.js object/class as the model for your
application (e.g. it should go in /models). Your circle
should have a radius property, two accessor properties
circumference and area, and a toString()
function property. If the radius is not provided when someone
creates a new circle object, use a default value of 0.
Add a single controller with three exposed functions:
renderIndex() renders the index page.
getCircle() uses the inputted radius from the query
string and uses it to contruct a new Circle object.
If radius is empty, construct a default circle.
renderResult() passes the circle to the index.ejs
page and renders it.
Modify your index.ejs page: Inside the empty first
<section> element, add the code to do the following
if a circle object exists as a local:
render a level-2 heading with the content "Circle Information".
below the heading, display the circle (it should automatically
use the toString() if you coded it correctly) in a paragraph
element.
below the circle object, display a paragraph element
containing "Circumference: " followed by the circle circumference
below the circle object, display a paragraph element
containing "Area: " followed by the circle area
TIP: when you render the index page, pass in a null circle
object.