Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Interactive Web Pages with JavaScript Traversing and Manipulating the DOM with JavaScript Perform: Traversing Elements with children

For those confused about this course, read this for a full explanation

There's a lot of questions about this course and I figured it might be a good idea to explain some of the most common aspects that may lead to some confusion. It's a fairly long read but by the end of it hopefully the various parts and the course as a whole can be understood more clearly.

First let's start off by taking a look at what binding a button is exactly, using addTask as an example.

var addButton = document.getElementsByTagName("button")[0];

addTask = function(){
    // do this...
}

addButton.onclick = addTask;

addButton is a variable that has been assigned the add button element using plain JavaScript. document.getElementsByTagName("button") returns an array of all buttons in the document, and as the add button in the task list is the very first button we grab that from the array using [0] at the end. Now we've assigned the variable to that button, and because it is a button it has built in properties by default, just like arrays have a .length property, buttons have their own properties and onclick is one of them. This onclick property is triggered when something happens to the button, in this case the onclick property gets triggered when the button is clicked. The button being clicked is called an event and events happen all the time, either by the user scrolling the page, clicking links, selecting forms etc, or by the document itself at various stages of loading and timings etc that can be used to generate things like pop-ups for example. Simply put, whenever you see a change on the page (and things changing behind the scenes), even if it's as simple as just moving the mouse, all of these are events. And in this case we have bound this addButton.onclick event to run the addTask function.

Just in case if some of the terminology may sound confusing in this course I'll quickly explain some:

Node: Basically any part of the HTML document is a node. There are many different types of nodes, but the ones most commonly used are element, attribute and text.

So for example if we create a button with a class of "link" and "go!" as the inner text it would look like this:

<button class="link">go!</button>

button is the element node, class="link" is the attribute node and "go!" is the text node.

Hook: This is where code is attached to another part of code, linking together code that is in 2 different languages or connecting different modules etc. The addButton.onclick = addTask is a hook, linking the addButton.onclick HTML event to the addTask JavaScript function.

Event Handler: This is the code that is run when an event has been triggered. The addTask function is an event handler, it handles what happens when a specific event is triggered.

Trigger: This is the action performed to start an event. Clicking the add button would be the trigger.

When we bind the addTask function to the addButton.onclick event we declare it without ()'s because if it was, as the page is loading and all of the variables are being cached it would call the function to assign whatever happens inside that function to the addButton.onclick property and not assign it the function itself.

Think of it like this, if you have a variable called time and you wanted to call a function called getTime(), you'd type:

var time = getTime();

function getTime(){
    return "10:36 AM";
}

And then time is equal to the value returned from the getTime function, which would be a string of "10:36 AM" and not the getTime function itself.

So in this instance where we want the addButton.onclick event to be the function itself, we assign it to the function without the ()'s. Now each time the addButton button is pressed, it triggers the .onclick event which is now the addTask function.

Also, in this instance it was also possible just to bind an anonymous function to the addButton.onclick event like so:

addButton.onclick = function(){
  // do this...
};

Rather than:

addTask = function(){
    // do this...
}

addButton.onclick = addTask;

But because of scalability and wanting both more manageable and readable code, the second option was probably the better option.

Assigning this one was easy because there is only one button that we need to bind that function to, whereas the others are a bit more complicated.

The important thing to realise with these other functions is how they work.

The deleteTask, editTask, taskIncomplete and taskComplete functions are all functions that will do something only to their parent list item, this works by using this.parentNode within the function. In this case "this" in this.parentNode is referring to the specific button that was pressed.

For example, if you had 5 delete buttons:

1 - Delete
2 - Delete
3 - Delete
4 - Delete
5 - Delete

And you pressed the second delete button, "this" refers to the second delete button only and not the others. They can all have the same function, but the code within the function will run specifically on the button you've pressed because it uses "this".

So in the instance of deleteTask, the function looks like this:

function deleteTask(){
  var listItem = this.parentNode;
  var ul = listItem.parentNode;
  ul.removeChild(listItem);
}

So "this" is the specific delete button that is being pressed, this.parentNode is the list item element that this button is contained in, the ul is the list item's parent, which is either going to be incomplete-tasks or completed-tasks. When the button is pressed it gets the list item it's contained in and stores that as a variable, and then the same for the ul, then it removes the list item from the ul.

That same function will work the same with all the list items' delete buttons, and as there are many list items that all have this delete button we need to find a way to bind that function to all the delete buttons.

This is done with the bindTaskEvents function.

function bindTaskEvents(taskListItem, checkBoxEventHandler){
    var checkbox = taskListItem.querySelector("input[type=checkbox]");
    var editButton = taskListItem.querySelector("button.edit");
    var deleteButton = taskListItem.querySelector("button.delete");
    checkbox.onchange = checkBoxEventHandler;
    editButton.onclick = editTask;
    deleteButton.onclick = deleteTask;
}

The function works by going through a list item's elements (using querySelector) and then binding each element to the appropriate function. To do this, when we call bindTaskEvents we have to pass it a list item, this being the first parameter in the function: "taskListItem".

Now, if this function had no other parameters it would do the same thing on all list items, but the behaviors of the checkboxes need to be different between those list items in the incomplete-tasks' ul, and the completed-tasks' ul. Even though they both have the same checkbox we want the checkbox in the incompleted-tasks to use the taskCompleted function and the checkbox in the completed-tasks to use the taskIncomplete function. This means we have to pass that through as another parameter in the bindTaskEvents function: "checkBoxEventHandler".

So the checkbox.onchange event can be either the taskCompleted or taskIncomplete function on the list item we pass through. Because of these two different behaviors, and because tasks already exist and more can be added and can also be moved, we have to call the bindTaskEvents function in several parts of the code. For instance when creating a list item and appending it to the incompleted-tasks ul, the bindTaskEvents will be passed the created list item as the first parameter and then the second parameter would be taskCompleted as that's what we want to happen when we change the checkbox. The same thing also needs to happen when a list item is moved, it needs to be updated so the checkbox.onchange event becomes the opposite of what it was before, those list items that had checkbox.onchange = taskCompleted need to be checkbox.onchange = taskIncomplete as they are now a child of the Completed tasks ul, and vice versa.

Alternatively, instead of having two separate functions - taskCompleted and taskIncomplete, you could use one function and then use an if statement within that function to check the id of the ul that the list item belongs to like so:

function moveTask(){
  var li = this.parentNode;
  var ul = li.parentNode;
  if(ul.id === "incomplete-tasks"){
    completeTaskHolder.appendChild(li);
  }
  else { 
    incompleteTaskHolder.appendChild(li);
  }
}

If you did it this way you wouldn't require an additional parameter in the bindTaskEvents function, so anywhere that you call the bindTaskEvents function, either in your for loops or at the end of your addTask function, you would only need to pass the list item into the bindTaskEvents function.

Either way as there are also a few list items to begin with these also have buttons that need to be bound, so this is why we use the for loops. In this example I will use the way Andrew made the bindTaskEvents function with two parameters as opposed to the alternative approach I just mentioned.

for(var i = 0; i < incompleteTasksHolder.children.length; i++){
    bindTaskEvents(incompleteTasksHolder.children[i], taskCompleted)
}

The incompleteTasksHolder is the ul containing all of the list items, so incompleteTasksHolder.children returns an array containing these list items. incompleteTasksHolder.children.length finds out how many list items there are, incompleteTasksHolder.children[i] is the list item at that point in the loop, so we can call bindTaskEvents to pass that list item, and as these are the list items in the incompleteTask ul we want to pass the taskCompleted as the second argument.

The other loop does exactly the same only for the completedTaskHolder, and these list items will now need the taskIncomplete as the second argument.

We only need to do those loops once because they are just to assign the list items that have already been made. When a list item is created or moved we only have to call the bindTaskEvents function on that list item, so in these instances the bindTaskEvents is called at the end of the addTask, taskCompleted and taskIncomplete functions.

Hope this helps clear up some confusion.

Very helpful, thank you!

It also seems to me that he could have utilized his own stages properly. If he had done so he would have known that he should use functions early on and saved time. Because he did not he had to go back and create the functions which meant that we were confused.

Renato Mandic
Renato Mandic
6,866 Points

Thank you very much for this!

This was so helpful! Thank you!

Tatiana Frambach
Tatiana Frambach
7,791 Points

Thank you!! This was SO helpful <3

Will Macleod
Will Macleod
Courses Plus Student 18,780 Points

"Even though they both have the same checkbox we want the checkbox in the incompleted-tasks to use the taskCompleted function and the checkbox in the completed-tasks to use the taskIncomplete function. "

What do you mean by this? Why do i want checkbox in the incompleted-tasks to use taskCompleted function? vise versa?

Will, I think it's because the taskCompleted function moves list items from the incomplete-tasks ul into the task-completed one, so you are changing the checkbox to say "this task is completed" and vice versa, with the taskIncomplete function moving the list item from the completed-tasks ul into the incomplete one.

Gavin Broekema
seal-mask
.a{fill-rule:evenodd;}techdegree
Gavin Broekema
Full Stack JavaScript Techdegree Student 22,443 Points

About 10 minutes away from logging off of Treehouse and finding another JavaScript course to take, I came across this post. You are my savior. Bookmarked.

Emil Torres
Emil Torres
8,653 Points

Fantastic explanation, thank you so much.

Nicklas Augustine
Nicklas Augustine
8,383 Points

Very impressive post! This really cleared up the mess presented by the teacher.

I like your moveTask function. It seems to live up to the DRY methodology. If you use moveTask() then you could also clean up the iteration and just have one for loop in your code selecting all list items at once like:

for(var i = 0; i < allListItems.length; i++){
    bindTaskEvents(allListItems[i])
}

where allListItems is var allListItems = document.getElementsByTagName('li');

Josh Reynolds
Josh Reynolds
10,734 Points

Much clearer explanation thank you! Just like the jQuery course, Andrew jumps around way to much and creates a total mess of what he is trying to teach and makes it very hard to follow along.

I'm glad to see I'm not the only one that gets stuck at this point

Andreas Kratzel
Andreas Kratzel
2,858 Points

Thank you very much, had to read it twice though, but it gave me an "aha"-moment. :)

Sergey Scherbo
Sergey Scherbo
5,784 Points

So there no difference in how we want to declare our function, if it "function expression" or "function declaration", we just want to make our function itself equal to the addButton.onclick. Am i right? I apologize for my English.

1 Answer

Jeremy Castanza
Jeremy Castanza
12,081 Points

Definitely, good information and great post. I love how you broke down some of the items with a step-by-step approach.

Treehouse should consider modifying the "Interactive Web Pages with Javascript" course and include some of the topics that you're covering here. It feels like this course was a big jump from the other beginner Javascript courses and there was a lot of glossing over some very important concepts with too much reliance on the MDN documentation, which is highly technical and full of jargon.

There needs to be more emphasis on the WHY behind these concepts rather than just watching someone else code. If people understand the WHY, then they're more likely to retain the information and apply it to their programming. There also needs to be more opportunity for coding.

I like Andrew's methodology of planning his coding and his 4Ps approach. However, I think his videos would benefit from the approach that Dave takes when it comes to breaking things down in a step-by-step (i.e. line-by-line explanation) and allowing for lots of practice (i.e. "Code challenges").