Skip to main content

Lab 7: Templating

Overview

In this lab, you will be working on connecting the NodeJS backend server (provided in the lab template repository) to front-end or client-side webpages using Handlebars.

To receive credit for this lab, you MUST show your work to the TA during the lab, and push to the github by the deadline. Please note that submissions will be due right before your respective lab sessions in the following week. For Example, If your lab is on this Friday 10 AM, the submission deadline will be next Friday 10 AM. There is a "NO LATE SUBMISSIONS" policy for labs.

Learning Objectives

LO1. Setup Environments for Templating
L02. Get Started with Handlebars
L03. Learn about routing and navigation between pages
L04. Apply these skills and build some more pages for the application.

Part A - Pre-Lab Quiz

Complete Pre-Lab quiz on Canvas before your section's lab time.

Part B

Clone your GitHub repository

info
You need to accept the invite to the GitHub classroom assignment to get a repository.

Github Classroom Assignment
For the next two steps, make sure to edit the name of the repository after the copying the command into your terminal.
git clone git@github.com:CU-CSCI3308-Fall2024/lab-7-templating-<YOUR_USER_NAME>.git

Navigate to the repository on your system.

cd lab-7-templating-<YOUR_USER_NAME>

1. Directory structure

Before we start the lab, make all the .handlebars files according to the structure mentioned below:

|--node_modules
|--express/
|--express-handlebars/
|--handlebars/
|--pg-promise/
|--<...other packages>
|--init_data
|--create.sql
|--insert.sql
|--views
|--partials
|--footer.hbs
|--nav.hbs
|--title.hbs
|--head.hbs
|--message.hbs
|--layouts
|--main.hbs
|--pages
|--courses.hbs
|--home.hbs
|--login.hbs
|--logout.hbs
|--.env
|--.gitignore
|--docker-compose.yaml
|--package.json
|--index.js

2. Setting up your environment

In this lab, we will be using 2 containers, one for PostgreSQL and one for Node.js. If you need a refresher on the details of Docker Compose, please refer to lab 1.

1. Create the docker-compose.yaml file

We have updated the configuration of the db container for this lab. Copy the configuration shown below into your docker-compose.yaml file.

docker-compose.yaml
version: '3.9'
services:
db:
image: postgres:14
env_file: .env
expose:
- '5432'
volumes:
- lab-7-templating:/var/lib/postgresql/data
- ./init_data:/docker-entrypoint-initdb.d
web:
image: node:lts
user: 'node'
working_dir: /home/node/app
env_file: .env
environment:
- NODE_ENV=development
depends_on:
- db
ports:
- '3000:3000'
volumes:
- ./:/home/node/app
command: 'npm start'
# This defines our volume(s), which will persist throughout startups.
# If you want to get rid of a hanging volume, e.g. to test your database init,
# run `docker-compose rm -v`. Note that this will remove ALL of your data, so
# be extra sure you've made a stable backup somewhere.
volumes:
lab-7-templating:

2. Set up .env file

Create a .env file and copy over the following into it:

.env
# node variables
SESSION_SECRET="super duper secret!"
# database credentials
POSTGRES_USER="postgres"
POSTGRES_PASSWORD="pwd"
POSTGRES_DB="students_courses_db"

3. Starting Docker Compose

Now that we've configured the docker-compose.yaml, starting docker compose is quite simple.

docker compose up
Please be patient

Depending on different machines, it could take some time for node to initialize and run in your servers(READ DOCKER LOGS CAREFULLY).Recall from Lab 6, after the server was ready your could access it through http://localhost:3000.

4. Shutting down containers

Now that we've learned how to start docker compose, let's cover how to shutdown the running containers. As long as you're still in the same directory, the command is as follows.

docker compose down -v

4. Restarting Containers

This lab does not involve any changes to the server, so you would not need to restart docker.

And that's it!

tip

How to check your docker logs?

Check this link out to debug your docker.

3. Database Provided

We've set up the tables shown below and you can find the SQL for it in the /init_data/create.sql file and the data in /init_data/insert.sql. You are NOT required to import that file into the db container.

In the docker-compose.yaml, we have already mapped the db container's volume to the init_data/ folder. When starting up, the db container will read the init_data/create.sql and init_data/insert.sql file and initialize the course database.

ER_Diagram

4. Session variables

Before we get into the routes provided for this lab, it is VERY important to understand session variables.

When the client makes a login request to the server, the server will create a session and store it on the server-side. When the server responds to the client, it sends a cookie. This cookie will contain the session’s unique id stored on the server, which will now be stored on the client. This cookie will be sent on every request to the server.

A cookie is a key-value pair that is stored in the browser. The browser attaches cookies to every HTTP request that is sent to the server.

In a cookie, you can’t store a lot of data. A cookie cannot store any sort of user credentials or secret information. If we did that, someone could easily get hold of that information and steal personal data for malicious activities.

On the other hand, the session data is stored on the server-side, i.e., a database or a session store. Hence, it can accommodate larger amounts of data. To access data from the server-side, a session is authenticated with a secret key or a session id that we get from the cookie on every request.

In index.js ...

We are using express-session package to set the session object. To store or access session data, simply use the request property req.session, which is (generally) serialized as JSON by the store, so nested objects are typically fine.

5. Get Started with Handlebars

1. Handlebars Syntax

Check out the tags section of the Handlebars documentation. It covers the notations listed below.

SymbolDescription
{{ }}Used for evaluating variables, helpers, and expressions.
{{#if}}Begins an if block for conditional rendering.
{{else}}Denotes the alternative content in an if block.
{{unless}}Begins a block that renders its contents if the expression evaluates to false.
{{#each}}Begins an iteration block for iterating over arrays or objects.
{{{html}}}Renders raw HTML content (triple curly braces).
{{! comment }}Allows adding comments within the template.

2. Partials

danger

Check for TODOs in the code snippets provied below. Do not remove the comments that has a TODO in it. Make sure to complete all the TODOs provided.

A partial is a fragment of a webpage's html, meaning it is not a complete webpage that could be rendered by a browser. Instead, we create re-usable components of our webpage which makes it easier to maintain our website's code. Updating a partial like one that has the markup for a navigation bar will update it across your entire website without having to update each individual page.

In handlebars, we can include partial in a .hbs file using the syntax:

{{> myPartial}}

For this lab, we will create four partials and include them in main.hbs.

A. title.hbs (TODO)

  1. Let's create the title partial. Copy the markup as shown in the code block below to /views/partials/title.hbs. Try to understand the syntax of handlebars and how its rendered
/views/partials/title.hbs
{{#if first_name}}
<title> {{first_name}} - CSCI 3308 Lab 7 </title>
{{else}}
<title> CSCI 3308 Lab 7 </title>
{{/if}}

B. head.hbs (TODO)

  1. Let's create the head partial. Copy the markup as shown in the code block below to /views/partials/head.hbs.
  2. Complete the TODOs in the code. Checkout Handlebars Partials
/views/partials/head.hbs
<!DOCTYPE html>
<html lang="en" class="h-100">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>

<!-- TODO: Include the `title` partial here -->

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

</head>
<body class="h-100 d-flex flex-column">

C. nav.hbs (TODO)

The nav, short for the navigation bar, will contain the links to all the pages.

  1. Let's create the nav partial. Copy the markup as shown in the code block below to /views/partials/nav.hbs
  2. Complete the TODOs in the code.
/views/partials/nav.hbs
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="navbar-collapse collapse w-100 order-1 order-md-0 dual-collapse2">
<a class="navbar-brand" href="/"></a>
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<!-- TODO: For 'Home', Add a <a> tag with class="nav-link" and href='/' API that navigates to home page if logged in, or login page if not -->
</li>
<li class="nav-item">
<!-- TODO: For 'Courses', Add a <a> tag with class="nav-link" and href='/courses' API that navigates to courses page if logged in, or login page if not -->
</li>
<li class="nav-item">
<!-- TODO: For 'Log out', Add a <a> tag with class="nav-link" and href='/logout' API that navigates to the logout page -->
</li>
</ul>
</div>

<div class="navbar-collapse collapse order-3 dual-collapse2">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="#">
<!-- TODO: Add the email id which is passed using: {{ variable_name }}. This will display the email id on the top right corner of the page -->
</a>
</li>
</ul>
</div>
</nav>
</header>

D. footer.hbs (TODO)

The footer partial includes all of our javascript files & the closing code for our webpage's html.

  1. Let's create the footer partial. Copy the markup as shown in the code block below to /views/partials/footer.hbs.
  2. Add the <script> tag to include the bootstrap javascript library.
/views/partials/footer.hbs
<footer class="text-center text-muted w-100 mt-auto fixed-bottom">
<!-- TODO: Add the <script><script /> tag to include bootstrap javascript -->
<p>
&copy; Copyright 2024 : CSCI 3308 - Lab 7 Template Strings
</p>
</footer>
</body>
</html>

E. message.hbs (TODO)

The message partial includes an alert message that can be added in your webpage wherever you want an alert to pop up.

  1. Let's create the message partial. Copy the markup as shown in the code block below to /views/partials/message.hbs.
  2. We will be using this partial in the courses.hbs. ( which you will be implementing in PART B)
/views/partials/message.hbs
{{#if message}}
<div class="alert alert-{{#if error}}danger{{else}}success{{/if}}" role="alert">
{{ message }}
</div>
{{/if}}

3. Layouts

Now lets tie these pieces to the actual webpages, in the views/layouts/main.hbs which will import the above partials ( head, nav, footer ) and help build a complete webpage.

1. main.hbs (TODO)

For this lab, we will include the partials in the main.hbs pages and we will use this outline for the pages that we will create for this lab. The main content of the upcoming webpages will be rendered by the {{body}}

views/layouts/main.hbs
<!-- TODO: Add the head.hbs partial here -->
<!-- TODO: Add the nav.hbs partial here -->
{{{body}}}
<!-- TODO: Add the footer.hbs partial here -->
tip

Refer to 5.2 to see how to include partials in a page.

6. Part B: Routes and its associated pages

RENDER V/S REDIRECT

res.render generates HTML content for the current URL. res.redirect sends the client to a different URL.

We have created routes, in index.js, that you will be using to make your webpages. The list contains a detailed explanation of what the API returns and how they would be encorporated in frontend pages.

1. login.hbs

Routes - Provided

1. Route: /

  • Method: GET

  • Functionality: If logged in, shows the homepage with user details. If not logged in, the user is asked to login.

  • Response: If the session is set, redirects to /login if the session, else renders pages/home page along with the user information. An example of the json response from the route is shown below.

    {
    "first_name": "Janek",
    "last_name": "Andrich",
    "email": "jandrich0@colorado.edu",
    "year": 2022,
    "major": "Computer Science",
    "degree": "BS"
    }

2. Route: /login

  • Method: POST

  • Functionality: Takes email as input from the request body and checks if that user is present in the database. If the user is present, it creates a session variable req.session.user and saves the information of the user that has logged in, and renders the home.hbs (Part C) page showing the welcome message. If the user is not present, it will redirect to the login page.

  • Input:

    {
    "email": "AnEmailFromInsertDotSQLFile@email.com"
    }
  • Response: Redirects to the default / route which renders the home page if successful and user info is returned to the client.

3. Route: /login

  • Method: GET

  • Functionality: Renders the login.hbs page (to be worked on by you) that shows a form to enter user information. The submit button of the form would call POST /login API.

  • Input: N/A

  • Response: Renders pages/login. There is no data returned from the server.

Page - To Do

For the login page, we will be creating a HTML form.

  1. Create a login.hbs file under /views/pages.
  2. Create a HTML form that takes Email as an input field and add a button of the type='submit'. The text on the button should be 'Login'.
  3. In the form element, set the action attribute to be "/login" and method attribute to be POST. When the form is submitted by clicking the submit button, it will trigger a call to the POST /login route defined in our index.js.
danger

Please use the email ids provided in /init-data/insert.sql file as input to login. The login form WILL NOT work with other custom email Ids.

Output: The login page should look like the following:

login

2. courses.hbs

Routes - Provided

1. Route: /courses

  • Method: GET
  • Functionality:
    • Returns {courses: [{...}]}, where each entry is the row from the database resulting from a join of courses and student_courses tables. It also returns action as "delete" or "add" based on if the course has been taken. If the course is taken, action will be returned as "delete". Else, it will return action as "add".
  • Response: Renders pages/courses page with the user's email, courses and the action.

The list of courses has the following fields:

[
{
"course_id": 1300,
"course_name": "Introduction to Programming",
"credit_hours": 3,
"taken": true
},
{
"course_id": 3308,
"course_name": "Software Development Methods and Tools",
"credit_hours": 3,
"taken": false
},
...
]

2. Route: /courses/add

  • Method: POST

  • Functionality: Checks if the added course has satisfied all the prerequisites and inserts the course into the student_courses database.

  • Input: Course ID in the request body.

  • Response: Renders pages/courses page with the updated list of courses and a message.

    [
    {
    "course_id": 6789,
    "course_name": "XYZ",
    "credit_hours": 3,
    "taken": true
    }
    ]

Page - To Do

For the courses.hbs page, we will be displaying the list of courses returned by the /courses API.

  1. Create a courses.hbs file under /views/pages.

  2. This page displays the list of courses. To create this page, you will be working with if-else statements and foreach loops.

    Step 1: Display the list of courses

    • Create a HTML table with 3 columns - Course ID, Course Name, Credit Hours.
    • You will loop through the courses list and display their entries as rows in the table. Use {{#each}}...{{/each}} to loop through the courses. Refer: #each

    Step 2: Each row must have an ADD button which lets users add a course

    • Create a HTML form with a submit button in each row. The action of the form must redirect to /courses/add route with a method POST.
    • As we know, /courses/add route will take course_id as an input to add the course. Within the form, create an <input> tag with type = "hidden" name = "course_id" value = {{course_id}} to pass course_id to the API call as a body. By making the input type to be hidden, the input box will not appear on the webpage.

    Step 3: Validate the value of taken

    • For the submit button, you will have to take care of two conditions.
      • If the taken field in the courses list is true, then the submit button of the row (which is enclosed within a form) will be disabled.
      • Else, the submit button of the row (which is enclosed within the form) will be 'ADD'.
    • Use {{#if}}..{{else}}...{{/if}} conditions to validate the value of taken. Refer: #if

    Step 4: Add alerts

    • When the 'ADD' button is clicked, the /courses/add API will return a message varible. Display the message using the message.hbs partial provided.
    • You can call the partial like this : {{ >message }} in the courses.hbs page to display the message.

Output: On the nav-bar, if 'Courses' is clicked, then the following page should show up:

my_courses

With alerts:

my_courses

Part C:

1. home.hbs

Routes - Provided

1. Route: /

  • Method: GET

  • Functionality: If logged in, shows the homepage with user details. If not logged in, the user is asked to login.

  • Response: If the session is set, redirects to /login if the session, else renders pages/home page along with the user information. An example of the json response from the route is shown below.

    {
    "username": "janek",
    "first_name": "Janek",
    "last_name": "Andrich",
    "email": "jandrich0@colorado.edu",
    "year": 2022,
    "major": "Computer Science",
    "degree": "BS"
    }

Page - To Do

  1. Create a home.hbs file under /views/pages.
  2. When home.hbs page loads, you have all the information of the user who is logged in. To display the data, create a HTML Table and display the user data received from the backend on loading this page.
  3. Refer: Handlebars Simple Expressions

Output: The home page would look like:

home

2. logout.hbs

Routes - Provided

Route: /logout

  • Type: GET

  • Functionality:

    • Destroys the session saved.
  • Input: Student ID taken from the session variable

  • Response: Renders pages/logout

Page - To Do

  1. Create a logout.hbs file under /views/pages.
  2. This page should show up when logout on the navbar is clicked. This page would have a simple HTML container with a heading stating that the logout was successful.

Output: Here is what your Logout page would look like:

logout

Submission Guidelines

Commit and upload your changes

Run the following commands inside your root git directory (in your lab7-templating-<username> folder).

git add .
git commit -m "Added hbs pages and partials"
git push
tip

Once you have run the commands given above, please navigate to your GitHub remote repository (on the browser) and check if the changes have been reflected.

You will be graded on the files that were present before the deadline. If the files are missing/not updated, you could receive a grade as low as 0. This will not be replaced as any new pushes to the repository after the deadline will be considered as a late submission and we do not accept late submissions.

Regrade Requests

Please use this link to raise a regrade request if you think you didn't receive a correct grade. If you received a lower than expected grade because of missing/not updated files, please do not submit a regrade request as they will not be considered for reevaluation.

Rubric

DescriptionPoints
PART A: Pre-Lab QuizComplete the Pre-Lab Quiz before your lab20
PART B: Setting up PartialsThe pages are correctly created and displayed. Navbar links are functional and when logged in, has the email id displayed on the right.10
PART B: login.hbsThe login form has the necessary fields and calls the correct route when submitted10
PART B: courses.hbsIf logged in, shows the user's taken courses (grayed out disabled add buttons), and all courses (not taken courses have a green enabled add button), alert message shows up when a new course is added20
PART C: home.hbsIf logged in, shows the user's information10
PART C: logout.hbsDisplays the logout message when the user logs out10
In class check-inYou showed your work to the TA or CM.20

Please refer to the lab readme or individual sections for information on file names and content to be included in them.