Events

CSCI-UA.0480-008

A Clicked Button

Our textbook, Eloquent JavaScript, sets up the following scenario…

  • imagine that you're working with some user interface library
  • … that has a button that contains some state - whether it has been clicked or not
  • the only way to determine if the button is being pressed is by asking the button for its state
  • how can you have an action occur as soon as the button is clicked?


You'd have to repeatedly ask (read) the button for its state (at a very tiny time interval), and then perform the desired action.

  • this approach is called polling
  • it could be a bit resource intensive, as you have to do very often to make the button click seem responsive

Event Handlers

Another paradigm for dealing with events is to have an API that allows functions to be called as a reaction to an event.

The API that JavaScript in the browser offers allows us to:

  1. register a function as a handler for a specific event
  2. have that function called when the event occurs


All of this is done through a method called addEventListener

addEventListener

Where does this addEventListsener come from, and what kind of object can you call it on (let's try it out)?

  • document.addEventListener
  • document.body.addEventListener
  • document.getElementsByTagName('div')[0].addEventListener


Remember that Node objects have a prototype of EventTarget… and all elements are just nodes in the DOM


let ele = document.getElementsByTagName('div')[0];
while (Object.getPrototypeOf(ele)) { 
	console.log(Object.getPrototypeOf(ele)); 
	ele = Object.getPrototypeOf(ele);
}

So… that means every DOM element has addEventListener, and you can use it to listen for events on that specific element.

addEventListener Example

Here's a quick example of using addEventListsener:

The markup:


<button>Hello</button>

Print hello to the console whenever the button is clicked:


const b = document.querySelector('button');
b.addEventListener('click', sayHello);
function sayHello(evt){
  console.log('hello');
}

addEventListener Details

So… some things to note about addEventListener

It takes two arguments, an event (as a string), and a callback

  1. the event name is a string
  2. the callback is a function with an optional parameter - the event object that represents the event
    • the event object may have information such as which mouse button was clicked
    • what its x and y value are
    • the unicode code point of the key pressed
    • etc.

The Event Listener's Callback

A few more details about the callback

  1. within the callback function, this is set to the element the that event listener was added to
  2. the callback can be named or anonymous
    • but you have to have a named function to remove it with removeEventListener (we'll see this a little later)
    • removeEventListener('eventName', nameOfCallback);
  3. The callback should not be an arrow function!
    • why? we want to retain this for the element…


Great, let's see some of this in action

Handling Clicks, This

Make all of the paragraphs clickable… and change the text of the paragraph so that it displays the x and y position of the mouse click using the event object


<style>p {border: 1px solid #000}</style> 
<p>FOO</p> <p>BAR</p> <p>BAZ</p>

function handleClick(event) {
	this.textContent = event.x + ',' + event.y;
}

const paragraphs = document.querySelectorAll('p');
paragraphs.forEach((p) => {
	p[i].addEventListener('click', handleClick);
});

Where Does This Code Live?

So, now that you have code:

  1. that registers an event listener…
  2. and specifies a function that handles the event


What are some criteria for where this stuff should live?

  • probably in an external file included at the end of the body
  • we could even use that DOMContentLoaded event, right?
    • that is, only call once the DOM has been fully loaded
    • …just in case your code depends on the presence of a specific element



function main() {
    console.log('THE DOM IS RED E');
}
document.addEventListener('DOMContentLoaded', main);

Events Types

Again we'll be using these two events types (out of the many that are available - check out the mdn docs):

  • click - triggered on mouse click (press and release) on a single element
  • DOMContentLoaded - document has been completed loaded and parsed (without waiting for CSS, images, etc.)


Some other events that may be useful in your projects include:

  • blur - when an element loses focus
  • focus - when an element receives focus
  • keydown - when a key is pressed down
  • mousemove - when the mouse moves
  • mouseover - when the mouse hovers over an element

Event Object Revisited

The callback to addEventListener is called with an object that represents the event that occurred

Depending on the event, the object may have the following properties

  • target - the element that received the event
  • x - the x coordinate of a mouse click
  • y - the y coordinate of a mouse click
  • key - the key pressed (the character or a text description, such as ArrowDown

Event Object, this and Removing Event Handlers

Using an example from the previous slides, let's remove the event listener on click so that it only says hello on the first click… and does nothing afterwards. We'll also log out some event object properties


const b = document.querySelector('button');
b.addEventListener('click', sayHello);
function sayHello(evt){
  console.log('hello');
}

console.log(evt.x, evt.y, evt.which);
this.removeEventListener('click', sayHello);

Bubbling / Propagation

So… what happens if you have two elements nested within each other, and both have event listeners?

Imagine that both the div and h2 have event listeners. What happens when the h1 is clicked?


<div> 
I have an event listener
<h1>So do I</h1>
</div>
  1. the event handler of the more specific element (the innermost element) gets called first
  2. …then the event bubbles up through the element's parent elements, triggering their event handlers as well

Bubbling / Propagation Example

Let's try nesting two elements (maybe a button in an article), add adding event listeners to both.


<article>
  <h1>About Events</h1>
  <button>Click to Say Hello</button>
</article>

// in js
const a = document.querySelector('article');  
const b = document.querySelector('button');
a.addEventListener('click', function(evt) {
  console.log('article!');  
});
b.addEventListener('click', function(evt) {
  console.log('button!');
});

Preventing Propagation

You can also prevent events from bubbling up by calling stopPropagation() on the event object. Let's try it with the previous example to stop the paragraph event listener from being triggered.


// modify your previous button event listener
b.addEventListener('click', function(evt) {
  console.log('button!');
  // the event won't bubble up!
  evt.stopPropagation();
});

Preventing Default Event Actions

Most events have default actions on them. That is, there are some elements that react to events already. Can you think of any?

  • clicking an input of type submit will GET or POST a form
  • clicking on a link will take you to that link


But… what if the default action was not your intention?

Use the preventDefault() method on the event object!

preventDefault Example

Create a link… but add an event listener to stop the browser from going to the page linked to…


<a href="http://nyu.edu">a link to nyu</a>

// in js
const a = document.querySelector('a');  

a.addEventListener('click', function(evt) {
  console.log('link clicked!');  
  evt.preventDefault();
});

And Now for Something Silly

Let's make this face spin when we click it


<div id="face">
  &#128581;
</div>

Some styles:


#face {
    font-size: 15em;
    display: inline-block;
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.dizzy {
    animation: spin 5s linear infinite;
}

And the JavaScript

Simply toggle the dizzy class


document.addEventListener('DOMContentLoaded', main);

function main() {
    const face = document.querySelector('#face');
    face.addEventListener('click', function clicked(evt){
        this.classList.toggle('dizzy');
    });
}
🙅

(Click me!)