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 TemplatingL02. 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
Github Classroom Assignment
- Using an SSH key
- Using a Personal Access Token (PAT)
git clone git@github.com:CU-CSCI3308-Fall2024/lab-7-templating-<YOUR_USER_NAME>.git
git clone https://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.
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:
# 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
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!
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
.
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.
The difference between session and cookie
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.
Symbol | Description |
---|---|
{{ }} | 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
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)
- 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
{{#if first_name}}
<title> {{first_name}} - CSCI 3308 Lab 7 </title>
{{else}}
<title> CSCI 3308 Lab 7 </title>
{{/if}}
B. head.hbs (TODO)
- Let's create the
head
partial. Copy the markup as shown in the code block below to/views/partials/head.hbs
. - Complete the TODOs in the code. Checkout Handlebars Partials
<!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.
- Let's create the
nav
partial. Copy the markup as shown in the code block below to/views/partials/nav.hbs
- Complete the TODOs in the code.
<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.
- Let's create the
footer
partial. Copy the markup as shown in the code block below to/views/partials/footer.hbs
. - Add the
<script>
tag to include the bootstrap javascript library.
<footer class="text-center text-muted w-100 mt-auto fixed-bottom">
<!-- TODO: Add the <script><script /> tag to include bootstrap javascript -->
<p>
© 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.
- Let's create the
message
partial. Copy the markup as shown in the code block below to/views/partials/message.hbs
. - We will be using this partial in the courses.hbs. ( which you will be implementing in PART B)
{{#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}}
<!-- TODO: Add the head.hbs partial here -->
<!-- TODO: Add the nav.hbs partial here -->
{{{body}}}
<!-- TODO: Add the footer.hbs partial here -->
Refer to 5.2 to see how to include partials in a page.
6. Part B: Routes and its associated pages
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 renderspages/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 thehome
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.
- Create a
login.hbs
file under/views/pages
. - Create a HTML form that takes
Email
as an input field and add a button of thetype='submit'
. The text on the button should be 'Login'. - In the form element, set the
action
attribute to be "/login" andmethod
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.
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:
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".
- Returns
- 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.
Create a
courses.hbs
file under/views/pages
.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 withtype = "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 istrue
, 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'.
- If the
- Use
{{#if}}..{{else}}...{{/if}}
conditions to validate the value oftaken
. 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:
With alerts:
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 renderspages/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
- Create a
home.hbs
file under/views/pages
. - 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. - Refer: Handlebars Simple Expressions
Output: The home page would look like:
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
- Create a
logout.hbs
file under/views/pages
. - 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:
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
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
Description | Points | |
---|---|---|
PART A: Pre-Lab Quiz | Complete the Pre-Lab Quiz before your lab | 20 |
PART B: Setting up Partials | The 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.hbs | The login form has the necessary fields and calls the correct route when submitted | 10 |
PART B: courses.hbs | If 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 added | 20 |
PART C: home.hbs | If logged in, shows the user's information | 10 |
PART C: logout.hbs | Displays the logout message when the user logs out | 10 |
In class check-in | You 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.