APIs(Application Programming Interfaces) are constructs made available in programming to allow developers to create complex functionality more easily. They abstract complex code away from users allowing more simple syntax.
APIs in client side of JavaScript are not part of the language itself, but built on top of the language. There are mainly 2 types
<canvas>
)Note : Difference between JS library and framework -> Control
Code interacts with APIs using one or more JS objects which serve as the container for the data the API uses and the functionality provided by API.
The following example shows how various objects related to audio API
is related and interacted to play, pause and control volume of mp3 file in web page.
//Web API therefore access by window object
//Audio context : Represents audio graph that can be used to mainpulate audio playing inside browser (has various functions to manipulate audio)
//Audio graph -> general process of how various audio nodes (ex : input, output) interact with each other
const AudioContext = window.AudioContext||window.webkitAudioContext;
//Create AudioContext instance to mainpulate our track
const audioCtx = new AudioContext();
//Create constants that refers to html elements
const audioElement = document.querySelector('audio');
const playBtn = document.querySelector('button');
const volumeSlider = document.querySelector('.volume');
//Create MediaElementAudioSourceNode which represents the source of audio within <audio> in html page
//access element by audioCtx object (method associated to audio context)
//Inputs audio tag to get access to audio source
const audioSource = audioCtx.createMediaElementSource(audioElement);
//Event handlers to toggle between play and pause when button is pressed
//Reset display back to beginning when song has finished playing
//Note : arrow functions define this differently -> refers to the scope where function is present
playBtn.addEventListener('click', function() {
//Check if context is in suspended state initially
//Activate autoplay
if(audioCtx.state === 'suspended') {
audioCtx.resume();
}
//If track is stopped, play it
//this refers to the button itself
if(this.getAttribute('class') == 'paused') {
audioElement.play();
this.setAttribute('class', 'playing');
this.textContent = 'Pause';
}
//If track is playing, stop it
else if(this.getAttribute('class') === 'playing') {
audioElement.pause();
this.setAttribute('class', 'paused');
this.textContent = 'Play';
}
});
//If track ends
audioElement.addEventListener('ended', function() {
playBtn.setAttribute('class', 'paused');
playBtn.textContent = 'Play';
})
//Gain object created to adjust the volume of audio fed through it
const gainNode = audioCtx.createGain();
//Event listener to change the volume by slider
volumeSlider.addEventListener('input', function() {
gainNode.gain.value = this.value;
})
//Connect the different nodes(input, volume etc) in the audio graph
//audio source -> voume -> destination
//audio node is connected to gain node to adjust volume, which is connected to destination node to be played in computer
audioSource.connect(gainNode).connect(audioCtx.destination);
AudioContext()
object created (audioCtx
-> rerpesents audio processing graph)audioSource
-> audio source nodegainNode
-> volume nodeWhen using an API, the entry point for the API should be clear. In the previous example by using audio API, AudioContext
object was the entry point which was needed to do any audio manipulation.
The DOMP API also has a simple entry point -> Its features are generated from the document
object. Ex)
const em = document.createElement('em'); // create a new em element
const para = document.querySelector('p'); // reference an existing p element
em.textContent = 'Hello there!'; // give em some text content
para.appendChild(em); // embed em inside para
The Canvas API relies on getting a context object to use to manipulate things. The context object is created by getting a reference to the <canvas>
element and then calling the HTMLCanvasElement.getContext()
method :
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
Then the properties and methods can be called by using the context object
The APIs come in pair with event handlers to take functional actions. Ex)
let requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';
let request = new XMLHttpRequest();
request.open('GET', requestURL);
request.responseType = 'json';
request.send();
request.onload = function() {
const superHeroes = request.response;
populateHeader(superHeroes);
showHeroes(superHeroes);
}
XMLHttpRequest()
constructoronload
functions specifies what we do with the response is returnedWeb APIs sometimes have additional security mechanisms in place. Ex) some of the more modern WebAPIs will only work on pages served over HTTPS due to them transmitting potentially sensitive data. Also some WebAPIs request permission to be enabled from the user once calls to them are made in your code.
Window
object. Functions includeNavigator
objectDocument
object. Used to manipulate info on HTML and CSS that comprises the document. Document currently loaded in each one of your browser tabs is represented by a document object model. This is a "tree structure" representation created by the browser that enables the HTML structure to be easily accessed by programming languages.
Each element of the document has its own entry and is called a node.
AJAX enables asynchronous request to be made by the client. Partial information of the web page can be requested and the server can response to it without the whole page being refreshed.
These technologies allow web pages to directly handle making HTTP requests for specific resources available on a server and formatting the resulting data as needed before it is displayed.
XmlHttpRequest
API is sent. Benefits of using AJAX
document.querySelector('#button').addEventListener('click', loadText);
function loadText() {
//Create XHR object
let request = new XMLHttpRequest();
//console.log(request.readyState); -> state = 0
//OPEN - type of request, url/file address, async
request.open('GET', 'texf.txt', true);
//console.log(request.readyState); -> state = 1
// onload : run when the load event fires (when the response has returned)
// Response data will be available in "response" property of XHR request object
request.onload = function() {
//Print text when text is found (request was successful)
if(this.status == 200) {
// console.log(this.responseText);
document.getElementById('text').innerHTML = this.responseText;
}
else if(this.status == 404){
//console.log('Not found');
document.getElementById('text').innerHTML = '404 Not Found';
}
}
//Sends request to server
request.send();
}
loadText()
is activatedrequest
object is instantiated from XMLHttpRequest()
classopen()
method specifies what HTTP request method to use to request the resource from the network, and what its URL isonload
is an event listener that executes when the response has returned -> Executes when the readyState
value reaches 4onreadystatechange
: Executes function regardless of state (starts from 1 and executes request and state gradually is processed)onprogress
: Executes on readystate value of 3request
, data received from the server is then transfered to the DOM elements and shown to usersJSON.parse()
to be able to convert the text format of JSON to objects that would be recognized by the web page.Note :
readyState
valuesopen()
open()
send()
establishedstatus
value fetch()
returns a promise, which resolves to the response sent back from the server — we use .then() to run some follow-up code after the promise resolves, which is the function we've defined inside it.
The following example shows how to print an array of student info stored in a JSON file in the web.
function getUser() {
fetch('user.json')
.then((response)=>(response.json()))
.then((data)=> {
let output = `<h2>Users</h2>`;
data.forEach(function(user){
output += `
<ul class="list-group mb-3">
<li class="list-group-item">ID : ${user.id}</li>
<li class="list-group-item">Name : ${user.name}</li>
<li class="list-group-item">EMail : ${user.email}</li>
</ul>
`
});
document.getElementById('output').innerHTML = output;
})
}
.json()
method takes the raw data contained in the response body and turns it into json..text()
or .blob()
.then()
to deal with the json object.Similar to fetch in the term that it is based on promise to deal with Http request and response. Different from fetch that :
It is also possible to use async/await
together with the axios methods to have better readability
Ex of GET and POST request from client side
//GET request
(async () => {
try {
const result = await axios.get('http://www.google.com');
console.log(result);
console.log(result.data);
} catch(error) {
console.log(error);
}
})();
//POST request
(async () => {
try {
const result = await axios.post('http://www.google.com', {
name : 'Yoonseo Han',
age : '22'
});
console.log(result);
console.log(result.data);
} catch(error) {
console.log(error);
}
})();
The main difference between GET and POST request is that POST sends data as a property to the server.
Function to dynamically control data from the HTML form
tags. Normally comes together with AJAX.
const formData = new FormData();
formData.append('name', 'Yoonseo Han');
formData.has('name'); //Returns true
formData.get('name'); //Returns Yoonseo Han
formData.delete('name');
Can store data in the form of key:value
by using the append()
method from the generated object.
Then the generated form data can be sent as the second parameter for the POST request to send it to server
const result = await axios.post('http://www.google.com', formData);
Are APIs that are provided by an external third party. Unlike Browser APIs that are built into the browser - you can access them from JS immediately, they are located on 3rd party servers and to access them from JS, you first need to connect to the API functionality and make it available on your page.
Ex) linking to a JavaScript library available on the server via a <script> element
<script src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"></script>
<link type="text/css" rel="stylesheet" href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css"/>
Third party APIs tend to use developer keys to allow developers access to the API functionality, which is more to protect the API vendor than the user.
Ex)
L.mapquest.key = 'YOUR-API-KEY-HERE';
Requiring a key enables the API provider to hold users of the API accountable for their actions. When the developer has registered for a key, they are then known to the API provider, and action can be taken if they start to do anything malicious with the API (such as tracking people's location or trying to spam the API with loads of requests to stop it working, for example).
Client-side storage consists of JS APIs that allow you to store data on client and then retrieve when needed. These were used on the purpose on
The requirement of client-side storage would mainly be due to the characteristics of HTTP of being stateless.
HTTP cookie is a small piece of data that a server sends to user's web browser.The browser may store the cookie and send it back to the same server with later requests. These days cookies arent used that much however is stil used for storing data related to user personalization and state, e.g. session IDs and access tokens
General structure
name=value; Expires=[Date]; Domain=[Domain]; Path=[Path]; [Secure]; HttpOnly
Set-Cookie
and Cookie
headersSet-Cookie
: Sends cookies from the serer to user agent with the following structure
Set-Cookie: <cookie-name>=<cookie-value>
Ex)
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
Then, with every subsequent request to the server, the browser sends all previously stored cookies back to the server using the Cookie header.
GET /sample_page.html HTTP/2.0
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Lifetime of a cookie can be defined in 2 ways
Expires
attributeSet-Cookie: id=yooncer00; Expires=Thu, 31 Oct 2021 07:28:00 GMT;
To ensure cookies are sent securely without access of uninted parties : Use Secure
and HttpOnly
attribute
Secure
: A cookie with the following attribute is only sent to server with an encrypted request over the HTTPS protocolo (not sent with HTTP). Insecure sites (with http:) can't set cookies with Secure
attribute
HttpOnly
: A cookie with the following attribute is inaccessible by JS and cant be modified using Document.cookie
. It is only sent to server
The Domain
and Path
attributes define the scope of a cookie : What URLs the cookies should be sent to
Domain
attribute
Specifies which hosts can receive a cookie. If unspecified, the attribute defaults to the same host that initially set the cookie, excluding subdomains. IF Domain
is specified -> subdomains are always included
Ex) Domain=mozilla.org
, then cookies are available on subdomains like developer.mozilla.org
Path
attrivute
Indicates a URL path that must exist in the requested URL domain's subfile to send the Cookie
header
Ex) Path=/docs
, the following /docs
, /docs/Web/
paths match
It is possible to create a new cookie via JS using the Document.cookie
property.
//Set cookie property name and value
//Set the expire date of cookie
function setCookie(name, value, expireDate) {
//escape() : encode a string to change to from that URL can recognize
let cookieStr = name + '=' + escape(value) +
((expireDate==null)?"":("; expires" + expireDate.toGMTString()));
document.cookie = cookieStr;
}
Characteristics of web storage
There are 2 tpes of web storage :
Session storage is created and destroyed together with the session. A session is the linked situation between the browser window and website.
Session storage persists data for as long as the browser is open, and the data is lost when the browser is closed. Therefore even though multiple windows open the same website with the same domain, as the windows are different there is a unique session storage for each window.
Local storage is created for each web server (regardless of window). The local storage is not cleared eventhough the window accessing the storage is closed. Also multiple windows share the same local storage therefore share data with each other.
When a web page is loaded on browser window, the session storage and the local storage is automatically defined in the local computer, with 2 objects to access them :
sessionStorage
, localStorage
localStorage.setItem("score", "A+");
localStorage["score"] = "A+";
let myScore = localStorage.getItem("score");
let myScore = localStorage["score"];
Note : If there is no key item with the following name, null is returned
localStorage.removeItem("score");
localStorage.clear();
To be able to access each item, we need the key value. Therefoe
let key = sessionStorage.key(ind);
following method returns the key value in the following index
Note : The order of items stored is not equal to the order the items were added to the storage
When there is a change in the web storage, the browser initiates an event and notices it to different windows that is connected to the following webpage.
-> A storage
event is initiated and sended to every other window to notice change of stored data
Properties of StorageEvent
key
: The string of the changed item's key. If event initiated due to clear()
, return null
newValue
: The value of the item(key) that has changed
oldValue
: The previous value of the item(key) before changed. If a new item was included, return null
storageArea
: Web storage object in which event happend
url
: Web page's url that caused event
//Listen for storage event -> when there is change in local or session storage
window.addEventListener("storage", storageEventListener);
//Event sent to another window (not the one opened right now)
function storageEventListener(event) { //event : Object of StorageEvent
let eventDetail = `key : ${event.key},
Old value : ${event.oldValue},
New value : ${event.newValue},
Storage area : ${event.storageArea},
url : ${event.url}
`;
console.log(eventDetail);
document.getElementById("textarea").innerHTML = eventDetail;
}
IndexedDB is a transactional database system which is a JavaScript-based object-oriented database. IndexedDB lets you store and retrieve objects that are indexed with a key.
The main structure of each IndexedDP :
Initially, the user would have to set and open up a database when the window is loaded (by using event hanlder).
window.onload = function() {
//Open database; it is created if it doesnt exist
//Creates a variable to open version 1 of a database with the name "notes_db"
//Database operations takes time : Therefore operations are asynchronous
//Create a request object then use event handlers to run code when request completes
let request = window.indexedDB.open('notes_db',1);
}
After setting up the database, it is refered by using an additional variable request
. The variable adds event listeners to respond the 3 events that may happen after request is made to catch the DB :
.onerror
: When database didn't open successfully.onsuccess
: when database opened successfully -> request returns Database.onupgradeneeded
: The following events will be updated every time the window is refreshed and loaded again.
Within the .onload
event handler, the following event handlers are added :
//onerror handler : When database didn't open successfully
request.onerror = function() {
alert('Database failed to open');
};
//onsuccess handler : when database opened successfully
//Request returns successfully
request.onsuccess = function() {
console.log('Database opened successfully');
//store the opened database object in the db variable
//db variable manipulates database
db = request.result;
//Run the function to display the data inside the DB
displayData();
};
//Update when change in database occurs : Define database structure
request.onupgradeneeded = function(event) {
//Grab a reference to the opened database
let db = event.target.result;
//Create a new object store inside our opened database
let obejctStore = db.createObjectStore('note_os', {keyPath : 'id', autoIncrement : true});
//Define what data items the objectStore will contain
obejctStore.createIndex('title', 'title', {unique:false});
obejctStore.createIndex('text', 'text', {unique:false});
console.log('Database setup complete');
};
To define the structue of the DB using object store, the following method was used inside the .onupgradeneeded
event listener
let obejctStore = db.createObjectStore('note_os', {keyPath : 'id', autoIncrement : true});
Note : For each record, object store can derive key from on of 3 sources : key generator, key path or explicitly specified value
autoIncrement
creates a unique number that is automatically incrementedkeyPath : 'id'
assigns the id property as the primary keyHowever combination of key generator + key path is also available(like the previous example shown).
Then within the object store, indexes(fields) are added. Indexes are a kind of object store used to retrive data from the reference object store by a "specified" property.
Index lives inside the reference object store and contains the same data, but uses a specified property as its key path instead of the reference store's primary key.
obejctStore.createIndex('title', 'title', {unique:false});
obejctStore.createIndex('text', 'text', {unique:false});
The syntax is
var myIDBIndex = objectStore.createIndex(indexName, keyPath, objectParameters);
unique
and multiEntry
unique
-> Whether index allows duplicate values for single keymultiEntry
-> Determine how method behabes when indexed property is an arrayAll operations in IDB are asynchronous, using promises where the IDP API uses requests. All data operations in IndexedDB are carried out inside a transaction with the following form :
1) Get database object
2) Open transaction on database
3) Open object store on transaction
4) Perform operation on object store
//First argument : List of object stores that the transaction will span (pass empty array if you want transaction to sapn all object stores)
//Second argument : Defines type of transaction (if empty : get read-only transaction)
let transaction = db.transaction(['note_os'], 'readwrite');
//call and access an object store that is currently in the database
//access through the returned transaction object
let objectStore = transaction.objectStore('note_os');
transaction
methodtransaction()
takes 2 arguments and returns a transaction object -> transaction object allows us to access the object store and modify itreadonly
(when does not change data but only get the data value) or readwrite
function addData(event) {
//prevent default
event.preventDefault();
//Grab the values entered into the form fields and store them in a temporary object to be inserted into DB
//Dont need to specify id value - auto generated
let newItem = {title : titleInput.value, text : textInput.value};
//Open transaction
let transaction = db.transaction(['note_os'], 'readwrite');
let objectStore = transaction.objectStore('note_os');
//Make request to add new item object to the object store
//Event handlers required for request when task is completed
let request = objectStore.add(newItem);
//When request is finished : eventhandlers
request.onsuccess = function() {
//When new object added to object store, clear the form : ready for another entry
titleInput.value='';
textInput.value='';
};
//When transaction has successfully finished,
transaction.oncomplete = function() {
console.log(`Transaction completed : Database modification finished.`);
//Update the display of data to show the newly added item
displayData();
};
transaction.onerror = function() {
console.log('Transaction not opened due to error');
};
}
To add new data inside the IDB, .add()
method is used in the following syntax :
someObjectStore.add(data, optionalKey);
To delete data, call the delete
method on the object store
someObjectStore.delete(primaryKey);
In the following example :
function deleteItem(event) {
//Retrieve name of the task we want to delete
//Convert it to a number before using it with DB : Because attribute is stored in string from (retrieved from DB)
//The event target points to the button itself -> paretnode is the list tag
let noteId = Number(event.target.parentNode.getAttribute('data-node-id'));
//Open transaction object and delete the task, finding it using the id we retrieved above
let transaction = db.transaction(['note_os'], 'readwrite');
let objectStore = transaction.objectStore('note_os');
//Delete the record with followin id from the DB
let request = objectStore.delete(noteId);
//Report the data item has been deleted from the DB
transaction.oncomplete = function() {
//Delete the parent of the button -> list item, so no longer displayed
event.target.parentNode.parentNode.removeChild(event.target.parentNode);
console.log(`Note ${noteId} deleted`);
//If list item is empty, display a notice message
if(!noteList.firstChild) {
let listItem = document.createElement('li');
listItem.innerText = 'No notes stored';
noteList.appendChild(listItem);
}
}
}
To read data, call the get
method on the object store.
someObjectStore.get(primaryKey);
A method to retrieve all of the data is to use a cursor. A cursor selects each object in an object store or index one by one.
A cursor is created by
someObjectStore.openCursor(optionalKeyRange, optionalDirection);
Then it returns a promise that resolves with a cursor object representing the first object in the object store or undefined
if there is no object.
//openCursor() method to open a request for a cursor
//Then chain event handlers to deal when cursor is successfully returned
objectStore.openCursor().onsuccess = function(event) {
//Get reference to the cursor (result of the openCursor() when successfully returned)
let cursor = event.target.result;
//If the cursor contains a record from the data store
if(cursor) {
//Create a list and display it by adding to HTML -> append inside the list
const listItem = document.createElement('li');
const h3 = document.createElement('h3');
const para = document.createElement('p');
listItem.appendChild(h3);
listItem.appendChild(para);
noteList.appendChild(listItem);
//Put the data from the cursor inside the h3 and para variable
h3.textContent = cursor.value.title;
para.textContent = cursor.value.text;
//Store the id of the data item inside an attribute of the list item
listItem.setAttribute('data-node-id', cursor.value.id);
//Create delete button and place it in each list
const deleteBtn = document.createElement('button');
listItem.appendChild(deleteBtn);
deleteBtn.textContent = 'Delete';
//Event handler so that when button is clicked, list is deleted
deleteBtn.onclick = deleteItem;
//Iterate to next item(record) in the cursor
cursor.continue();
} else {
//When list is empty, display message
if(!noteList.firstChild) {
const listItem = document.createElement('li');
listItem.textContent = 'No notes stored';
noteList.appendChild(listItem);
}
//No more cursor items to iterate
console.log('Notes all displayed');
}
}
When we call idb.open
: specifies the database version number in 2nd parameter. If version number is greater than version of existing databse, onupgradeneeded
is executed -> Allows to add object stores and indexes to database
UpgradeDB object has special oldVersion
property that indicates the version number of database existing in browser
var dbPromise = idb.open('test-db7', 2, function(upgradeDb) {
switch (upgradeDb.oldVersion) {
case 0:
upgradeDb.createObjectStore('store', {keyPath: 'name'});
case 1:
var peopleStore = upgradeDb.transaction.objectStore('store');
peopleStore.createIndex('price', 'price');
}
});
upgradeDb.oldVersion
has the value of 0case 1
creating a "price" index on "store" object store