Today we turn our attention to the Browser. We look firstly at Web Accessibility, a critical part of website development, ensuring that people with disabilities can use the web equally.
We then discuss the HTML Document Object Model (DOM), HTML DOM Events and how to write JavaScript in the browser.
Modern web applications are powered by JavaScript. There are lots of popular JavaScript frameworks like React, Angular, Vue that help to organise and support building applications in a browser. We focus on a micro-framework called AppRun which mimics the design of React, Elm and to some degree Vue and Angular.
The Web is an essential part of every day life. Web Accessibility means that people with disabilities can use the web equally. At least 1 in 5 people in the UK have a long term illness, impairment or disability; many more have a temporary disability.
The standards for Web Accessibility are governed by the World Wide Web Consortium (W3C) and are internationally recognised.
The UN Convention on the Rights of Persons with Disabilities defines access to content on the Web as a basic human right. The UK Public Sector Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018 came into force for public sector bodies on 23 September 2018. Public Sector websites are required by law to meet accessibility standards.
Listen to this video introduction to Web Accessibility from the W3C Web Accesssibility Initiative to understand why Web Accessibility is so important. Enable Closed Captions using the CC option in You Tube if you require it.
The WCAG is built around four principle guidelines;
Web Accessibility is essential for many but useful for everyone.
Use the Lighthouse
tool in Chrome Developer Tools to generate a Web Accessibility report for all the pages in your restaurant application.
/accessibility
in the GitHub repo which has your restaurant code/accessibility
folderSelect to Emulate vision deficiencies
in the Rendering
section of Lighthouse. Select each vision deficiency and see whether the user is still able to navigate the site / view content ok
Install the SiteImprove plugin to Chrome and run a report on each of the pages in your restaurant app. How many issues does SiteImprove find? Why could these issues cause issues for users with disabilities? Now edit your HTML to fix these issues
Install Screen Reader (or use Windows Narrator) and tab through your website. If you were vision impaired, would you understand which restaurant you were viewing or deleting? If not, what could you do to improve site navigation for those using a screen reader.
If you finish early, try checking out the accessibility of other websites too. Try the ATG Ticket Website with a Vision Deficiency of 'Achromatopsia' - how does it look?
How can you run JavaScript in the browser? There are 2 main ways:
You can write JavaScript inline, within an HTML file by enclosing your JavaScript in a <script>
tag. As the page loads in the browser the HTML file is read from top to bottom, and any JavaScript in a <script>
tag is executed when the page loads.
As an example, Google Analytics scripts (which measure how users interact with your website) are added within the tag:
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
You can also load JavaScript from a file by using the <script>
tag and including the src
HTML attribute to reference the file:
<script src="/main.js"></script>
When the JavaScript file (in this case main.js
) has been loaded by the page, it will be executed.
If we have libraries, modules or 3rd party JavaScript that we want to load (e.g. Google Analytics), these should go in the <head>
tag of the HTML page.
JavaScript that targets HTML elements (usually our own application code) should be the final thing on the page to be included. In practice that means your application code should be included in a <script>
tag just before the closing </body>
tag. This is because application code will often target an HTML element and if that element has not been loaded, our JavaScript will Error and Fail 😟.
Within the Browser, it's very important to understand that each JavaScript file that is loaded, shares the 'global scope' of the open browser window
object. For example, a class defined in one file, will be available for another file to access once they are both loaded on the page. Any script can freely define/delete/change/call anything in the global scope. This is not the case in Node.js
where the global scope of a module is the module itself.
You can't use the file system in the browser. In Node.js
we made use of module.exports
and require
to load multiple .js
files on our page. This allowed us to separate our code into distinct units which made it easier to understand. However, it should be noted that loading of each separate JavaScript file specified by require
involves a separate round trip to the server and, in addition, there is no guarantee of the order the files will be returned.
Browsers don't have the require
method defined, however you can use tools such as browserify to allow you to use module.exports
and require
within your frontend code, in the same way that you would use it in Node.
You can also use'bundlers', for example, webpack, rollup, parcel and browserify to take JavaScript you have written in multiple files and 'bundle' them (with any dependencies) into one single file that you can serve to the client.
Tools such as nodemon and watchify can also help you develop JavaScript applications 're-bundling' the code when file changes are detected('hot reloading').
Try out the different ways of using JavaScript in front-end code by completing the following 2 assignments:
Modify your restaurant code to include Facebook 'like' and 'share' icons when you click on a restaurant. If you get problems seeing the icons, please disable 'Ad Blockers' in your browser.
Notice how you are including a Facebook script. Once this script is included, you are able to use the Facebook 'like' CSS class.
Now use inline JavaScript in your HTML to add a simple counter which increments each time the page is loaded:
<div>Number of page loads:
<span id="Counter"></span>
</div>
<script>
var n = localStorage.getItem('on_load_counter');
if (n === null) {
n = 0;
}
n++;
localStorage.setItem("on_load_counter", n);
document.querySelector('#counter').innerText = `Visits to this page: ${localStorage.getItem('on_load_counter')}`
</script>
When a web page is loaded, the browser creates an Object Model of the web page. To illustrate this, let's create a DOM representation of this JavaScript:
<html>
<head>
<link rel="stylesheet" href="/style.css"/>
</head>
<body>
<section>
<p id="intro">Introduction</p>
To access an element in an HTML page, you always go via the document
object.
The most common way to access HTML elements with JavaScript is via their id, e.g.
<script>
const myElement = document.getElementById("intro");
</script>
The most common way to retrieve/change the content of an element is by using the innerHTML
property, i.e.
<script>
document.getElementById("intro").innerHTML = "Background";
</script>
You can also change the value of an attribute using:
<script>
document.getElementById("intro").setAttribute("href", "/style2.css");
</script>
event
object is 'injected' into event handlersJavaScript's main primitive is the Object
. Let's create a simple HTML button that triggers a JavaScript event
object.
<button id="my-button" onclick="console.log(event)">Click me</button>
What kind of event
is triggered? What else can you learn about the event that just happened? Where on earth does the event
that we are console.logging come from anyway?
The event
object (and it must be named 'event' not 'evt', 'e' or anything else) is injected by the browser into every event handler. In this example, we are using the HTML attribute onclick
with a string value of the JavaScript we wish to run when the event is triggered. We can also have a named function defined in our main.js
file and include that in the string of JavaScript that is run when the event is triggered.
function myButtonHasBeenClicked(event) {
console.log(event)
}
<button onclick="myButtonHasBeenClicked(event)">Click me</button>
onclick
is an event listener. It listens for clicks. An event listener will have a companion called an event handler. myButtonHasBeenClicked
is an 'event handler'.
There are other ways to attach event listeners to elements in the DOM. For example, the code below is another way to do the same thing we are doing above, but just using JavaScript.
const button = document.getElementById('my-button');
button.addEventListener('click', myButtonHasBeenClicked);
Here, we use the DOM to navigate to the button via its id. Instead of adding the event listener inline, we add it to the instance of the element in JavaScript. We call addEventListener
with 2 arguments, a string that is the name of the type of listener and the function to call (with the event object) to handle the event.
You can remove an event listener as follows:
button.removeEventListener('click', myButtonHasBeenClicked)
There are many different types of listener including:
type | description | Belongs To |
---|---|---|
abort | The event occurs when the loading of a media is aborted | UiEvent, Event |
afterprint | The event occurs when a page has started printing, or if the print dialogue box has been closed | Event |
animationend | The event occurs when a CSS animation has completed | AnimationEvent |
animationiteration | The event occurs when a CSS animation is repeated | AnimationEvent |
animationstart | The event occurs when a CSS animation has started | AnimationEvent |
beforeprint | The event occurs when a page is about to be printed | Event |
beforeunload | The event occurs before the document is about to be unloaded | UiEvent, Event |
blur | The event occurs when an element loses focus | FocusEvent |
canplay | The event occurs when the browser can start playing the media (when it has buffered enough to begin) | Event |
canplaythrough | The event occurs when the browser can play through the media without stopping for buffering | Event |
change | The event occurs when the content of a form element, the selection, or the checked state have changed (for <input> , <select> , and <textarea> ) |
Event |
click | The event occurs when the user clicks on an element | MouseEvent |
contextmenu | The event occurs when the user right-clicks on an element to open a context menu | MouseEvent |
copy | The event occurs when the user copies the content of an element | ClipboardEvent |
cut | The event occurs when the user cuts the content of an element | ClipboardEvent |
dblclick | The event occurs when the user double-clicks on an element | MouseEvent |
drag | The event occurs when an element is being dragged | DragEvent |
dragend | The event occurs when the user has finished dragging an element | DragEvent |
dragenter | The event occurs when the dragged element enters the drop target | DragEvent |
dragleave | The event occurs when the dragged element leaves the drop target | DragEvent |
dragover | The event occurs when the dragged element is over the drop target | DragEvent |
dragstart | The event occurs when the user starts to drag an element | DragEvent |
drop | The event occurs when the dragged element is dropped on the drop target | DragEvent |
durationchange | The event occurs when the duration of the media is changed | Event |
ended | The event occurs when the media has reach the end (useful for messages like "thanks for listening") | Event |
error | The event occurs when an error occurs while loading an external file | ProgressEvent, UiEvent, Event |
focus | The event occurs when an element gets focus | FocusEvent |
focusin | The event occurs when an element is about to get focus | FocusEvent |
focusout | The event occurs when an element is about to lose focus | FocusEvent |
fullscreenchange | The event occurs when an element is displayed in fullscreen mode | Event |
fullscreenerror | The event occurs when an element can not be displayed in fullscreen mode | Event |
hashchange | The event occurs when there has been changes to the anchor part of a URL | HashChangeEvent |
input | The event occurs when an element gets user input | InputEvent, Event |
invalid | The event occurs when an element is invalid | Event |
keydown | The event occurs when the user is pressing a key | KeyboardEvent |
keypress | The event occurs when the user presses a key | KeyboardEvent |
keyup | The event occurs when the user releases a key | KeyboardEvent |
load | The event occurs when an object has loaded | UiEvent, Event |
loadeddata | The event occurs when media data is loaded | Event |
loadedmetadata | The event occurs when meta data (like dimensions and duration) are loaded | Event |
loadstart | The event occurs when the browser starts looking for the specified media | ProgressEvent |
message | The event occurs when a message is received through the event source | Event |
mousedown | The event occurs when the user presses a mouse button over an element | MouseEvent |
mouseenter | The event occurs when the pointer is moved onto an element | MouseEvent |
mouseleave | The event occurs when the pointer is moved out of an element | MouseEvent |
mousemove | The event occurs when the pointer is moving while it is over an element | MouseEvent |
mouseover | The event occurs when the pointer is moved onto an element, or onto one of its children | MouseEvent |
mouseout | The event occurs when a user moves the mouse pointer out of an element, or out of one of its children | MouseEvent |
mouseup | The event occurs when a user releases a mouse button over an element | MouseEvent |
mousewheel | Deprecated. Use the wheel event instead | WheelEvent |
offline | The event occurs when the browser starts to work offline | Event |
online | The event occurs when the browser starts to work online | Event |
open | The event occurs when a connection with the event source is opened | Event |
pagehide | The event occurs when the user navigates away from a webpage | PageTransitionEvent |
pageshow | The event occurs when the user navigates to a webpage | PageTransitionEvent |
paste | The event occurs when the user pastes some content in an element | ClipboardEvent |
pause | The event occurs when the media is paused either by the user or programmatically | Event |
play | The event occurs when the media has been started or is no longer paused | Event |
playing | The event occurs when the media is playing after having been paused or stopped for buffering | Event |
popstate | The event occurs when the window's history changes | PopStateEvent |
progress | The event occurs when the browser is in the process of getting the media data (downloading the media) | Event |
ratechange | The event occurs when the playing speed of the media is changed | Event |
resize | The event occurs when the document view is resized | UiEvent, Event |
reset | The event occurs when a form is reset | Event |
scroll | The event occurs when an element's scrollbar is being scrolled | UiEvent, Event |
search | The event occurs when the user writes something in a search field (for <input="search"> ) |
Event |
seeked | The event occurs when the user is finished moving/skipping to a new position in the media | Event |
seeking | The event occurs when the user starts moving/skipping to a new position in the media | Event |
select | The event occurs after the user selects some text (for <input> and <textarea> ) |
UiEvent, Event |
show | The event occurs when a <menu> element is shown as a context menu |
Event |
stalled | The event occurs when the browser is trying to get media data, but data is not available | Event |
storage | The event occurs when a Web Storage area is updated | StorageEvent |
submit | The event occurs when a form is submitted | Event |
suspend | The event occurs when the browser is intentionally not getting media data | Event |
timeupdate | The event occurs when the playing position has changed (like when the user fast forwards to a different point in the media) | Event |
toggle | The event occurs when the user opens or closes the <details> element |
Event |
touchcancel | The event occurs when the touch is interrupted | TouchEvent |
touchend | The event occurs when a finger is removed from a touch screen | TouchEvent |
touchmove | The event occurs when a finger is dragged across the screen | TouchEvent |
touchstart | The event occurs when a finger is placed on a touch screen | TouchEvent |
transitionend | The event occurs when a CSS transition has completed | TransitionEvent |
unload | The event occurs once a page has unloaded (for <body> ) |
UiEvent, Event |
volumechange | The event occurs when the volume of the media has changed (includes setting the volume to "mute") | Event |
waiting | The event occurs when the media has paused but is expected to resume (like when the media pauses to buffer more data) | Event |
wheel | The event occurs when the mouse wheel rolls up or down over an element | WheelEvent |
To experience events in JavaScript you are going to create your own adventure game.
Step 1 is to make use of a fun website called Creepy Events written by Matthew MacDonald. Your objective is to wire up the JavaScript functions to the buttons and input boxes to complete the story.
Step 2 is to create your own adventure game based on the code from Step 1 and making use of as many HTML DOM events as possible. As a minimum you should include:
Developers use frameworks to help them organise their code. When lots of people work on a codebase having a pattern to follow makes it much easier for team members to join and add to the code. We are going to use a simple framework called AppRun. If you can use AppRun you will find learning React or Elm much easier.
Before we look at the framework lets think about the design pattern that underpins React, Elm and AppRun.
Both Elm and Redux follow the same pattern. Your app has state (this is the data Model). You app renders a view of that state. The view can trigger updates to some or all of the state. That state update triggers a re-render and your view is redrawn to represent the new state. This kind of programming style is called 'event driven'. It is 'declarative' like a spreadsheet.
You know how a spreadsheet works. Once your spreadsheet is all setup (or 'declared'), if you change a value in one cell it can trigger updates to other cells that depend on that value. AppRun works like this.
To load AppRun, we simply create an HTML page with the content below. This loads the AppRun library in a <script>
tag from unpkg.com
.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/apprun/dist/apprun-html.js"></script>
</head>
<body>
<main id="app"></main>
<script src="main.js"></script>
</body>
</html>
State in AppRun is simply a plain JavaScript object. ALL the data in our app belongs here. Our 'TODO list' will be a simple array which holds all our tasks.
We'll create a main.js
file to hold the state:
const state = {
tasks: []
}
To display the tasks we need a view
function. This function will receive the state
object (i.e. list of tasks) and will return a string of HTML. This is like the templating we did with Handlebars, but AppRun's template syntax employs the full power of JavaScript expressions, while Handlebars’ syntax is quite limited in comparison.
const view = state => `
<section>
<h2>Tasks</h2>
<ul>
${state.tasks.map(task => `<li>${task.text}</li>`).join("")}
</ul>
<section>
<section>
<form>
<input name="text" placeholder="Add a task" />
<button>Add</button>
</form>
</section>
`
Remember the view
function is just JavaScript, so you can add branching logic, iteration, calculation and do other stunts, we will end up with a string of valid HTML that will get added to the root HTML node (the <main id="app">
element).
We want to be able to add a task. This requires us to capture the form submission, get the form data, create a task and add that into the state. Our update object will be the ONLY thing that interacts with the state. This is the most tricky part of AppRun to understand.
const update = {
add: (state, form) => {
const data = new FormData(form)
const task = {
id: window.crypto.getRandomValues(new Uint32Array(2)).join(""),
text: data.get('text'),
status: 0
}
state.tasks.push(task)
return state
}
}
What is going on here? We have a JavaScript object. The properties are the names of update functions. In the example above we have defined the 'add' update function. Every update function will receive the state as its first argument. It is injected (like the event object). If we give our update function an argument, that will appear as the second argument. We pass in the form instance. Use new FormData(form)
to de-serialize the form data, then create a new object that represents a task. I am generating a random id. You can play with window.crypto.getRandomValues(new Uint8Array(2))
in the browser console and see what it does. We then update the state, and finally as we must in each update function, we return the new version of the state.
This update will trigger a re-render of our view function.
For this to work we need to add an event listener to our view, and add the update function as the event handler.
<section>
<form onsubmit="app.run('add', this); return false;">
<input name="text" placeholder="Add a task" />
<button>Add</button>
</form>
</section>
What event are we listening for? Notice we call app.run('add', this);
this is the form. We return false
as this will prevent the default behavior of the form submission reloading the page! Will all this declared we are ready to initiate our app. Add the following to your main.js
app.start('app', state, view, update)