JavaScript Events

source

JavaScript is considered to be and event driven, which is to say that your program waits for events to define its control flow; The program is waiting for events to react to.

Essentially we use JavaScript to channel and define the user experience as they interact with the user interface. We are waiting for the user to click on something or to fill out a form, submit a form mouse over a region, the return of a HTTP request is also an event.

The basic syntax for this mechanism in JavaScript looks like this:


object.eventListener(event, function);
					

We can attach these listener objects to elements in the DOM in order that a function be called when the prescribed event occurs.


myDiv.eventListener("click", doSomething);

function doSomething(ev) {
	console.log(ev.type); // click
	console.log(ev.target); // myDiv
}
					

When the object to which the event listener is attached is acted upon, the event listener will pass an object event into the registered function.

Working from the following html, we will explore some event listeners.


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>events</title>
</head>
<body>
  <p>
    <button id="btn">Button</button>
  </p>
  <p>
    <a href="http://google.com" id="link">Google</a>
  </p>
  <p>
    <input type="text" id="text" />
  </p>
  <script src="events.js"></script>
</body>
</html>
					

... and our JavaScript, we can pass in either named functions or use an anonymous function, and that in order to get the link script to run we must first disable the effect of the anchor tag.

Notice how we can see which event listener is firing whilst using the text box, on entering a character, the input fires, on leaving the input box both the change and the blur events fire, change is for the change of state from input entry to no longer active, blur for the loss of focus.

let btn = document.getElementById("btn");
let lnk = document.getElementById("lnk");
let txt = document.getElementById("txt");
let res = document.getElementById("listener-example");

btn.addEventListener("click", buttonClicked);
function buttonClicked(ev) {
  console.log(ev.type, ev.target, ev.currentTarget);
}

lnk.addEventListener("click", ev => {
  ev.preventDefault(); // Stop the anchor link from being followed.
  console.log(ev.type, ev.target, ev.currentTarget);
});

txt.addEventListener("input", ev => {
  console.log(ev.type, ev.target, ev.target.value);
});

txt.addEventListener("change", ev => {
  console.log(ev.target, ev.target.value);
});

txt.addEventListener("blur", ev => {
  console.log(ev.target, ev.target.value);
});
					

Google

As the code is currently written, if the script is declared in the head of the file, then it returns an error, non of the document elements exist yet and as such we are trying to put listeners onto null objects. We can resolve this by wrapping all of our listeners in a function. The document does not know about all of its elements and their id"s until it has finished loading, that in itself is also an event.

With this in mind, we can add an event listener to the document itself and call our wrapper script when the document load event fires.


document.addEventListener("DOMcontentLoaded", init);
					

Bubbling or Capture

source

When an event is triggered inside of a nested element, the event bubbles up through all of the nested elements until it gets to the document body, from where it is caught by the enveloping browser or system. How the system treats the event depends upon a final parameter that we can provide after the callback function; We have the option also to provide a boolean value that defines how the event will be treated and in which order events will occur when there are multiple listeners reacting to the same event.

useCapture

The standard methods of functioning of events was decided upon by the w3c as the after combining the two very different methods that were in use when the standard was formulated, at the time both Microsoft and Netscape had browsers which both reacted differently to events.

Netscape used the term bubbling up to the DOM, which meant that the nested event was acted upon first and that each element in the nesting above the target node that also has a listener attached would act upon the event in order.

Microsoft used what they named as capturing the event, descending down into the nesting acting upon listeners that are closer to the document body first, triggering the target elements listener last.

The addEventListener can also take a third parameter, useCapture, a boolean which when set to true instructs the listener to act in accordance with the capture method.

By default the useCapture value is set to false, meaning that the events will bubble up though all of the nested elements in the Netscape way. This is used the majority of the time, so we do not often see this final parameter, as bubble is the most commonly used setting.


<div class="bubble" id="div1-a">
  div one
  <div id="div2-a">
    div two
    <div id="div3-a">
      div three
      <div id="div4-a">
        div four
      </div>
    </div>
  </div>
</div>

<script>
  let res = document.getElementById("out-a");
  let div1 = document.getElementById("div1-a");
  let div2 = document.getElementById("div2-a");
  let div3 = document.getElementById("div3-a");
  let div4 = document.getElementById("div4-a");

  let highlight = ev => {
    // Add css gold class to the element.
    console.log(ev.type, ev.target, ev.currentTarget);
    let target = ev.currentTarget;
    console.log(target);
    target.className = "gold";
    reset(target);
  };

  let reset = _element => {
    setTimeout(() => {
      _element.className = "";
    }, 2000);
    setTimeout(() => {
      res.innerHTML = "";
    }, 5000);
  };

  div2.addEventListener("click", ev => {
    const target = ev.currentTarget;
    console.log(target.attributes[0].value);
  });
					

Each of the divs has a listener placed upon them, that will set the background colour to gold when triggered, div two has two listeners. So that we can see how the sequence that includes a completely separate listener functions.


  [div1,div2,div3,div4].forEach(element => {
    element.addEventListener("click", highlight);
  });
</script>
					

Bubble Example

We can see in the output the order of the triggering of the listeners.

div one
div two
div three
div four

Capture Example

... and now with the capture variable set to true.

div one
div two
div three
div four

The capture model from Microsoft has several problematic properties which is the main reason that it is not recommendable to use them. A subject which is explained in more depth in this article.

Stop Propagation

The w3c have provided two functions to help give us some control over the flow of the triggering of listeners when using the bubble up method, by way of two functions, stopImmediatePropagation, and stopPropagation.

We will add the stopPropagation function to our highlight function, this will stop the propagation of its call to its parent elements. For our second div that has two listeners upon it we will place the stopImmediatePropagation which is require to stop both the propagation of the event and the triggering of the highlight function as a second listener upon the same element.


let res = document.getElementById("out-c");
let div1 = document.getElementById("div1-c");
let div2 = document.getElementById("div2-c");
let div3 = document.getElementById("div3-c");
let div4 = document.getElementById("div4-c");

let highlight = ev => {
  // Stop propagation of highlight function event.
  ev.stopPropagation();
  console.log(ev.type, ev.target, ev.currentTarget);
  let target = ev.currentTarget;
  target.className = "gold";
  reset(target);
};

let reset = _element => {
  setTimeout(() => {
    _element.className = "";
  }, 2000);
  setTimeout(() => {
    res.innerHTML = "";
  }, 5000);
};

div2.addEventListener("click", ev => {
  // Stop propagation of message event as well as all other listeners.
  ev.stopImmediatePropagation();
  const target = ev.currentTarget;
  console.log(target.attributes[0].value);
});

[div1,div2,div3,div4].forEach(element => {
  element.addEventListener("click", highlight);
});
					
div one
div two
div three
div four

Event target and currentTarget

source

Looking at the following simple code, we will investigate the differences between event.target and event.currentTarget, so as to better demonstrate what they are.


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>target and currentTarget</title>
<style>
</style>
</head>
<body>
  <h1>event.target vs event.currentTarget</h1>
  <main>
    This text is inside the <main> element.
    <div>This text is inside the <div> element.</div>
  </main>
  <script>
    document.querySelector("main").addEventListener("click", clicked);
    document.querySelector("div").addEventListener("click", clicked);

    function clicked(ev) {
      console.log("The click that was attached to ", ev.tagName,
      "is currently at", ev.currentTarget.tagName);
    }
  </script>
</body>
</html>
					

Clicking upon the main text sends one single event, we see that both the target and the currentTarget are the same, when we click upon the div text, then two events are fired, one for the target and the second for its parent the main element, here we see that target always shows the targeted element and that currentTarget shows whichever element is currently being triggered by the event.

Built in JS handleEvent method

source

Event listeners also have a built in handleEvent method, this means that we can create a function upon an object that fires when events set upon that object are triggered.

In the following code, we are adding the listeners to our object on creation, using the this key word to target the parent object when the init function is called. The event that is being passed into our handleEvent function will react to the this reference upon the listeners.

It is then possible to use a switch stament to handle different types of events in different ways.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>handleEvent</title>
<style>
h1 {
  font-size: 1rem;
}
</style>
  </head>
  <body>
    <h1>Built-in EventListener handleEvent method</h1>
    <button id="btn">Click the button</button>
    <script>
      let obj = {
        init: function() {
          document.querySelector("#btn").addEventListener("click", this);
          document.querySelector("#btn").addEventListener("focus", this);
          document.querySelector("#btn").addEventListener("blur", this);
        },
        handleEvent: function(ev) {
          switch(ev.type) {
            case "click":
              this.something(ev);
              break;
            case "focus":
              this.something(ev);
              break;
            case "blur":
              this.something(ev);
              break;
            case "explode":
              break;
          }
        },
        something: function(ev) {
          // Gets called by click event list.
          console.log("btn was", ev.type + "ed");
        }
      };
      // Get things started.
      obj.init();
    </script>
  </body>
</html>
					

Keyboard Events

source

Setting up another event listener this time for the keyboard, we have set them this time upon a text input element. There are three stages to a keyboard event, keydown keyup and keypress if all are added to listeners then a keypress will trigger three events, one when the key is activated another when it is disactivated and finaly when both the former have when both the former have finished, this is the order that the events will fire in.

tagName

The event target obtained from currentTarget contains a property called tagName, this is the first html tag of the target element in full.

char

There are several different possibilites for recovering which unicode character key was pressed, depending upon which browser and the browsers age, so to retrieve the character we need to check all three, ev.char ev.charCode ev.which

We can check all three using the || condition in one line

The char returned is the number of the byte, if we would like to convert this to the actual character we can use the String.fromCharCode method.

If we want to also pick up keyboard input when we are not within the text input area, we need add a listener for the keyboard on the document body. For this example the listener is being put on the body of the document that is in an iframe and as such it is the body inside that frame which will activate the listener when focused.


let char = ev.char || ev.charCode || ev.which;
					

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Keyboard Events</title>
    <script src="keyboard.js"></script>
<style>
p {
  outline: 1px solid #333;
  margin: 2rem;
}
input {
  margin: 2rem;
}
input:focus {
  background: gold;
}
</style>
  </head>
  <body>
    <p>
      <input type="text" id="txt" />
    </p>
  </body>
</html>
					

// events.js
//
// Keyboard events in the browser.
// ev.char || ev.charCode || ev.which
// keydown keyup keypress

let log = console.log

document.addEventListener("DOMContentLoaded", init);

function init() {
  let txt = document.getElementById("txt");
  txt.addEventListener("keydown", anyKey);
  document.body.addEventListener("keydown", anyKey);
}

function anyKey(ev) {
  log(ev.type, ev.target);
  let target = ev.currentTarget;
  let tag = target.tagName;
  let char = ev.char || ev.charCode || ev.which;
  let s = String.fromCharCode(char);
  log(s, char, tag);
}
					

Comparing MouseEnter & MouseLeave with MouseOver & MouseOut

When dealing with mouse events, there are a few that are very similar. MouseEnter and MouseLeave, MouseOver and MouseOut. Both MouseEnter and MouseOver are triggered when you move your mouse over an object, MouseLeave and MouseOut are triggered when you leave that object. On the surface they look very similar. The following code shold help to clarify the differences between them.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport" content="width=device-width, initial-scale=1.0">
    <title>MouseEnter ∧ MouseLeave vs MouseOver ∧ MouseOut</title>
<style>
h1 {
  font-size: 1rem;
}
p {
  padding: 1rem;
  border: 2px solid black;
}
.enter {
  color: white;
  padding: 1rem;
  background: purple;
}
.over {
  color: black;
  padding: 1rem;
  background: lightgreen;
}
.blue {
  background: lightblue;
  color: black;
}
</style>
  </head>
  <body>
    <h1>Comparing MouseEnter & MouseLeave<br>with MouseOver & MouseOut</h1>
    <div class="enter">
      <p>This area uses MouseEnter and MouseLeave.</p>
      <p>The first paragraph.</p>
      <p>The second paragraph.</p>
    </div>
    <div class="over">
      <p>This area uses MouseOver and MouseOut.</p>
      <p>The first paragraph</p>
      <p>The second paragraph.</p>
    </div>
    <script>
      let res = parent.window.document.getElementById("out-04");
      document.querySelector(".enter p").addEventListener("mouseenter", entering);
      document.querySelector(".enter p").addEventListener("mouseleave", leaving);
      function entering(ev) {
        ev.currentTarget.style.borderColor = "gold";
        console.log("mouseenter p");
        res.innerHTML += "<pre>mouseenter p</pre>";
        res.scrollTop = res.scrollHeight;
      }
      function leaving(ev) {
        ev.currentTarget.style.borderColor = "black";
        console.log("mouseleave p");
        res.innerHTML += "<pre>mouseleave p</pre>";
        res.scrollTop = res.scrollHeight;
      }
      document.querySelector(".over p").addEventListener("mouseover", overing);
      document.querySelector(".over p").addEventListener("mouseout", outing);
      function overing(ev) {
        ev.currentTarget.style.borderColor = "gold";
        console.log("mouseover p");
        res.innerHTML += "<pre>mouseover p</pre>";
        res.scrollTop = res.scrollHeight;
      }
      function outing(ev) {
        ev.currentTarget.style.borderColor = "black";
        console.log("mouseout p");
        res.innerHTML += "<pre>mouseout p</pre>";
        res.scrollTop = res.scrollHeight;
      }

      // No differences so far ...

      document.querySelector(".enter").addEventListener("mouseenter", function(ev) {
        ev.currentTarget.classList.add("blue");
        console.log("mouseenter div. Add blue.");
        res.innerHTML += "<pre>mouseenter div. Add blue.</pre>";
        res.scrollTop = res.scrollHeight;
      });
      document.querySelector(".enter").addEventListener("mouseleave", function(ev) {
        ev.currentTarget.classList.remove("blue");
        console.log("mouseleave div. Remove blue.");
        res.innerHTML += "<pre>mouseleave div. Remove blue.</pre>";
        setTimeout(function() {
          res.innerHTML = "";
        }, 6000);
        res.scrollTop = res.scrollHeight;
      });
      document.querySelector(".over").addEventListener("mouseout", function(ev) {
        ev.currentTarget.classList.remove("blue");
        console.log("mouseout div. Remove blue.");
        res.innerHTML += "<pre>mouseout div. Remove blue.</pre>";
        setTimeout(function() {
          res.innerHTML = "";
        }, 6000);
        res.scrollTop = res.scrollHeight;
      });
      document.querySelector(".over").addEventListener("mouseover", function(ev) {
        ev.currentTarget.classList.add("blue");
        console.log("mouseover div. Add blue.");
        res.innerHTML += "<pre>mouseover div. Add blue.</pre>";
        res.scrollTop = res.scrollHeight;
      });
    </script>
  </body>
</html>
					

When we mouse over the .enter div, first the div mouseenter listener fires and the background turns blue, then when we get to the paragraph its mouseenter listener fires and the border changes to gold, moving out of the paragraph the mouseleave trigger fires and the border reverts to black, then finally moving off of the div the mouseleave fires on the div and the background reverts to its original colour.

mouseenter div. Add blue.
mouseenter p
mouseleave p
mouseenter div. Remove blue.
					

The difference between the two sets of listeners becomes apparent when we mouse over the next .over div, first the mouseover listener fires and the background changes to blue, then as we mouseover the paragraph the mouseout fires turning the div back to the original colour before the mouseover on the paragraph fires and sets the border to gold, next the mouseover from the div adds the blue background gain. The same happens on leaving the paragraph and the div. The events are removing and resting their actions on every state change.

mouseover div. Add blue.
mouseout div. Remove blue.
mouseover p
mouseover div. Add blue.
mouseout p
mouseout div. Remove blue.
mouseover div. Add blue.
mouseout div. Remove blue.
					

We can see that mouse over and mouse out are using 8 events signals as the events are bubbling where as mouse enter and mouse leave are maintaining state and making half as many event calls, though both types of listeners appear to have the same effect, the mouse enter and mouse leave are a lot more efficient.

Related Target Property

source

Another event property that can be, at times, good to know, is the relatedTarget property. RelatedTarget has to do with where you are coming from or where you are going to, whenever you have pairs of related events that are part of going or doing pair. These are where relatedTarget comes into play.

  • mouseEnter mouseLeave
  • mouseOver mouseOut
  • dragEnter dragExit
  • focusIn focusOut
  • focus blur

In this example we have two text fields, on with focusIn/focusOut on the input and the other with focus and blur. Somewhat similar to mouseEnter/mouseLeave and mouseOver/mouseOut, focus and blur bubble up through the other elements, whereas focusIn and focusOut don't.

The occasions in which we require elements that are nested inside of other elements to have their events bubble up are quite rare, some examples are images that are inside of anchors, images and anchors are both capable of being focused. Text fields, anything that you put inside of a form can have focus. Other than these there are not a lot of things that would require this type of event behaviour.

Most of the time, these two are doing exactly the same thing.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport" content="width=device-width, initial-scale=1.0">
    <title>Related Target Property</title>
<style>
h1 {
  font-size: 1rem;
}
p {
  padding: 1rem;
  border: 2px solid black;
}
.in-out {
  color: white;
  padding: 1rem;
  background: purple;
}
.focus-blur {
  color: black;
  padding: 1rem;
  background: lightgreen;
}
input {
  width: 17.5rem;
}
</style>
  </head>
  <body>
    <h1>Event.relatedTarget</h1>
    <div class=in-out>
      <input type="text" id="one" value="This area uses focusin and focusout." />
      <p>The events focusin and focusout are similar to mouseenter and mouseleave.</p>
      <p>They do NOT bubble.</p>
    </div>
    <div class="focus-blur">
      <input type="text" id="two" value="This area uses focus and blur." />
      <p>The events focus and blur are similar to mouseover and mouseout.</p>
      <p>They DO bubble.</p>
    </div>
    <section>The <code>Event.relatedTarget</code> property also applies to
      mouseenter, mouseleave, mouseover, mouseout, dragenter and dragexit.</section>
    <script>
      document.querySelector(".in-out input").addEventListener("focusin", goIn);
      document.querySelector(".in-out input").addEventListener("focusout", goOut);
      function goIn(ev) {
        ev.currentTarget.style.color = "gold";
        console.log("focusin input left");
        if (ev.relatedTarget) {
          ev.relatedTarget.style.color = "red";
          console.log("Just left relatedTarget", ev.relatedTarget.id);
        }
      }
      function goOut(ev) {
        ev.currentTarget.style.color = "black";
        console.log("focusout input left");
        if (ev.relatedTarget) {
          console.log("Headded for relatedTarget", ev.relatedTarget.id);
        }
      }
      document.querySelector(".focus-blur input").addEventListener("focus", doFocus);
      document.querySelector(".focus-blur input").addEventListener("blur", doBlur);
      function doFocus(ev) {
        ev.currentTarget.style.color = "gold";
        console.log("focus input right");
        if (ev.relatedTarget) {
          ev.relatedTarget.style.color = "red";
          console.log("Just left relatedTarget", ev.relatedTarget.id);
        }
      }
      function doBlur(ev) {
        ev.currentTarget.style.color = "black";
        console.log("blur input right");
        if (ev.relatedTarget) {
          console.log("Headded for relatedTarget", ev.relatedTarget.id);
        }
      }
    </script>
  </body>
</html>
					

Clicking into and then between the first and second text boxes, we can see the effect of the relatedTarget being set as the text turns to red. The output of all events is displayed in the panel to the right.

Creating and Dispatching Custom JS Events

source

We are by now familiar with some of the standard events, DOMContentLoaded, click, mouseover, mouseout, onsubmit. All of the standard events that come built in with the browser API's. Every now and then we need to have an event that does not really fit with the pattern of any of the existing events. There may be some process that we want to have happen when creating an object and something happens to it. This is a situation that can be resolved by creating a custom event.

Setting up a simple page that has some paragraphs added to it upon load, we can set a fuction that will remove a paragraph that is clicked upon by putting a listener upon the paragraph concerned; As we have already seen on many objects before now.

Now what we want to do is to create our own custom event, there are two different ways that we can do this. One is with the command new Event passing in a string which will be associated with the new event type. The second method is with new CustomEvent which requries the same string as a name, but we can also add an object to it that has a property called detail, we will now explore how this can be used.

First we create an event saving it into a variable and giving it a name with its first string argument to define the name of the type. This name is then used when setting a listern upon an object, and finally the event variable is used, by sending it into a dispatch to trigger the event.

It is worth pointing out here that event listeners can be the cause of memory leaks in JavaScript as such it is a best practice to always remove listeners if and when they are no longer required; A persistant listener can keep an object alive so long as it is still attached to the DOM tree in some way, this needs to be paied attetion to when removing or destroying objets

more details on memory leaks

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport"  content="width=diveice-width, initial-scale=1.0">
    <title>Custom Events</title>
<style>
h1 {
  font-size: 1rem;
}
</style>
  </head>
  <body>
    <h1>Creating Events</h1>
    <main>
      <!-- Add stuff here -->
    </main>
    <script>
      let output = parent.window.document.getElementById("out-06");
      parent.window.document.getElementById("btn-06").addEventListener("click", function() {
        output.innerHTML = "";
        birth();
      });

      // 1). let evt = new Event("explode");
      // 2). let evt = new Event("explode", {detail:{speed:20, volume:40}});

      let born = new Event("born");
      let died = new CustomEvent("died", {detail:{time:Date.now())});

      document.addEventListener("DOMContentLoaded", function() {
        m = birth()
        m.addEventListener("click", function(ev) {
          removeParagraph(m, m.firstElementChild);
        });
      });

      function birth() {
        let m = document.querySelector("main");
        addParagraph(m, "This is a new paragraph.");
        addParagraph(m, "A new Star Wars movie is comming soon.");
        return m
      }

      function addParagraph(parent, txt) {
        let p = document.createElement("p");
        p.textContent = txt;
        // Set up and dispatch events.
        p.addEventListener("born", wasBorn);
        p.addEventListener("died", hasDied);
        p.dispatchEvent(born);
        // Add to screen.
        parent.appendChild(p);
      }
      function removeParagraph(parent, p) {
        // dispatch event
        p.dispatchEvent(died);
        // remove element from screen
        parent.removeChild(p);
      }
      function wasBorn(ev) {
        console.log(ev.type, ev.target);
        output.innerHTML += `<pre>${ev.type} ${ev.target.innerText}</pre>`;
      }
      function hasDied(ev) {
        console.log(ev.type, ev.target, ev.detail.time);
        output.innerHTML += `<pre>${ev.type} ${ev.target.innerText} ${ev.detail.time}</pre>`;
        // Remove the listeners
        ev.target.removeEventListener("born", wasBorn);
        ev.target.removeEventListener("died", hasDied);
      }
    </script>
  </body>
</html>
			

When we click on a paragraph the died event the removeParagraph function is called in which the custom died event is dispatched.

Touch Events

source

Many of the events that we have dealt with so far have been for navigation with a mouse, however when writing applications for many mobile devices such as tablets and phones, you do not have a mouse; For these devices we have touch events. The events that we will be working with are touchstart touchend touchcancel and touchmove. Their are no events for swipe, swipeleft swipeup/down/right rotate pinch zoom. Those do not exist, if you would like to use some of those, you will have to write your own script, using the events that we have, to piece that together.

One thing that is different with a mouse and with touch, is that we can have one, two, three, or four finger contact, whereas with a mouse there is always only one.

We can test the touch function in most browsers devtools, for at the very least an approximation of how the page will function.

To access the touch event we can call the property Event.touches this will give us all of the properties that we might require to code our event reaction.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport" content="width=device-width, initial-scale=1">
    <title>Touch Events on Mobile Devices</title>
    <style src="events/touch.css"> </style>
  </head>
  <body>
    <div class="case">
      <div class="screen">
        <div class="menu">
          <div class="square"></div><div class="square"></div><div class="square"></div><div class="square"></div><div class="square"></div><div class="square"></div><div class="square"></div>
        </div>
        <div class="text">
          <h1>Touch Events</h1>
          <p>This is a paragraph with some text, it is waiting to be touched, it can
            only be activated on a touch capable device like a mobile phone or a
            tablet. Clicking it with a mouse will not accomplish anything.</p>
        </div>
        <div class="footer">
          <div class="rec chk1"></div><div class="rec chk1"></div><div class="rec chk1"></div><div class="rec chk1"></div><div class="rec chk1"></div><div class="rec chk1"></div><div class="rec chk1"></div><br>
          <div class="rec chk2"></div><div class="rec chk2"></div><div class="rec chk2"></div><div class="rec chk2"></div><div class="rec chk2"></div><div class="rec chk2"></div><div class="rec chk2"></div>
        </div>
      </div>
    </div>
    <script>
      // Touch events - touchstart, touchend, touchmove, touchcancel
      // work on devices that are touch capable.
      // No error on other devices because touchstart is just a name, like winlottery.
      // The event will probably never happen on my laptop.
      document.querySelector('p').addEventListener('touchstart', f);
      document.querySelector('p').addEventListener('touchend', f);
      function f(ev) {
        console.log(ev.touches)
      }
    </script>
  </body>
</html>
					

example

Planning Your DOM Event Targets

source

How do we decide what to attach our event listeners to? Next up, we will be looking at the different ways that we attach both listeners and information, in order that we can get at that information by way of the listener.

Often times, when dealing with data in html, elements within lists will have decorating element tags, such as in our example, part of the list-item content is within span tags, for reasons of styling. The problem that this can create is that if we were to attach the listener to the list item itself, if the user clicks on the piece of text within the span tags, then the span element will be the target of the event. If they click upon the number, the target will be the list item, the li; So you see that we have two different targets in one place.

In the script below we are setting listeners upon both the <li> and the <ul> tags. In understanding this we will need to be aware of the differenc between target and currentTarget. We must also be aware that the users can click either upon the number or upon the span. The span is an essential part of the styling which is very much a part of the user interface.

It is common practice, when the displayed information is split up by styling, to inclued within the parent element, custom data-* attributes, this way we know that if we get to the <li>, reguardless of which part of the element that they have clicked upon, the <li> covers the entire row and from within it we can access our data-* attributes.

The use of data-* is a part of the HTML5 specification, we can use it to store which ever data we would like, so long as the attribute name begins with data and a hyphen.

We really have three options here, when it comes to where to put the listener; We could put it on either of the span, on the <li> or on the <ul>. If we put it on the &ls;ul> we are being a little bit more efficient. Regardless of where we put the listener, we have to be able to travel through all of the html in list, understanding the child parent relationship between these different elements. If the listener is on the <ul>, then we need a way of getting to the <li>, if the listener is on the <li>, then I have to deal with the fact that I could click upon the <span>, then we will have to check which it was.

The event is picked up no matter where in the list that we click, our output tells us which element has registered the event.

Remembering that there are different ways of placing and treating an even, and depending upon the situation, here we have removed the listener from the <ul> and use the listeners that have been placed upon the list items. From where we are able to use the data attributes.


<script>
  let output = parent.window.document.getElementById("out-08");
  parent.window.document.getElementById("btn-08").addEventListener("click", () => {
    output.innerHTML = "";
  });
  let partyStarted = function() {
    // add event listener to element(s) on the page.
    //document.querySelector(".list-view").addEventListener("click", onClick);
    // target will be ul

    let lis = document.querySelectorAll(".list-item");
    lis.forEach(li => {
      li.addEventListener("click", onClick);
      // target will be li
    });
    let spans = document.querySelectorAll(".list-item span");
  }

  let onClick = function(ev) {
    // ev is the click event... but who called it?
    alert("target " + ev.target + " and currentTarget " + ev.currentTarget);
    output.innerHTML += `<p>target ${ev.target} and currentTarget ${ev.currentTarget}</p>`;
    output.scrollTop = output.scrollHeight;

    let id = ev.currentTarget.getAttribute("data-id");
    let nm = ev.currentTarget.getAttribute("data-name");
    let h2 = document.querySelector("h2");
    h2.textContent = id + ": " + nm;
  }

  document.addEventListener("DOMContentLoaded", partyStarted);
</script>
					

Next we will add another function and demonstrate how we can achieve the same task in another way, this time using the <ul> listener.

Removing Event Listeners

source

We have looked in depth at adding event listeners, but it is also important to know that we should remove them too. The following code adds a post every second, we can remove from the displayed list by clicking on list elements. However, this will remove the element from the display but not from the computers memory, the fact that we have added a listener to the element means that it is still referenced by the main window object and as such is not destroyed by the garbage collector.

We must remove the event listener from the element before removing the element if we want the system to then free up the memory, not doing so in this type of situation in which things are repeatedly being created, is a memory leak.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport" content="width=device-width, initial-scale=1">
    <title>Removing Event Listeners</title>
<style>
h1 {
  font-size: 1rem;
}
.gold {
  color: gold;
}
</style>
  </head>
  <body>
    <main>
      <h1>Post Feed</h1>
      <ul class="list-view">
        <li class="list-item">first post</li>
        <li class="list-item">second post</li>
      </ul>
      <script>
        let partyStarted = function() {
          // add listener to existing items.
          document.querySelectorAll(".list-item").forEach(li => li.addEventListener("click", randomThing));
          // add another list item every 2 seconds.
          stop = setInterval(addItem, 1000);
        }

        let randomThing = function(ev) {
          let li = ev.currentTarget;
          li.className = 'gold';
          console.log("randomThing");

          setTimeout((function() {
            // remove the li from the page after 1/2 second.
            this.removeEventListener("click", randomThing);
            this.parentElement.removeChild(this);
          }).bind(li), 500);
          li = null;
        }

        let addItem = function() {
          let ul = document.querySelector(".list-view");
          let li = document.createElement("li");
          li.className = "list-item";
          li.textContent = `Post number ${Date.now()}`;
          <span style="color: red">li.addEventListener("click", randomThing);</span>
          ul.appendChild(li);
        }
        document.addEventListener("DOMContentLoaded", partyStarted);
      </script>
    </main>
  </body>
</html>
					

Currently if we remove elements as fast as they are being added, our memory use will still keep increasing. To remedy this we need to remove the event listener before removing the element.


<script>
  let randomThing = function(ev) {
    let li = ev.currentTarget;
    li.className = 'gold';
    console.log("randomThing");

    setTimeout((function() {
      // remove the li from the page after 1/2 second.
      <span style="color: limegreen">this.removeEventListener("click", randomThing);</span>
      this.parentElement.removeChild(this);
    }).bind(li), 500);
    li = null;
  }
</script>
					

Right Click Menu's

source

			

Handling Global JS Errors

source

We could, when writing JavaScript to run in browsers, wrap our entire code in a try catch block. This is not a very elegant solution and it does not work for scripts that have been pulled in from other sources. With the inclusion of the import key word in ES6, we are loosing the ability to do this anyway, which is to say that it is not a good practice to adopt.


/* how not to handle global errors. */
try {
  function a() {
    referenceA.error.causing();
  }

  function b() {
    referenceB.error.causing();
  }

  a();

} catch(err) {
  console.error(err)
} */
					

There exists a method on the main window function to which we can set the function that is to be run upon receiving an error anywhere in the window. This function can have numerous arguments, all of which get passed in when an error occurs.

window.onerror()


window.onerror = function(msg, url, line, col, err) {
  console.log("error:", msg, url, line, col, err);
}
notthere;
					

When we react to the error in this way, all of our data is output in the console log, however we also receive an error marked as an 'uncaught error' in the console as well, because we are still not doing anything with it, it has still not been dealt with. For this we can use the error event listener.


window.addEventListener("error", function(ev) {
  console.log(ev.type);
});
					

event.preventDefault

We see the error type, but still we get the 'uncaught error' message, because we have still yet to do something about the error. We can prevent the default logging of the error to the console by requesting that in our code.


window.addEventListener("error", function(ev) {
  console.log(ev.type);
  ev.preventDefault();
  console.warn(ev);
});
					

console.warn()


window.addEventListener("error", function(ev) {
  console.log(ev.type);
  ev.preventDefault();
  console.warn(ev);
});
					

We can also add an onerror method to document elements and objects, here we will add an error to the image.


document.addEventListener("DOMContentLoaded", function() {
  let img = document.getElementById("badImg");
  img.onerror = function() {
    console.warn("image did not load");
  }
});
					

There is another situation in which we will not receive this behaviour, it is when we try to load a script from an external resource. The information generated by external scripts is hidden by the browser for reasons of security. We can get around this by adding the attribute crossorigin="anonymous" to our script tags. This tells the browser that it is not to send any information about itself to the server when it request the script file. When this attribute is set, if the corresponding server has its permissions set to allow the same, then if errors occur in that script they can be caught and dealt with in the browser, as we have been doing for our local errors, including all the internal details. Without this set, all you will receive in the case that there is and error, is a generic 'script error'.


<script src="http://otherdomain.com/script.js" crossorigin="anonymous"></script>
					

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta type="viewport" content="width=device-width, initial-scale=1">
    <title>Window Error Event</title>
<style>
h1 {
  font-size: 1rem;
}
</style>
  </head>
  <body>
    <h1>Window Error Events & Handling</h1>
    <p>The window object has an Error event that lets us handle any error that
      happens on the page. Much better practice than wrapping your entire page
      in a try catch block.</p>
    <p><img src="notthere" id="badImg" alt="no image here" /></p>

    <script src="http://otherdomain.com/script.js" crossorigin="anonymous"></script>

    <script>
      document.getElementById("btn-11").addEventListener("click", function() {

        document.addEventListener("DOMContentLoaded", function() {
          let img = document.getElementById("badImg");
          img.onerror = function() {
            console.warn("image did not load");
          }
        });

        window.addEventListener("error", function(ev) {
          console.log(ev.type);
          console.warn(ev);
          ev.preventDefault();
        });

        notthere;
      }
    </script>
  </body>
</html>
					

Modifying Text As You Type

We have various different listeners for acting upon key presses, as we have seen in the keyboard events section. We will be looking at keydown keyup and keypress which can be placed upon any element on the page, along with input which is explicitly for input elements.

No matter what order the listeners are found in your code, they will always fire in the same order. keydown keypress keyup. Neither keydown or keyup are able to tell you what the charcode is, only keypress has this data. Only keyup happens after the key value has been recorded and as such is able to update the key on the same iteration.

keydown

The keydown event returns no charcode, as the char has not yet been read, there is as yet still no input value, the input is not converted into upper case until the next key is pressed.

keypress

The keypress event contains the charcode data and value of the key press thus the letter, but its value has still not been stored. The letter pressed remains in lower case, the value displayed in the input field is one step behind that which is made into upper case.

keyup

The keyup event happens after the key has been read, we have no data about the charcode and the letter has been added to the value property of the input object. Our input characters are changed into upper case upon their entry.

input

The input listener is explicitly for input fields, the input give us access to the full value and really is the best choice for updating the value inside of an input text element, we do not have the charcode but in many cases it is simply not required.

TransitionEnd and AnimationEnd Events

source

In this next script we will use the transition end and animation end events to change the name of an animation, making the glow animation apply only during the transformation.

With the animation set upon the transition end, as have instructed the animation to run only once, once it has been used, it no longer effects any elements on the page. To resolve this issue we reset the animation to an empty string when it has finished its animation cycle. Now every time that the animation is placed upon a paragraph, it is triggered to run, and we have resolved the playing only once issue.

Scrolling pages with JavaScript and CSS

source

The following code sets out an array of paragraphs upon which we have a script that is set on the header, driving the underlying text to scroll.

console.group & console.groupEnd

The console object also has a method group, which allows us to simplify more complicated logging by grouping output messages together. We will be using a lot of comments here so this will come in handy.

scrollBy & scrollTo

The scrollBy and scrollTo functions are similar, scrollBy takes x/y values that will move the target object by those values from its current position. Where as scrollTo takes precise coordinate to which you can request an object be moved.

Angled Numbers

The numbered paragraph effect has been created by setting a data-* attribute for each paragraph with the desired number, this number is then picked up and applied using CSS by setting the content attribute using the attr(data-num) designation. All have been rotated by 20deg's and every nth(odd) element is rotated by negative 20deg's.


<p data-num="1">Some text ...</p>

p::before{
  content: attr(data-num);
}
				

Intro to JavaScript Audio Effects on Webpages

source

In order to play audio with javascript on a web page, we can set a <audio> element on the page with no controles attribute without which it is not displayed on the page, we can the control this using javascript.


<audio id="a" src=""></audio>
<script>
  let audio = document.getElementById("a");
</script>
					

Another option is to create an element instead. A few lines have been included here, that appeds the tag to the body and remove the controls, but we don't need this for the code to still work. They have been out to hilight this.


<script>
  let audio = document.createElement("audio");
  // audio.removeAttribute("controls");
  // document.body.appendChild(audio);
</script>
					

What is the difference between these two different techniques? If we add the audio to the html before hand, we have got only one thing that is capable of playing audio, whereas using the second technique each event will get its own player.

It is not good to have multiple audio sources playing at the same time, you will overwhelm your users quickly if you do so, we also need to be careful about the volume as well.


audio.volume = 0.1;
					

We have to be very careful about sound on web pages, there should always be a way to disable it.

media events

Sometimes we will want to tie in our sounds with events, there exist also media events, which are all of the events that can occur with video and audio elements. There are a multitude all used in the same way as any other events with listeners.

https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#events

If we would like to get a little more control over the playback of our sounds, we can track their state ourselves, this is only one of many approaches to doing this.

Using a variable called SOUNDS, we can store the sound elements whilst they are being played back, and set the same data-file attribute on our newly created audio element so that we can store and retrieve the playing audio's file name.


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Audio Effects with JavaScript</title>
<style>
h1 {
  font-size: 1.2rem;
}
p {
  font-size: 1rem;
  cursor: pointer;
}
p::after {
  display: inline-block;
  content: attr(data-file);
  font-size: 1rem;
  margin-left: 1rem;
  color: rgba(0,0,0,0.5);
}
</style>
  </head>
  <header>
    <h1>Audio Effects with JavaScript</h1>
  </header>
  <main>
    <button id="btn-15">off</button>
    <p data-file="clear-throat">click me to play a sound</p>
    <p data-file="doorbell">mouseover to play a sound</p>
    <p data-file="static">double-clik me to play a sound</p>
  </main>
  <audio id="a" src=""></audio>
  <script>
    document.addEventListener("DOMContentLoaded", init);
    let btn = document.getElementById("btn-15")
    btn.addEventListener("click", () => {
      allowSound = !allowSound;
      console.log("sound:", allowSound);
      if (allowSound) {
        btn.textContent = "off";
      } else {
        btn.textContent = "on";
        for (let key in SOUNDS) {
          if (SOUNDS[key]) {
            SOUNDS[key].pause();
            SOUNDS[key] = null;
          }
        }
      }
    });
    const SOUNDS = {
      "clear-throat":null,
      "doorbell":null,
      "static":null
    };
    let allowSound = true;

    function init() {
      let p1 = document.querySelector("p[data-file]");
      let p2 = document.querySelector("p:nth-of-type(2)");
      let p3 = document.querySelector("p:last-of-type");
      p1.addEventListener("click", play);
      p2.addEventListener("mouseover", play);
      p3.addEventListener("dblclick", play);
    }

    function play(ev) {
      let p = ev.currentTarget;
      ev.preventDefault();

      let fn = p.getAttribute("data-file");
      let src = "media/" + fn + ".mp3";
      if (SOUNDS[fn]) {
        SOUNDS[fn].pause();
        SOUNDS[fn] = null;
      }
      console.log(src);

      //let audio = document.getElementById("a");
      let audio = document.createElement("audio");
      // audio.removeAttribute("controls");
      // document.body.appendChild(audio);
      audio.src = src;
      audio.volume = 0.1;
      if (allowSound) {
        SOUNDS[fn] = audio;
        audio.setAttribute("data-file", fn);
        audio.play();
      }

      /*********************************************************************
        Event list for <audio> and <video>
        https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
      **********************************************************************/
      // Listen for the event that ends sound
      audio.addEventListener("playing", goAudio);
      audio.addEventListener("ended", doneAudio);
    }

    function goAudio(ev) {
      console.log(ev.target.src, "has started playing");
    }

    function doneAudio(ev) {
      console.log(ev.target.src, "has stopped playing");
      let fn = ev.target.getAttribute("data-file");
      SOUNDS[fn] = null;
    }

  </script>
</html>
					

Capture DOM Changes with MutationObservers

source

With JaveScript we are able to make changes to practicaly any element in the DOM which is to say elements that are on the web page. An object has been added to JavaScript that is expressly for helping us to track our changes to DOM objects and our web pages.

For this we use the MutationObserver into whos object we pass the element or object that we would like to survail, along with a config object that difines what it is about that object that we wish to keep information on. We must define at the very least one of childList attributes or characterData.


let config = {
  attributes:true,
  attributeOldValue:true,
  attributeFilter: ["data-thing", "title", "style"],
  childList: true,
  subtree: true,
  characterData: false,
  characterDataOldValue: false
};

observer = new MutationObserver(mutated);
observer.observe(p, config);
				
attributesTracks the elements attributes for changes, removal of attributes, changes in their values or the addition of new attributes.
attributeOldValueKeep the old values of attributes after they have been changed.
attributeFilterFor a more fine grained control over which attributes to watch.
childListTrack all direct children of the element tag that is being observed.
subtreeTrack everything that is inside of the targeted tags tree.
characterDataTrack all instances of the characterData node abstract interface. (more general than just attributes)
characterDataOldValueKeep the old values of characterData nodes that have been modified.

A mutation object will give us mutation records, each of which has an selection of properties.

observer.disconnect

The observer disconnect method will instruct the observer to stop tracking changes.

observer.takeRecords

The takeRecords method will empty out the observer and give you an array of all of the changes that it contained, an array of everything that has happened to the observer.

HTML5 Screen Orientation API

source

We can detect which orientation the user's screen is by way of a few specific functions, for different types of browser and some key words. We can also add a listener to the widow for the case that it is a mobile device that chages orientation. This is pretty much all that we need to know for dealing with screen orientation.


<main>
  <div id="output"></div>
</main>
<script>
  let orn = getOrientation();
  let out = document.getElementById("output");
  out.textContent = orn;

  function getOrientation() {
    let _orn = screen.msOrientation ||
      (screen.orientation || screen.mozOrientation).type;
    switch(_orn) {
      case "portrait-primary":
        break;
      case "portrait-secondary":
        break;
      case "landscape-primary":
        console.log("This is the laptop/desktop version");
        break;
      case "landscape-secondary":
        break;
      case undefined:
        // Not supported.
        break;
      default:
        // Someting unknown.
    }
    return _orn;
  }

  // This listener will never fire on a laptop but on a mobile device it
  // will.
  window.addEventListener("orienttaionchange", ev => {
    orn = getOrientation();
    out.textContent = orn;
    console.dir(ev);
  });
</script>
				

Load VS DOMContentLoaded Event

source

We can see in the console.lot here that the load events always happens after the DOMContentLoaded event, this is because DOMContentLoaded only needs to wait for the html in the document, literally only the direct DOM content, where as load needs all related linked objects to load as well.

There is a very big difference between these different load events, especialy when a webpage is being accessed from a slow network, the choice of which to use can make a massive difference to when it is that your scripts are run.

clientX vs pageX vs screenX vs offsetX

source

Fine Tune your Touch Events with Passive mode

source

We have already seen that the object.addEventListener() function can take a third argument, a boolean that instructs the DOM which type of behaviour the listener should have, either bubbling or capture mode.

The need for being passive came about with the introduction of touch devices. When a user has touched a paragraph and their finger has still not yet moved, how do we know whether they are going to be interacting with that paragraph or whether they are going to swiping to scroll the page; Passive has to do with this. Instead of the boolean as the third parameter to the eventListener, what we now have is an options object.

There are three different properties that we can put into this options object, the first is a 'capture' boolean which is the same as our previous useCapture setting, the other options are 'once' and 'passive'.

Once is a really useful setting, it indicates that we only want the event to fire one time and then we would like the browser to remove it.

When using either a mobile device, or the mobile device emulation in the devtools, we see along with our paragraph background change, a touchstart event fired, with this we can touch and drag to scroll the screen and perform other such mobile navigation touch screen tasks, this is where we come to the passive setting, if we set passive to 'false' where the default is 'true', ev.preventDefault() can be used within the events callback function to cancel this touch start scrolling effect completely.

How to Build a Long Press Event

source

We are now going to build our own long press event, this is not something that natively exists in javascript just as there is no tap event either, long press is considered to be a gesture, as such we will see how event can be combined to create new events types.

DOM Structures with Event composedPath

source

The composedPath method returns the full path of the object that has been selected and returns that in an array, which allows you to interact with any of the elements up through the chain of nested elements to the document and then the window.

Controlling Copy & Paste with JS

source

Monitor Events in Chrome Dev Tools

source

Inside the chromium console we have access to a method called monitorEvents(), we can add listeners to read output from in the console by calling this. Take the following example:


monitorEvents(document.body, "keydown");
				

DOM Element matches and closest methods

source

Here are a couple of useful DOM methods, document.queryContent retrieves elemnts by thier class or type definition. object.matche() returns a boolean value that states whether or not that targed element matches the given classes and types. The target.closest() method returns the closeest element in the bubbeling that mathches the requested class or type.

HTML5 File and FileList Objects

source

File objects are built in to javascript and are a part of the web API forms. Much of the time when we are dealing with forms we are essentially dealing with text, however there is also an input type for forms called file, which allows the user to select files from their operating system and to bring them into your web form.

By default the file selector will only allow the user to select one file, to enable the selection of more, simply add the multiple attribute to the input field.

When calling scripts from buttons within a form, we need to use the preventDefault method on the event to stop the form from activating its action.

If we would like to specify which file types are permitted, we can use the attribute, accept, to define which file types are expected.

If we would like to use our own design of button, rather than the input with type=file, we must still have an input of type file on the webpage somewhere, but it does not mean that we have to show it, we can not use display none or visibility hidden. Or screen readers will ignore it entirely, so we need to hide it visually whilst keeping it as a part of the page. Grabbing that element from within a script and then calling the object.click() method upon that object, has the same effect as clicking upon the actual input button in the browser.

Pasting Text into Multiple Input Fields

source

We have all seen the types of inputs that are used in security and for code entry in which there are multiple text input boxes one for each section of the code. How could we go about achieving the same type of behaviour as these multiple input fields?

The default behaviour when we have multiple fields is to only paste into the selected field and to require either mouse or tab to move into the next required text input area.

To be able to paste the entirety of a code into the boxes without requiring that the user perform all sorts of gymnastics is a desirable behaviour to provide.