DOM Events

Dr. Greg Bernstein

Updated: September 8th, 2019

DOM Events

Readings

  1. Introduction to DOM Events

  2. Events and the DOM

References

What are DOM events?

Almost anything interaction or change with or to a web page is accompanied by an event, e.g., click, scroll, load, drag, change, input, error, resize, etc…

This allows us as JavaScript programmers great flexibility in interacting with users.

Where do events come from and go?

From “Intro to DOM Events”:

Events can be triggered on any part of a document, whether by a user’s interaction or by the browser. They don’t just start and end in one place; they flow through the document, on a life cycle of their own. This life cycle is what makes DOM events so extensible and useful.

Compatibility

In the past different browsers implemented events differently, however modern browsers adhere well to the W3C standards. So we will use the standard interfaces rather than a library such as jQuery which did a great job at dealing with inconsistencies across browsers for us.

DOM Inheritance

EventTarget is an interface implemented by Element, document, and window as well as others. Key methods are:

  • EventTarget.addEventListener(): Register an event handler of a specific event type.

  • EventTarget.removeEventListener(): Removes an event listener.

Listening for Events

element.addEventListener(type, callback, capture);

  • type (string): This is the name or type of event that you would like to listen to.

  • callback (function): The event object, containing data about the event, is passed as the first argument.

  • capture (boolean): This declares whether the callback should be fired in the “capture” phase — more on this later.

Listener Example

See file DOMEvents1.html. HTML:

<h3 id="Test1">Click Me</h3>
<p>Response follows:</p>
<p id="Test1Result"></p>

Listener Example

let el1 = document.getElementById("Test1");
let el2 = document.getElementById("Test1Result");
function myCallback() {
    let content = "Test1 executed at " + new Date();
    el2.innerHTML = content;
}
// Add listener
el1.addEventListener('click', myCallback);

Removing a Listener Example

<h3 id="Test2" class="ClickTarget">Click Me</h3>
<p>Response follows:</p>
<h4 id="Test2Result"></h4>

Removing a Listener Example

let el3 = document.getElementById("Test2");
let el4 = document.getElementById("Test2Result");
function myCallback2() {
    let content = "Test1 executed at " + new Date();
    el4.innerHTML = content;
    el3.removeEventListener('click', myCallback2);
}
// Add listener
el3.addEventListener('click', myCallback2);

Maintaining Callback Context

From “Intro to DOM Events”: An easy gotcha is callbacks being called with the incorrect context.

<h3 id="Test3" class="ClickTarget">Click Me</h3>
<p>Response follows:</p>
<section id="Test3Result"></section>

Maintaining Callback Context

JavaScript code with error:

let el5 = document.getElementById("Test3");
let el6 = document.getElementById("Test3Result");
let myInfo = {
    name: "Dr. B",
    favorites: ["Science", "Coding", "Windsurfing", "Guitar"],
    myCallback3: function() {
        try { //Create a list of favorits
        let favItems = this.favorites.map(function(fav){
            return `\<li\> ${fav} \</li\>`;}); //Why "\"?
        let content = `\<h3\>${this.name}\</h3\>`
        content += `\<ol\>${favItems.join("")}\</ol\>`;
        el6.innerHTML = content;
        } catch (e) {
            el6.classList.add("Error");
            el6.innerHTML= "\<strong\>Error \</strong\>"
                +e.toString();
        }
    }
}
// Add listener
el5.addEventListener('click', myInfo.myCallback3);

this Again!!!

Even though we handed a method from our object as a callback it seems that this wasn’t set right! Try this simple console test:

myObj = {name: "Dr. B",
            myFunc: function(){alert("Hi " + this.name);}};
anotherFunc = myObj.myFunc;
anotherFunc(); //Invoke the function
anotherFunc.bind(myObj)(); //Bind myObj as this and invoke
anotherFunc.bind({name: "CS3520"})(); //Bind any obj with a name prop

bind to the Rescue

Corrected code:

let el7 = document.getElementById("Test4");
let el8 = document.getElementById("Test4Result");
let myInfo2 = {
    name: "Dr. B",
    favorites: ["Science", "Coding", "Windsurfing", "Guitar"],
    myCallback3: function() {
        try { //Create a list of favorits
        let favItems = this.favorites.map(function(fav){
            return `\<li\> ${fav} \</li\>`;}); //Why "\"?
        let content = `\<h3\>${this.name}\</h3\>`
        content += `\<ol\>${favItems.join("")}\</ol\>`;
        el8.innerHTML = content;
        } catch (e) {
            el8.classList.add("Error");
            el8.innerHTML= "\<strong\>Error \</strong\>"
                +e.toString();
        }
    }
}
// Add listener
el7.addEventListener('click', myInfo2.myCallback3.bind(myInfo2));

The Event Object

“What” and “from where” properties from MDN:

  • Event.type: The name of the event
  • Event.target: the target to which the event was originally dispatched
  • Event.currentTarget: the currently registered target for the event
  • And more…

MouseEvent Object

Inherits from Event and adds:

  • MouseEvent.button, .buttons: The buttons being pressed when the mouse event was fired
  • MouseEvent.clientX, .clientY: The X and Y coordinates of the mouse pointer in local (DOM content) coordinates.

  • MouseEvent.altKey, .ctrlKey, .metaKey, .shiftKey: Was a particular key also pressed when the mouse event happend.

Event Object Example

HTML:

<h2>The Event Object</h2>
<p>We'll set up the div below as a mouse down target.</p>
<div id="MouseTarget" style="width: 500px; height: 200px;
    cursor: pointer; background-color: aqua;"></div>
<h4>Event Info:</h4>
<p id="EventInfo"></p>

Event Object Example

HTML:

let mouseTarget = document.getElementById("MouseTarget");
let infoOutput = document.getElementById("EventInfo");
function mouseHandler(event) {
    let info = "type: " + event.type;
    info += " target: " + event.target;
    info += " currentTarget: " + event.currentTarget;
    info += " clientX: " + event.clientX;
    info += " clientY: " + event.clientY;
    infoOutput.innerHTML = info;
    console.log(event);
}
mouseTarget.addEventListener("mousedown", mouseHandler);

Event Propagation

More Event Object

“Status and such” properties from MDN:

  • bubbles: Boolean indicating whether the event bubbles up through the DOM
  • cancelable: Boolean indicating whether the event is cancelable
  • isTrusted: Indicates whether or not the event was initiated by the browser or by a script
  • And more…

The Event Object

Some methods from MDN:

  • preventDefault(): Cancels the event (if it is cancelable).
  • stopPropagation(): Stops the propagation of events further along in the DOM.
  • stopImmediatePropagation(): For this particular event, no other listener will be called.
  • And more…

Event Propagation

From W3C:

  • capture phase: The event object propagates through the target’s ancestors from the Window to the target’s parent.

  • target phase: The event object arrives at the event object’s event target. If the event type indicates that the event doesn’t bubble, then the event object will halt after completion of this phase.

Event Propagation

From W3C:

  • bubble phase: The event object propagates through the target’s ancestors in reverse order, starting with the target’s parent and ending with the Window.

Event Propagation

Event Propagation

The Event.eventPhase values:

  • 1: Capturing Phase
  • 2: At Target
  • 3: Bubbling Phase

Graphical Example (1)

From MDN Event.eventPhase page

For HTML set up a nested series of <div>s:

<input type="checkbox" id="chCapture" />
<label for="chCapture">Use Capturing</label>
<div id="d1">d1
    <div id="d2">d2
        <div id="d3">d3
            <div id="d4">d4</div>
        </div>
    </div>
</div>

Graphical Example (2)

divInfo = document.getElementById("divInfo");
divs = document.getElementsByTagName('div');
chCapture = document.getElementById("chCapture");

Graphical Example (2)

Use some CSS and JS to turn divs into boxes. Also see my file DOMEvents2.html

    div {
        margin: 20px;
        padding: 4px;
        border: thin black solid;
    }
divs[i].style.backgroundColor = (i & 1) ? "#f6eedb" : "#cceeff";

Graphical Example (3)

Graphical Example (4)

Attach listeners:

// In for loop over divs
d.addEventListener("click", OnDivClick, false);
// Use capture checkbox info.
if (chCapture.checked)
    d.addEventListener("click", OnDivClick, true);

Try it at DOMEvents2.html

Delegate Event Listeners

Too Many Listeners to Manage?

  • Do we need to attach a listener for every element we want to hear events from?
  • No. We’ve got bubbling!
  • Do we need a separate event handler for every element?
  • No. We get Event information so we can create more general handlers.

Example Many Buttons

<p>Here are a lot of buttons:</p>
<section id="Controls">
    <input type="button" value="Hi">
    <input type="button" value="World">
    <input type="button" value="Web">
    <input type="button" value="Code">
    <input type="button" value="Clear">
    <input type="button" value="HTML">
    <input type="button" value="JavaScript">
    <input type="button" value="HTTP">
    <input type="button" value="CSS">
    <input type="button" value="DOM Events">
</section>
<p>Output below:</p>
<section id="Output">
</section>

Many Buttons

Many Buttons

Want to add text to output area based on buttons click. May add lots more buttons later or change words on buttons.

Put callback on “container”

let controls, output; // Variables for Elements
window.onload = function() {
    controls = document.getElementById("Controls");
    output = document.getElementById("Output");
    // Add listener to controls section
    controls.addEventListener('click', handleButtons);
}

Make callback Generic

    function handleButtons(event) {
        let target = event.target;
        // Filter out events we don't care about
        if (target.tagName !== "INPUT") {
            return;
        }
        let value = target.getAttribute("value");
        if (value === "Clear") {
            output.innerHTML = "";
        } else {
            output.innerHTML += value + " ";
        }
    }

Give it a Try

At DOMEvents3.html