Inventory Delivery By Classification
With the initial implementation of M-V-C complete, we are ready to put it to work to deliver content. Our content consists of vehicles in inventory by classification as well as information about individual vehicle. This activity deals with delivering vehicles in inventory based on their classification.
Video Overview
The video provides a general overview of the activity, but does not contain the detail needed to complete each process. Watch the video to obtain a general idea, but follow the written steps to complete the activity. This is the Transcript of the video.
Inventory Routes
In the last activity, you built a dynamic navigation bar containing links from the classifications in the csemotors database. Now we will add the logic and structure to deliver inventory items, based on their classification, to the browser when a navigation link is clicked.
- Open the working project in VSC.
- Create a new file, named inventoryRoute.js in the routes folder.
-
At the top of the new file three resources must be declared, so they
can be used: Express, and new Express router, and an inventory
controller (which will be built later in this activity). The code
shown below should accomplish this:
// Needed Resources const express = require("express") const router = new express.Router() const invController = require("../controllers/invController")
-
An Explanation
- Line 1 - a comment for the lines to follow.
- Line 2 - brings Express into the scope of the file.
-
Line 3 - uses Express to create a new
Router
object. Remember in lesson 2 that using separate router files for specific elements of the application would keep the server.js file smaller and more manageable? That's what we're doing. - Line 4 - brings the inventory controller into this router document's scope to be used.
Add the Route
When visiting a car dealer site, it is not uncommon to filter the inventory in some way: make, price, or type. Our goal with this route is the latter, to indicate that we wish to see all the vehicles in inventory of a particular type, based on which navigation link was clicked.
If you were to go to the index.js file in the utilities folder and
look at the code in the getNav()
function, you would find that the
path for each link looks something like this:
/inv/type/#
. The "#" would be replaced by an integer,
which is the classification_id
value of the classification. The route
we build must match the route found in the link. Add the following
lines of code to the inventoryRoute file, below the
lines previously entered:
// Route to build inventory by classification view
router.get("/type/:classificationId", invController.buildByClassificationId);
module.exports = router;
An Explanation
- Line 1 - a comment for the route.
-
Line 2 - the route, which is divided into three
elements:
- "get" indicates that the route will listen for the GET method within the request (typically a clicked link or the URL itself).
-
/type/:classificationId
the route being watched for (note that theinv
element of the route is missing, but it will be accounted for later). -
invController.buildByClassification
indicates thebuildByClassification
function within the invController will be used to fulfill the request sent by the route.
- Line 3 - left intentionally blank.
- Line 4 - exports the router object for use elsewhere.
- Check that VSC does not show any warnings or errors for the code.
- Save the file.
server.js File
With the inventory route file built, let's incorporate its functionality into the server.js file.
- Open the server.js file.
- At the top of the file, find the "Require Statements" area and require the inventory route file you just created. Use the variable
inventoryRoute
to store the required resource. Use the require statement for the static file as an example. - Scroll down to the "Routes" area of the file.
-
Beneath the "Index route" add the following code:
// Inventory routes app.use("/inv", inventoryRoute)
-
An Explanation
- Line 1 - A comment to introduce the route
-
Line 2 - composed of three elements:
-
app.use()
is an Express function that directs the application to use the resources passed in as parameters. -
/inv
is a keyword in our application, indicating that a route that contains this word will use this route file to work with inventory-related processes; "inv" is simply a short version of "inventory". -
inventoryRoute
is the variable representing theinventoryRoute.js
file which was required (brought into the scope of the server.js file) earlier.
-
-
In short, any route that starts with
/inv
will then be redirected to the inventoryRoute.js file, to find the rest of the route in order to fulfill the request.
The Inventory Controller
In the inventoryRoute file, we indicated that the inventory controller would be required and a function within that controller would be used. It's time to build this controller and the function.
- Find the controllers folder, click it, then create a new file named invController.js.
- Add the code shown below to the controller file:
const invModel = require("../models/inventory-model")
const utilities = require("../utilities/")
const invCont = {}
/* ***************************
* Build inventory by classification view
* ************************** */
invCont.buildByClassificationId = async function (req, res, next) {
const classification_id = req.params.classificationId
const data = await invModel.getInventoryByClassificationId(classification_id)
const grid = await utilities.buildClassificationGrid(data)
let nav = await utilities.getNav()
const className = data[0].classification_name
res.render("./inventory/classification", {
title: className + " vehicles",
nav,
grid,
})
}
-
An Explanation
-
Line 1 - brings the inventory-model.js file
into scope and stores its functionality into a
invModel
variable. -
Line 2 - brings the utilities > index.js file into scope and stores its functionality into an
utilities
variable. - Line 3 - left intentionally blank.
-
Line 4 - creates an empty object in the
invCont
variable. - Line 5 - left intentionally blank.
- Lines 6-8 - a multi-line comment.
-
Line 9 - creates an asynchronous, anonymous
function which accepts the
request
andresponse
objects, along with the Expressnext
function as parameters. The function is stored into a named method ofbuildByClassificationId
. -
Line 10 - collects the
classification_id
that has been sent, as a named parameter, through the URL and stores it into theclassification_id
variable.req
is the request object, which the client sends to the server.params
is an Express function, used to represent data that is passed in the URL from the client to the server.classificationId
is the name that was given to theclassification_id
value in the inventoryRoute.js file (see line 7 of that file). -
Line 11 - calls the
getInventoryByClassificationId
function (you'll build that next), which is in the inventory-model file and passes theclassification_id
as a parameter. The function "awaits" the data to be returned, and the data is stored in thedata
variable. - Line 12 - calls a utility function to build a grid, containing all vehicles within that classification (you'll build this later in this activity). Note that the "data" array is passed in as a parameter. An HTML string, containing a grid, is returned and stored in the grid variable.
-
Line 13 - calls the function to build the
navigation bar for use in the view and stores it in the
nav
variable. -
Line 14 - extracts the name of the
classification, which matches the
classification_id
, from the data returned from the database and stores it in theclassName
variable. -
Line 15 - calls the Express
render
function to return a view to the browser. The view to be returned is named classification, which will be created within an inventory folder, within the already existing views folder. - Line 16 - build the "title" value to be used in the head partial, but you'll notice that it is dynamic to match the data.
-
Line 17 - contains the
nav
variable, which will display the navigation bar of the view. -
Line 18 - contains the HTML string, containing the -
grid
- of inventory items. - Line 19 - ends the "render" function which started on line 11.
- Line 20 - ends the function started on line 9.
-
Line 1 - brings the inventory-model.js file
into scope and stores its functionality into a
-
Add two empty lines at the bottom of the file. On the last empty
line add the following export statement:
module.exports = invCont
- Check that VSC does not show any warnings or errors.
- Save the file.
The Inventory Model
In the controller function, a function from the inventory model is called in order to get vehicles that belong to a particular classification. It's time to add this function to the model file.
- Find and open the inventory-model.js file in the models folder.
- Create several empty lines between the existing function and the module.exports line.
- Leaving one empty line between the existing function and the new function, create the new function as shown:
/* ***************************
* Get all inventory items and classification_name by classification_id
* ************************** */
async function getInventoryByClassificationId(classification_id) {
try {
const data = await pool.query(
`SELECT * FROM public.inventory AS i
JOIN public.classification AS c
ON i.classification_id = c.classification_id
WHERE i.classification_id = $1`,
[classification_id]
)
return data.rows
} catch (error) {
console.error("getclassificationsbyid error " + error)
}
}
-
An Explanation
- Lines 1-3 - a multi-line comment.
-
Line 4 - declares an asynchronous function by
name and passes a variable, which should contain the
classification_id
value, as a parameter. - Line 5 - opens a try - catch block.
-
Lines 6-12 - creates an SQL query to read the
inventory and classification information from their respective
tables using an INNER JOIN. The query is written using a
parameterized statement. The "$1" is a placeholder, which will be
replaced by the value shown in the brackets "[]" when the SQL
statement is run. The SQL is queried against the database via
the database pool. Note the
await
keyword, which means this query will wait for the information to be returned, where it will be stored in thedata
variable. - Line 13 - sends the data, as an array of all the rows, back to where the function was called (in the controller).
-
Line 14 - ends the
try
and opens thecatch
, with anerror
variable being supplied to store any error that may occur. - Line 15 - writes the error, if any, to the console for us to read. We will have to deal with a better error handler in the future.
- Line 16 - closes the
catch
block. - Line 17 - ends the function.
- Very important! This function must now be included in the exports at the bottom of the file. If not, it will not be usable by the controller.
-
Add the function to the module.exports code, like this:
module.exports = {getClassifications, getInventoryByClassificationId};
- Ensure that VSC does not show any warnings or errors.
- Save the file.
The buildClassificationGrid Function
In the previous activity, you created a utility file for storing functions that are not directly part of the M-V-C process. That is where you will build this function. Its purpose is to take an array of inventory items, break each item and its data out of the array and embed it into HTML. When done, there will be a string that will be embedded into the view. It will need CSS styling to be applied to make it look appropriate. But, let's build the function:
- Find and open the utilities > index.js file.
- Move below the existing function, create several blank lines and create the function.
/* **************************************
* Build the classification view HTML
* ************************************ */
Util.buildClassificationGrid = async function(data){
let grid
if(data.length > 0){
grid = '<ul id="inv-display">'
data.forEach(vehicle => {
grid += '<li>'
grid += '<a href="../../inv/detail/'+ vehicle.inv_id
+ '" title="View ' + vehicle.inv_make + ' '+ vehicle.inv_model
+ 'details"><img src="' + vehicle.inv_thumbnail
+'" alt="Image of '+ vehicle.inv_make + ' ' + vehicle.inv_model
+' on CSE Motors" /></a>'
grid += '<div class="namePrice">'
grid += '<hr />'
grid += '<h2>'
grid += '<a href="../../inv/detail/' + vehicle.inv_id +'" title="View '
+ vehicle.inv_make + ' ' + vehicle.inv_model + ' details">'
+ vehicle.inv_make + ' ' + vehicle.inv_model + '</a>'
grid += '</h2>'
grid += '<span>$'
+ new Intl.NumberFormat('en-US').format(vehicle.inv_price) + '</span>'
grid += '</div>'
grid += '</li>'
})
grid += '</ul>'
} else {
grid += '<p class="notice">Sorry, no matching vehicles could be found.</p>'
}
return grid
}
An Explanation
- Lines 1-3 - A multi-line comment.
- Line 4 - declares the function as asynchronous and expects a data array as a parameter.
- Line 5 - declares a variable to hold a string.
- Line 6 - an "if" to see if the array is not empty.
- Line 7 - creates an unordered list element and adds it to the grid variable.
- Line 8 - sets up a "forEach" loop, to break each element of the data array into a vehicle object.
- Lines 9-25 - builds a single HTML <li>. Withing the list item is an <a> element that surrounds an <img> element. Next is a <div> that contains a horizontal rule, followed by an <h2> that contains another <a> with the Make and Model of the vehicle. Finally, is a <span> that contains a formatted price, in US dollars.
- Line 26 - closes the foreach process.
- Line 27 - closes the unordered list.
- Line 28 - ends the "if" and opens an "else". The else is executed if the data array is empty.
- Line 29 - stores a <p> with a message indicating that no vehicles match the classification.
- Line 30 - ends the "else".
- Line 31 - returns the variable to the calling location.
- Line 32 - ends the function.
- Be sure to carefully review the code, particularly lines 9 through 25 to understand the structure and what is happening.
- Ensure there are no warnings or errors.
- Save the file.
Have you paid attention to the function names?
I trust that as you have built the functions in the inventory model that you noticed that while we added comments, the function names should clearly describe what they do. Feel free to add to the comments. Whatever you do, be sure to make your function names clear and meaningful to describe their purpose.
Take a Breath
Feel like you just ran a half-marathon? I don't blame you. But, if you're here, it means that you finished everything up to this point. All that's left is to build the view and deploy it to the server, so we can see if it works. Let's build the view in the next activity.