# Lesson 4: Working with Browser Events

## Lesson Overview

To recap, we'll be looking at :

* **Events** - what they are
* **Responding to an event** - how to listen for an event and respond when one happens
* **Event Data** - harness the data that is included with an event
* **Stopping an event** - preventing an event from triggering multiple responses
* **Event Lifecycle** - the lifecycle stages of an event
* **DOM Readiness** - events to know when the DOM itself is ready to be interacted with

## Seeing An Event(Debug only)

如何開眼？

Chrome browser has a special `monitorEvents()` function that will let us see different events as they are occurring.

```javascript
// start displaying all events on the document object
monitorEvents(document);

// turn off the displaying of all events on the document object.
unmonitorEvents(document);
```

Check out the documentation on the Chrome DevTools site: [monitorEvents documentation](https://developers.google.com/web/tools/chrome-devtools/console/events#monitor_events)

![](/files/-M6uO-8WQ9Tyev7DpgfB)

可以看到當你在browser中做這些事情的時候，都會有event產生：

* `click`
* `dblclick`
* `scroll`
* `resize`

## Respond to Events <a href="#header-title" id="header-title"></a>

`EventTarget` Interface

![](/files/-M6uOj6xCaJLsQ_B9Q5b)

The [EventTarget page](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) says that EventTarget:

> is an interface implemented by objects that can receive events and may have listeners for them.

and

> Element, document, and window are the most common event targets, but other objects can be event targets too…

If you take a look at the EventTarget Interface, you'll notice that it doesn't have *any* properties and only three methods! These methods are:

* `.addEventListener()`
* `.removeEventListener()`
* `.dispatchEvent()`

### 1. Adding An Event Listener <a href="#adding-an-event-listener" id="adding-an-event-listener"></a>

Let's use some pseudo-code to explain how to set an event listener:

```javascript
<event-target>.addEventListener(<event-to-listen-for>, <function-to-run-when-an-event-happens>);
```

So an event listener needs three things:

1. an event target - this is called the **target**
2. the type of event to listen for - this is called the **type**
3. a function to run when the event occurs - this is called the **listener**

The `<event-target>` (i.e. the *target*) goes right back to what we just looked at: everything on the web is an event target (e.g. the `document` object, a `<p>` element, etc.).

The `<event-to-listen-for>` (i.e. the *type*) is the event we want to respond to. It could be a click, a double click, the pressing of a key on the keyboard, the scrolling of the mouse wheel, the submitting of a form...the list goes on!

The `<function-to-run-when-an-event-happens>` (i.e. the *listener*) is a function to run when the event actually occurs.

Let's transform the pseudo-code to a *real* example of an event listener:

```javascript
const mainHeading = document.querySelector('h1');

mainHeading.addEventListener('click', function () {
  console.log('The heading was clicked!');
});
```

Check out the documentation for more info: [addEventListener docs](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)

![](/files/-M6uRTrEHXPWwO2t3srQ)

### 2. Add Event Listener to the Project <a href="#add-event-listener-to-the-project" id="add-event-listener-to-the-project"></a>

在index file closing body tag前加入你的js script：

![](/files/-M6uSKA2Ss6bMNxR3DZq)

js file的內容就是之前console裡面寫的code。

To see a full list of all of the possible events you can listen for, check out the Events documentation: [list of events](https://developer.mozilla.org/en-US/docs/Web/Events)

## Remove an Event Listener <a href="#header-title" id="header-title"></a>

Let's say you only want to listen for **just the first click event**, respond to it, and ignore all other click events. The `.addEventListener()` event will listen for and respond to *all* click events.&#x20;

To remove an event listener, we use the `.removeEventListener()` method. It sounds straightforward enough, right? However, before we look at `.removeEventListener()`, we need to take a brief review of object equality.

#### Are Objects Equal in JavaScript <a href="#are-objects-equal-in-javascript" id="are-objects-equal-in-javascript"></a>

Equality is a common task in most programming languages, but in JavaScript, it can be a little bit tricky because JavaScript does this thing called type coercion where it will try to convert the items being compared into the same type. (e.g. string, number,). JavaScript has the double equality (`==`) operator that *will allow type coercion*. It also has the triple equality (`===`) symbol that will prevent type coercion when comparing.

![](/files/-M6uZ7OIbCE5gu7o_b7R)

忽然發現js定義object field的方式不太一樣：

```javascript
var a = {
    myFunction: function quiz() { console.log('hi'); }
};
```

Ok, so why do we care about any of this object/function equality? The reason is that the `.removeEventListener()` method requires you to pass *the same exact listener function* to it as the one you passed to `.addEventListener()`.

```javascript
<event-target>.removeEventListener(<event-to-listen-for>, <function-to-remove>);
```

Remember, the *listener* function must be the *exact* same function as the one used in the `.addEventListener()` call...not just an identical looking function.

This code will successfully add and then remove an event listener:

```javascript
function myEventListeningFunction() {
    console.log('howdy');
}

// adds a listener for clicks, to run the `myEventListeningFunction` function
document.addEventListener('click', myEventListeningFunction);

// immediately removes the click listener that should run the `myEventListeningFunction` function
document.removeEventListener('click', myEventListeningFunction);
```

Code that does not work

![](/files/-M6u_Ywh2hAYH_rh2oc9)

Chrome上的Event listener可以用來查看目前選擇的element上有哪些listener：

![](/files/-M6uaFLEI_mKGx5hhFg4)

找到function所在的js位置：

![](/files/-M6ub8qXadJBrb2jyEcx)

![](/files/-M6ubDHXHBpa9hJLOcCa)

## Phases of an Event <a href="#header-title" id="header-title"></a>

![](/files/-M6uc8nDgxJ5l6-73knl)

### 1. Event Phases <a href="#event-phases" id="event-phases"></a>

There are three different phases during the lifecycle of an event. They are:

* the **capturing** phase
* the **at target** phase
* and the **bubbling** phase

And they actually follow the order above; first, it's *capturing*, then *at target*, and then the *bubbling* phase.

Most event handlers run during the **at target** phase, such as when you attach a click event handler to the button. The event arrives at the button (its **target**), and there's only a handler for it right there, so the event handler gets run.

But sometimes you have a collection of items -- such as a list -- and want to have one handler cover every item (and still have the option of individual handlers for some items.) By default, if you click on a child item and a handler doesn't intercept the click, the event will "bubble" upward to the parent, and keep bubbling until something handles it or it hits the document.

{% hint style="warning" %}
**Capturing**, on the other hand, **lets the parent intercept an event before it reaches a child**.
{% endhint %}

為什麼要有phases? 為了讓parent element可以在event到child之前或之後listen same event而後做動作。

![](/files/-M6ujK3svDw2ZmuBD7y7)

要如何在listener中指定phase？

Up until this point, we've only seen the `.addEventListener()` method called with *two* arguments, the:

* event *type*
* and the *listener*

There's actually a *third* argument to the `.addEventListener()` method; the *useCapture* argument. From it's name, you'd think that if this argument were left out, `.addEventListener()` would default to using the *capturing* phase. This is an incorrect assumption! **By default, when `.addEventListener()` is called with only two arguments, the method defaults to using the bubbling phase.**

However, in this code, `.addEventListener()` is called with *three arguments* with the third argument being `true` (meaning it *should invoke the listener earlier, during the capturing phase*!).

```javascript
document.addEventListener('click', function () {
   console.log('The document was clicked');
}, true);
```

![](/files/-M6uk6Ks7OMvBC6a6fln)

![](/files/-M6ukLje2Z_eHpYRm-qZ)

### 2. The Event Object <a href="#the-event-object" id="the-event-object"></a>

Now that you know that event listeners fire in a specific order *and* how to interpret and control that order, it's time to shift focus to the details of the event itself.

取得event object資訊！

When an event occurs, the browser includes an **event object**. This is just a regular JavaScript object that includes a ton of information about the event itself. According to MDN, the `.addEventListener()`'s *listener* function receives:

> a notification (an object that implements the Event interface) when an event of the specified type occurs

Up until this point, I've been writing all of the *listener* functions without any parameter to store this event object. Let's add a parameter so we *can* store this important information:

```javascript
document.addEventListener('click', function (event) {  // ← the `event` parameter is new!
   console.log(event);
});
```

Notice the new `event` parameter that's been added to the listener function. Now when the listener function is called, it is able to store the event data that's passed to it!

### 3. The Default Action <a href="#the-default-action" id="the-default-action"></a>

As we just looked at, the event object stores a lot of information, and we can use this data to do all sorts of things. However, **one incredibly common reason that professionals use the event object for, is to prevent the default action from happening**.

Think about an anchor link on a webpage. There are probably a couple dozen links on this page! What if you wanted to run some code and display some output when you click on one of these links. If you click on the link, it will automatically navigate you to the location listed in its `href` attribute: that's what it does by default.

What about a form element? When you submit a form, by default, it will send the data to the location in its `action` attribute. What if we wanted to validate the data before sending it, though?

Without the event object, we're stuck with the default actions. However, the event object has a `.preventDefault()` method on it that a handler can call to prevent the default action from occurring!

```javascript
const links = document.querySelectorAll('a');
const thirdLink = links[2];

thirdLink.addEventListener('click', function (event) {
    event.preventDefault();
    console.log("Look, ma! We didn't navigate to a new page!");
});
```

## Avoid Too Many Events <a href="#header-title" id="header-title"></a>

以下的code created 200 event listener and 200 functions

```javascript
const myCustomDiv = document.createElement('div');

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    newElement.addEventListener('click', function respondToTheClick(evt) {
        console.log('A paragraph was clicked.');
    });

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);
```

### Refactoring The Number of Event Listeners <a href="#refactoring-the-number-of-event-listeners" id="refactoring-the-number-of-event-listeners"></a>

如何優化？

第一個優化：大家共用同一個function（少了199個function）

```javascript
const myCustomDiv = document.createElement('div');

function respondToTheClick() {
    console.log('A paragraph was clicked.');
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    newElement.addEventListener('click', respondToTheClick);

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);
```

第二個優化：大家共用同一個event listener（少了199個event listener）

```javascript
const myCustomDiv = document.createElement('div');

function respondToTheClick() {
    console.log('A paragraph was clicked.');
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    myCustomDiv.appendChild(newElement);
}

myCustomDiv.addEventListener('click', respondToTheClick);

document.body.appendChild(myCustomDiv);
```

Now the browser doesn't have to store in memory two hundred different event listeners and two hundred different listener functions. That's great for performance!

但是第二個優化有缺點：

However, if you test the code above, you'll notice that we've lost access to the individual paragraphs. There's no way for us to target a specific paragraph element. So how do we combine this efficient code with the access to the individual paragraph items that we did before?

We use a process called **event delegation**.

### Event Delegation <a href="#event-delegation" id="event-delegation"></a>

Remember the event object that we looked at in the previous section? That's our ticket to getting back the original functionality!

The event object has a `.target` property. This property references the *target* of the event. Remember the capturing, at target, and bubbling phases?...these are coming back into play here, too!

Let's say that you click on a paragraph element. Here's roughly the process that happens:

1. a paragraph element is clicked
2. the event goes through the capturing phase
3. it reaches the target
4. it switches to the bubbling phase and starts going up the DOM tree
5. when it hits the `<div>` element, it runs the listener function
6. inside the listener function, `event.target` is the element that was clicked

So `event.target` gives us direct access to the paragraph element that was clicked. Because we have access to the element directly, we can access its `.textContent`, modify its styles, update the classes it has - we can do anything we want to it!

```javascript
const myCustomDiv = document.createElement('div');

function respondToTheClick(evt) {
    console.log('A paragraph was clicked: ' + evt.target.textContent);
}

for (let i = 1; i <= 200; i++) {
    const newElement = document.createElement('p');
    newElement.textContent = 'This is paragraph number ' + i;

    myCustomDiv.appendChild(newElement);
}

document.body.appendChild(myCustomDiv);

myCustomDiv.addEventListener('click', respondToTheClick);
```

所以為了要access其中一個element，就是利用event object在bubbling phase中取得event target的資訊來做操作。

#### 1. Checking the Node Type in Event Delegation <a href="#checking-the-node-type-in-event-delegation" id="checking-the-node-type-in-event-delegation"></a>

回傳的event要記得加上type checking

In the code snippet we used above, we added the event listener directly to the `<div>` element. The *listener* function logs a message saying that a paragraph element was clicked (and then the text of the target element). This works perfectly! However, there is nothing to *ensure* that it was actually a `<p>` tag that was clicked before running that message. In this snippet, the `<p>` tags were direct children of the `<div>` element, but what happens if we had the following HTML:

```javascript
<article id="content">
  <p>Brownie lollipop <span>carrot cake</span> gummies lemon drops sweet roll dessert tiramisu. Pudding muffin <span>cotton candy</span> croissant fruitcake tootsie roll. Jelly jujubes brownie. Marshmallow jujubes topping sugar plum jelly jujubes chocolate.</p>

  <p>Tart bonbon soufflé gummi bears. Donut marshmallow <span>gingerbread cupcake</span> macaroon jujubes muffin. Soufflé candy caramels tootsie roll powder sweet roll brownie <span>apple pie</span> gummies. Fruitcake danish chocolate tootsie roll macaroon.</p>
</article>
```

In this [filler text](http://www.cupcakeipsum.com/), notice that there are some `<span>` tags. If we want to listen to the `<article>` for a click on a `<span>`, you *might* think that this would work:

```javascript
document.querySelector('#content').addEventListener('click', function (evt) {
    console.log('A span was clicked with text ' + evt.target.textContent);
});
```

This will work, but there's a major flaw. The listener function will still fire when either one of the paragraph elements is clicked, too! In other words, this listener function is not verifying that the target of the event is *actually a `<span>` element*. Let's add in this check:

```javascript
document.querySelector('#content').addEventListener('click', function (evt) {
    if (evt.target.nodeName === 'SPAN') {  // ← verifies target is desired element
        console.log('A span was clicked with text ' + evt.target.textContent);
    }
});
```

Remember that every element inherits properties from [the Node Interface](https://developer.mozilla.org/en-US/docs/Web/API/Node). One of the properties of the Node Interface that is inherited is `.nodeName`. We can use this property to verify that the target element is actually the element we're looking for. When a `<span>` element is clicked, it will have a `.nodeName` property of `"SPAN"`, so the check will pass and the message will be logged. However, if a `<p>` element is clicked, it will have a `.nodeName` property of `"P"`, so the check will fail and the message will *not* be logged.

{% hint style="warning" %}
The .nodeName property will return a capital string, not a lowercase one. So when you perform your check make sure to either:&#x20;

(1) check for capital letters&#x20;

(2) convert the .nodeName to lowercase
{% endhint %}

```javascript
// check using capital letters
if (evt.target.nodeName === 'SPAN') {
    console.log('A span was clicked with text ' + evt.target.textContent);
}

> // convert nodeName to lowercase
if (evt.target.nodeName.toLowerCase() === 'span') {
    console.log('A span was clicked with text ' + evt.target.textContent);
}
```

### Recap

In this section, we looked at Event Delegation. Event Delegation is the process of delegating to a parent element the ability to manage events for child elements. We were able to do this by making use of:

* the event object and its `.target` property
* the different phases of an event

#### Further Research <a href="#further-research" id="further-research"></a>

* [Article: Event delegation](https://javascript.info/event-delegation)
* [Article: How JavaScript Event Delegation Works](https://davidwalsh.name/event-delegate)

## Know When The DOM Is Ready <a href="#header-title" id="header-title"></a>

### 1. The DOM Is Built Incrementally <a href="#the-dom-is-built-incrementally" id="the-dom-is-built-incrementally"></a>

Do you remember the video we watched of Illya from Google explaining how the DOM is parsed? A key thing to point out is that when the HTML is received and converted into tokens and built into the document object model, is that this is a sequential process. When the parser gets to a `<script>` tag, it must wait to download the script file and execute that JavaScript code. *This is the important part and the key to why the placement of the JavaScript file matters!*

Let's look at some code to show (more or less) what's happening. Take a look at this initial part of an HTML file:

```javascript
<!DOCTYPE html>
<html lang="en">
<head>
  <link rel="stylesheet" href="/css/styles.css" />
  <script>
    document.querySelector('footer').style.backgroundColor = 'purple';
  </script>
```

This isn't the full HTML file...BUT, it's all that's been parsed so far. Notice at the bottom of the code that we have so far is a `<script>` file. This is using **inline JavaScript rather than pointing to an external file. The inline file will execute faster because the browser doesn't have to make another network request to fetch the JavaScript file**. But the outcome will be exactly the same for both this inline version and if the HTML had linked to an external JavaScript file.

```javascript
document.querySelector('footer').style.backgroundColor = 'purple';
```

The problem is with the `.querySelector()` method. When it runs...there's no `<footer>` element to select from the constructed document object model yet! So instead of returning a DOM element, it will return `null`. This causes an error because it would be like running the following code:

```javascript
null.style.backgroundColor = 'purple';
```

Now, we've already used one solution to this issue. Remember that we moved the JavaScript file down to the bottom of the page. Think about why this would make things work. Well, if the DOM is built sequentially, \_if\_ the JavaScript code is moved to the very bottom of the page, then by the time the JavaScript code is run, all DOM elements will already exist!

However, an *alternative* solution would be to use browser events!

所以第一種方法是把js file放在最後面。

第二種方法是用event listener。

### 2. The Content Is Loaded Event

When the document object model has been fully loaded, the browser will fire an event. This event is called the `DOMContentLoaded` event, and we can listen for it the same way we listen to any other events:

```javascript
document.addEventListener('DOMContentLoaded', function () {
    console.log('the DOM is ready to be interacted with!');
});
```

![](/files/-M6uwZuOq4mi8Weq92Iz)

![](/files/-M6uwdg3II6TH0xkZjlu)

### 3. Using the `DOMContentLoaded` Event <a href="#using-the-domcontentloaded-event" id="using-the-domcontentloaded-event"></a>

Because we now know about the `DOMContentLoaded` event, we can use it to *keep* our JS code in the `<head>`.

Let's update the previous HTML code to include this event:

```javascript
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="/css/styles.css" />
    <script>
      document.addEventListener('DOMContentLoaded', function () {
          document.querySelector('footer').style.backgroundColor = 'purple';
      });
    </script>
```

Pretty cool, right?!? We have the JavaScript code in the `<head>` element, but it is now wrapped in an event listener for the `DOMContentLoaded` event. This will prevent the DOM-styling code from running when the browser gets to it. Then, when the DOM has been constructed, the event will fire and this code will run.

If you're looking at somebody else's code, you may see that their code listens for the `load` event being used instead (e.g. `document.onload(...)`). `load` fires later than `DOMContentLoaded` -- `load` waits until all of the images, stylesheets, etc. have been loaded (everything referenced by the HTML.) Many older developers use `load` in place of `DOMContentLoaded` as the latter wasn't supported by the very earliest browsers. But if you need to detect when your code can run, `DOMContentLoaded` is generally the better choice.

However, just because you *can* use the `DOMContentLoaded` event to write JavaScript code in the `<head>` that doesn't mean you *should* do this. Doing it this way, we have to write *more* code (all of the event listening stuff) and more code is usually not always the best way to do something. Instead, it would be better to move the code to the bottom of the HTML file just before the closing `</body>` tag.

So when would you want to use this technique? Well, JavaScript code in the `<head>` will run before JavaScript code in the `<body>`, so if you do have JavaScript code that needs to run *as soon as possible*, then you could put that code in the `<head>` and wrap it in a `DOMContentLoaded` event listener. This way it will run as early as possible, but not too early that the DOM isn't ready for it.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://owen31302.gitbook.io/github-education/become-a-front-end-web-developer-or-udacity-3.-javascript-and-the-dom/3.-javascript-and-the-dom/lesson-4-working-with-browser-events.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
