Tapping into the browser

Modern web browsers can do many things, especially on mobile devices. In this lab we shall explore some of the many application programming interfaces (API) for accessing various features of the browser. These are also called Javascript APIs, Device APIs, HTML 5 APIs, Open Web Platform APIs.

Task 1

Follow the link below to get started.

Open Lab 8

HTML 5 allows for some simple client side form validation without requiring any javascript at all!

Task 2


Enter the following markup into fetch.html and try to submit the form without fields, improper emails and unsafe passwords.

 <div class="row">
    <form id="myForm" class="col s12" >
      <div class="row">
        <div class="input-field col s12">
          <input name="username" type="text" minlength="8" required>
          <label for="username">Username</label>
        </div>
      </div>
      <div class="row">
        <div class="input-field col s12">
          <input name="password" type="password"  required pattern="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$">
          <label for="password">Password</label>
        </div>
      </div>
      <div class="row">
        <div class="input-field col s12">
          <input name="email" type="email" required>
          <label for="email">Email</label>
        </div>
      </div>
                
      <div class="row">
        <div class="col">
          <input type="submit" class="btn waves-effect waves-light blue" value="Sign Up" required/>
        </div>
      </div>
     
    </form>
  </div>

Run the workspace, navigate to fetch, then try submitting the form.


The browser should prevent the form from submitting by displaying an error message instead.

This is done via validation attributes such as:

The regular expression given to the password field would validate input if:

Now that the form input is valid we can send the data to an application server to be stored in a database. This will be done by performing AJAX but instead of getting data like in lab 2 we shall be posting data.

Firstly we shall implement a function called submit() which collects the data from a form.

The submit function is what we call an event handler. As it's intended to only be invoked in response to an event. In this case the event is the submission of the form.

Task 3.1

Add the following to fetch.html then try submitting the form with valid data.

//the Event object is passed to any event handler called via an event attribute eg onclick, onsubmit etc
function submit(event){
 event.preventDefault();//prevents page redirection
     
 //event target returns the element on which the event is fired upon ie event.target === myForm

 const myForm = event.target;
 const formData = new FormData(form);//get form data
 const data = Object.fromEntries(formData);//convert form data to object

 console.log(data);
}

//attach the submit function to the submit event of myForm    
document.forms['myForm'].addEventListener('submit', submit);

The form should submit and log the data to the console

Event handlers are often passed to HTML event attributes or addEventListener as callback functions. In either case the event handler is passed an Event object by the high order function (event attribute/ addEventListener).

Task 3.2

Secondly, we implement a postData() function which performs an HTTP request to send the data to a server.

async function postData(url, data){
  try{

     let response = await fetch(
       url, 
       {
            method: 'POST',
            body: JSON.stringify(data),//convert data to JSON string
          headers: { 'Content-Type':'application/json' }// JSON data
       },
     );//1. Send http request and get response
     
     let result = await response.text();//2. Get message from response
     console.log(result);//3. Do something with the message
   
   }catch(error){
     console.log(error);//catch and log any errors
   }
}

We now can update submit() to post the data to the following endpoint https://nmendezapps.firebaseio.com/users.json. The server at that url saves all data that is posted to it.

Task 3.3

Now update submit()in fetch.html to call postData()

//the Event object is passed to any event handler called via an event attribute eg onclick, onsubmit etc
function submit(event){
 event.preventDefault();//prevents page redirection
     
 //event target returns the element on which the event is fired upon ie event.target === myForm

 const myForm = event.target;
 const formData = new FormData(form);//get form data
 const data = Object.fromEntries(formData);//convert form data to object

 postData('https://nmendezapps.firebaseio.com/users.json', data);
}

//attach the submit function to the submit event of myForm    
document.forms['myForm'].addEventListener('submit', submit);

Now submitting the form would cause the data you entered to be viewable at

https://nmendezapps.firebaseio.com/users.json !

This first web api we will be covering is a familiar one. Fetch() allows us to perform HTTP requests with javascript. If we look into the code of scripts.js we can see how fetch is used to dynamically load other html files into the page.

Take a look at the navigate() function.

scripts.js

async function navigate(title, url){
  document.title = title;
  let content = document.querySelector('#content');
  if(url === null){
    content.innerHTML = "";
  }else{
    let response = await fetch(url);//fetch another page eg battery.html
    content.innerHTML = await response.text();
    executeScripts();
  }
}

This function receives a url to the other pages in the workspace, but instead of redirecting the browser to the page, it injects the page into the current browser window.

index.html

<section id="content">
</section>

The page is then injected into this content section of index.html. This allows us to navigate to the different pages without performing a redirect!

We can also manipulate the browser's history. This would enable back navigation for our users despite never leaving index.html. We achieve this by using the history object.

In index.html the list of links are children of an element identified by ‘menu'

index.html

<div id="menu" class="collection blue blue-text darken-3">
  <a href="/location.html" class="collection-item blue-text darken-3">Geolocation</a>
  <a href="/battery.html" class="collection-item blue-text darken-3">Battery Status</a>
  <a href="/events.html" class="collection-item blue-text darken-3">Custom Events</a>
  <a href="/broadcast.html" class="collection-item blue-text darken-3">Broadcast Channel</a>
  <a href="/bluetooth.html" class="collection-item blue-text darken-3">Bluetooth</a>
</div>

First an event handler was attached to the anchor tags on the page.

script.js

//event handler
function handleClick(event){
  event.preventDefault();
  event.stopPropagation();
  let a = event.target;//get the anchor tag element
  let text = a.text;//get text content of the anchor element
  let url = a.href;//get the url of the anchor element
  history.pushState({title:text, url: url}, null, a.href);//pass the url and text to the history
  navigate(a.text, a.href);//then navigate to page
}

const menu = document.querySelector('#menu');
menu.addEventListener('click', handleClick, false);

We add an event handler to whenever a link is clicked in the menu. The event handler handleClick() takes the text and the url of the clicked link and gives it to the history object.

Next, the handleBack() event handler is setup to set the url in the urlbar and the title of the page

scripts.js

function handleBack(event){
  //if no links were clicked pushState() is never called
  //as pushState() is never called there will be no data in event.state
  if(event.state == null){
    navigate('Web APIs', null);
  }else{
    //links were clicked before so we can get the text and url passed from handleClick()
    navigate(event.state.title, event.state.url);  
  }
}

window.addEventListener('popstate', handleBack);//attach event handler to back navigation

This is why back navigation works, observe that the page title is changing as well.

Geolocation allows us to get the location of the client, once they have granted permission. We call navigator.geolocation.getCurrentPosition() to request the users position.

If they have accepted it is passed to the success() function which writes it to the DOM. We also attach the locate() event handler to the #locateBtn.

location.html

  var options = {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 0
  };

  function success(pos) {
    var crd = pos.coords;
    document.querySelector("#result").innerHTML = `
      Your current position is: <br>
      Latitude : ${crd.latitude} <br>
      Longitude: ${crd.longitude} <br>
      More or less ${crd.accuracy} meters.
    `;
  }

  function error(err) {
    console.warn(`ERROR(${err.code}): ${err.message}`);
  }

  function locate(){
    navigator.geolocation.getCurrentPosition(success, error, options);
  }

  document.querySelector('#locateBtn').addEventListener('click', locate);

The success function receives a GeolocationPosition object which we name pos. From pos we can access latitude longitude as well as other properties. The options variable is said to be a configuration object; it sets configuration options when it is passed to getCurrentPosition() such as the timeout (ms).

If you click the button and accept the permission, your location would have been displayed however the repl environment blocks geolocation. You can download the site and try it on your own machine.

Battery status lets us view the clients battery level and monitor its charging state.

We use the BOM object navigator to get the battery object. From there we can get the battery level and whether it is charging. We pass the charge level to an html5 progress bar as a visualization.

battery.html

 <div id="result">
         <label> Level
            <progress class="progress blue col s6"  id="batbar" max="1"></progress>
          </level>
          <br>
          Status: <span id="state"></span>
</div>

Battery level will be a floating point number between 0 and 1 so the max value of the progress bar is set to 1 to reflect 100%. main() will set the value of the progress bar and write the charging state to the DOM after receiving the battery object.

battery.html

  async function main(){
    let battery = await navigator.getBattery();
    let batbar = document.querySelector('#batbar');
    batbar.value = battery.level; 
    document.querySelector('#level').innerHTML = (battery.level * 100)+'%';
    document.querySelector('#state').innerHTML = battery.charging ? "Charging" : "Not Charging"; //ternary operator (a shorthand if statement)
  }

  main();

getBattery() returns the battery object as a Promise which means that we must use await to get the object and the function must be declared as async.

This allows the battery level of the client to be displayed.

While we know how to create event handlers for events such as ‘click' or ‘hover' custom events allow us to create our own events and decide when they are triggered and event handlers on our elements. Click the custom events item in the menu.

We want the form to emit a custom event which the 4 cards on the bottom will respond to. The cards all belong to a class called .card-panel.

First we set up all elements with an eventHandler to our custom ‘my-Event' event and we define an event handler receiveMessage() which writes the data in the event to the card text.

events.html

//event handler  
function receiveMessage(event){
    let text = event.detail.text; //get text from custom event
    let element = event.target; //get element on which the event occurred on
    element.innerHTML = text;//set text of card to text from event
  }
 

//set up listeners
  let cards = document.querySelectorAll('.card-panel');
  //attach the event handler to all elements which belong the the class .card-panel
  for(let card of cards){
    card.addEventListener('my-Event', receiveMessage);
  }

Then we define our custom event and specify which elements would trigger the event and when it will be triggered using dispatchEvent().

We want to trigger the event when we submit a message from the form so we define this in sendMessage().

events.html

  function sendMessage(event){
    event.preventDefault();//prevent the page from refreshing
    let form = event.target;
    let text = document.querySelector('#message').value;
    
    //create custom event
    let myevent = new CustomEvent('my-Event', {
      detail:{
        text: text//set text in custom event
      }
    });

    //trigger custom event on all listening elements
    let cards = document.querySelectorAll('.card-panel');
    for(let card of cards){
      card.dispatchEvent(myevent);
    }
    //empty text field
    form.reset(); 
  }
//attach sendMessage() to submit event of form
document.forms['myform'].addEventListener('submit', sendMessage);

Everytime the form is submitted an event is triggered, all of the cards which are listening to the custom event respond by setting their text to the data submitted in the form.

This is a simple peer to peer messaging scheme between two pages of the same website whether they are in tabs, windows or iframes.

This is achieved by creating a BroadcastChannel object. To send messages we use the postMessage() method on the object.

broadcast.html

  function sendMessage(event){
    event.preventDefault();
    let messageInput = document.querySelector('#message');
    let message = messageInput.value;
    channel.postMessage(message);
    document.querySelector('#textarea1').value+=`Peer 1: ${message}\n`;
    messageInput.value = "";//empty text field
  }

To receive messages we set the onmesssage property to an eventHandler.

broadcast.html

channel.onmessage = function handleMessage(event){
    document.querySelector('#textarea1').value+=`Peer 2: ${event.data}\n`;
  }

Navigate to "Broadcast Channel" then click on the "Open Peer" button. This would open a page in another tab.

You can exchange messages between the tabs.

We can have a website scan and connect to bluetooth devices. This is done by using the bluetooth property of the navigator object.

bluetooth.html

async function search(){
    try{
      let device = await navigator.bluetooth.requestDevice({"filters":[{"services":["alert_notification"]}]});
      document.querySelector('#result').innerHTML = `
        Name: ${device.name}<br>
        ID: ${device.name}<br>
        Connected: ${device.gatt.connected}<br>
      `;
    }catch{
      alert('Paring Cancelled');
    }
  }

When clicking the search button the browser would start scanning for bluetooth devices.

setInterval() is a timing function whereby we can perform iteration but control the time interval between loops. It works by receiving a function and a number of milliseconds, the function would be called every time the specified number of milliseconds has passed.

interval.html

  <div class="col s12">
    <div class="row">
      <h2 id="result"></h2>
    </div>
    <div class="row">
      <input type="button" class="btn waves-effect waves-light blue" id="start" value="Start Clock" />
      <input type="button" class="btn waves-effect waves-light blue" id="stop" value="Stop Clock" />
    </div>
  </div>

  <script>
    let result = document.querySelector('#result');
    let start = document.querySelector('#start');
    let stop = document.querySelector('#stop');

    console.log(start, stop)
    
    let timer;
    
    //print the current time to the dom
    function printDate(){
      result.innerHTML = new Date().toLocaleTimeString();
    }
    
    function startTimer(){
       //execute printDate() ever second
       timer =  setInterval(printDate, 1000);
    }

    function stopTimer(){
      clearInterval(timer); // stops the timer
    }

    start.addEventListener('click', startTimer);
    stop.addEventListener('click', stopTimer);

  </script>

If you need to run the function once after a delay setTimeout() can be used instead. The function would be executed once after the specified time in milliseconds has passed.

Congratulations on making it to the end feel free to use these labs for javascript reference when building out your web applications.

References & Additional Reading

https://blog.bitsrc.io/11-chrome-apis-that-give-your-web-app-a-native-feel-ad35ad648f09

https://blog.bitsrc.io/10-useful-web-apis-for-2020-8e43905cbdc5

https://github.com/web-api-examples/web-api-examples.github.io

http://browserapis.wtf/

https://platform.html5.org/