Skip to content

csc123kazerouni/typescript-in-the-browser

Repository files navigation

Programming in the browser

In class, I alluded to the fact that the JavaScript language is "special" because, unlike other languages like Python or Java, it can be run inside pretty much every web browser.

In this module we'll see a simple example of writing TypeScript code and running it inside a web page.

This GitHub Codespace demonstrates an example, and is also a place where you can freely play around with this programming environment.

There is no "submission" associated with this Codespace, but feel free to commit and push if you would like to save your changes.

The HTML

First, open up index.html. The "entry" point to any website is usually named index.html.

Most of this file pretty much looks like the HTML files we saw in the last class. It has the <head> tag that contains "metadata" like the page title and a link to its (pretty basic) CSS stylesheet.

I want to draw your attention to line 15 in index.html. It says:

<script src="./script.ts" type="module"></script>

This line uses a <script> tag. The <script> tag is used to include TypeScript or JavaScript code in an HTML web page. The src attribute stands for "source", and refers to the path to the TypeScript code we want to include.

In this case, we want to include the file script.ts, which we expect to be found in the current folder (that's what the ./ means).

But hold on! Browsers can only read and run JavaScript code, not TypeScript code. What's the deal?

This particular project uses some special tooling to "intercept" the TypeScript code and turn it into JavaScript code before giving it to the browser.

So even though we wrote TypeScript code, with all its types and nice features, what the browser receives is "pure" JavaScript, which is what it understands. A win win!

The TypeScript

Let's turn our attention to script.ts. It contains no code right now. Let's change that.

Go ahead and add the following code to the file.

const message: string = "Hallo!";
const sum: number = 10 + 12;

The lines above should be familiar to you; they are regular TypeScript assignment statements.

Open up your Terminal, and run the following commands:

  1. First, run npm install to make sure all the dependencies are installed.
  2. Then, run npm start to start your website.

You should see a little pop-up with a URL showing you where your web page can be seen.

This "start" action runs a web server. This means that your web page is accessible through a URL/

The result is that our TypeScript code is going to run as soon as our HTML page loads.

So what's the deal? Why aren't we seeing any outputs or flashing lights or anything?

Well, the script.ts is not really doing anything "visible". It's just creating a couple of variables. Let's add two lines to script.ts:

const message: string = "Hallo!";
const sum: number = 10 + 12;

const sumMessage: string = `${message} The sum of 10 and 12 ${sum}.`;
alert(sumMessage);

The variable sumMessage stores a longer string message, using the ${...} syntax we learned about earlier this quarter. What's new here is the alert function.

This alert function is specific to the browser, i.e., you would not have been able to use it in your previous TypeScript labs. It's only now that our code is being run by the browser that it knows how to do things like alert.

So what exactly is alert? Well, re-run your code and find out! (Go back to the webpage and refresh it, though it may have already jumped the gun!)

What happened?

Hopefully, you see a window popup with the message "Hallo! The sum of 10 and 12 is 22.". This is the main utility of running TypeScript in the browser---we can now interact with the user interface, and use that ability to create interactive websites.

Trying something different

As I mentioned above, when you're running TypeScript in the browser, you have access to some special functions and variables that let you interact with what is seen in the browser.

The alert function is one example of this.

Another important item to know about is the document object. This is a giant object that represents, well, the document that is being displayed by the webpage. By modifying attributes of this object, you can affect what is seen on the webpage.

For example, add the following line to the bottom of script.ts.

document.body.style.backgroundColor = 'red';

Go back to the webpage.

Now, you should see the alert popup like before, and after you click "OK", the background color of the entire webpage should change.

Let's talk about what happened.

  • document, as I said, is an object representing the entire webpage.
  • The document object has a "child" object called body. In your HTML page, this is everything that comes inside the <body> ... </body> set of tags (i.e., everything that's actually visible on the page).
  • Since the body is something that is being displayed, it has a set of style attributes.
  • As we saw when we learned CSS in Week 1, the background color is an attribute that we can manipulate. We do this by giving a value to the backgroundColor attribute.
  • The value we gave it is the string 'red'. Our runtime environment (i.e., the browser) can recognise some common colours by their names (e.g., "red", "green") and also some uncommon colours (e.g., "burlywood").

And voila! The background color of the HTML page changed. In the following modules we'll experiment with some more powerful abilities we have when we run code in the browser.

Working with CORGIS datasets

This section is meant to demonstrate how you can work with a CORGIS dataset of your choosing.

While CORGIS provides a wonderful array of datasets about various topics, the structure in which it makes data available can be a bit tricky to work with. Specifically, I am talking about its JSON data — “JSON” stands for “JavaScript Object Notation”, and it is the data format we have been using all quarter.

I really don’t want the data format to stop you from exploring the topic you’re interested in. So this demo will show you how to work with a format that may not necessarily be similar to what we’ve seen this quarter.

Hopefully, the lessons we learn here can be used to work with datasets from any source, not just CORGIS!

An example dataset

I am going to pick a dataset more-or-less at random from the CORGIS website. For this example, let’s go with The Astronauts dataset. It contains publicly available data about astronauts before January 15, 2020.

An example record for this dataset is:

{
  "Profile": {
    "Astronaut Numbers": {
      "Overall": 1,
      "Nationwide": 1
    },
    "Name": "Gagarin, Yuri",
    "Gender": "male",
    "Birth Year": 1934,
    "Nationality": "U.S.S.R/Russia",
    "Military": true,
    "Selection": {
      "Group": "TsPK-1",
      "Year": 1960
    },
    "Lifetime Statistics": {
      "Mission count": 1,
      "Mission duration": 1.77,
      "EVA duration": 0.0
    }
  },
  "Mission": {
    "Role": "pilot",
    "Year": 1961,
    "Name": "Vostok 1",
    "Vehicles": {
      "Ascent": "Vostok 1",
      "Orbit": "Vostok 2",
      "Decent": "Vostok 3"
    },
    "Durations": {
      "Mission duration": 1.77,
      "EVA duration": 0.0
    }
  }
}

Each record in this dataset is actually a mission, not an astronaut. So some individual astronauts might appear multiple times in this dataset.

As you can see, the dataset contains nested records. That is, it contains records within records. From a TypeScript perspective, this means you would use the box bracket syntax ([]) to grab specific data from inside the record, possibly going a couple of levels deep into the object.

We can read this data with the following code. (The starter code I've given you for the final project contains something similar; you should replace the URL with whatever dataset you want to use.)

// Read the data as one giant string
const response = await fetch("https://corgis-edu.github.io/corgis/datasets/json/astronauts/astronauts.json");

// Parse the string into an array of objects
const fullMissionData = await response.json();

An example task

For example, suppose we have all the astronaut data and we want to find the average amount of EVA time that occurs per astronaut per mission. “EVA” stands for “extravehicular activity”. In our dataset, the EVA duration field represents the number of hours an astronaut spent outside their spacecraft in outer space (e.g., because they’re fixing something on the outside of the craft, they’re on the Moon, and so on).

We can write something like this (assuming that we have a variable called fullMissionData that contains all the records):

// From the array of objects, obtain an array of numbers
// representing each astronauts EVA time on each mission
const evaHours: number[] = fullMissionData.map(item => item['Mission']['Duration']['EVA duration']);

// Then compute the average like you're used to
const totalEVAHours: number = evaHours.reduce((a, b) => a + b, 0); // Or use a for loop
const averagePerAstronautPerMission: number = totalEvaHours / evaHours.length;

Using the dataset in Vega-lite

If we want to use the data in Vega-Lite, we’ll need to do some work to get it into the shape we want.

Recall how you draw charts in Vega-lite: You provide a data attribute (the data to be drawn), and you map individual fields in the data to specific visual channels. We do this by giving each encoding a "field" name that corresponds to a field in our data objects.

However, in our Astronauts data, EVA duration is a couple of levels deep in each object. So we can't simply provide a string to refer to the field we want.

So we will use TypeScript to “flatten” the object into the shape we want before plotting a chart.

Flattening an object for plotting in Vega-lite

Our goal is to plot, for each individual astronaut, the total amount of EVA time they logged.

So instead of working with the relatively complicated objects we currently get from the dataset, we can flatten each record into a much simpler object containing only the fields we care about. Specifically, the astronaut’s name and their EVA hours for the given mission.

Our newer simpler records will follow this interface:

interface AstronautEVAHours {
  name: string,
  evaHours: number
}

So are taking each complicated Mission record, and turning it into a simpler AstronautEVAHours record.

This sounds like a map operation.

We can write a function to prepare our new list.

const getNameAndEVAData = (fullMissionData):  AstronautEVAHours[] => {
  return fullMissionData.map((mission) => {
    return {
      name: mission['Profile']['Name'],
      evaHours: mission['Mission']['Durations']['EVA duration']
    }
  });
}

Once we’ve got this list, we can use it in a Vega-Lite chart like normal.

To do that, let's import some functions and interfaces that we'll need for Vega-Lite.

At the top of your file, add this:

import embed, { VisualizationSpec } from 'vega-embed';

Then, below all the code we've already written, add this code:

const chartData: AstronautEVAHours[] = getNameAndEVAData(data);

const vis: VisualizationSpec = {
  title: "EVA Hours of " + chartData.length + " astronauts.",
  data: {
    values: chartData
  },
  mark: 'bar',
  encoding: {
    x: {
      field: 'name',
      type: 'nominal'
    },
    y: {
      field: 'evaHours',
      type: 'quantitative',
      aggregate: 'sum'
    },
  }
}

embed('#chart', vis);

Go to your tab with your web page open. Do you see the chart? Nope!

That's because our last line (embed('#chart', vis)) tried to embed our visualization into the first element it found with the an id of chart.

However, no such element exists in our HTML file!

Head back to index.html and, before the <script> tag, add an empty <div> with the id chart. Note that we don't include the # when we set the id; we only use it when we're referring things that have that id.

<div id="chart"></div>

Now when you refresh the page, you should see a chart appear.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published