Better way to do this

Better way to do this
0

#1

Hi All.

First off: Hope ya’ll having a great time! :sunny:

I’m practising JS with this To do list exercise from edX (DEV279x) and just out of curiosity, is there a better way to do this:

?

Just curious if there’s a shorter/better way to do this.


#2

You have a bug in that when you click on the Add task button, both the click event of the Add task button executes and the change event of the input field executes. This results in an en empty li element being added below the first element.

My JS solution prevents that from occurring. Also, if you change the names of your html element ids to match your variable names, you can avoid using the getElementById part, because JS automatically creates variables with the same id name as long as they are valid syntax (no spaces just like valid variable name syntax).

This streamlines the code a bit, because you are not having to create all of those declarations at the beginning.

JS

// checking for false below prevents the change event from also firing
addTaskButton.addEventListener('click', () => false ? addTask() : 'do not fire change event');

inputField.addEventListener('change', () => addTask());

clearTaskButton.addEventListener('click', () => clearTask());

function addTask() {
  var newListItem = document.createElement('li');
  newListItem.innerHTML = inputField.value;
  listItems.appendChild(newListItem);
  inputField.value = ''; // reset field value to blank for next entry
}

  /* remove all childNodes after cleartaskButton has been clicked. 
Got this code from https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_node_removechild_while */
function clearTask() {
  while (listItems.hasChildNodes()) {
    listItems.removeChild(listItems.firstChild);
  }
}

#3

Hey Randell,

Thanks man. I did not yet had my share of the arrow functions just yet. Great to learn more about this.

I did check the Elements tab in Console after adding a newTodo but can’t see any empty Li added. Where am i suppose to see that? It does makes sense though because i’ve added the function in both the addTaskButton and inputField events without checking if addTaskButton ‘click’, () => false ?

Also do you have a MDN link for more info about:

Also, if you change the names of your html element ids to match your variable names, you can avoid using the getElementById part, because JS automatically creates variables with the same id name as long as they are valid syntax (no spaces just like valid variable name syntax).

I’ve checked this link below but no info about that. Would love to read more about that.


#4

#5

Hey Randell,

I’ve been at this for some time and keep getting this annoying bug.

I’ve added checkboxes after newListItems are added and wanted to add a “line-through” after checkbox is clicked. It’s just i keep seeing this aanoying bug:

“taskDone.addEventListener is not a function”

I have added a new id to the checkboxes and used arrow functions to call the taskDone() function using:

taskDone.addEventListener('click', () => taskDone());

checkbox.id             = "taskDone";

function taskDone() {
    checkbox.newListItem.style.textDecoration = "line-through"; // childNode.parentNode is this the right way for doing this ??
}

What am i doing wrong here?


#6

Would you mind throwing your project into Codepen?

Thanks.


#7

Remember ids need to be unique, so the same id should never appear on any other element. Your current addTask function assigns “taskDone” to id attribute of each checkbox. That will be a problem. That is one of the reason why the following will never work as intended.

taskDone.addEventListener('click', () => taskDone());

Instead, I suggest creating an event listener on the ul element with id=“listItems” which will check for any click events on inputs with type=“checkbox”.

How?

Step #1) You could create a stand-alone function that takes an argument which will be the event object for any element clicked on in the ul element. This function would reference the target property of the event object which will either be an input with type of “checkbox” or it will be the li element containing the input. The function could check the event target’s type and if it is “checkbox”, then apply your style to li element (the parent of the checkbox input). The trick here is what if the user accidentally clicks the wrong checkbox and needs to uncheck it and have the line through go away. Then you would either have to add some extra logic once you have determined the element clicked is a checkbox and see if the line-through style has already been applied or not. If it has, then remove it, if it has not then add it. Unfortunately, this adds much more complexity to the logic.

Is there another way? Yes, you could define a CSS class named line-through and add set the text-decoration property to line-through (like text-decoration: line-through;), then use the toggle method on the event target’s classList property and specify ‘line-through’ as the argument. An unrelated example is shown below.

Example CSS

h1 {
  color: blue;
}

.myClass {
  color: red;
}

Example JavaScript

/* The code below assumes we have an h1 element with id="myElem" in
our HTML (not shown). The line below would toggle the color from blue
to red or red to blue */

myElem.classList.toggle('myClass');

HINT: Remember, you will need to change the text-decoration of the Parent Element of the checkbo input and not the checkbox input.

Step #2) Once you have created the function described in #1 above, then you would and event listener on the ul element and specify the name of the function. Note, that you will not being calling the function as you did via arrow function syntax on the other event handlers. For example, if I have a function named myFunc that I want to use as the callback function of a click event handler for an element with id=“myElem” , then I would write:

myElem.addEventListener('click', myFunc);

Hopefully the comments I have provided will get you started. If you have any questions about anything I have written (which you probably will), post your questions here.

#8

Thanks @RandellDawson,

I’ve been at this for some time now and managed to get the strike-through work, but only for the first li item :persevere:

I’ve added a new pen here:

I think there must be an array which loops through checkbox[i], which corresponds to newListitem[i] ?? What’s the best way to do this?

what i’ve added so far is:

listItems.addEventListener("click", function(){
    checkClicked();
});

function checkClicked() {
    // get checkbox
    var checkBox = document.querySelector("input[type=checkbox]");
    // get listItem
    var listItem = document.querySelector(".newListItem");
    // if checkbox is checked line-through listItem
    if (checkBox.checked == true) {
        listItem.style.textDecoration = "line-through";
    }
    else {
        listItem.style.textDecoration = "none";
    }
}

Love to read your reply.


#9

(checkBox.checked == true)

can be shortened to

(checkBox.checked)

If you like Ternary Operators,
you can change the whole section from

if (checkBox.checked == true) {
  listItem.style.textDecoration = 'line-through';
} else {
  listItem.style.textDecoration = 'none';
}

to

checkBox.checked
  ? (listItem.style.textDecoration = 'line-through')
  : (listItem.style.textDecoration = 'none');

#10

You are not taking advantage of what the addEventListener method returns when an element inside the ul element is clicked. Read over thedocumentation for addEventListener and you will see you can specify an argument for the anonymous callback function to access the Event object. The event object has a lot of information in it such as the actual element targeted (which is what you need to know). I will give you an example of how to use the event object to access the element being targeted with an example using radio buttons. It will give you something to study and try to emulate in your own code.

The following very simple code will display which radio button is clicked in the console. The event.target is the actual DOM element which was targeted, so you could easily style this element as you want.

HTML

<p>Please click your preferred language:</p>

<div id="languages">
  <input type="radio" id="english" name="language" value="english">
  <label for="english">English</label>
  
  <input type="radio" id="french" name="language" value="french">
  <label for="french">French</label>

  <input type="radio" id="spanish" name="language" value="spanish">
  <label for="spanish">Spanish</label>
</div>

JavaScript

languages.addEventListener('click', event => {
  const target = event.target;
  const id = target.id;
  const type = target.type;
  if (type === 'radio') {
     console.log('Radio button with id="' + id + '" was clicked');
  }
});

Get element index for prev/next button
#11

Hey @miku86coding, tyvm for that shortened version. I’ve not yet had any exercises with Ternary Operators but looking forward to learn more.

I’ve also just checked your website and it looks great man. How did you get that toggle menu icon to dropdown with added space when you click on it. I hope it makes sense but what i mean is take a look at my landing page project here:

https://romson.github.io/MobiWallet---Product-landing-page/

When you click on that Hamburger icon in small screen view it does not add any space to the menu. How do i get that like on your website?


#12

You are not taking advantage of what the addEventListener method returns when an element inside the ul element is clicked. Read over thedocumentation for addEventListener 1 and you will see you can specify an argument for the anonymous callback function to access the Event object. The event object has a lot of information in it such as the actual element targeted (which is what you need to know). I will give you an example of how to use the event object to access the element being targeted with an example using radio buttons. It will give you something to study and try to emulate in your own code.

So basically i connect the argument to the “class” and “type” and then loop through it with (checkBox[i].checked) => { argument[i].style.textDecoration = “line-through”; }

?


#13

Found a way to get this working.

for (var i = 0; i < checkBox.length && i < listItem.length; i++) {
        if (checkBox[i].checked) {
            listItem[i].style.textDecoration = "line-through";
        }
        else {
            listItem[i].style.textDecoration = "none";
        }
    }

Would this be considered clean code to work around this?

@RandellDawson I am curious about the solution you suggested about specifying an argument for the anonymous callback function to access the Event object. Could you please share your solution with an example of how to get about that?


#14
listItems.addEventListener("click", isCheckBox);

function isCheckBox({target}) {
  if (target.type === 'checkbox') {
    taskDone(target.parentElement);
  }
} 

function taskDone(box) {
  box.classList.toggle('line-through');
}

#15

Wow nice code you got there Randell. Would be very proud of myself to write such beautiful lines of JS someday.