@ George Madeley @ Personal Studies @ 3/15/24
This is a collection of notes that I, George Madeley, took when taking the Codecademy JavaScript, Intermediate JavaScript, and JavaScript DOM courses. I do not take ownership of the material covered and these notes should only be used for educational purposes.
Section 2: Intermediate JavaScript
1 - JavaScript Interactive Website
2 - DOM Events with JavaScript
3 - Templating with Handlebars
When we write console.log() what we put inside the parathesis will get printed, or logged, to the console.
console.log();
There are type types of code comments in JavaScript:
- A single line comment will comment out a single line and is denoted with two forward clashes // preceding it:
// this is a comment
- A multi-line comment will comment our multiple lines and is denoted with /* to begin the comment, and */ to end the comment.
/*
This is a
multi-line
comment
*/
Data types are the classifications we give to the different kinds of data that we use in programming. In JavaScript, there are seven fundamental datatypes:
-
Number,
-
String,
-
Boolean,
-
Null,
-
Undefined,
-
Symbol,
-
Object
The first six are primitive data types.
An operator is a character that performs a task in our code. JavaScript includes the following operators:
-
Add +,
-
Subtract -,
-
Multiple *
-
Divide /
-
Remainder %
We can do string concatenation by using the following command:
console.log('wel' + 'come');
You can retrieve property information by appending the string with a period and the property name:
'Hello'.length;
JavaScript provides several string methods. We call, or use, these methods by appending an instance with:
-
A period,
-
The name of the method,
-
Opening and closing parenthesis.
'Hello'.toUpperCase();
JavaScript offers a lot of inbuilt objects. An example is the math object.
To declare a variable, we use the var keyword:
var myName = "Arya Stark";
There are a few general rules for naming variables:
-
Variable names cannot start with numbers,
-
Variable names are case sensitive,
-
Variable names cannot be the same as keywords.
The let keyword signal that the variable can be reassigned a different value:
let meal = 'lasagna';
A const variable cannot be reassigned because it is constant.
A const variable must be assigned on declaration.
We can insert, or interpolate, variables into strings using template literals. Template literal is wrapped by backticks `. Inside the template literal, you'll see a placeholder, ${myPet}. The value of myPet is inserted into the template literal.
console.log(`I am a pet ${myPet}.`);
If you ever need to check the data type of variable's value, you can use the typeof operator.
console.log(typeof unknown1);
The following is a JavaScript if statement:
if (true) {
...
}
Below is an if...else statement:
if (false) {
...
} else {
...
}
Below is a list of comparison operators:
-
Less than <
-
Greated than <
-
Less than or equal to <=
-
Greater than or equal to >=
-
Is equal to ===
-
Is not equal to !==
Below is a list of logical operators:
-
And &&
-
Or ||
-
Not !
A false value is one of the following:
-
0
-
Empty strings "" or '',
-
Null
-
Undefined,
-
NaN or not a number
We can use truthy and falsy for variable assignments:
let variable = anotherVariable || 'something';
If anotherVariable is a false value, variable will be set to 'something'.
We can use a ternary operator to simplify an if...else statement:
if (isNightTime) {
...A
} else {
...B
}
Th above if statement will then become the following:
isNightTime ? ...A : ...B;
...A occurs when the condition is true and ...B occurs when the condition is false.
A switch statement provides an alternative syntax that is easier to read and write:
switch (groceryItem) {
case 'tomato':
console.log('Tomatoes are $0.49');
break;
case 'lime':
console.log('Limes are $1.49');
break;
case 'papaya':
console.log('Papayas are $1.29');
break;
default:
console.log('Invalid item');
break;
}
There are many ways to create a function. One way to create a function is by using a function declaration:
To call a function in your code, you type the function name followed by parentheses.
getWorld();
When you declare a function, they are called parameters. When you call a function, they are called arguments.
Parameters are able to have default arguments.
function greeting(name='stranger') {
...
}
A function needs to return a value. To do this, we use the keyword return.
We can declare a function inside an expression:
const calculateArea = function(width, height) {
return width * height;
}
To invoke the function, we perform the following:
calculateArea(arg1, arg2);
We can also declare functions using an arrow.
const rectangleArea = (width, height) => {
let area = width * height;
return area;
}
If there is only 1 parameter, brackets aren't required.
If there function is on one line, the curly brackets are not required and neither is return.
const sumNumbers = number => number + number;
Scope defines where variables can be accessed or referenced.
A block is the code found inside a set of curly brackets {}. Blocks help us group one or more statements together and serve as an important structural marker for our code.
Scope is the context in which our variables are declared. In global scope, variables are declared outside of blocks.
When a variable is defined inside a block, it is only accessible to the code within the curly braces {}. There are also known as local scope.
Always using global scope causes the following problems:
-
Spaces fills up quickly as the variables are stored there until the program finishes despite not being used.
-
At risk to change from malware.
Scope pollution is when we have too many global variables that exist in the global namespace, or when we reuse variables across different scopes.
We can write a list in JavaScript using arrays.
let newYearsResolutions = [ 'Keep a journal', 'Take a falconry class', 'Learn to juggle' ];
An array literal creates an array by wrapping items in a square bracket [].
Arrays can store any data type.
['element', 10, true];
We can access individual items using their index. Arrays in JavaScript start at the 0^th^ index.
console.log(array[6]);
We can also access strings like arrays.
string = 'Hello!';
console.log(string[3]);
We can update elements in arrays by using the following code:
array[6] = 'New Value';
Even is an array in const, we can still change the values in the array, just not the array itself.
The .length property returns the number of items in the array.
array.length;
The .push() allows us to add items to the end of the an array.
itemTracker.push('item3', 'item4');
// itemTracker = ['item1', 'item2', 'item3', 'item4']
The .pop() method removes the last item of an array. .pop() returns the last element.
itemTracker.pop();
// itemTracker: ['item1', 'item2', 'item3']
When an array contains another array, it is known as a nested array:
const nestedArray = [1, 2, [3, 4, [5, 6]]];
To index a nested array, we use the following:
nestedArray[1][0];
The typical for loop contains an iterator variable that usually appears in all three expressions. A for loop example is below:
for (let counter = 0; counter < 4; counter++) {
console.log(counter);;
}
To go backwards, we simply use counter--.
for (let counter = 4; counter > 0; counter--) {
console.log(counter);
}
A while loop repeats the code inside of it indefinitely until it is told to stop by a predefined condition.
while (counter < 4) {
console.log(counter);
counter++;
}
In some cases, we want our code to run at least once and then loop on a specific condition. This is where do...while comes in.
do {
counter++;
console.log(counter);
} while (counter < 10);
The break keyword breaks the current loop disregarding whether the conditions of the loop have been met.
Higher order functions are functions that acct other functions as arguments and/or return functions as output.
What if we wanted to rename the function without sacrificing source code?
const busy = thisIsAFunction;
busy();
Make sure the function does not have parenthesis
Functions can act like objects. Functions contain properties and methods we can utilise.
busy.name;
That .name property returns the original name of the function.
With callbacks, we pass in a function itself by typing the function name without the parenthesis (as that would evaluate to the result of calling the function).
const timeFuncRuntime = funcParameter => {
...
}
const groceries = [ 'milk', 'eggs', 'bread', 'cheese', ];
groceries.forEach((item) => {
console.log(item);
});
.forEach() takes an argument of callback function. It loops through the array and executes the call back function for each element. During each execution, the current element is passed as an argument to the callback function. The return value of .forEach() will always be undefined. Below is another variation:
groceries.forEach(item => console.log(item));
When .map() is called on an array, it takes an argument of a callback function and returns a new array:
const numbers = [1, 2, 3, 4, 5];
const bigNumbers = numbers.map(number => {
return number * 10;
});
.filter() returns a new array. However, it returns an array of element after filtering out certain elements from the original array.
The callback function needs to return true or false to se if the array item has passed the filter.
const shortWords = words.filter(word => {
return word.length < 6;
});
Called .findIndex() on an array will return the index of the first element that evaluates to true if the callback function.
const lessThanTen = jumbledNums.findINdex(num => {
return num < 10;
});
If no element satisfies the condition, it will return -1.
The .reduce() method returns a single value after iterating through the elements of an array, thereby reducing the array.
const summedNums = numbers.reduce(
(accumulator, currentValue) => {
return accumulator + currentValue;
});
The accumulator will always be the first number in the array unless stated otherwise.
const summedNums = numbers.reduce((accum, value) => {
...
}, 100);
In the above example, the accumulator has been given the starting value of 100.
Objects can be assigned to variables just like any JavaScript type. We use curly braces {}, to designate an object literal.
let spaceShip = {};
We fill an object with unordered data. This data is organised into key-value pairs. A key is a literal name that points to a location in memory that holds a value.
let spaceShip = {
name: "Millennium Falcon",
maxSpeed: 1200,
maxCrew: 4,
};
We can access an objects properties by using the '.' Notation.
spaceShip.name;
- If we try to access a property that does not exist, we will get undefined.
The second way we can access a key's value is by using bracket notation:
spaceShip["maxSpeed"];
We must use bracket notation when access keys that have numbers, spaces, or special characters in them.
Objects are mutable. We can use either '.' or '[]' notation along with '=' to add ned key-value pairs to an object
spaceShip.maxSpeed = 1300;
spaceShip["maxCrew"] = 5;
We can also delete a property with the delete operator:
delete spaceShip.maxSpeed;
const spaceShip = {
invade: function() {
...
},
};
We can include methods in our object literals by creating ordinary, comma separated key-value pairs. Object methods are involved by appending the objects name with the dot operator:
spaceShip.invade();
A nested object is an object inside of an object. We can chain operators together to access nested properties:
spaceShip.nanoElectronics['backup'].battery;
Objects are passed by reference:
Pass By Reference point to the address.
Pass By Value creates a clone.
for...in will execute a given block of code for each property in an object.
for (let crewMember in spaceShip.crew) {
console.log(`${spaceShip.crew[crewMember].name}`);
}
Let's say we have a method which prints an objects property. If we run the method, nothing will print. To solve this, we use the this keywork on the property.
The this keyword does not work with arrow functions as the this keyword refers to the function stead of the object.
All attributes are mutable but what if we do not mean them to change? These is no inbuilt keyword, instead, we begin the variable name with an underscore to signal to the programmer not to change the variable value:
_amount: 100
Getters are methods that get and return the internal properties of an object.
get fullName() {
if (this._amount > 90) {
return 'SpaceShip';
} else {
return 'Alien Ship';
}
}
Setters can be used to reassign values of existing properties within an object.
Factory functions can be used to create multiple instances of an object quickly.
const monsterFactory = (name, age) => {
return {
name: name,
age: age
};
}
The key and variable are the same as in the example above, we can use the shorthand below:
const monsterFactory = (name, age) => {
return {
name,
age
};
}
In destructed assignment, we create a variable with the name of an objects key that is wrapped in curly braces {} and assign it to the object.
const { residence } = vampire;
Classes are a toll that developers use to quickly produce similar objects.
JavaScript calls the constructor() method every time it creates a new instance of a class.
class Surgeon {
constructor(name, department) {
this._name = name;
this._department = department;
}
}
An instance is an object that contains the property names and methods of a class, but with unique property values.
const Halley = new Surgeon('Halley', 'Cardiovascular');
Class method and getter syntax is the same as it is for objects except you can not include commas between methods.
When multiple classes share properties or methods, they become candidates for inheritance. With inheritance, you can create a parent class with properties and methods that multiple child classes share. The child classes inherit the properties and methods from their parent class.
class Cat extends Animal {
constructor(name, usesLitter) {
super(name);
this._usesLitter = usesLitter;
}
}
From the example above, the keyword extends is used to inherit from Animal. There is also the keyword super in the constructor method. This is to pass the required data to the parent constructor.
You must call super() first!
Static methods are only accessible through the class, not an instance of a class. See the example below:
Cat.generateName(); // YES
bryceTheCat.generateName(); // NO
A runtime environment is where your program will be executed. It determines what global objects your program can access, and it can also impact how it runs.
The most common runtime environment is a browser. In HTML, we can use the <script> tags to encapsulates JavaScript code:
<script>window.alert('Hello World!')</script>
Applications created for and executed in the browser are known as front-end applications.
The Node.js runtime environment was created to execute JavaScript code without a browser. Thus, enabling full-stack (front-end to back-end) applications using JavaScript.
To execute the JavaScript code in Node.js first make sure you have Node.js setup on your computer. Then open the termina and run the following command:
$ node my-app.js
/path/to/working/directory
Modules are reusable pieces of code in a file that can be exported and then imported for use in another file. A modular program is one whose components can be separated, used individually, and recombined to create a complex system.
In JavaScript, there are two runtime environments, and each has a preferred module implementation:
-
The Node runtime environment and the mode.exports and require() syntax.
-
The browser runtime environment and the ES6 import/export syntax.
To make the modules available to other files we use the following code:
module.exports.celciusToFahrenheit = this.celciusToFahrenheit;
module.exports.fahrenheitToCelcius = function(f) {
return (f - 32) * 5 / 9;
}
In the example above, we can either export a pre-existing function or export a function that we immediately declare.
The require() function accepts a string as an argument. That string provides the file path to the module you would like to import.
const converters = require('./converters.js');
const freezingPointF = converters.celciusToFahrenheit(0);
You can use object destructing to extract only the needed functions:
const { celciusToFahrenheit } = require('./converters');
const freezingPointF = celciusToFahrenheit(0);
To load a JavaScript module into HTML, we use the following code:
<script type='module' src='.JavaScript.js'></script>
To export a JavaScript module, we use the following ode:
export { functionA, functionB };
To import JavaScript modules, we use the following code:
import { functionA, functionB } from './example.js';
We can rename functions when we import them:
import { functionA as newName } from '...';
Every module also ahs the option to export a single value to represent the entire module called the default export.
export { resources as default };
Or
export default resources;
To import the default module:
import importedResources from './example.js';
The default export is an object, the values inside cannot be extracted until after the object is imported, like so:
const { valueA, valueB } = resources;
An asynchronous operation is one that allows the computer to "move on" to other tasks while waiting for the asynchronous operation to complete.
Promises are objects that represent the eventual outcome of an asynchronous operation. A promise object can be found in one of these three states:
-
Pending -- initial state
-
Fulfilled -- the operation ahs completed successfully, and the promise now has a resolved value.
-
Rejected -- the operation has failed, and the promise has a reason for the failure.
We refer to a promise as settled when it is no longer pending.
To create a promise object, we use the new keyword and the promise constructor method:
const myFirstPromise = new Promise(executorFunction);
The promise constructor takes a function parameter called the executor function which runs automatically when the function is called.
The executor function has two parameters:
-
resolve(),
-
reject().
The resolve() and reject() functions aren't defined by the programmer. When the promise constructor runs, it will pass its own resolve() and reject() functions into the executor function.
Basically, resolve() takes an argument and change the promises status to fulfilled. reject() does the same but changed the promises status to reject.
const myExecutor = (resolve, reject) => {
if (someCondition) {
resolve('I resolved!');
} else {
reject('I rejected!');
}
}
setTimeOut() is a Node API that uses callback functions to schedule tasks to be performed after a delay. setTimeOut() has two parameters: a callback function and a delay in milliseconds.
const delayedHello = () => {
console.log("Hello");
}
setTimeout(delayedHello, 1000);
The example above won't run the function until at least one second has gone by.
Below is how we will be using setTimeOut() to construct asynchronous promises:
const returnPromiseFunction = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {resolve('I resolved!')}, 1000);
});
}
Promise objects come with an aptly names .then() method. It takes two callback functions as arguments. We refer to these as handlers.
-
The first handler is the success handler (aka 'onFulfilled'),
-
The second handler is the failure handler (aka 'onRejected').
const prom = new Promise((resolve, reject) => {
...
});
const handleSuccess = (resolvedValue) => {
...
}
prom.then(handleSuccess);
Separation of concerns means organising code into distinct sections each handling a specific task. The .catch() function takes only one argument, onRejected. In case of a rejected promise, this failure handle will be invoked with the reason for rejection.
prom.then((resolvedValue) => {
...
}).catch((rejectionReason) => {
...
});
The process of chaining promises together is called composition. In order for our chain to work properly, we had to return the second promise, this ensures that the return value of the first .then() was our second promise.
To maximise efficiency, we should use concurrency, multiple asynchronous operations happening together. With promises, we can do this with the function Promise.all().
Promise.All() accepts an array of promises and returns a single promise.
The async...await syntax allows us to write asynchronous code that reads similarly to traditional synchronous, imperative programs.
The async keywork is used to write functions that handle asynchronous actions:
async function myFunc() {
...
};
myFunc();
async functions always return a promise. It will return in one of three ways:
-
If there is nothing returned, it will return a promise with the value undefined.
-
If there is a non-promise value, it will return a promise resolved to that value.
-
If there is a promise, it will return the promise.
The await keyword can only be used inside an async function.
await is an operator: it returns the resolved value of a promise. await halts, or pauses, the execution of our async function until a given promise is resolved.
async function myFunc() {
let resolvedValue = await myPromise();
console.log(resolvedValue);
}
myFunc();
We can also chain multiple promises together:
async function myFunc() {
let firstValue = await returnFirstPromise();
console.log(firstValue);
let secondValue = await returnSecondPromise();
console.log(secondValue);
}
myFunc();
With async...await, we use try...catch for handling errors.
try {
...
} catch (error) {
...
}
What is we have multiple promises that are independent of one another?
async function concurrent() {
const firstPromise = firstAsyncFunction();
const secondPromise = secondAsyncFunction();
console.log(await firstPromise, await secondPromise);
}
We await the result of the promises this allows the promises to run in parallel.
We can use Promise.All() to allow promises to run concurrently.
async function asyncPromAll() {
const resultArray = await Promise.all([
asyncTask1(),
asyncTask2(),
asyncTask3(),
]);
for (let i = 0; i < resultArray.length; i++) {
console.log(resultArray[i]);
}
}
Asynchronous JavaScript and XML (AJAX), enables requests to be made after the initial page load. Similarly, the XMLHTTPRequest (XHR) API, can be used to make many kinds of requests and supports other forms of data.
We will now outline how to create a XHR GET request.
-
Create the XMLHttpRequest object using the new operator
-
Save a URL to a const.
-
Set the response type of xhr to equal 'JSON'.
-
Set the xhr.onreadystatechange event handler equal to an anonymous arrow function.
-
Below the function, call the .open() method on the xhr object and pass it 'GET' and url as arguments. .open() creates a new request and the arguments passed in determine the type URL of the request.
-
The send!
const xhr = new XMLHttpRequest();
const url = 'https://jsonplaceholder.typicode.com/posts';
xhr.responseType = 'json';
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log(xhr.response);
}
}
xhr.open('GET', url);
xhr.send();
A query string is separated from the url using a ? character. After ?, you can create a parameter which is a key value pair joined by a =
'htpp://api.datamuse.com/words?key=value'
If you want to add another parameter you will have to use the & character to separate your parameter.
We will now outline how to create a XHR POST Request.
-
Create a new XMLHttpRequest
-
Save a URL to a const called url.
-
Create a new const called data. Use JSON.Stringify() to convert the data into a string.
-
Set the responseType property of xhr to be 'json'.
-
Set the xhr.onreadystatechange event handler equal to an anonymous arrow function
-
Call the .open() method and pass in 'POST' and the url as arguments.
-
Finally, call the .send() method and pass the data variable as an argument.
The first type of requests we're going to tackle are GET results using fetch(). The fetch() function:
-
Creates a request object that contains relevant information that an API needs.
-
Sends that request object to the API endpoint provided.
-
Returns a promise that resolves to a response object, which contains the status of the promise with information the API sent back.
fetch('https://api-to-call.com/endpoint').then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Request failed!');
}, networkError => {
console.log(networkError.message);
}).then(jsonResponse => {
return jsonResponse;
});
Now, we're going to learn how to use fetch() to construct POST requests!
fetch('http://api-to-call.com/endpoint', {
method: 'POST',
body: JSON.stringify({id: '200'})
}).then(response => {
if(response.ok) {
return response.json();
}
throw new Error('Request failed!');
}, networkError => {
console.log(networkError.message);
}).then(jsonResponse => {
return jsonResponse;
});
We will be going over how to write the boilerplate code for async GET requests.
async function getData() {
try {
const response = await fetch(
'http://api-to-call.com/endpoint/'
);
if (response.ok) {
const jsonResponse = await response.json();
return jsonResponse;
}
throw new Error('Request failed!');
} catch (error) {
console.log(error);
}
}
We will be going over how to write the boilerplate code for async POST request.
async function getData() {
try {
const response = await fetch(
'https://api-to-call.com/endpoint', {
method: 'POST',
body: JSON.stringify({id: 200})
}
);
if (response.ok) {
const jsonResponse = await response.json();
return jsonResponse;
}
throw new Error('Request failed!');
} catch (error) {
console.log(error);
}
}
Let's look at the function below:
function add(a, b) {
return a + b;
}
It I only pass in add(10), the function will return the result of the following operation:
10 + undefined;
Which will be NaN. This is an example of a non-currying function. To change this to a currying function, we return a nested function:
function addA(a) {
return function addB(b) {
return a + b;
}
}
By calling addA(10), the function won't do anything until to include the value of B.
addA(a)(b);
This now allows us to store the outer function in a variable with a predetermined value for A.
const add5 = add(5);
Then, when we call add5(10), a = 5 b = 10, therefore, it will return 15.
The nested function from earlier can be rewritten to using arrows:
let addA = a => b => a + b;
The <script> element allows you to add JavaScript code inside an HTML file.
<!DOCTYPE html>
<html>
<head>
<title>My Web Page</title>
</head>
<body>
<h1>this is an embedded JS example</h1>
<script>
function hello() {
alert("Hello, World!");
}
</script>
</body>
</html>
We can link JavaScript files into our HTML website by using the src attribute.
<script src="./exampleScript.js"></script>
The HTML file loads the contents in the order it came in. if a JavaScript file is embedded in the <header> element, it loads and runs the JavaScript file first before loading the next element in HTML.
The defer attribute specifies scripts should be executed after the HTML file is completely parsed.
The code is still loaded however, the script is just not executed.
The async attribute loads and executes the script asynchronously with the rest of the webpage. This means the JavaScript file is downloaded and executed as the rest of the page is loading. This optimizes the webpage load time.
The Document Object Model is a powerful tree-like structure that allows programmers to conceptualize hierarchy and access the elements on a webpage.
The DOM is a language-agnostic structure implemented by browsers to allow for web scripting languages, like JavaScript, to access, modify, and update the structure of a HTML webpage in an organised way.
-
A parent node is the closest connected node to another node in the direction towards the root.
-
A child node is the closest connected node to another node in the direction away from the root.
A node is the equivalent of each family member in a tree. A node is an intersecting point in a tree that also contains data.
There are nine different types of node objects in theedom tree, i.e., element node and text node are two examples.
Much like an element in a HTML page, the DOM allows us to access a node's attributes, such as class, id, and inline style.
The document keyword allows you to access the root of the DOM tree.
If you wanted to access the <body> element, you would use the following command.
document.body
You can access and set the contents of an element with the .innerHTML property.
document.body.innerHTML = 'Hello World';
The .innerHTML property can also add HTML elements:
document.body.innerHTML = '<h1>Hello World</h1>';
The DOM interfaces allow us to access a specific element with CSS selectors.
The .querySelector() method allows us to specify a CSS selector and then returns the first element that matches that selector.
document.querySelector('p');
To use classes or id's, male sure you include the prefix . Or # respectfully.
If you want to access elements directly by their id, you can use the aptly named .getElementById() method:
document.getElementById('bio').innerHTML = "
The quick brown fox jumps over the lazy dog.
";
We can also use the DOM to change the CSS styling of an element. The .style property of a DOM element provides access to the inline style of that HTML tag.
let blueElement = document.querySelector('.blue');
blueElement.style.backgroundColor = 'blue';
Not all CSS properties are available from the DOM, therefore, research online which ones are accessible.
To set a colour of a DOM in RGB, we use the hex value like below:
let blueElement = document.querySelector('.blue');
blueElement.style.backgroundColor = '#0000FF';
The .createElement(tagName) method creates a new element based on the specified tag name passed into it as an argument.
It does not append it to the document.
To assign the created element to the document, you must assign it to be a child of an element that already exists on the DOM.
let paragraph = document.createElement('p');
paragraph.id = 'info';
paragraph.innerHTML = 'This is a paragraph';
document.body.appendChild(paragraph);
The .removeChild() method removes a specified child from a parent.
let paragraph = document.querySelector('p');
document.body.removeChild(paragraph);
Because .querySelector returns the first element, the .removeChild() method would remove the first element.
If you want to hide an element instead, you can use the following code:
document.getElementById('sign').hidden = true;
You can add interactivity to DOM elements by assigning a function to run based on an event. Events can include anything from a click to a user mousing over an element.
let element = document.getElementById('interact');
element.onclick = function() {
element.style.backgroundColor = 'blue';
}
Each DOM element node has a .parentNode and .children property. The property will return a list of the elements children and return null if the element has no children.
The .firstChild property will grant access to the first child f that parent element.
Events on the web are user interactions and browser manipulations that you can program to trigger functionality. Some other examples of events are:
-
A mouse clicking on a button
-
Webpage files loading in the browser.
-
A user swiping right on an image.
After a specific event fire on a specific element, an event handler function can be created to run as a response.
Using the .addEventListener() method, we can have a DOM element listen for a specific event and execute a block of code when the event is detected.
let eventTarget = document.getElementById('eventTarget');
eventTarget.addEventListener('click', function(event) {
...
});
The example above listens for the click event.
Event handlers can also be registered using the .onEvent property.
eventTarget.onclick = eventHandlerFunction;
The .removeEventListener() method stops the event from "listening" for an event to fire when it no longer needs to. It takes two arguments:
-
The event type as a string
-
The event handler function
eventTarget.removeEventListener(
'click',
eventHandlerFunction
);
Anonymous functions cannot be removed
JavaScript stores events as Event Objects with their related data and functionalities as properties and methods. When an event is triggered, the event object can be passed as an argument to the event handler function.
function eventHandlerFunction(event) {
console.log(event.timeStamp);
}
eventTarget.addEventListener('click', eventHandlerFunction);
You do not need to pas the Event object manually
There are pre-determined properties associated with event objects:
-
.target -- to reference the element that the event is registered to
-
.type -- to access the name of the event
-
.timeStamp to access the number of milliseconds that passed since the document loaded and the event was tiggered.
There are a lot more event types out there. Research the different event types.
- Not all event types work on every element.
-
mousedown -- is fired when the user presses the mouse button down
-
mouseup -- is fired when the user releases the mouse button
-
mouseover -- is fired when the mouse enters the content of an element
-
is fired when the mouse leaves an element.
-
keydown -- is fired when a user presses a key down
-
keyup -- is fired when a user releases a key
-
keypress -- is fired when a user presses and releases a key
Keyboard events have unique properties assigned to their event object like .key property that stores the value of the key pressed by the user.
document.addEventListener('keydown', ...);
Handlebars.js is a library which provides you with a templating engine which allows you to generate reusable HTML with JavaScript.
To implement handlebars, you need to use a script tag in the <header> where the src attribute is equal to the cdn.
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js">
</script>
We also need another script element:
<script
id="ice-cream"
type="text/x-handlebars-template">
</script>
We then include the following lines in our JS code:
const source = document.getElementById(
'ice-cream'
).innerHTML;
const template = Handlebars.compile(source);
const context = {
flavour: 'chocolate'
};
const compiledHtml = template(context);
const iceCreamText = document.getElementById('scream');
iceCreamText.innerHTML = compiledHtml;
Now any HTML text we add between the opening and closing script tags of id="ice-cream" will be modified. But what parts will be modified?
Within the text, if there are any words wrapped in double curly braces, they will be modified.
<script
id="ice-cream"
type="text/x-handlebars-template"
>
<h2>Why {{flavour}} is the best</h2>
</script>
In the example above, {{flavour}} will be modified to equal the value flavour is within the context object.
The {{if}} helper is like the if condition in JavaScript but with a different syntax.
{{#if argument}}
<!-- code here -->
{{/if}}
We can add on else section to our if statements.
{{#if argument}}
<!-- code here -->
{{else}}
<!-- code here -->
{{/if}}
Another Helper that Handlebars offers is the {{each}} block which allows you to iterate through an array. Inside the {{each}} block, {{this}} acts as a placeholder for the element in the iteration.
{{#each someArray}}
<p>{{this}} is the current element!</p>
{{/each}}
someArray must equal an array within the context object.
Using {{this}} also gives you access to the properties of the element being iterated over.
<p>{{this.shape}}!</p>


