WebFund 2016W Lecture 15
Video
The video from the lecture given on March 8, 2016 is now available.
Student Notes
- Midterms are all graded (go check out CULearn)
- Midterms will be available during tutorials
Client-Side Data Population
- On the midterm, many people got the last question about the Jade each loop wrong
- The loop runs on the server, not in the browser
- We can look at the source code in the web browser and see that the table data is all populated
- To make the Jade-rendered HTML display nicely (with proper line break and indentations), pass the "pretty" attribute to the template:
res.render(‘list’, { title: ‘People listing’, items: state, pretty: true});
In previous examples we built the table rows using a for each loop in the Jade template. Today we will be moving the functionality to the web browser/client. This will allow our page to be more dynamic in the sense that the table will be rendered after the page has loaded. How does this happen? The list of people (in JSON) is requested from the server using an AJAX request and is then parsed and then put into the table.
- To do this we need to:
- Get the data to the browser
- Make the list from the data (using client-side JavaScript)
Sending List Data to the Browser
- Add a new route on the server
app.get(‘/items’, function(req, res){
res.send(state);
}
- This will send JSON-formatted data (the array) to the requesting client (the web browser)
- We will need to use jQuery (client-side JavaScript) to make this request and handle the response
- We'll write the script for this later
- We will need to use jQuery (client-side JavaScript) to make this request and handle the response
- We then change the /list route to render a new Jade template
app.get(‘/list’, function(req, res){
res.render(‘clientlist’, {title: “People listing”, pretty: true});
});
clientlist.jade
- We can copy the list.jade template and make some modifications to it
- We will need to link jQuery and the client-side script that we will be writting
block header
script(src=“jquery-1.12.0.js”)
script(src=“showlist.js”)
- We can remove the body of the old table and instead use
tbody#theListing (the pound sign indicates an ID)
- We need to give an ID to the tbody because in showlist.js we will need to let it know where we want to add content to the table
Processing the Data in the Browser
- Now we need to make the showlist.js script
- In this script, we will need to get get the array of objects (in JSON) from the server by making an AJAX request
- We can use
$.getJSON(‘/items’, displayItems);
to make this request and then call thedisplayItems
callback function- What is AJAX?
- It stands for Asynchronous JavaScript and XML
- It’s a library that communicates between the server and the client. It’s great because of it’s asynchronous nature which means it can do its work without needing to refresh the page.
- What is AJAX?
- Now we have an array of objects in the browser (the data has moved!)
- We need to display this data
- Once displayed they are still not in the source code! (We have updated the DOM but not the source code)
- We can use
$(function(){
//grab JSON from server
//insert data into DOM
function displayItems(items){
var i;
for ( i = 0; i < items.length; i++){
//Remember the id? This is DOM manipulation
$(“#theListing”).append(“<tr>” +
“<td>” + items[i].name + “</td>” +
“<td>” + items[i].city + “</td>” +
“<td>” + items[i].country + “</td>” +
“<td>” + items[i].birthday + “</td>” +
“<td>” + items[i].email + “</td>” +
“</tr>”);
}
}
$.getJSON(‘/items’, displayItems); //callback
// This is the AJAX request (as mentioned above)
// We are receiving the array of objects and calling the displayItems call back function.
// “items” is the array of objects we got from the server
});
Tutorial 6 Solution
- Make sure you complete this tutorial (super important for next assignment)
- We will walk through it today…
- The code already has some saved data it’s displaying
- The /results route passes the stats object in the the Jade template (we need to build up this stats object dynamically)
router.post(‘/add’, function(req, res){
var answer = { firstname : req.body.firstname};
// qlist holds all of the answers to the question (ex. for colours if would have “red”, “green”, etc)
var qlist = Object.keys(questions);
// for each answer to the question
qlist.forEach(function(q){
answer[q] = req.body[q];
}
answers.push(answer);
…the rest is the same…
}
- Now we need to gather the answers into a stats object
- When making an object, start with the properties you know it needs to have and add the rest later
function calcResultsStats(results){
var resultsStats = [];
Object.keys(questions).forEach(function(q){
var r = {};
var i;
var choice;
r.questionsName = q;
r.questionText = questions[q];
r.labels = [];
r.values = [];
var count = {}; // mapping of question answers to counts
for (i = 0; i < answers.length; i++){
choice = answers[i][q];
if( counts[choice])
counts[choice] = counts[choice] + 1;
else
counts[choice] = 1;
}
// Now we want to convert that object into two arrays
Object.keys(counts).forEach(function(c){
r.labels.push(c);
r.values.push(counts[c]);
});
resultsStats.push(r);
});
return resultsStats;
}
How Real Is All of This?
- While the web app was running during class, you could visit http://134.117.222.110:3000/
- It’s a real web server!
- What we don't have is a domain name (we must use the IP address instead)
- Root Name Servers:
- Canada (.ca)
- Carleton (carleton.ca)
- SCS (scs.carleton.ca)
- Anil (homeostatis.scs.carleton.ca)
- Root Name Servers:
- To have everything set up for you, you can use a PaaS (Platform as a Service)
- Node.js Developer Centre:
nam install azure
git commit -m “My first Node app”
git push azure master
- Modulus:
nam install modulus
modulus deploy
- Next time:
- Get Node running on OpenStack
- You won’t have to run a VM
Code
Full code:
examforms-jquery/examforms-jquery.js
var http = require('http');
var express = require('express');
var bodyParser = require('body-parser');
var logger = require('morgan');
var port = 3000;
var state = [];
var app = express();
app.set('view engine', 'jade');
app.set('views', __dirname);
app.use(logger('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static('.'));
app.get('/', function(req, res, next) {
res.render('index', { title: 'COMP 2406 Exam form demo' });
});
app.post('/add', function(req, res) {
var obj = { name: req.body.name,
city: req.body.city,
country: req.body.country,
birthday: req.body.birthday,
email: req.body.email };
state.push(obj);
res.redirect('/list');
});
app.get('/list', function(req, res) {
// res.render('list', { title: 'People Listing', items: state,
// pretty: true});
res.render('clientlist', { title: 'People Listing', pretty: true});
});
app.get('/items', function(req, res) {
res.send(state);
});
var serverUp = function() {
console.log("ExamForms listening on port " + port);
}
var serverDown = function() {
console.log("Server shutting down.");
process.exit(0);
}
var server = http.createServer(app);
server.listen(port);
server.on('listening', serverUp);
process.on('SIGINT', serverDown);
examforms-jquery/clientlist.jade
extends layout
block header
script(src="jquery-1.12.0.js")
script(src="showlist.js")
block content
h1= title
div
div
table
thead
th Name
th City
th Country
th Birthday
th Email
tbody#theListing
form(method="get", action="/")
button(type="submit") Home
examforms-jquery/showlist.js
$(function() {
// grab JSON from server
// insert data into DOM
function displayItems(items) {
var i;
for (i=0; i<items.length; i++) {
$("#theListing").append("<tr>" +
"<td>" + items[i].name + "</td>" +
"<td>" + items[i].city + "</td>" +
"<td>" + items[i].country + "</td>" +
"<td>" + items[i].birthday + "</td>" +
"<td>" + items[i].email + "</td>" +
"</tr>"
);
}
}
$.getJSON('/items', displayItems);
});
graph-demo/routes/index.js
<source lang="javascript" line> var express = require('express'); var router = express.Router();
var answers = []; var questions = {
color: "Favorite color?", day: "Favorite day of the week?", dog: "Favorite dog breed?", city: "Favorite city?"
}
function calcResultsStats(results) {
// var resultsStats = [ // {questionName: "color", // questionText: "Favorite color?", // labels: ["red", "blue", "yellow", "brown"], // values: [5, 7, 2, 0] // }, // {questionName: "day", // questionText: "Favorite day of the week?", // labels: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", // "Saturday", "Sunday"], // values: [0, 2, 1, 5, 70, 100, 20] // } // ];
var resultsStats = [];
Object.keys(questions).forEach(function(q) {
var r = {}; var i, choice; r.questionName = q; r.questionText = questions[q]; r.labels = []; r.values = []; var counts = {}; // mapping of question answers to counts
for (i = 0; i<answers.length; i++) { choice = answers[i][q]; if (counts[choice]) { counts[choice] = counts[choice] + 1; } else { counts[choice] = 1; } }
Object.keys(counts).forEach(function(c) { r.labels.push(c); r.values.push(counts[c]); }); resultsStats.push(r);
});
return resultsStats;
}
router.get('/', function(req, res) {
res.render('index', {title: 'COMP 2406 Graphing Demo',
questions: questions}); });
router.post('/add', function(req, res) {
var answer = { firstname: req.body.firstname}; var qlist = Object.keys(questions);
qlist.forEach(function(q) {
answer[q] = req.body[q];
});
answers.push(answer);
console.log(answer);
res.redirect('/results');
});
router.get('/results', function(req, res) {
var stats = calcResultsStats(answers); res.render('results', { title: 'Survey Results',
stats: stats, barwidth: 60 }); });
// app.get('/render', function (req, res, next) { // res.render('chart', {xlabel: "Months", // ylabel: "Failure rate (%)", // unit: '%', // barwidth: 40, // pretty: true, // items: [{value: 10, bin: "Jan"}, // {value: 20, bin: "Feb"}, // {value: 40, bin: "Mar"}, // {value: 40, bin: "Apr"}, // {value: 40, bin: "May"}, // {value: 40, bin: "Jun"}, // {value: 40, bin: "Jul"}, // {value: 40, bin: "Aug"}, // {value: 40, bin: "Sep"}, // {value: 40, bin: "Oct"}, // {value: 80, bin: "Nov"}, // {value: 100, bin: "Dec"}, // ]}); // });
module.exports = router; <source>