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 /zodiac.
Add the /views and /controllers directory,
plus we need the /public directory for static files.
We don't need a model for this project because we'll
be using the chinese-year module from an earlier
lesson (opens in new tab).
Our form on the public/index.html page
asks for a user's name and the
year. So we'll need to handle a request to load the
index page.
The form on the index page
When the user submits the form, we want to see the following
output:
The output of the program
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:
If you haven't done so already, initialize your app. Then make sure you
install all three modules/packages via npm install:
npm install chinese-year express ejs -s
Now you can run your program. Load the index page in your
browser, e.g. localhost:3000/zodiac That URL should automatically load your index
page with the form.
Fill in your name and birth year, then click the submit button.
You should see the output in the browser!
Exercises
Write an MVC Express application that displays
information about a circle with a specific radius.
When the user clicks the submit button, the
page should reload with the circle information in
the first <section> element.
The form asks for a radius
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.