Day 17: Events
Lesson 1: onclick
Basic onclick handler
Set your HTML to this.
1<button id="hi-button" onclick="sayHi()">Say Hi!</button>
And in your JavaScript
1const sayHi = () => { 2 console.log("Hi"); 3};
We have added an attribute to the <button>
element called onclick
that executes whatever code we pass in. In this case execute the sayHi
function. We say that the onclick handler executes the sayHi function
Now when you click the button you will see "Hi" in the console.
It's really important to understand here that you aren't passing sayHi
to the onclick handler, you are passing the line of code sayHi()
which happens to be an invocation of the sayHi
function.
Change onclick="sayHi()"
to onclick="sayHi"
and then click the button. You'll see that nothing appears in the console. This shows that the code we put in the " "
isn't a reference to a function that will be executed, but instead the code that will be run.
Why am I hammering on this so much?
Because this is the number one issue I see new developers continue to struggle with, as they move further into their developer career and start to use things like React and Vue.
For now, all you need to remember is that whatever is between those " "
marks is what will be executed.
If you wanted to run the same code without the function call you can do this.
1<button id="hi-button" onclick="console.log('hi')">Say Hi!</button>
Notice I had to change from double quotes to single quotes in the console.log
? If I had kept the double quotes the HTML would have gotten confused thinking you were closing the onclick
quotes.
Event listener for onclick
Let's go back to our todo listener
1<div id="welcome-message">Welcome</div> 2<ul id="todos"></ul>
1const todos = [ 2 { description: "Walk dog", important: true }, 3 { description: "Watch TV", important: false }, 4 { description: "Bake cake", important: false }, 5]; 6 7const todosList = document.getElementById("todos"); 8 9todos.forEach((todoItem) => { 10 const listItem = document.createElement("li"); 11 const content = document.createTextNode(todoItem.description); 12 13 if (todoItem.important) { 14 listItem.classList.add("warning"); 15 } 16 17 listItem.appendChild(content); 18 todosList.appendChild(listItem); 19});
What if we want to click a done button and have an item removed from the list?
Well we can add a button to each item to do just that.
We need to create a button, set the text that will appear on the button and then add it to the <li>
so it appears in the list.
Change your forEach
to look like this.
1todos.forEach((todoItem) => { 2 const listItem = document.createElement("li"); 3 const content = document.createTextNode(todoItem.description); 4 5 // creates button 6 const doneButton = document.createElement("button"); 7 8 //set the text with the innerHTML property 9 doneButton.innerHTML = "Done"; 10 11 if (todoItem.important) { 12 listItem.classList.add("warning"); 13 } 14 15 listItem.appendChild(content); 16 17 // adds the button to the list item 18 listItem.appendChild(doneButton); 19 20 todosList.appendChild(listItem); 21});
Now we have a button for each item, but if you click them they don't do anything 😞 Let's fix that.
The best way to do this is via the .onclick
property that our doneButton
comes with. As we saw above, it executes what ever code we give it. That means we need to give it a function that it will execute.
After we set the innerHTML
add this.
1doneButton.onclick = () => { 2 console.log("hello"); 3};
We create an anonymous function () => {}
that has a console.log()
inside it.
This is different to when we added it in the HTML. In the HTML we just wrote the contents of the function, we didn't have to create the funciton, the HTML and browser took care of that for us.
Don't believe me? Try this.
1doneButton.onclick = console.log("hello");
Doesn't work right?
Ok so now we need to write some code that will remove the <li>
element from the DOM.
Amazingly it's just one line of code, but this line of code has a couple of new concepts.
Firstly, the function we assign to button.onclick
that gets called when we click the button, will be passed a parameter called event
. This is a large object with lots of information, what we care about is the .target
property as they gives us a reference to which element actually caused this event to fire.
The second concept is that the .target
property is an HTML DOM element and so it knows where it is in the tree and, importantly for us, can give us access to the <li>
that our <button>
is in. Once we get access to that we can ask it to delete itself.
So, change your event handler to this.
1doneButton.onclick = (event) => { 2 event.target.parentNode.remove(); 3};
Now click your Done button and see that item removed from the screen.
While this removes the element from the DOM it doesn't actually remove the data from the todos
array.
You can see this by adding console.log(todos);
right after we .remove()
.
So our DOM is actually now out of sync with the data that makes it. For this example it doesn't matter very much however for completeness we should delete it from the array as well.
To do this we need to get the item description (Walk dog, Bake cake etc) and then find that item from the array and remove it.
See if you can solve that yourself 😀
Hints:
You need someway to get the description text from the
<li>
. This linelistItem.id = todoItem.description;
added right after you create theli
should help. Along withconsole.log(event.target.parentNode.id);
If the
id
from theli
matches the description in the array we can remove it. You can do this with.filter()
to filter out the item you don't want.Remember to get you
<li>
id before you remove it from the DOM.