Even More Javascript

This is lab explores even more crucial functionalities of javascript, some of which you won't see in any other language

Task 1

Just as in lab 1 follow the link below to open codesandbox. Fork the project then sign in.

Open Lab 7

These functions allow us to get immediate user input blocking all other actions on the page.
If you run the workspace at this point you shall see the following:

Notice the mark up on the page

Task 2

Let's add the following event handlers to the script tag of the page, press the button and observe the output.

index.html

  <script>
   function alertFun(){
    alert("Hello !");
   }

   function confirmFun(){
     let didConfirm = confirm("Are you sure about that?");
     if(didConfirm){
       alert("You confirmed :)");
     }else{
      alert("You did not confirm :(");
     }
   }

   function promptFun(){
    let val = prompt("What is your name?");
    alert(`You've entered: ${val}`);
   }

  </script>

The Browser Object Model is a browser-specific convention referring to all the objects exposed by the web browser. Web APIs which allow us to tap into the wider features of the web forms part of the BOM in addition to the Document Object Model (DOM) accessible via the document object.



The window object represents the BOM and lets us access other aspects of the browser such as the document or location via a property of window e.g. window.location or call them directly as a global object e.g. location.

Task 3

Execute the following and observe the output.

//get client data using navigator
console.log(window.navigator.cookieEnabled);
console.log(window.navigator.onLine);
console.log(navigator.appVersion);
console.log(navigator.userAgent)
console.log(navigator.platform);

//get window metadata using window
console.log(window.location.href);//get full url
console.log(window.location.protocol);
console.log(window.location.hostname);

function redirect(url){
  window.location.assign(url);//redirects the page to another url
}

window.onload = function(event){
   console.log("Page has loaded");
   //do other javascript stuff here
}

Using window.onload is a pattern which can benefit the performance of the application. It can be used to pause your javascript execution until the page has fully loaded.

//Global variables declared with var are automatically added to the window object
var bob = 'bob';
const sally = 'sally';
    
console.log(window.bob === bob);//true
console.log(window.sally === sally);//false

var document = 'hello this is my variable';
console.log(document);//give the DOM object instead 
console.log(window.document === document)//true because declaration ignored
   ...
   <script src="A.js"></script>
   <script src="B.js"></script>
</body>

The Document Object Model (DOM) refers to the hierarchy of HTML elements of the page. It is part of the BOM and can be accessed via window.document or directly via the document object.

Accessing DOM Elements

One of the methods we can use to access an element on the DOM is the querySelector() method. document.querySelector() will return the element that matches any valid css selector passed to it.

For example, we can access the span element on the page with the following.

const result = document.querySelector("#result");
console.log(result);

If the following is executed in the browser, the span element with the id result will be logged to the console.

However the result variable is not an html string but rather a HTML Element object.

DOM Manipulation

We can use the innerHTML property on HTML elements to change its html content on the page. We can also inject children elements by assigning strings written in html. For example the following code:

will inject the h2 with content "My span" to the page.

Accessing Styles, Classes & Attributes

We can use the properties of HTML Element objects to retrieve and manipulate the element's styles, attributes and classes using javascript.

console.log(result.style);//shows all the styles of the object

result.style.backgroundColor = 'lightgrey';
console.log(result.getAttribute('id'));//result

result.removeAttribute('id');//removes an attribute
result.setAttribute('id','result');//sets an attribute

result.classList.add('content');//adds a class
result.classList.remove('content');//removes a class

Task 4

Write and execute the following in the script tag.

const result = document.querySelector('#result');
result.innerHTML = '<h2>My Span</h2>';
result.style.color = 'blue';

As shown in section 1, we can bind javascript functions to DOM events using onevent attributes such as onclick. The code below will call the function myFun() when the button is clicked. ie a click event occurs on the button.

<input id="myBtn" type="button" onclick="myFun()" value="click me"/>

However we can use the addEventListener() method on HTML elements to attach event handlers at runtime.

Task 5

Add the above code to the page then pass the mouse over the button and click the button several times.

<input id="myBtn" type="button" onclick="myFun()" value="click me"/>

<script>
  function myFun(){
    alert("hello");  
  }

  function myFun2(){
    console.log("myFun2 called");
  }

 //receives the event parameter from addEventListener high order function
  function logEventType(event){
    console.log(event.type);
  }

  let myBtn = document.querySelector("#myBtn");

  //attach myFun2 in addition to myFun to the click event of myBtn
  myBtn.addEventListener("click", myFun2);

  //There are other events such as mouseover and mouseout
  
  //Any callback passed to addeventListener receives an event object
  myBtn.addEventListener("mouseover", logEventType);//logs 'mouseover'
  myBtn.addEventListener("mouseout", logEventType);// logs 'mouseout'
 //these events will fire when the mouse pointer hovers/passes over the button
</script>

You should get an alert on every click and in the console tab that each mouseover and mouseout event has been logged due to the event listeners attached to the button.

Exercise

Write markup and code in index.html to do the following;

  1. Render an image from https://www.placecage.com/ using the img tag.
  2. Create a function called ‘loaded()' which logs to the console ‘img loaded'.
  3. Attach the loaded() function to the "load" event of the image.

Javascript objects are always references. This means if a function receives an object as a parameter, it is really receiving a reference to the object.

When a function performs operations on an object those operations are applied to wherever the object is originally declared. Because of references, changes made to an object are applied to anywhere else the object is used.

let bob = {
  name: 'Bob',
  balance: 10
}

function add10(a){
  a.balance+= 10; // a is a pointer/reference to myObj
}

console.log(bob.balance);//10
add10(bob);
console.log(bob.balance);//20


As a result of this, when you assign a variable to an existing object, then both variables are the same reference.

If you want to make a copy to an object to avoid this can accomplish that with the following using Object.assign

let obj2 = myobj;

//obj2 and myobj are the same variable

obj2.name = "Shelly";

console.log(myobj.name);//Shelly

//if you need to create a new object and copy its values use Object.assign

let obj3 = {};
Object.assign(obj3, myobj);

obj3.name = 'Smith';

console.log(myobj.name, obj3.name);//Shelly, Smith

This is said to perform a shallow copy because any nested objects in myobj will have references to them in copy. It's best to avoid copying in the first place if you want to be able to create objects of a particular structure then implement a constructor function.

Task 5

Execute the following and observe how changing ob2 affects myobj

let obj2 = myobj;

//obj2 and myobj are the same variable

obj2.name = "Shelly";

console.log(myobj.name);//Shelly

//if you need to create a new object and copy its values use Object.assign

let obj3 = {};
Object.assign(obj3, myobj);

obj3.name = 'Smith';

console.log(myobj.name, obj3.name);//Shelly, Smith

The same also applies to arrays.

Task 6.1

Update Index html with the following markup. Notice the table body is given an id as that is the target for rendering data on the Document Object Model (DOM).

<!DOCTYPE html>
<html>
  <head>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <title>Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  </head>

  <body>
    <nav>
        <div class="nav-wrapper teal" style="padding-left: 10px;">
           <a href="#!" class="brand-logo">Ajax Demo</a>
        </div>
    </nav>
    <main class="row" style="padding: 40px">
         <table>
           <thead>
                <tr>
                   <th>#</th>
                   <th>name</th>
                   <th>Type</th>
                   <th>website</th>
                </tr>
                </thead>
                  <tbody id="result">
                                                
                  </tbody>
           </table>
     </main>
     <script>
     </script>
  </body>
</html>

We have utilized 3rd party css from https://materializecss.com/. To add some pleasing styling to our site.

The result is an empty table. Next we would want to write some javascript code to fetch the data then write it out to the table. This can be done in two functions; loadData() and writeTable().

First let's start with writeTable(). This should be familiar from the previous lab, writing html elements for every element in an array.

For every object in an array we want to display the id, name, brewery_type and website_url properties in the columns of the table. For example

// if a record looks like.
let record = {
   id: 1,
   name: "bob's"
   brewery_type: "fire",
   website_url:"https://bobs.com"
} 

//*note the use of backticks
//using string interpolation
//a row in the table would look like;

Let row = `<tr> 
    <td>${record.id}</td>
    <td>${record.name}</td>
    <td>${record.brewery_type}</td>
    <td>${recoard.website_url}</td>    
</tr>`;

We want to do this for every record in an array so the draw table function would do this in a for loop.

Task 6.2

Implement drawTable() in the script tag of

index.html.

function drawTable(records){
    let result = document.querySelector('#result');
    //add html code inside of result
    let html = '';// create html string
    for(let record of records){
        //build html string
        html += `<tr>
          <td>${record.id}</td>
          <td>${record.name}</td>
          <td>${record.brewery_type}</td>
          <td>${record.website_url}</td>    
        </tr>`;
    }
    result.innerHTML = html;//add html string to DOM
}

//testing the function
drawTable([{
   id: 1,
   name: "bob's",
   brewery_type: "fire",
   website_url:"https://bobs.com"
}]);


The result should look like

Now we have a function that can present elements of the array to the DOM, the next step is the fetch the data.

The core functionality that makes client side web application dynamic is Asynchronous Javascript and XML (AJAX). Whereby we use javascript code to allow the browser to make an XMLHttpRequest. When a response is received by the server we output the result to the page.

Task 6.1

Visit the link https://api.openbrewerydb.org/breweries/search?query=harry and view the result. You should see the following.

That url is said to be an Application Programming Interface (API) endpoint. What it simply means is that the server which resides at that address is programmed to only return data as opposed to html pages. Parameters can be specified in the url to affect the data being returned.

In this case the query parameter "query" is given the value "harry" hence brewery data is returned for breweries which partially match with "harry".

To make the data look more presentable we can have Javascript request this data and write it out into our web application.

Task 6.2

Implement the following function to fetch the data and pass it on to drawTable()

//function MUST be declared async
async function getData(url){
   try{
     let response = await fetch(url);//1. Send http request and get response
     let result = await response.json();//2. Get data from response
     drawTable(result);// 3. Do something with the data
  }catch(e){
      console.log(e);//catch and log any errors
  }
}
getData("https://api.openbrewerydb.org/breweries/search?query=harry")

The data should now be automatically rendered with the data it retrieved from performing an HTTP request.

The functions drawTable() and getData() are VERY IMPORTANT implementations which you would rely on for this course. Be sure to understand the pattern to apply it in the assignment, exams and otherwise.

In the last section, brewery data is requested as soon as the page is loaded. Often in web applications, we cannot anticipate the exact data needed by the user. Data may be requested dynamically with respect to some parameters supplied through user input.

You can view this example running at the following workspace.

Open Lab 7.1

Take a look at the updated getData() function.

      async function getData(url, renderFun){
        try{
          let response = await fetch(url);//1. Send http request and get response
          let result = await response.json();//2. Get data from response
          renderFun(result);// 3. Do something with the data
        }catch(e){
            console.log(e);//catch and log any errors
        }
      }

//get all brewery data on page load and render a table
getData("https://api.openbrewerydb.org/breweries", drawTable)

Note the updated drawTable() function

      function drawTable(records){
          let result = document.querySelector('#result');
          //add html code inside of result
          let html = '';// create html string
          for(let record of records){
              //build html string
              html += `<tr id="${record.id}">
                <td>${record.id}</td>
                <td>${record.name}</td>
                <td>${record.brewery_type}</td>
                <td><a href="#${record.id}" onclick="getData('https://api.openbrewerydb.org/breweries/${record.id}', drawDetails)" >View More Details</a></td>    
              </tr>`;
          }
          result.innerHTML = html;//add html string to DOM
      }

This concludes this lab on AJAX which you shall rely on heavily for this course, with the last two functions being the very core.

References & Additional Reading

JS Values vs References

12 Javascript Concepts

JS Classes vs Objects

HTML Element