Modern Javascript
sourcetypes
JavaScript can be written between either the head or the body tags in a page of html. It is better to place it at the bottom of the body tags, so that the page can load before the script is read. This is fine for short scripts but the majority of the time we link to exterior files.
JavaScript has the following types:
type | example |
---|---|
Number | 1,2,3,100,3.14 |
String | "Hello, World!" "another string" |
Boolean | true false |
Null | explicitly set with no value |
Undefined | not yet defined |
Object | Complex data structures Dates, Arrays, Literals etc |
Symbol | Used with objects |
Variable declaration
Variables are declared using either var
,
let
or const
. var
is the
oldest way of declaring variables, where let
and
const
are more recent additions to the JavaScript
syntax. The const
variable can not be altered after
initialisation where the var
and the let variables can.
The let initialised variables can also change type.
The main difference between the different variable declaration types
is the scope, var
are function scoped where as let a
block scoped, const
can not be changed after
declaration.
Numbers
var x = 25;
const y = 100;
console.log(x, y); // 25 100
x = 30;
y = 125;
console.log(x, y); // 30 125
let z = 23;
console.log(z); // 23
z = "a new type";
console.log(z); // a new type
The browser devtools console is effectively a javascript shell, commands can be written directly into the console and they will run on the press of enter.
String
Strings have several methods accessible to them that will return information about the content of the string.
We can access individual characters within a string by using array notion
var str = "Hello, World!";
console.log(str[0]); // H
string members
We can access string object values members such as it"s length.
console.log(str.length) // 23
string functions
We can also call functions such as,
console.log(str.toUpperCase()); // HELLO, WORLD!
console.log(str.toLowerCase()); // hello, world!
concatenation
Strings can be concatenated using the + unary operator between string
literals and variables containing strings.
var title = "the bloggers log";
var name = "mario";
var likes = 10;
var concat = "The site called " + title +
" by " + name + " has " + likes + " likes";
console.log(concat);
// The site called the bloggers log by mario has 10 likes
This can become a little unwieldy at times.
template strings
An alternative for longer string requirements is to use string templates.
var tmpl = `The site called ${title} by ${name} has ${likes} likes`;
console.log(tmpl);
// The site called the bloggers log by mario has 10 likes
arrays
arrays are a subtype of the object, they can store any types, this is how they are instantiated.
let days = ["mon", "tues", "wed", "thur", "fri", "sat", "sun"];
console.log(days); // [ "mon", "tues", "wed", "thur", "fri", "sat", "sun" ]
console.log(days[2]); // wed
functions
Functions can be initialised is a couple of ways, we have the normal function declaration and also function expressions in which functions are declared as variables. Notice that the expression requires as semi colon where as the declaration does not.
function declaration and expression
function greet(name) {
console.log(`hello ${name}`)
}
greet("Bob") // hello Bob
const greeting = function(name) {
console.log(`hello ${name}`)
};
greeting("Jane") // hello Jane
Functions can have values set in their definition that provide default values, for the case in which values are not provided.
const speak = function(name = "Luigi", time = "night") {
console.log(`good ${time} ${name}`
};
speak(); // good night Luigi
speak("day", "Jill"); // good day Jill
arrow functions
There is another way to express functions known as the arrow function. It is for the most part the same as function expressions, except for some slightly different behaviour concerning the "this" key word, "this" will be covered a little later on.
const area = (radius) => {
return 3.14 * radius**2;
};
console.log(area(51) // 8167.14
const area2 = radius => {
return 3.14 * radius**2; // 11304
};
console.log(area2(60); // 8167.14
const greeting = () => {
console.log("hello");
}
greeting(); // hello
const square = x => x * x;
console.log(square(4)); // 16
callback functions
Functions can also be passed into a function as an argument, to be called by that function within its procedure or used in some other way by that function; These are known as callback functions.
const doThis = (callbackFunc) => {
let value = 40;
callbackFunc(value);
}
doThis(function(value){
console.log(value);
});
doThis(value => {
console.log(value);
});
forEach
forEach is an example of a method on a type that takes a callback function.
let people = ["bob", "jim", "jane", "sue", "willy"];
people.forEach(person => {
console.log(person);
});
people.foreach((person, index) => {
console.log(index, person);
});
defining html with a callback
const ul = document.querySelector(.people);
let people = ["bob", "jim", "jane", "sue", "willy"];
let html = ``
people.forEach(person => {
html += `<li style="color: purple">${person}</li>`
});
ul.innerHTML = html;
Objects
Objects in javascript can have properties and methods, methods are functions that are stored within the object. Javascript has some built in objects as a language, such as a date object, and a math object.
object literal
We can create object literals by declaring them in a way that is similar to arrays, only the objects are enclosed in curly braces {}.
let user = {
name: 'Billy',
age: '30',
email: 'billy@bob.com',
location: 'Sydney',
blogs: ['Throw another prawn on the barbi Shela',
'hunting kangaroo\'s']
}
console.log(user);
Object properties can be accessed by calling the member name using the dot notation, else they can be called by name treating the object as a hashmap. Using the hashmap to call the data can be useful as it permits us to use a variable to retrieve an objects property value.
console.log(user.name); // Billy
console.log(user['name']); // Billy
let local = '';
let key = 'location';
local = user[key];
console.log(local); // Sydney
methods on objects
A major part of objects us is the calling of functions or methods on them. We can define an objects method by defining a function when the object literal is declared.
let user = {
name: 'Billy',
age: '30',
email: 'billy@bob.com',
location: 'Sydney',
blogs: ['Throw another prawn on the barbi Shela',
'hunting kangaroo\'s']
login: function() {
console.log('user login');
}
}
user.login(); // user login
this
If we want to use any properties of the object from inside of any of its methods, we use the 'this' keyword to refer to the enveloping object.
let device = {
name: 'ZR315',
type: 'sensor',
value: '512',
read: function() {
console.log(this.value);
}
}
device.read(); // 512
Calling this out side of an objects method will refer to the global window objects. If this is used inside of an arrow function the this will refer to what ever this was in the scope that the arrow function was called. As such, if we want 'this' to refer to the properties of an object from within an objects method, then we must define that method using the function key word.
We can abbreviate the use of the function key word to brackets following after the functions name definition, the 'this' keyword will still refer to the enclosing object in this case.
let device = {
name: 'ZR315',
type: 'sensor',
value: '512',
read() {
console.log(this.value);
}
}
device.read(); // 512
Math object
Javascript comes with some pre built objects one of which is the math object, it can be called by typing Math. Logging this math object to the console gives us a full listing of all of its properties and methods.
console.log(Math); // Math { … }
console.log(Math.PI); // 3.141592653589793
const area 7.7;
console.log(Math.round(area)); // 8
console.log(Math.floor(area)); // 7
console.log(Math.ceil(area)); // 8
console.log(Math.trunc(area)); // 7
const random = Math.random();
console.log(random); // 0.2040760237726449
console.log(Math.round(random * 100)); // 20
primitive or reference
In javascript there are two principle types:
- primitive
- - numbers
- - strings
- - boolean
- - null
- - undefined
- - symbols
- reference
- - all types of objects
- - object literals
- - arrays
- - functions
- - dates
- - all other objects
When we create primitive types they are stored in stack memory where as reference types are created on the heap. A resulting effect of this is a difference in the way that the two types of variables are copied. When primitive type are copied, the data is doubled and an actual copy is made. When reference types are copied, the pointer to the data on the heap is copied, making a second reference, but both the original and the new reference point to the same data on the heap, if one reference alters the data, it is changed for the second reference also.
let scoreOne = 60;
let scoreTwo = scoreOne;
// scoreOne: 60, scoreTwo: 60
console.log(`scoreOne: ${scoreOne}, scoreTwo: ${scoreTwo}`);
scoreTwo = 120;
// scoreOne: 60, scoreTwo: 120
console.log(`scoreOne: ${scoreOne}, scoreTwo: ${scoreTwo}`);
const user1 = {name: 'Tom', age: 30};
const user2 = user1;
console.log(user1, user2); // Object { name: "Tom", age: 30 }
// Object { name: "Tom", age: 30 }
user1.age = 60;
console.log(user1, user2); // Object { name: "Tom", age: 60 }
// Object { name: "Tom", age: 60 }
The DOM
sourceOpening any html page within the browser and opening up the devtools window, we can see the DOM by typing, document in the console and hitting return.
We can use this same call to document within our scripts to access elements upon the page. To achieve this there are a couple of steps, the first is to reach in to select the element that we wish to act upon, this is known as querying the DOM. The second step is to modify the content, for now we will look at the first step, retrieving an element.
DOM 01
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 01</title>
</head>
<body>
<h1 id="page-title">The DOM</h1>
<div>
<p>Hello World</p>
<p>Jabber wocky doin' jabbering</p>
<p class="error">this is an error message</p>
</div>
<div class="error">This div is an error too.</div>
<script src="dom_01.js"></script>
</body>
</html>
Step one: Querying the DOM
document.querySelector()
We will use document.querySelector() to store the targeted element in a variable. The query selector takes a css selector as a string, it will scan through the document until it finds the first selector that matches the request and return that.
DOM 01 JS
let data = document.querySelector("p");
console.log(data);
This gets us the first p tag in the document, but how would we get others if we require a later one? We can target also using the class, to achieve this we call the css class selector with a dot before the name.
let data = document.querySelector(".error");
console.log(data);
Now, if we would like to select the second div, which also has the class .error, how should we proceed. We can not use the .error class as that would select the first p tag that also has it and we can not use the div as it is not the first div either, we will have to combine them, we can do this by using "div.error".
let data = document.querySelector("div.error");
console.log(data);
When you are not sure what a selector for an element should be, you can always go to the browser and select the element on the page and right click it, and select "inspect", then in the devtools right click the now selected element and click upon copy css selector; This will copy the elements unique selector to the clipboard.
let data = document.querySelector("body > h1");
console.log(data);
document.querySelectorAll()
If we would like to select more than one elment, we use another method called querySelectorAll, again we give the css selector for the elements that we require. This will return us a NodeList of those selected elements, though similar to an array it is not quite the same thing, we have only a select few methods not all those that are available upon an array.
let paras = document.querySelectorAll("p");
console.log(paras);
We do have the [] selector for addressing individual list members, we also have the forEach method
// Select all paragraph tags.
console.log(paras[2]); // <p class="error">
paras.forEach(para => {
console.log(para); // <p>
// <p>
// <p class="error">
});
// Select all error class.
let errors = document.querySelectorAll(".error");
errors.forEach(error => {
console.log(error); // <p class="error">
// <div class="error">
});
document.getElementById()
We can also use id"s to individually select page elements, for this an id attribute must first be set upon the desired element, id"s must be unique and as such you can only use them once in any particular scope.
We do not need to use the "#" before the id as we would if searching for the id as a css selector, it is already implicit due to our use of this specific function.
let title = document.getElementById("page-title");
console.log(title); // <h1 id="page-title">
document.getElementsByClassName()
We can also select directly by class name:
errors = document.getElementsByClassName("error");
console.log(errors); // HTMLCollection {
// 0: p.error, 1: div.error, length: 2 }
This time we receive an HTMLCollection, this is similar but not identical to the NodeList that we have previously had returned to us. We can still target individual members of the collection with the square brackets notation [n], but we can not use the forEach method call. The differences between the NodeList and the HTMLCollection can be seen by opening up the tree in the console and selecting the prototype of each type their within.
document.getElementsByTagName()
let paras = document.getElementsByTagName("p");
console.log(paras);
console.log(paras[0]);
Any of the elements that can be selected with any of the three methods just demonstrated, can also be selected using the querySelector that we saw first. The NodeList returned by the querySelector methods having the advantage of being interable with the forEach method.
Step two: Altering an Element
innerText
To alter the content of an element after selecting it we use a property called innerText, that will give us the text contained within an element. Setting this property to another value will change what is displayed by that element on the rendered page.
const para = document.querySelector("p");
console.log(para.innerText); // Hello World
para.innerText = "Ferrets are fun!";
Combining this with our prior knowledge we can list out the content of a NodeList into the console, or, modifying each element like so:
const paras = document.querySelectorAll("p");
paras.forEach(para => {
console.log(para.innerText);
para.innerText += " the new bit.";
});
innerHTML
We have seen now to change the inner text of an element, but what if we would like to change an elements HTML markup? For this we have the innerHTML property.
let content = document.querySelector(".content");
console.log(content.innerHTML);
content.innerHTML = "This is a new h1
"
For the next example, imagining that we have polled a database and received a list of people back from the query. What we are going to do is to output an HTML element for each of the people in the array.
const people = ["mario", "bob", "suzy"]
people.forEach(person => {
content.innerHTML += `${person}
`;
});
getAttribute
Using a new html page, we will this time seek to effect the href attribute within an anchor tag element. For this we will use the getAttribute method.
DOM 02
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 01</title>
</head>
<body>
<h1 style="color: orange;">The DOM</h1>
<a href="http://google.com">link to somewhere.</a>
<p class="error">This is and error.</p>
<script src="dom_02.js"></script>
</body>
</html>
Using the following script we can read the href attributes value. We can also set attributes using the method setAttribute.
let link = document.querySelector("a");
console.log(link.getAttribute("href")); // http://googly.com
link.setAttribute("href", "wibble.com");
link.innerText = "Go to wibble";
We can also retrieve the names of any classes that have been applied by retrieving the class attribute.
let msg = document.querySelector("p");
console.log(msg.getAttribute("class"));
msg.setAttribute("class", "success");
We can also set new atributes upon elements that exist in the html, it does not matter that the attribute does not yet exist upon the tag, javascript will create it.
msg.setAttribute("style", "color: green;");
There is one major draw back to setting attributes in this way. When we add to an attribute that already exists, such as the orange colour in our h1 heading, then that pre existing style is completely overwritten.
let title = document.querySelector("h1");
title.setAttribute("style", "margin: 50px");
The style property of an html element can be addressed in another way, the style attribute is a property upon the element and as such can be addressed directly instead.
console.log(title.style);
title.style.color = orange;
When the attribute that we wish to address contains a hyphen, such as font-size, we use camel case to call them, fontSize.
title.style.fontSize = "2rem";
Attributes can be deleted by setting them to an empty string.
title.style.margin = "";
To demonstrate how we can add and remove classes to HTML tags we will use a new DOM file, and this time also a separate css stylesheet.
DOM 03
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 03</title>
<link rel="stylesheet" href="dom_03.css">
</head>
<body>
<h1>The DOM 03</h1>
<p class="error">
rþin ualan ineunidil sea trgebn hmabgasr hoouðinsók dyr
þhanniefee etrenin soit þoebsoro tlim elmoodh yþidnð fa
eðe euttiw ȝlloho thess ranw
</p>
<script src="dom_03.js"></script>
</body>
</html>
CSS
.error {
padding: 10px;
color: crimson;
border: 1px dotted crimson;
}
.success {
padding: 10px;
color: limegreen;
border: 1px dotted limegreen;
}
classList.add classList.remove
In this new html document we have a paragraph element with no classes on it, we again access this element document.querySelector the returned content can be examined for its classes using the classList property.
const content = document.querySelector("p");
console.log(content.classList); // DOMTokenList [ ]
We can add classes to the classList of the element by calling the add method upon it.
content.classList.add("error");
console.log(content.classList); // DOMTokenList [ "error" ]
We can also remove classes using the remove method.
content.classList.remove("error");
console.log(content.classList); // DOMTokenList [ ]
... And finally adding our success class:
content.classList.add("success");
A little test
This next document has a many paragraph tag elements inside of it, all with a string of text, the challenge is to find all those paragraphs that contain the word "error", and then set the error class upon them, and all that contain the word "success" and set the success class upon those.
DOM 04
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 04</title>
<link rel="stylesheet" href="dom_03.css">
</head>
<body>
<h1 class="title">The DOM 04</h1>
<p>this <span style="display: none";>error</span> and that</p>
<p>the success was final</p>
<p>not a chair but a table</p>
<p>then againg there could be success</p>
<p>error he stated!</p>
<p>what knot not that ought but nought forgot</p>
<p>the tide change but that was an error</p>
<p>once the dust settles, success will be</p>
<script src="dom_04.js"></script>
</body>
</html>
Here is a script that we might write to solve this problem.
let paras = document.querySelectorAll("p");
paras.forEach(para => {
if (para.innerText.indexOf("error") >= 0) {
para.classList.add("error");
}
if (para.innerText.indexOf("success") >= 0) {
para.classList.add("success");
}
});
Our solution here is not ideal, it is not clear that the 0 value of the index when a text begins with the search term that is being sought, needs to be included in the condition.
textContent
The innnerText returns all visible text from the element, but there is another method that will return the text, called textContent. The difference between the tow is that innerText will not return text that is hidden where as textContent will. I have added a span across one of the errors in the above markup and as such the first paragraph is no longer receiving the error style, changing our property to textContent will fix this.
textContent.includes()
We can also use the includes() method on either of the properties, rather than looking and testing the position of the index of our search term.
let paras = document.querySelectorAll("p");
paras.forEach(para => {
if (para.textContent.includes("error")) {
para.classList.add("error");
}
if (para.textContent.includes("success")) {
para.classList.add("success");
}
});
toggle
Next we will see how to toggle a class, that is to say that if the class is not present upon the element concerned, then the class is added, if it is found then it is removed.
const title = document.querySelector(".title");
title.classList.toggle("test");