Build upon the code created in Assignment 3 by incorporating the Handlebars view engine to render our JSON data visually in the browser using .hbs views and layouts.Β Additionally, update our data-service module to allow for people to be updated using a web form.
Β
NOTE: If you are unable to start this assignment because Assignment 3 was incomplete β email your professor for a clean version of the Assignment 3 files to start from (effectively removing any custom CSS or text added to your solution).
Specification:
As mentioned above, this assignment will build upon your code from Assignment 3.Β To begin, make a copy of your assignment 3 folder and open it in Visual Studio Code.Β Note:Β this will copy your .git folder as well (including the βherokuβ remote for assignment 3).Β If you wish to start fresh with a new git repository, you will need to delete the copied .git folder and execute βgit initβ again in.
Β
Part 1: Getting Express Handlebars & Updating your views
Β
Step 1: Install & configure express-handlebars
- Use npm to install the βexpress-handlebarsβ module
- Wire up your server.js file to use the new βexpress-handlebarsβ module, ie:
- βrequireβ it as exphbs
- add the app.engine() code using exphbs({ β¦ }) and the βextnameβ property as β.hbsβ and the βdefaultLayoutβ property as βmainβ (See the Week 6 Notes) o call app.set() to specify the βview engineβ (See the Week 6 Notes)
- Inside the βviewsβ folder, create a βlayoutsβ folder
Step 2: Create the βdefault layoutβ & refactor home.html to use .hbs
Β
- In the βlayoutsβ directory, create a βmain.hbsβ file (this is our βdefault layoutβ)
- Copy all the content of the βhome.htmlβ file and paste it into βmain.hbsβ
o Quick Note: if your site.css link looks like this href=βcss/site.cssβ, it must be modified to use a leading β/β, ie href=β/css/site.cssβ
- Next, in your main.hbs file, remove all content INSIDE (not including) the single <div class=βcontainerβ>β¦</div> element and replace it with {{{body}}}
- Once this is done, rename home.html to home.hbs
- Inside home.hbs, remove all content EXCEPT what is INSIDE the single <div class=βcontainerβ>β¦</div> element (this should leave a single <div class=βrowβ>β¦</div> element containing two βcolumnsβ, ie elements with class βcol-md- β¦β and their contents)
- In your server.js file, change the GET route for β/β to βrenderβ the βhomeβ view, instead of sending home.html
- Test your server β you shouldnβt see any changes. This means that your default layout (βmain.hbsβ), βhome.hbsβ and server.js files are working correctly with the express-handlebars module.
Step 3: Update the remaining βaboutβ, βaddPeopleβ and βaddPictureβ files to use .hbs
Β
- Follow the same procedure that was used for βhome.htmlβ, for each of the above 3 files, ie:
- Rename the .html file to .hbs o Delete all content EXCEPT what is INSIDE the single <div class=βcontainerβ>β¦</div> element
- Modify the corresponding GET route (ie: β/aboutβ, β/pictures/addβ or β/people/addβ) to βres.renderβ the appropriate .hbs file, instead of using res.sendFile
- Test your server β you shouldnβt see any changes, except for the fact that your menu items are no longer highlighted when we change routes (only βHomeβ remains highlighted, since it is the only menu item within our main.hbs βdefault layoutβ with the class βactiveβ
Step 4: Fixing the Navigation Bar to Show the correct βactiveβ item
Β
- To fix the issue we created by placing our navigation bar in our βdefaultβ layout, we need to make some small updates, including adding the following middleware function above your routes in server.js:
Β
app.use(function(req,res,next){Β Β Β Β let route = req.baseUrl + req.path;Β Β Β Β app.locals.activeRoute = (route == β/β) ? β/β : route.replace(/\/$/, ββ);Β Β Β Β next();
});
Β
This will add the property βactiveRouteβ to βapp.localsβ whenever the route changes, ie: if our route is β/people/addβ, the app.locals.activeRoute value will be β/people/addβ.
- Next, we must use the following handlebars custom βhelperβ (See the Week 6 notes for adding custom βhelpersβ)β
Β
navLink: function(url, options){Β Β Β Β return β<liβ +
((url == app.locals.activeRoute) ? β class=βactiveβ β : β) +
β><a href=ββ + url + ββ>β + options.fn(this) + β</a></li>β;
}
- This basically allows us to replace all of our existing navbar links, ie: <li><a href=β/aboutβ>About</a></li> with code that looks like this {{#navLink β/aboutβ}}About{{/navLink}}. The benefit here is that the helper will automatically render the correct <li> element add the class βactiveβ if app.locals.activeRoute matches the provided url, ie β/aboutβ
- Now that our helpers are in place, update all the navbar links in main.hbs to use the new helper, for example:
o <li><a href=β/aboutβ>About</a></li> will become {{#navLink β/aboutβ}}About{{/navLink}}
- Test the server again β you should see that the correct menu items are highlighted as you navigate between views
Part 2: Rendering the Pictures in the β/picturesβ route
Β
Next, weβll work with pictures.Β Β Itβll be easier if 1 or more pictures have been added via the application, so do this now.
Β
Step 1: Add / configure βpictures.hbsβ view and server.js
- First, add a file βpictures.hbs in the βviewsβ directory
- Inside your newly created pictures.hbs file, add the following code to render 1 (one) of your (already-existing) uploaded pictures, ie (picture β1518186273491.jpgβ β your picture will have a different timestamp):
<div class=βrowβ>
<div class=βcol-md-12β³>
<h2>Pictures</h2>
<hr />
</div>
Β
<div class=βcol-md-4β³>
<img src=β/pictures/uploaded/1518186273491.jpgβ class=βimg-responsive img-thumbnailβ />Β Β Β Β </div>
</div>
Note the classes βimg-responsiveβ and βimg-thumbnailβ.Β These are simply bootstrap classes that correctly scale and decorate the picture with a border. See https://getbootstrap.com/docs/3.3/css/#images / https://getbootstrap.com/docs/3.3/css/#images-shapes for more information
- Next, modify your GET route for /pictures. Instead of executing res.json and sending the βpicturesβ array, you should use res.render(βpicturesβ, Object Containing βpicturesβ Array Here); so that you can send the object containing the array of pictures as data for your βpicturesβ view
- Once this is complete, modify your pictures.hbs file using the handlebars #each helper to iterate over the
βpicturesβ array, such that every picture is shown in its own <div class=βcol-md-4β³>β¦</div> element
(effectively replacing our single βstaticβ picture).Β This will have the effect of giving us a nice, responsive grid of multiple βcol-md-4β columns, each containing its own picture.
o NOTE: you can use {{this}} within the loop to get the current value of the item in the array of strings
(this will be the filename of the current picture, ie: β1518186273491.jpgβ)
- If there are no pictures (ie the βpicturesβ array is empty), show the following element instead:
<div class=βcol-md-12 text-centerβ>
<strong>No Pictures Available</strong> </div>
- NOTE: since we are hosting our app on heroku, you will notice that once the app βsleepsβ and starts up again, any uploaded pictures are gone. This is expected behavior.Β However, if you wish to develop an application that will persist its images between restarts of the app, you can look at something like Cloudinary (this service provides a mechanism to upload pictures to Cloudinary from your server.js code and store them online using their service)
Part 3: Updating the People Route & Adding a View
Β
Rather than simply outputting a list of people using res.json, it would be much better to actually render the data in a table that allows us to access individual people and filter the list using our existing req.params code.
Β
Step 1: Creating a simple βPeopleβ list & updating server.js
Β
- First, add a file βpeople.hbsβ β in the βviewsβ directory
- Inside the newly created βpeople.hbsβ view, add the html:
<div class=βrowβ>
<div class=βcol-md-12β³>
<h2>People</h2>
<hr />
Β
<p>TODO: render a list of all People first and last names here</p>
Β
</div>
</div>
Β
Β
- Replace the <p> element (containing the TODO message) with code to iterate over each person and simply render their first and last names (you may assume that there will be an βpeopleβ array (see below).
- Once this is done, update your GET β/peopleβ route according to the following specification
- Every time you would have used res.json(data), modify it to instead use res.render(βpeopleβ, {people:
data})
- Every time you would have used res.json({message: βno resultsβ}) β ie: when the promise has an error (ie in .catch()), modify instead to use res.render({message: βno resultsβ});
- Test the Server β you should see the following page for the β/peopleβ route:
Β
Β
Β
Step 2: Building the Table & Displaying the error βmessageβ
- Update the people.hbs file to render all of the data in a table, using the bootstrap classes: βtable-responsiveβ
(for the <div> containing the table) and βtableβ (for the table itself) β Refer to theΒ completed sample here https://gentle-earth-90224.herokuapp.com/people
- The table must consist of 5 columns with the headings: Person ID, Full Name, Phone Number, Address, and Car Vin
- Additionally, the Name in the Full Name column must link to /person/id where id is the personβs id for that row o The βPhoneβ column must be a βtelβ link to the userβs phone number for that row
- The βCar Vinβ link must link to /cars?vin=vin where vin is the vin number for the person for that row
- Beneath <div class=βcol-md-12β³>β¦</div> element, add the following code that will conditionally display the βmessageβ only if there are no people (HINT: #unless people)
Β
<div class=βcol-md-12 text-centerβ>
<strong>{{message}}</strong> </div>
This will allow us to correctly show the error message from the .catch() in our route
Β
Step 3: Adding new query route for /stores
Update server.js to add a new query route for stores
- /stores?retailer=value
- return a JSON string consisting of all stores where value could is retailer attribute of the stores object β this can be accomplished by calling the getStoresByRetailer(value) function of your data-service (defined below)
- Add getStoresByRetailer(data) Function
- In data-service.js this function will provide an array of βstoresβ objects whose retailer property matches the data parameter using the resolve method of the returned promise.
- If for some reason, the length of the array is 0 (no results returned), this function must invoke the reject method and pass a meaningful message, ie: βno results returnedβ.
Β
Β
Β
Part 4: Updating the Stores Route & Adding a View
Now that we have the βPeopleβ data rendering correctly in the browser, we can use the same pattern to render the βStoresβ data in a table:
Β
Step 1: Creating a simple βStoresβ list & updating server.js
Β
- First, add a file βstores.hbsβ in the βviewsβ directory
- Inside the newly created β stores.hbsβ view, add the html:
<div class=βrowβ>
<div class=βcol-md-12β³>
<h2>Stores</h2>
<hr />
Β
<p>TODO: render a list of all stores idβs and names here</p>
Β
</div> </div>
- Replace the <p> element (containing the TODO message) with code to iterate over each store and simply render their id, name, phone and address values (you may assume that there will be a βstoresβ array (see below).
- Once this is done, update your GET β/storesβ route according to the following specification
o Instead of using res.json(data), modify it to instead use res.render(βstoresβ, {stores: data});
- Test the Server β you should see the following page for the β/storesβ route:
Β
Β
Β
Step 2: Building the Table
- Update the stores.hbs file to render all of the data in a table, using the bootstrap classes: βtable-responsiveβ (for the <div> containing the table) and βtableβ (for the table itself).
- The table must consist of 4 columns with the headings: Store id, Store Name, Store Phone, and Address
- The βStore Nameβ link must link to /cars?make=make where make is the make of the retailer for that row
- The βPhoneβ column must be a βtelβ link to the userβs phone number for that row
- Refer to the example online at https://gentle-earth-90224.herokuapp.com/stores
Β
Part 5: Updating the Cars Route & Adding a View
Now that we have the βPeopleβ and βStoresβ data rendering correctly in the browser, we can use the same pattern to render the βCarsβ data in a table:
Β
Step 1: Creating a simple βCarsβ list & updating server.js
Β
- First, add a file βcars.hbsβ in the βviewsβ directory
- Inside the newly created β cars.hbsβ view, add the html:
<div class=βrowβ>
<div class=βcol-md-12β³>
<h2>Cars</h2>
<hr />
Β
<p>TODO: render a list of all cars idβs and names here</p>
Β
</div> </div>
- Replace the <p> element (containing the TODO message) with code to iterate over each car and simply render their id, make, model, year and vin values (you may assume that there will be a βcarsβ array (see below).
- Once this is done, update your GET β/carsβ route according to the following specification
o Instead of using res.json(data), modify it to instead use res.render(βcarsβ, {cars: data});
- Test the Server β you should see the following page for the β/carsβ route:
Β
Β
Β
Step 2: Building the Table
- Update the cars.hbs file to render all of the data in a table, using the bootstrap classes: βtable-responsiveβ (for the <div> containing the table) and βtableβ (for the table itself).
- The table must consist of 5 columns with the headings: Car id, Car Make, Car Model, Car Year and Vin
- The βCar makeβ link must link to /stores?retailer=retailer where retailer is the retailer of the make for that row
- The βCar Yearβ β link must link to /cars?year=year where year is the year of the car for that row
- The βCar Vinβ link must link to /people?vin=vin where vin is the vin of the car for that row
- Refer to the example online at https://gentle-earth-90224.herokuapp.com/cars
Β
Part 6: Updating Existing People
The last piece of the assignment is to create a view for a single person.Β Currently, when you click on an personβs name in the β/peopleβ route, you will be redirected to a page that shows all of the information for that person as a JSON-formatted string (ie: accessing http://localhost:8080/person/21, should display a JSON formatted string representing the corresponding person β person 21).
Now that we are familiar with the express-handlebars module, we should add a view to render this data in a form and allow the user to save changes.
Β
Step 1: Creating new .hbs file / route to Update Person
Β
- First, add a file βperson.hbsβ in the βviewsβ directory
- Inside the newly created βperson.hbsβ view, add the html (NOTE: Some of the following html code may wrap across lines to fit on the .pdf β be sure to check that the formatting is correct after pasting the code):
<div class=βrowβ>
<div class=βcol-md-12β³>
<h2>{{person.first_name}} {{ person.last_name}} β Person: {{ person.id}}</h2>
<hr />
<form method=βpostβ action=β/person/updateβ>
<fieldset>
<legend>Personal Information</legend>
<div class=βrowβ>
<div class=βcol-md-6β³>
<div class=βform-groupβ>
<label for=βfirst_nameβ>First Name:</label>
<input class=βform-controlβ id=βfirst_nameβ name=βfirst_nameβ type=βtextβ value=β{{person.first_name}}β />
</div>
</div>
<div class=βcol-md-6β³>
<div class=βform-groupβ>
<label for=βlast_nameβ>Last Name:</label>
<input class=βform-controlβ id=βlast_nameβ name=βlast_nameβ type=βtextβ value=β{{person.last_name}}β />
</div>
</div>Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β </div>
</fieldset>
<hr />
<input type=βsubmitβ class=βbtn btn-primary pull-rightβ value=βUpdate Personβ /><br /><br /><br />Β Β Β Β Β Β Β Β </form>
</div>
</div>
Β
- Once this is done, update your GET β/person/:idβ route according to the following specification
o Use res.render(βpersonβ, { person: data }); inside the .then() callback (instead of res.json) and use res.render(βpersonβ,{message:βno resultsβ}); inside the .catch() callback
- Test the server (/person/1) β this will get you started on creating / populating the form with user data:
Β
Β
- Continue this pattern to develop the full form to match the completed sample here β you may use the code in the sample to help guide your solution
o Id: type: βhiddenβ, name: βidβ o Phone: type: βtextβ, name: βphoneβ o Address (Street): type: βtextβ, name: βaddressβ o Address (City): type: βtextβ, name: βcityβ o Personβs Vin Number: type: βtextβ, name: βvinβ
- No validation (client or server-side) is required on any of the form elements at this time
- Once the form is complete, we must add the POST route: /person/update in our server.js file:
Β
app.post(β/person/updateβ, (req, res) => {Β Β Β Β console.log(req.body);
res.redirect(β/peopleβ);
});
This will show you all the data from your form in the console, once the user clicks βUpdate Personβ. However, in order to take that data and update our βpeopleβ array in memory, we must add some new functionality to the data-service.js module:
Step 2: Updating the data-service.js module
Β
- Add the new method: updatePerson(personData) that returns a promise. This method will:
o Search through the βpeopleβ array for a person with an id that matches the JavaScript object (parameter personData). o When the matching person is found, overwrite it with the new person passed into the function (parameter personData) o Once this has completed successfully, invoke the resolve() method without any data.
- Now that we have a new updatePerson() method, we can invoke this function from our newly created app.post(β/person/updateβ, (req, res) => { β¦ }); Β Simply invoke the updatePerson() method with the req.body as the parameter.Β Once the promise is resolved use the then() callback to execute the res.redirect(β/peopleβ); code.
- Test your server in the browser by updating Person 21 (Regina Dunphy). Once you have clicked βUpdate Personβ and are redirected back to the People list, Person 21 should show your changes!
Β
Part 7: Pushing to Heroku
Once you are satisfied with your application, deploy it to Heroku:
- Ensure that you have checked in your latest code using git (from within Visual Studio Code)
- Open the integrated terminal in Visual Studio Code
- Log in to your Heroku account using the command heroku login
- Create a new app on Heroku using the command heroku create
- Push your code to Heroku using the command git push heroku master
- IMPORTANT NOTE: Since we are using an βunverifiedβ free account on Heroku, we are limited to only 5 apps, so if you have been experimenting on Heroku and have created 5 apps already, you must delete one (or verify your account with a credit card). Once you have received a grade for Assignment 1, it is safe to delete this app (login to the Heroku website, click on your app and then click the Delete appβ¦ button under βSettingsβ).
Β
Testing: Sample Solution
Β
To see a completed version of this app running, visit: https://gentle-earth-90224.herokuapp.com/
Β
Please note: This solution is visible to ALL students and professors at Seneca College.Β It is your responsibility as a student of the college not to post inappropriate content / images to the shared solution.Β It is meant purely as an exemplar and any misuse will not be tolerated.
Β
Β




