WebFund 2016W Lecture 15: Difference between revisions

From Soma-notes
LeeCroft (talk | contribs)
No edit summary
 
(3 intermediate revisions by one other user not shown)
Line 3: Line 3:
The video from the lecture given on March 8, 2016 [http://homeostasis.scs.carleton.ca/~soma/webfund-2016w/lectures/comp2406-2016w-lec15-08Mar2016.mp4 is now available].
The video from the lecture given on March 8, 2016 [http://homeostasis.scs.carleton.ca/~soma/webfund-2016w/lectures/comp2406-2016w-lec15-08Mar2016.mp4 is now available].


==Notes==
==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:
 
<code><pre>res.render(‘list’, { title: ‘People listing’, items: state, pretty: true});</pre></code>
 
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
 
<code><pre>app.get(‘/items’, function(req, res){
  res.send(state);
}
</pre></code>
 
*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 then change the /list route to render a new Jade template
 
<code><pre>
app.get(‘/list’, function(req, res){
  res.render(‘clientlist’, {title: “People listing”, pretty: true});
});
</pre></code>
 
=====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
 
<code><pre>
block header
  script(src=“jquery-1.12.0.js”)
  script(src=“showlist.js”)
</pre></code>
 
*We can remove the body of the old table and instead use
 
<code><pre>
tbody#theListing (the pound sign indicates an ID)
</pre></code>
 
*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 <code>$.getJSON(‘/items’, displayItems);</code> to make this request and then call the <code>displayItems</code> 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.
**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)
 
<code><pre>
$(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
});
</pre></code>
 
 
===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)
 
<code><pre>
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…
}
</pre></code>
 
*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
 
<code><pre>
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;
}
</pre></code>
 
===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)
*To have everything set up for you, you can use a PaaS (Platform as a Service)
**Node.js Developer Centre:
<code><pre>
nam install azure
git commit -m “My first Node app”
git push azure master
</pre></code>
<ul><ul>
<li>Modulus:</li>
</ul></ul>
<code><pre>
nam install modulus
modulus deploy
</pre></code>
 
*Next time:
**Get Node running on OpenStack
**You won’t have to run a VM


==Code==
==Code==
Full code:
* [http://homeostasis.scs.carleton.ca/~soma/webfund-2016w/code/examforms-jquery.zip examforms-jquery.zip]
* [http://homeostasis.scs.carleton.ca/~soma/webfund-2016w/code/graph-demo-sol.zip graph-demo-sol.zip]


===examforms-jquery/examforms-jquery.js===
===examforms-jquery/examforms-jquery.js===
Line 92: Line 286:


===examforms-jquery/showlist.js===
===examforms-jquery/showlist.js===
<source lang="javascript" line>
$(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);
});
</source>
===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>

Latest revision as of 18:37, 14 March 2016

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 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 the displayItems 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.
    • 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)
$(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)
  • 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>