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.

  1. Open the working project in VSC.
  2. Create a new file, named inventoryRoute.js in the routes folder.
  3. 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")
    
  4. 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

server.js File

With the inventory route file built, let's incorporate its functionality into the server.js file.

  1. Open the server.js file.
  2. 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.
  3. Scroll down to the "Routes" area of the file.
  4. Beneath the "Index route" add the following code:
    // Inventory routes
    app.use("/inv", inventoryRoute)
    
  5. 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 the inventoryRoute.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.

  1. Find the controllers folder, click it, then create a new file named invController.js.
  2. 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,
  })
}
  1. 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 and response objects, along with the Express next function as parameters. The function is stored into a named method of buildByClassificationId.
    • Line 10 - collects the classification_id that has been sent, as a named parameter, through the URL and stores it into the classification_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 the classification_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 the classification_id as a parameter. The function "awaits" the data to be returned, and the data is stored in the data 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 the className 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.
  2. Add two empty lines at the bottom of the file. On the last empty line add the following export statement:
    
      module.exports = invCont
    
  3. Check that VSC does not show any warnings or errors.
  4. 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.

  1. Find and open the inventory-model.js file in the models folder.
  2. Create several empty lines between the existing function and the module.exports line.
  3. 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)
  }
}
  1. 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 the data 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 the catch, with an error 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};
      
  2. Ensure that VSC does not show any warnings or errors.
  3. 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:

  1. Find and open the utilities > index.js file.
  2. 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
}
  1. 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.
  2. Be sure to carefully review the code, particularly lines 9 through 25 to understand the structure and what is happening.
  3. Ensure there are no warnings or errors.
  4. 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.