Skip to content

AshFetch’em- Gotta Try and Catch’-then all!

Time to dive back into scary JavaScript waters! Before doing so, let’s just make sure we are warmed-up for our swim!

Back to JavaScript variables again!

Lists/arrays in JavaScript

In JavaScript, we can store multiple values in a single variable using an array!

An array is a list of values separated by commas , and enclosed in square brackets [].

For example, an array of numbers can look like this:

let myArray = [1, 2, 3, 4, 5];

An array of strings can look like this:

let myStringArray = ["apple", "banana", "cherry", "date", "elderberry"];

Arrays can also store different types of values, like this:

let myMixedArray = [1, "apple", 3.14, true];

Also, arrays are zero-indexed, meaning the first element is at index 0, the second element is at index 1, and so on.

Accessing elements in an array

To access an element in an array, we use the index of the element in square brackets [].

Using the previous example:

let myArray = [1, 2, 3, 4, 5];
console.log(myArray[0]); // This will print 1
console.log(myArray[1]); // This will print 2

JavaScript Warm-up: Using Variables

Instead of hard coding the values, we can use variables to store the values. For example, instead of writing:

// Initialize the map
const map = new maplibregl.Map({
    container: 'map', // container ID
    style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1', // Your style URL
    center: [-118.4430,34.0691], // Starting position [lng, lat]
    zoom: 15 // Starting zoom level
});

We can write:

// Declare global variables 
const centerLngLat = [-118.444, 34.070];
const startingZoomLevel = 10;

// Initialize the map
const map = new maplibregl.Map({
    container: 'map', // container ID
    style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1', // Your style URL
    center: centerLngLat, // Starting position [lng, lat]
    zoom: startingZoomLevel // Starting zoom level
});

This is a little more work, but it is much easier to reuse the values and also change them later since they are all defined at the top! For example, if we wanted to change the map center, we can just change the value of centerLngLat instead of having to change the value in multiple places.

Finished warm-up code

js/init.js
// Declare global variables 
const centerLngLat = [-118.444, 34.070];
const startingZoomLevel = 10;

// Initialize the map
const map = new maplibregl.Map({
    container: 'map',
    style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1',
    center: centerLngLat,
    zoom: startingZoomLevel
});

function addMarker(lat,lng,title,message){
    let popup_message = `<h2>${title}</h2> <h3>${message}</h3>`
    new maplibregl.Marker()
        .setLngLat([lng, lat])
        .setPopup(new maplibregl.Popup()
            .setHTML(popup_message))
        .addTo(map)
    createButtons(lat,lng,title);
    return message
}

function createButtons(lat,lng,title){
    const newButton = document.createElement("button"); 
    newButton.id = "button"+title; 
    newButton.innerHTML = title; 
    newButton.setAttribute("lat",lat); 
    newButton.setAttribute("lng",lng); 
    newButton.addEventListener('click', function(){
        map.flyTo({
            center: [lng,lat],
        })
    })
    document.getElementById("contents").appendChild(newButton);
}

JavaScript Objects - Boxing-up our variables

An object is a unique variable that can store many other variables! Think of it as a big box where many other Lego pieces or even other boxes can be put inside!

let myJavaScriptObject = {"key_name": "value", "key_2_name":"value"}

Your object can look like this too:

    let myJavaScriptObject = {
        "key_name": "value", 
        "key_2_name":"value"
        };
Wait! Didn’t we see this somewhere?

Yep! It was in the GeoJSON we created!

The meaning behind GeoJSON

GeoJSON actually stands for “geographic” JavaScript Object Notation! 🤯

In a JavaScript object, each value has a key and a value.

The : symbol seperates the key from the value, like this:

let myObject = {"key":"value"};
  • Everything in an object is contained within the curly braces {}
  • Anything to left of the : is the key
  • Anything to right of the : is the value
  • New key-value pairs are separated by a comma, ,
  • ⚠ Warning ⚠! Never end an object with a ,!!!!

Accessing an object’s property

To access an object’s properties we use the . notation.

For example, myObject.key will return the value, which in this case is.. value!

No 🚀 spaces 🪐 in variable names!

You cannot use spaces in variable definitions like let my map;, so stick with camelCase or snake_case when naming varibles with multiple words. When defining keys in objects, you can use spaces, but it is not recommended.

If you do encounter a key with a space in it, like, let anObject = "my annoying key": "is this", you cannot use the . syntax to access it you must use this alternative method: anObject["my annoying key"]

⚽In-class Exercise #2 - Variables and console.log

Tasks

  1. Re-copy this week’s lab template with index.html and init.js
  2. Put the centerLngLat and startingstartingZoomLevel values into an object called mapOptions.
  3. Use console.log() to get the object to show up in the console in Firefox.

Reminder: Working with our Dev Console

In VS Code, start Live Server by clicking Go Live.

After Firefox runs, open the Console:

  • You can either right click anywhere on a page with the mouse and clicking on Inspect or press F12 on the keyboard.

Remember to think of the Console as the Command Line/Terminal for your browser.

Answer

Your code should look like the following:

    let mapOptions = {'centerLngLat': [-118.444,34.0709],'startingZoomLevel':5}
    const map = new maplibregl.Map({
        container: 'map', // container ID
        style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1', // Your style URL
        center: mapOptions.centerLngLat, // Starting position [lng, lat]
        zoom: mapOptions.startingZoomLevel // Starting zoom level
    });
  1. In the console, type in mapOptions (or whatever you chose to name your object) then press Enter.

  2. You should see your JavaScript object, mapOptions!

Reflection

Think about the benefits of having variables in an object, is it easier to read for you? Harder?

Knowing how to check the console will help us test our JavaScript code and even run functions and methods without leaving the browser!

Wait for the map to load with map.on('load')

Remember last lab where we discussed methods? Well, we can use a method called map.on('load') to run a function when the map is loaded!

This is VERY important because we want to make sure the map is loaded before we add anything to it, such as our markers or GeoJSON data!

Here is how it looks:

map.on('load', function() {
    // Add your code to run after the map is loaded here
});

Check the console

If you want to check if the map is loaded, you can use console.log() to see if the map is loaded!

map.on('load', function() {
    // Add your code to run after the map is loaded here
    console.log("Yay! The map is loaded!")
});

Go ahead and open the console to see if the message appears!

Finished ⚽Exercise #2 code

Make sure your code looks like the following before moving on:

js/init.js
let mapOptions = {'centerLngLat': [-118.444,34.0709],'startingZoomLevel':5}

const map = new maplibregl.Map({
    container: 'map',
    style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1',
    center: mapOptions.centerLngLat,
    zoom: mapOptions.startingZoomLevel
});

function addMarker(lat,lng,title,message){
    let popup_message = `<h2>${title}</h2> <h3>${message}</h3>`
    new maplibregl.Marker()
        .setLngLat([lng, lat])
        .setPopup(new maplibregl.Popup()
            .setHTML(popup_message))
        .addTo(map)
    createButtons(lat,lng,title);
    return message
}

function createButtons(lat,lng,title){
    const newButton = document.createElement("button"); 
    newButton.id = "button"+title; 
    newButton.innerHTML = title; 
    newButton.setAttribute("lat",lat); 
    newButton.setAttribute("lng",lng); 
    newButton.addEventListener('click', function(){
        map.flyTo({
            center: [lng,lat],
        })
    })
    document.getElementById("contents").appendChild(newButton);
}

map.on('load', function() {
    // Add your code to run after the map is loaded here
    console.log("Yay! The map is loaded!")
});

Time to fetch and then do something

To access data, we will use the JavaScript Fetch API to fetch our GeoJSON file and then add it to our map.

When we access the GeoJSON file with the Fetch API we then get many methods to use with it.

A fetch looks like this:

fetch("map.geojson")

Wait! No variable declaration?! 😢

Why do you think so?

Answer

The fetch API is actually a built-in function for JavaScript, much like console.log()!

Good? Let’s carry on then!

fetch actually does nothing by itself! It needs to do something with the data. Thus, fetch is almost always used together with the then method as follows:

fetch("map.geojson") //(1)! 
    .then(function aFunctionName(data){//(2)!
        return data.json()//(3)!
    })
    .then(function anotherFunctionName(data){ //(4)!
        // Do something with the data
        processData(data);
    });
  1. map.geojson is location of the GeoJSON file relative to our file. If you moved the file to a subdirectory called data, then you would have to make this data/map.geojson.
  2. Here is our first chain, we are trying to fetch our geojson file. We will call a generic function in here.
  3. For the next step we need a json, so we return the value as a json with the .json() method!
  4. This is the next then i.e. our second chain!
  5. This calls L.geoJSON() and adds our data to the map.

Catch-ing errors

If there is an error in the fetch or then methods, you can use the catch method to catch the error and do something with it.

Here is an example:

fetch("map.geojson")
    .then(function aFunctionName(data){
        return data.json();
    })
    .then(function anotherFunctionName(data){
        // Do something with the data
        processData(data);
    });
    .catch(function(error) {
        console.log("An error occurred: ", error);
    });

The catch method will catch any errors that occur in the fetch or then methods and log them to the console.

Anoynmous functions

Since our .then is a one-time call, we can actually avoid naming the function! This is because we will not be using it again!

So we can make our function anoymous by removing the name part of it and using the => symbol!

Here’s how the simpler fetch-then should look:

fetch("map.geojson") //(1)! 
    .then(response => { //(2)! 
        return response.json();
    })
    .then(data =>{ //(3)!
        // do something with the data
    });

Looks much better, right? Well… We can shorten it even more!!!

WHAT IS THIS => 😡?!!!

The => is a shortcut to define an anoynmous function and is called an arrow-function!

Going forward we will use the arrow-function because it is shorter, but if you want to use the more verbose function syntax (like function(data){}) you can.

Putting our fetch and then into the map.on('load')

Since we want to make sure the map is loaded before we add our GeoJSON data, we will put our fetch and then into the map.on('load') method!

Here is how it looks:

map.on('load', function() {
    fetch("map.geojson")
        .then(response => { 
            return response.json();
        })
        .then(data =>{
            // do something with the data
        });
});

Create a function to processData()

This function will loop through each of the data and apply various functions to it, such as adding markers or buttons. A function like this is useful as a helper function because it can separate what we want the website to do from the immediate data processing.

Here is how it looks:

function processData(results) {
    console.log(results);
}

map.on('load', function() {
    fetch("map.geojson")
        .then(response => { 
            return response.json();
        })
        .then(data =>{
            processData(data);
        });
});

Alright! Now the stage is set to talk about for-loops and arrays!

🏁Checkpoint

  • You should have a map.geojson file in the same directory as your index.html and init.js files.
  • You should have a map.on('load') method that fetches the map.geojson file and then calls a processData function.

Before moving on, check to see if JavaScript code looks like the following:

js/init.js
// declare variables
let mapOptions = {'centerLngLat': [-118.444,34.0709],'startingZoomLevel':5}

const map = new maplibregl.Map({
    container: 'map', // container ID
    style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1', // Your style URL
    center: mapOptions.centerLngLat, // Starting position [lng, lat]
    zoom: mapOptions.startingZoomLevel // Starting zoom level
});

function addMarker(lat,lng,title,message){
    let popup_message = `<h2>${title}</h2> <h3>${message}</h3>`
    new maplibregl.Marker()
        .setLngLat([lng, lat])
        .setPopup(new maplibregl.Popup()
            .setHTML(popup_message))
        .addTo(map)
    createButtons(lat,lng,title);
    return message
}

function createButtons(lat,lng,title){
    const newButton = document.createElement("button"); // (1)! 
    newButton.id = "button"+title; // (2)! 
    newButton.innerHTML = title; // (3)! 
    newButton.setAttribute("lat",lat); // (4)! 
    newButton.setAttribute("lng",lng); // (5)! 
    newButton.addEventListener('click', function(){
        map.flyTo({
            center: [lng,lat], //(6)!
        })
    })
    document.getElementById("contents").appendChild(newButton); //(7)! 
}


map.on('load', function() {
    fetch("map.geojson")
        .then(response => response.json())
        .then(data => {
            processData(data); // Call processData with the fetched data
        });
});

function processData(data) {
    console.log(data);
}