w11 Client-side scripting

Fetch

2022-03-29T15:30:00-05:00

Agenda

  1. Revisit button click
  2. Add a result field
  3. Add an image
  4. Make an API call with fetch()
  5. Use the response from fetch() to manipulate elements.

Slides

https://comp426-2022-spring.github.io/slides/w11-00.html

HTML DOM Element setAttribute() - w3schools

Use Express to deliver HTML files - Chris Sev

Promises - MDN

Buttons and Forms

HTML DOM Button Object

HTML DOM Form Object

fetch

Introduction to fetch() - Matt Gaunt

How to Use the JavaScript Fetch API to Perform HTTP Requests - Ryan Glover

fetch() - MDN

Using the Fetch API - MDN

How to use Fetch with async/await - Dmitri Pavlutin

Notes

Below is the coin flip interface that we put together in class.

You can serve this page using browser-sync -sw, if you have placed it in a node package with browsersync installed in it. It will serve on port 3000.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<html>
	<head>
		<title>Demo Coin</title>
		<style>
			img#quarter {
				width: 100px;
			}
		</style>
	</head>
	<body>
		
<!-- A "coin" button -->
		<button id="coin">Flip?</button>
<!-- A status paragraph -->
		<p id="active"></p>
<!-- A result paragraph-->
		<p>Result: <span id="result"></span></p>
<!-- An image of a US quarter (North Carolina variant) -->
		<img src="coin.jpg" id="quarter">

		<script>
// Event listener for whatever is being clicked 
//			document.addEventListener("click", activeNow);
// Replace text in anything with "active" id
			// function activeNow() {
			// 	const active_now = document.activeElement
			// 	document.getElementById("active").innerHTML = active_now;
			// 	console.log(active_now)
			// }
// Button coin flip element
			const coin = document.getElementById("coin")
// Add event listener for coin button
			coin.addEventListener("click", flipCoin)
			function flipCoin() {
                fetch('http://localhost:5000/app/flip/', {mode: 'cors'})
  				.then(function(response) {
    			  return response.json();
  				})
				.then(function(result) {
					console.log(result);
					document.getElementById("result").innerHTML = result.flip;
					document.getElementById("quarter").setAttribute("src", result.flip+".jpg");
					coin.disabled = true
				})
//				let flip = "FLIPPED"
//				document.getElementById("coin").innerHTML = flip;
//				console.log("Coin has been flipped. Result: "+ flip)
			}
		</script>
	</body>
</html>

You will need to enable Cross-Origin Resource Sharing (CORS) server-side since your server will be listening on a different port (5000).

To do this, run npm install cors in your server package repository.

Then add the following to your server.js:

1
2
3
4
// Add cors dependency
const cors = require('cors')
// Set up cors middleware on all endpoints
app.use(cors())

Here are the coin images that I used:

coin heads tails

async/await and FormData

2022-03-31T15:30:00-05:00

Agenda

  1. Using async and await with fetch
  2. Use fetch to POST data to API
  3. Serve static html from Express

How to use Fetch with async/await - Dmitri Pavlutin

How to use fetch to POST form data as JSON to your API - Simon Plenderleith

Serving static files with Node and Express.js - Janith Kasun

Notes

Here is the working example for a simple form that sends a POST request to an API endpoint defined at http://localhost:5000/app/flip/coins/.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<html>
	<head>
		<title>Demo Coin</title>
	</head>
	<body>
		<div id="multi">
			<h1>Flip many coins</h1>
			<form id="coins">
				<label for="number">
					<strong>How many coins?</strong>
				</label>
				<input type="number" name="number" id="number">
				<input type="submit" value="Flip 'em!">
			</form>

			<h2>Results</h2>
				<p id="heads"></p>
				<p id="tails"></p>
		</div>
		<script>
			// Our flip many coins form
			const coins = document.getElementById("coins")
			// Add event listener for coins form
			coins.addEventListener("submit", flipCoins)
			// Create the submit handler
			async function flipCoins(event) {
				event.preventDefault();
				
				const endpoint = "app/flip/coins/"
				const url = document.baseURI+endpoint

				const formEvent = event.currentTarget

				try {
					const formData = new FormData(formEvent);
					const flips = await sendFlips({ url, formData });

					console.log(flips);
					document.getElementById("heads").innerHTML = "Heads: "+flips.summary.heads;
					document.getElementById("tails").innerHTML = "Tails: "+flips.summary.tails;
				} catch (error) {
					console.log(error);
				}
			}
			// Create a data sender
			async function sendFlips({ url, formData }) {
				const plainFormData = Object.fromEntries(formData.entries());
				const formDataJson = JSON.stringify(plainFormData);
				console.log(formDataJson);

				const options = {
					method: "POST",
					headers: {
						"Content-Type": "application/json",
						Accept: "application/json"
					},
					body: formDataJson
				};

				const response = await fetch(url, options);
				return response.json()
			}
		</script>
	</body>
</html>

To make this work, you will need to have the following body parser middleware (built into Express) enabled in your server.js script:

1
2
// Allow JSON body messages on all endpoints
app.use(express.json())

In addition, you will need this endpoint defined, which differs slightly from the versions that you might have developed for previous assignments:

1
2
3
4
5
6
// Flip a bunch of coins with one body variable (number)
app.post('/app/flip/coins/', (req, res, next) => {
    const flips = coinFlips(req.body.number)
    const count = countFlips(flips)
    res.status(200).json({"raw":flips,"summary":count})
})

And finally, if you place index.html into a subdirectory named www, you can add this line to your server script and it will serve the interface along with the API when you run node server.js:

1
2
// Serve static HTML files
app.use(express.static('./www'))