COMP 2406 Winter 2014 (Carleton University) Assignment 1 Solutions PART A (2 points each, 16 total) 1. app.js, line 13: var user = require('./routes/user'); No effect: The list() function from user.js is never used. 2. app.js, line 14: var http = require('http'); A runtime error is generated, since the HTTP module is required to start a web server which will be accessible by the user's browser. 3. app.js, line 29: app.use(express.static(path.join(__dirname, 'public'))); The styling on the home page of the web application is lost. This is because the CSS files and fonts are stored in the "public" directory, which is made publicly accessible by executing the code in line 29. 4. routes/index.js, line 6: var state = []; A runtime error "500 ReferenceError: state is not defined" occurs when a new user is added to the list. This error happens because the list of users is stored in the "state" array which was commented out in index.js. The runtime error occurs because the application attempts to add a user to an array when in fact the variable doesn't hold an array (its value is undefined). 5. routes/index.js, line 18: state.push(obj); Adding users no longer works - the people listing page still appears, but no users are listed. This is because the "state.push(obj)" line is what copies the new user's details into the "state" array. 6. views/layout.jade, line 10: block content The web application returns a blank page. This is because layout.jade is the template which is used by the home page and user listing page. Both of those pages extend the layout template, and insert their content into the content block. Removing the content block from the template therefore results in a blank page. 7. views/index.jade, line 6: div.well The styling on the main page disappears. This is because the div tag is what instructs the browser to use the "well" class from the bootstrap library. Removing the div tag removes the styling (see http://getbootstrap.com/components/#wells) and instead gives it the h1 (heading) style from the previous line, which is why the text becomes oversized. 8. views/index.jade, line 8 (change method="post" to method="get"): form(method="post", action="/add") A "Cannot GET ..." error is returned when trying to add a user. Changing method="post" to method="get" causes the submit button in the form to issue an HTTP GET request instead of an HTTP POST request. The reason why this breaks the application is because it assumes that the HTML form data is submitted by issuing an HTTP POST request, so app.js is only configured to respond to POST requests issued to the URL '/add', and will therefore return an error if it receives a GET request at that URL (observe in app.js that app.post('/add'...) is present, whereas app.get('/add'...) is absent). PART B 1. (2 points) Make the application listen on port 3200. A: In app.js, change the listen call to say 3200 instead of 3010 (line 34) *** app.listen(3200, function(){ console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); }); *** 2. (2 points) Add a "Home" button to the submit results screen (/add) that takes you back to the form screen. A: You have to modify add.jade, but there are a few ways you can do it. First, you can make an empty form: *** form(method="get", action="/") button(type="submit") Home *** you can add an onclick attribute in add.jade *** button(type="button" onclick="window.location='/'") Home *** Or you could use CSS to style a link to look like a button. 3. (4 points) Add a "Phone" field to the end of the form that behaves similarly to all of the other form entries. A: You need to change three files. In views/index.jade add a div for phone just like the other properties *** div input#phone(type="text", name="phone") label.add-on(for="phone") | Phone *** In routes/index.js add phone to the options of add res.render (just after date for example) *** phone: req.body.phone, *** in views/add.jade add an element for phone just like the other properties *** p Phone: #{phone} *** 4. (6 points) Change the output page views/add.jade to output a table that is nicely formatted using bootstrap. You'll probably want to embed the table inside of a panel and/or a well. The "Home" link should be at the very bottom of the page. A: The content block in the views/add.jade file should look something like this: div(class='panel panel-default') div(class='panel-heading')= title table.table thead tr th Name th City th Country th Birthday th Email th Phone tbody each item in items tr td #{item.name} td #{item.city} td #{item.country} td #{item.birthday} td #{item.email} td #{item.phone} form(method="get", action="/") button(type="submit") Home 5. (10 points) Implement an erase all records operation with confirmation a) Add a link to the bottom of the initial page (defined in views/index.jade) that says "Erase All Records". This should link to /erase A: Add the following to the bottom of views/index.jade: form(method="get", action="/erase") button(type="submit") Erase All Records OR a(href='/erase') Erase All Records b) /erase will bring up a page that has a single question on it: "Are you sure you want to erase all records?", followed by two links/buttons: Erase and Cancel. Cancel should go back to /. Erase should do a post to "/erase" with a hidden form value of "confirmed=yes". Make sure to make both Erase and Cancel look the same (even if you implement them differently). A: In app.js, add the following line: app.get('erase', routes.erase) app.post('erase', routes.erase) In routes/index.js, add the following function (blank for now, see the next part for final contents): exports.erase = function(req,res) { }; Create views/erase.jade as follows: extends layout block content p Are you sure you want to erase all records? form(action='/erase', method='post') input#confirmed(value='yes', hidden='true', name='confirmed') button(type="submit") Erase form(action='/', method='get') button(type="submit") Cancel b) The server should check that it got a yes for confirmed and then should erase all records. It should return to the browser a page that says "All records erased." with a link that says "Home" and goes back to /. A: Create views/confirmed.jade as follows: extends layout block content p All records erased. form(action='/', method='get') button(type="submit") Home In routes/index.js, insert the following function: exports.erase = function(req,res) { if (req.body.confirmed === 'yes') { state = []; res.render('confirmed'); } else { res.render('erase'); } }; PART C 1. (5 points) Why are the functions in routes/index.js assigned to be properties of the exports object? A: They are assigned as properties to the exports object, so that they can be imported into app.js when require('./routes') is called. The require() method returns the exports object from the file that is given to it as a parameter. This is how modules are implemented in node. Note that require('./routes') is effectively the same as require('./routes/index.js'). When the require() function is given a directory as a parameter, it assumes that index.js is what is being asked for. 2. (5 points) When are lines 36 and 37 of app.js (the calls to app.get() and app.post()) run? What do these lines do? A: These lines run only once, before the web server starts listening for incoming connections. Their purpose is to set up the routes (in other words, they instruct the server what to do when HTTP GET or POST requests are received at specific URLs). 3. (5 points) When is views/layout.jade used, and what does it do? A: It is essentially a template that can be used by other jade files, which is useful for when you want to create many pages with similar styling. This saves time, since you can define the general layout and include all the necessary style sheets in the layout file so that you do not have to repeat it in all the other files. For any jade file for which you would like to use the template, you must include the line "extends layout" at the top of the file. 4. (5 points) Explain how the #{item.country} in views/add.jade gets its value. Specifically outline, with reference to specific functions and data structures, how the country entered by the user on the initial page ends up output by this statement. A: i) Assume the user types in "Canada" for the country. When the user clicks the "Submit" button, the form data is sent via an HTTP POST request. The input field name "country" and the value "Canada" will be sent as a parameter in the POST request. ii) When node receives a POST request to /add, it will call routes.add(), which creates a new object as follows: var obj = { name: req.body.name, city: req.body.city, country: req.body.country, birthday: req.body.birthday, email: req.body.email, phone: req.body.phone}; The right-hand side "req.body.country" retrieves the value for the "country" attribute in the POST request, and assigns it to the attribute "country" in the new object. The following line: state.push(obj); Copies the new object into the "state" array. The following line: res.render('add', { title: 'People Listing', items: state }); Renders an HTML page using the "add.jade" template, and passes it the "state" array, which will be accessible in the jade file through the variable name "items". iii) The add.jade file can then loop through the "items" array with the following line: each item in items Where accessing "item.country" will return the user's original input.