Lab 4
Javascript II

DSC 106: Data Visualization

These labs dogfood

All slides and lab materials using open web technologies.

We are using inspire.js for these slides!

How does the Web work?

The URL

The Web’s primary innovation

https:// dsc106.com /labs/1/hello.html
What many people don’t realize is that the Web was primarily a usability innovation, and the URL was the centerpiece of that. Before the Web, we still had computers connected to the internet, that we could access remotely. However, accessing resources involved several steps: connecting to the remote computer, navigating to the directory where the resource was, and then downloading it. The URL encoded all information to retrieve a resource into a single string that could be copied, pasted, and even clicked. Let’s take a look at an example URL to a file on our course website. It starts with the protocol, which is the language that the client and server will use to communicate. This is usually `https` nowadays, which is a secure version of the `http` protocol. Then, we have the host or authority, which is the computer that hosts the resource we want to access. Finally, we have the path to the resource, which is the location of the resource on the host.

Local URLs Part 1

The file: protocol

file:// /Users/giorgianicolaou/Documents/dsc106/labs/1/hello.html
Let’s look at a different example. Download the file `hello.html` from the course website and double click it to get it to open in your browser. You will probably see something similar to this. This allows you to open files from your local filesystem and view them in a browser. Note that there is no host (since `file:` protocol URLs always refer to your own computer) and that the path starts from the root of your filesystem. This means that opening a malicious website this way could potentially access any file on your computer, which is why the `file:` protocol is very locked down today as it’s considered unsafe. We will use the `file:` protocol for now, but in future labs, we will learn how to run a _local server_ to view our files, which is a lot more flexible. Read more: - ["What is a URL?" on MDN](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL)

Understanding Relative Paths

This slide shows how different relative path syntaxes work: - `./` → current directory - `../` → up one directory - `/` → root of the website These are interpreted relative to the **location of the current file**, not the server.

The Web: Open by design

The Web was designed to be open: you can view the code of any website, and even modify it on the fly! There are two browser features for this: - _View Source_ shows you the HTML code of the page, as it was sent by the server. To use this you can right click on the page and select _View Source_, or press `Ctrl+Shift+U` or `Cmd+Shift+U`. - _Dev Tools_ allow you to inspect the current status of all code being used to create the page, and even modify it on the fly. You can also view all HTTP requests and responses, and even simulate different network conditions. Note that this requires reloading the page. To open dev tools: - On Mac: Press `Cmd+Shift+I` - On Windows: `F12` or `Ctrl+Shift+I` on Windows - From the menu bar: View → Developer → Developer Tools - In many browsers you can also right click anywhere on the page and select _Inspect Element_ Note that this can be a double edged sword: while it can be very educational to look at how existing websites are built, there are many, *many* websites that are not following best practices, or do things in a suboptimal way. Often this is a result of their age, or the tradeoffs they had to make (e.g. performance or browser support over maintainability).

The Web Platform

HTML

Structure


				<h1>Hello world!</h1>
				<p>This is my
					<em>first</em> web page! 🤯
				</p>
			

CSS

Presentation


				body {
					font: 100%/1.5 system-ui;
				}

				h1 {
					color: deeppink;
					font-size: 300%;
				}
			

JS

Behavior


				document.onclick = event => {
					alert(`You clicked at
					${event.x}, ${event.y}`);
				};
			
The collection of technologies that we use to create websites and web applications is called [The Web Platform](https://en.wikipedia.org/wiki/Web_platform#:~:text=The%20Web%20platform%20is%20a,Task%20Force%2C%20and%20Ecma%20International). It consists of three main technologies: HTML, CSS, and JavaScript. - HTML is used to specify the structure of the content, for example headings, paragraphs, lists, etc. - CSS is used to specify how content *looks*, for example colors, fonts, spacing, layout, etc. CSS will be the focus of the next lab, so we will learn a lot more about it then. - Finally, JavaScript is used to specify how content *behaves*, for example what happens when you click a button, or when you scroll the page, as well as for automation. - Ideally, these should be designed to be as independent as possible (separation of concerns). [CSS Zen Garden](https://csszengarden.com/) was built to demonstrate this point by showing how the same HTML can be styled in completely different ways by different CSS files, and significantly contributed to CSS’s adoption at the time. You can view a web page with the HTML, CSS, and JS from this slide at [`labs/1/first.html`](../first.html)

Similarties & differences

Error handling

HTML is forgiving

It tries to fix your mistakes


				<em>Emphasized
					<strong>Both
				</em> Important</strong>
			

				<em>Emphasized
					<strong>Both</strong>
				</em>
				<strong>Important</strong>
			

CSS is forgiving

It ignores your mistakes


				h1 {
					color: slategray;
					foobar: yolo;
				}
			

				h1 {
					color: slategray;
				}
			

JS is strict

It stops at the first mistake


				alert("Hello world!"
			
Uncaught SyntaxError: missing ) after argument list
In fact, this forgiving nature of HTML and CSS is core to the Web’s success. Attepmpts to define a stricter version of HTML (XHTML) failed miserably. This is also one reason why it’s generally a bad idea if your webpage depends on JS for essential content.

Dev Tools & Errors

Component-driven development

Traditional Architecture

Components give us a *different* separation of concerns than what we have seen so far (and arguably more pragmatic and scalable).

Basic Example Structure in JavaScript Modules


        📂 File Structure:
        index.html
        main.js
        modules/
            canvas.js
            square.js

        📌 canvas.js — Handles Canvas Setup
        function create(parent, width, height, id) {
            let wrapper = document.createElement("div");
            wrapper.id = id;
            let canvas = document.createElement("canvas");
            canvas.width = width;
            canvas.height = height;
            wrapper.appendChild(canvas);
            parent.appendChild(wrapper);
            return { context: canvas.getContext("2d"), id };
        }

        function createReportList(wrapperId) {
            let list = document.createElement("ul");
            document.getElementById(wrapperId).appendChild(list);
            return list.id;
        }

        📌 square.js — Handles Square Operations
        const name = "square";

        function draw(context, x, y, size, color) {
            context.fillStyle = color;
            context.fillRect(x, y, size, size);
            return { size, x, y, color };
        }

        function reportArea(listId, length) {
            let list = document.getElementById(listId);
            let item = document.createElement("li");
            item.textContent = `Square area: ${length * length}`;
            list.appendChild(item);
        }

        function reportPerimeter(listId, length) {
            let list = document.getElementById(listId);
            let item = document.createElement("li");
            item.textContent = `Square perimeter: ${length * 4}`;
            list.appendChild(item);
        }

        📌 main.js — Bringing It All Together
        import { create, createReportList } from "./modules/canvas.js";
        import { draw, reportArea, reportPerimeter } from "./modules/square.js";

        const parent = document.body;
        const { context, id } = create(parent, 400, 400, "canvas-wrapper");
        const listId = createReportList(id);

        let square = draw(context, 50, 50, 100, "blue");
        reportArea(listId, square.size);
        reportPerimeter(listId, square.size);
    
- `canvas.js` handles canvas setup and creates report lists. - `square.js` handles drawing squares and calculating their area/perimeter. - `main.js` brings it all together by importing functions and executing them. This modular approach improves maintainability, readability, and reusability.

Python vs JavaScript: Syntax Comparison


        📌 Function Definitions
        // JavaScript
        function greet(name) {
            return "Hello, " + name;
        }

        # Python
        def greet(name):
            return "Hello, " + name

        📌 For Loops
        // JavaScript
        for (let i = 0; i < 5; i++) {
            console.log(i);
        }

        # Python
        for i in range(5):
            print(i)

        📌 Conditionals
        // JavaScript
        if (x > 10) {
            console.log("Large");
        } else {
            console.log("Small");
        }

        # Python
        if x > 10:
            print("Large")
        else:
            print("Small")
    
This slide compares core syntax between Python and JavaScript for: - **Function definitions** - **For loops** - **Conditional logic** These are useful to understand the **structural similarities** and **syntax differences** when switching between the two languages.

Python vs JavaScript: More Syntax Examples


        📌 Importing Modules
        // JavaScript
        import fs from 'fs';

        # Python
        import os

        📌 Variable Declarations
        // JavaScript
        let count = 5;
        const name = "Alice";

        # Python
        count = 5
        name = "Alice"

        📌 Lists vs Arrays
        // JavaScript
        let fruits = ["apple", "banana", "cherry"];
        fruits.push("date");

        # Python
        fruits = ["apple", "banana", "cherry"]
        fruits.append("date")
    
- Both languages use a **list-like structure**: arrays in JavaScript, lists in Python. - Operations like `push()` in JavaScript and `append()` in Python are analogous. - Understanding these similarities helps when porting or comparing logic across the two.

Reactivity

If you’ve used a spreadsheet, you already know what reactivity is. The core idea of reactivity is that when a value changes, all dependent values are updated automatically.

CSS is reactive

As we have already seen, CSS is reactive. For example, if we update a CSS variable via JS, everything that depends on it (including other variables) is updated automatically.

JS is not reactive


				a:hover {
					background: gold;
				}
			

				<a href="#">Come here</a>
			

				<a href="#"
				   onmouseover="this.style.background = 'gold';"
				>Come here</a>
			

				<a href="#"
				   onmouseover="this.style.background = 'gold';"
				   onmouseout="this.style.background = '';"
				>Come here</a>
			
Remember this? As a part of CSS pseudo-classes are reactive: not only does the background here become `gold` when the link is hovered, but it also returns to its previous state automatically, when the link stops being hovered. However, when we use JS to do the same thing, we have to manually set the background back to its original value ourselves.

Implenting reactivity is hard

+ =

			<input type="number" id="input_a"> +
			<input type="number" id="input_b"> =
			<input type="number" id="input_c" disabled>
		

Reactivity with vanilla JS


			let a = 1, b = 2, c;
			input_a.value = a;
			input_b.value = b;

			input_a.addEventListener("input", e => {
				a = Number(input_a.value);
				updateC();
			});
			input_b.addEventListener("input", e => {
				b = Number(input_b.value);
				updateC();
			});

			function updateC() {
				c = a + b;
				input_c.value = c;
			}

			updateC();
		
The naîve approach to implementing reactivity ourselves is to add event listeners to everything that could possibly change, and update everything that may have been affected. This gets very complicated very fast, since we need to keep track of all the dependencies and update them all manually. [Live demo](https://codepen.io/leaverou/pen/OJGMyvQ)

Reactivity with vanilla JS Part 2


			let a = 1, b = 2, c;
			input_a.value = a;
			input_b.value = b;

			function render() {
				a = Number(input_a.value);
				b = Number(input_b.value);
				c = a + b;
				input_c.value = c;
			}

			render();
			input_a.addEventListener("input", render);
			input_b.addEventListener("input", render);
			document.body.addEventListener("input", render);
		
Because this is so tedious, what we end up doing instead is is bunching updates together and updating more things than we need to, often everything at once with a single function. Then, every time anything updates, we call that single function and update *everything*. With that approach, we could even listen to the `input` event on an ancestor, since it [*bubbles*](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling), a practice known as [event delegation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation). However, the more complex the app, the slower this practice becomes, as we are updating more than we need to. Also, this makees it impossible to implement *two-way binding*, i.e. also update the input when the value changes, not just the other way around. [Live demo](https://codepen.io/leaverou/pen/OJGMydE)

Reactivity boils down to recalculation

There is one clear theme in both previous examples: Implementing reactivity boils down to recalculating things. The tricky bit is knowing *what* to recalculate and *when*. The what is typically implemented with a [*dependency graph*](https://en.wikipedia.org/wiki/Dependency_graph). When a value changes, we recalculate everything that depends on it, and everything that depends on those, and so on. The when is typically implemented with *events* when it’s about updating data from user actions, and [accessors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors) and [proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) when it's about updating the UI from data.

What is a Web API?

Websites are for humans

APIs are for programs


			{
				"id": "cuisine-of-nepal-san-francisco",
				"name": "Cuisine of Nepal",
				"image_url": ...,
				"is_closed": false,
				"url": ...,
				"review_count": 303,
				"categories": [...],
				"rating": 4.5,
				"coordinates": {...},
				"transactions": [
					"delivery",
					"pickup",
					"restaurant_reservation"
				],
				"price": "$$",
				"location": {...},
				"phone": "+14156472222",
				"display_phone": "(415) 647-2222",
				"distance": 2502.5961202999997,
				"attributes": {
					"gender_neutral_restrooms": true
				},
				"data": {
					"reservation_openings": [
						"13:30",
						"13:45",
						"14:00"
					]
				}
			},
		

Async values


		let response = await fetch("https://api.github.com/users/giorgianicolaou");
		let json = await response.json();
		// Do stuff with json
	

		fetch("https://api.github.com/users/giorgianicolaou")
			.then(response => response.json())
			.then(json => {
				// Do stuff with json
			});
	
Many functions in JS cannot return a result within a reasonable amount of time. For example, [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) sends an arbitrary HTTP request and reads the response. This can take a long time. To avoid blocking execution, we use *asynchronous* functions, which return a [*promise*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) which will eventually have the value we want. In fact, using `fetch()` involves two promises: one for the response, and one for the JSON data.

Using Async and Await


		async function fetchUserData() {
			try {
			  const response = await fetch("https://api.github.com/users/giorgianicolaou");
			  const user = await response.json();
			  console.log("User data:", user);
		  
			  const reposResponse = await fetch(user.repos_url);
			  const repos = await reposResponse.json();
			  console.log("Repositories:", repos);
			} catch (error) {
			  console.error("Error:", error);
			}
		  }
		  
		  fetchUserData();