Javascript Events

Any time you use a web page or an app on your smartphone you generate all kinds of events even if you can’t visually see them.

An event is something you do on a page like scrolling, clicking etc. When you interact with a page you may trigger thousands of these events.

In most cases, the browser or app doesn’t do anything special in response. Unless we write some Javascript that handles that events response. If you have seen my blog post on the history of Javascript you already know that Javascript was originally created to respond to user interactions. For example, when a user fills out a contact form and they click on the submit button, we can validate the form fields to make sure there aren’t empty or if the data is correct. If anything is incorrect we can show an error message and if it passes validation we can show a success message.

Some events aren’t necessarily tied directly to the user’s actions. The load event occurs when the browser has completely loaded all of HTML, CSS and Javascript for the page. There are many other events besides the click and load events like the user scroll, touch and drag when using a touch screen device but these are just a few you can see a full list of events here developer.mozilla.org.  Now we know a bit more about events how do we use them?

Functions as Parameters

Before we can understand how Javascript responds to user events we first need to understand more about functions. Functions are first-class citizens in Javascript. Meaning that functions are treated like every other data type such as number, string or an object. Just as you can pass those data types to a function you can also pass another a function as an argument. Initially, this concept hard to wrap my head around but once you try it a few times it becomes easy. Learning how to pass a function around as a parameter can help get control of how it runs. Let’s look at a simple example.

function sayHello(name){
 console.log('Hello my name is ' + name);
}

sayHello('Gary');

function run(func, arg){ 
 func(arg) 
}
run(sayHello, 'Gary');

You should recognize this style of writing a function declaration. We can call this sayHello function by using parentheses and giving it a string as an argument.  All this function does is print Hello my name is with the string value to the console. The more interesting function is run. Notice how it accepts two parameters named func and arg. Simply we pass a function and a parameter to run.  The run function will call that func function with that arg argument.

It’s important to remember the two parameters being passed to run are two different data type. One is a function and the other is a string but both are first-class citizens and can be both passed as arguments in the same way. Because of this, we can actually pass the function directly into another function for example.

run(function sayHello(name){ 
 console.log('Hello my name is ' + name); 
}, 'Gary');

We’ve actually done more than pass a function directly into another function this is called an expression. You may be asking why would I ever need this?

setTimeout()

Passing a function into another function can be a handy thing to know it helps to determine when and how a function gets executed. For example, say we want to delay when the execution of a function. The window object as a method call setTimeout that can do that for us. The setTimout() method calls a function or evaluates an expression after a given number of milliseconds.  Quick tip when using setTimout.

  • 1000 = 1 second
  • The function is only executed once. If you need repeat execution use the setInterval() method.
  • Use the clearTimeout() method to prevent the function from running.
function sayHello(name){
 console.log('Hello my name is ' + name);
}

window.setTimeout(sayHello, 1000, 'Gary');

In the example above I’ve replaced our run method with the setTimeout method. The setTimeout Methods expects a function to be the first parameter and an integer which will be the delay in milliseconds. After that, we pass the parameters we want our function to receive. This is also known as a callback function because we want to call it back after a certain amount of time. If we look over what we’ve done we’ve handed control of our sayHello function to and another function that is triggered by an event.

addEventListener()

We’ve already looked at how functions can be triggered by events. Elements can also target events using the addEventListener method. The addEventListener attaches an event to a specified element, document or window object. The registered event sets up the callback to fire whenever the specified event occurs. The callback functions is often where you will select and manipulate dom elements. There are too many events to cover in one blog post but you can read more on the Mozilla developer network. The click event is the most common one so let’s look at two others for a bit of variety the mouseover and mouseout events.

<ul>
    <li>grapes</li>
    <li>amethyst</li>
    <li>lavender</li>
    <li>plums</li>
</ul>

<script>
  const items = document.getElementsByTagName('li');
  for (let i = 0; i < items.length; i += 1) {     
    items[i].addEventListener('mouseover', () => { 
      items[i].textContent = items[i].textContent.toUpperCase(); 
    }); 
    
    items[i].addEventListener('mouseout', () => { 
      items[i].textContent = items[i].textContent.toLowerCase(); 
    });
  }
</script>

In our example, we have a list of items that we capitalize when the mouse enters an item and then changes it back to lower case whenever the mouse moves off. To achieve that I start by selecting all of the list times with the getElementsByTagName method and store it in a const called list. The getElementsByTagName returns a collection so to access each item I’ve used a for loop.

The first argument of the addEventListner is add the event you want to listen for in our case the mouseover and mouseout events. The second argument is the callback function. I’ve used that callback to update each list item by selecting the textContent property and reassigning it the same value with the toUpperCase method when the mouse enters the list item or toLowerCase method with mouse leaves the list item.

There is a problem in our example, however. Say we now want to add a new item to our list after the assignment of addEeventListener. If were to mouse over that new item it will not change. This is because we added it after we’ve attached the behaviour to the initial list times, in other words, the new item doesn’t not have the event listener. Lucky there’s a nice way to fix this.

Event Bubbling

The solution to this potential issue is event bubbling. Whenever an event gets triggered on an element numerous things happening behind the scenes. An event received by an element doesn’t stop with that one element. That event moves to other elements eg the parent and the ancestors of that element. That process is called event bubbling. Let’s look at our DOM again for some visual aid.

If we were to click on our li element it receives the click event but immediately after its parent the ul receives the same click event. That means if you had a click event on the ul that handler function would also run. And it doesn’t stop there it will also travel up past the ul to its parent the body and that continues to the top of the tree. In other words, the events rise up like bubbles through the dom hence the name event bubbling.

What this allows us to do is listen for events on ancestor elements eg. Say we set a click handler on the body the callback function for that event will trigger whenever any of its children are clicked. It may not seem useful but this will allows us to write more powerful handlers. The example below is the same code we used to add events to each of our li tags the difference being that I’ve attached the event to the parent ul.

<ul id="items">
    <li>grapes</li>
    <li>amethyst</li>
    <li>lavender</li>
    <li>plums</li>
</ul>

<script>
  const items = document.getElementById('items'); 

  items.addEventListener('mouseover', () => { 
    items[i].textContent = items[i].textContent.toUpperCase(); 
  }); 
    
  items.addEventListener('mouseout', () => { 
    items[i].textContent = items[i].textContent.toLowerCase(); 
  });
</script>

By attaching it to the parent of the list items as all of the events would still be caught and handled as they would bubble up. This makes our code much simpler to implement as well as using less of the browser memory for all of those individual listeners and there is one more benefit. Since the handlers are not bound to individual list any item, new items can be added after the binding of the event listener and no additional steps are needed fixing our original problem. But we have a new problem how do we tell what list item triggered the event. Lucky there is a solution to that too.

Event Object

Lucky there is a solution to that too. When the event handler is called you pass the event object as the first argument. The event object has some useful info about the event as well with a few methods. If you head to the Mozilla Developer network we can see a full list of its properties and methods on the event object. The property I want to use is target. The target property is used to reference the element that first received the event for example.

document.addEventListner('click', (event) => {
  console.log(event.target);
});

In this example, I’ve added an event listener to the document object. Its callback function is logging any element that gets clicked. That means with one event handler we have a callback function for every element on the page thanks to how events bubble up through the dom. This also means we can use this to fix our issue of knowing what list item was clicked.

<ul id="items">
    <li>grapes</li>
    <li>amethyst</li>
    <li>lavender</li>
    <li>plums</li>
</ul>

<script>
  const items = document.getElementsById('items'); 
     
  items.addEventListener('mouseover', (event) => { 
    event.target.textContent = event.target.textContent.toUpperCase(); 
  }); 
    
  items.addEventListener('mouseout', (event) => { 
    event.target.textContent = event.target.textContent.toLowerCase(); 
  });
</script>

The first step to fix our code is to add the event object as a parameter so the object is available inside the handlers. Then we add need to get the specific element that triggers the event by using the target property. And that’s we can see the list items have the same behaviour as before and if we were to add another item to the list after the assignment of event listeners that same behaviour will still happen on the new list item.

Wrapping Up

This blog post has covered a lot about events, events listeners, event bubbling and how to smartly handle events. All the basics of making things interactive. Thank you for reading, and being part of the discussion. If you would like to chat or have any questions drop me an email I’m always happy to talk.