WebFund 2024F: Tutorial 3: Difference between revisions
No edit summary |
|||
(6 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
'' | In this tutorial you'll be playing with [https://homeostasis.scs.carleton.ca/~soma/webfund-2024f/code/formdemo.zip formdemo], a simple Deno application demonstrating how to accept and process HTML forms. You'll also start learning how to debug web applications. | ||
Once you have the application up and running you should do the following tasks. | |||
==References== | |||
These pages may help you with some of the specifics of this document: | |||
* [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table Tables in HTML] | |||
* [https://docs.deno.com/runtime/fundamentals/debugging/ Deno debugging] | |||
* [https://developer.mozilla.org/en-US/docs/Tools Firefox developer tools] | |||
* [https://developer.chrome.com/devtools/index Chrome/Chromium developer tools] | |||
==Tasks== | |||
* Figure out what happens when you delete six distinct lines from the application of your choosing. In other words, delete a line, restart the web app, see what happened. Make changes to all files. | |||
* Run formdemo under the deno debugger and connect to it. Set at least one breakpoint and notice how you can step through the code as it runs. Note that you'll need to be using Chrome or another browser using the V8 JavaScript runtime (or just use VSCode): | |||
deno run --inspect-brk --allow-net --allow-read formdemo.js | |||
If you're using an SSH jumphost, you need to forward both port 8000 and 9229, e.g. | |||
ssh -L 8000:localhost:8000 -L 9229:localhost:9229 -J janedoe@access.scs.carleton.ca student@134.117.33.1 | |||
* Change the top-level app page to have a button saying "List People" that lists the people that have already been entered. Note that you should link to a page that is already in the code that does this! | |||
==Code== | ==Code== | ||
All code: [https://homeostasis.scs.carleton.ca/~soma/webfund-2024f/code/formdemo.zip formdemo.zip] | All code: [https://homeostasis.scs.carleton.ca/~soma/webfund-2024f/code/formdemo.zip formdemo.zip] | ||
To install, unpack, and run formdemo in the class VM: | |||
wget https://homeostasis.scs.carleton.ca/~soma/webfund-2024f/code/formdemo.zip | |||
unzip formdemo.zip | |||
cd formdemo | |||
deno run --allow-net --allow-read formdemo.js | |||
===formdemo.js=== | ===formdemo.js=== | ||
Line 120: | Line 144: | ||
for (let r of state) { | for (let r of state) { | ||
row.push("<tr>") | |||
row.push(rowMarkup(r.name)); | row.push(rowMarkup(r.name)); | ||
row.push(rowMarkup(r.city)); | row.push(rowMarkup(r.city)); | ||
Line 125: | Line 150: | ||
row.push(rowMarkup(r.birthday)); | row.push(rowMarkup(r.birthday)); | ||
row.push(rowMarkup(r.email)); | row.push(rowMarkup(r.email)); | ||
row.push("</tr>") | |||
} | } | ||
Line 206: | Line 232: | ||
async function handler(req) { | async function handler(req) { | ||
var | var origpath = new URL(req.url).pathname; | ||
var path = origpath; | |||
var r = await route(req); | var r = await route(req); | ||
Line 216: | Line 243: | ||
} | } | ||
console.log(`${r.status} ${req.method} ${r.contentType} ${ | console.log(`${r.status} ${req.method} ${r.contentType} ${origpath}`); | ||
return new Response(r.contents, | return new Response(r.contents, | ||
Line 225: | Line 252: | ||
} | } | ||
Deno.serve(handler); | Deno.serve({ port: 8000, hostname: "0.0.0.0" }, handler); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 17:29, 26 September 2024
In this tutorial you'll be playing with formdemo, a simple Deno application demonstrating how to accept and process HTML forms. You'll also start learning how to debug web applications.
Once you have the application up and running you should do the following tasks.
References
These pages may help you with some of the specifics of this document:
Tasks
- Figure out what happens when you delete six distinct lines from the application of your choosing. In other words, delete a line, restart the web app, see what happened. Make changes to all files.
- Run formdemo under the deno debugger and connect to it. Set at least one breakpoint and notice how you can step through the code as it runs. Note that you'll need to be using Chrome or another browser using the V8 JavaScript runtime (or just use VSCode):
deno run --inspect-brk --allow-net --allow-read formdemo.js
If you're using an SSH jumphost, you need to forward both port 8000 and 9229, e.g.
ssh -L 8000:localhost:8000 -L 9229:localhost:9229 -J janedoe@access.scs.carleton.ca student@134.117.33.1
- Change the top-level app page to have a button saying "List People" that lists the people that have already been entered. Note that you should link to a page that is already in the code that does this!
Code
All code: formdemo.zip
To install, unpack, and run formdemo in the class VM:
wget https://homeostasis.scs.carleton.ca/~soma/webfund-2024f/code/formdemo.zip unzip formdemo.zip cd formdemo deno run --allow-net --allow-read formdemo.js
formdemo.js
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2024 Anil Somayaji
//
// formdemo.js
// for COMP 2406 (Fall 2024), Carleton University
//
// Initial version: Sept 25, 2024
//
// run with the following command:
// deno run --allow-net --allow-read formdemo.js
//
const status_NOT_FOUND = 404;
const status_OK = 200;
var state = [];
function MIMEtype(filename) {
const MIME_TYPES = {
'css': 'text/css',
'gif': 'image/gif',
'htm': 'text/html',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'png': 'image/png',
'txt': 'text/text'
};
var extension = "";
if (filename) {
extension = filename.slice(filename.lastIndexOf('.')+1).toLowerCase();
}
return MIME_TYPES[extension] || "application/octet-stream";
};
function template_header(title) {
return `<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<link rel="stylesheet" href="/style.css">
</head>
`
}
function template_notFound(path) {
return template_header("Page not found") +
`<body>
<h1>Page not found</h1>
<p>Sorry, the requested page was not found.</p>
</body>
</html>
`
}
function template_addRecord(obj) {
return template_header("Person just added") +
`<body>
<body>
<h1>Person just added</h1>
<p>Name: ${obj.name}</p>
<p>City: ${obj.city}</p>
<p>Country: ${obj.country}</p>
<p>Birthday: ${obj.birthday}</p>
<p>Email: ${obj.email}</p>
</body>
</html>
`
}
function template_listRecords(state) {
const pageTop = ` <body>
<h1>People Listing</h1>
<div>
<div></div>
<table>
<thead>
<th>Name</th>
<th>City</th>
<th>Country</th>
<th>Birthday</th>
<th>Email</th>
</thead>
<tbody>`;
const pageBottom = ` </tbody>
</table>
</div>
<form method="get" action="/">
<button type="submit">Home</button>
</form>
</body>
</html>`;
var row = [];
function rowMarkup(s) {
return "<td>" + s + "</td>";
}
for (let r of state) {
row.push("<tr>")
row.push(rowMarkup(r.name));
row.push(rowMarkup(r.city));
row.push(rowMarkup(r.country));
row.push(rowMarkup(r.birthday));
row.push(rowMarkup(r.email));
row.push("</tr>")
}
return template_header("List of Records") +
pageTop + row.join("\n") + pageBottom;
}
function listRecords(state) {
var response = { contentType: "text/html",
status: status_OK,
contents: template_listRecords(state),
};
return response;
}
async function routeGet(req) {
const path = new URL(req.url).pathname;
if (path === "/list") {
return listRecords(state);
} else {
return null;
}
}
async function addRecord(req) {
var body = await req.formData();
var obj = { name: body.get("name"),
city: body.get("city"),
country: body.get("country"),
birthday: body.get("birthday"),
email: body.get("email") };
state.push(obj);
var response = { contentType: "text/html",
status: status_OK,
contents: template_addRecord(obj),
};
return response;
}
async function routePost(req) {
const path = new URL(req.url).pathname;
if (path === "/add") {
return await addRecord(req);
} else {
return null;
}
}
async function route(req) {
if (req.method === "GET") {
return await routeGet(req);
} else if (req.method === "POST") {
return await routePost(req);
} else {
return null;
}
}
async function fileData(path) {
var contents, status, contentType;
try {
contents = await Deno.readFile("./static" + path);
status = status_OK;
contentType = MIMEtype(path);
} catch (e) {
contents = template_notFound(path);
status = status_NOT_FOUND;
contentType = "text/html";
}
return { contents, status, contentType };
}
async function handler(req) {
var origpath = new URL(req.url).pathname;
var path = origpath;
var r = await route(req);
if (!r) {
if (path === "/") {
path = "/index.html";
}
r = await fileData(path);
}
console.log(`${r.status} ${req.method} ${r.contentType} ${origpath}`);
return new Response(r.contents,
{status: r.status,
headers: {
"content-type": r.contentType,
}});
}
Deno.serve({ port: 8000, hostname: "0.0.0.0" }, handler);
static/index.html
<!DOCTYPE html>
<html>
<head>
<title>COMP 2406 Simple form demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>COMP 2406 Simple form demo</h1>
<div>
<p>Fill out your info</p>
<form method="post" action="/add">
<div>
<input id="name" type="text" name="name">
<label>Name</label>
</div>
<div>
<input id="country" type="text" name="city">
<label>City</label>
</div>
<div>
<input id="country" type="text" name="country">
<label>Country</label>
</div>
<div>
<input id="birthday" type="text" name="birthday">
<label>Birthday</label>
</div>
<div>
<input id="email" type="text" name="email">
<label>Email</label>
</div>
<button type="submit">Submit</button>
</form>
</div>
</body>
</html>
static/style.css
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}