0

I can't addEventListener to my own element I get the error "TypeError: i.getAttribute is not a function at HTMLLIElement." I get

HTML CODE:

<ul>
    <li class="item"></li>
    <li class="item"></li>
    <li class="item"></li>
    <li class="item"></li>
    <li class="item"></li>
</ul>

JS:

var items = document.getElementsByClassName('item');

for (i of items) {
    i.addEventListener("click", function(i) {
        i.classList.add("selected");
    })
}

ERROR:

TypeError: i.getAttribute is not a function
    at HTMLLIElement.
emanuel
  • 3
  • 3
  • 1
    Have you simply confused yourself by re-using the same variable name in different scopes? What do you expect `i` to be within the click handler function? – David Jun 13 '23 at 12:49
  • I want to access the element in the "items" variable that I am looping in the – emanuel Jun 13 '23 at 12:52

3 Answers3

1

i.getAttribute is not a function

Because getAttribute is not a function on Events. And i is an Event:

function(i) {
  //...
}

If your goal is to access this i:

for (i of items) {

Which, incidentally, should probably not be an implicit global and should explicitly declare the variable in the current scope:

for (let i of items) {

Then don't shadow it with another variable of the same name. Either remove the Event reference entirely if you don't intend to use it:

function() {
  //...
}

Or, if you do intend to use it, give it a different name:

function(e) {
  //...
}
David
  • 208,112
  • 36
  • 198
  • 279
  • Hey David, I'm getting that error again, my code: `var items = document.getElementsByClassName('item'); for (let i of items) { i.addEventListener("click", (e) => { e.classList.add("selected"); }) }` – emanuel Jun 13 '23 at 12:57
  • 1
    @emanuel `e` i still the event object, not the element so it doesn't have a classlist. Try to use `e.currentTarget.classList.add("selected");`. CurrentTarget is the element that triggered the event handler. – Mark Baijens Jun 13 '23 at 12:59
  • 1
    @MarkBaijens [`currentTarget`](https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget) does not necessarily refer to the element that triggered the event (that would be `target`). It refers to the element to which the event handler has been attached. That makes working with event delegation a bit easier. – Scott Marcus Jun 13 '23 at 13:41
  • @ScottMarcus Thats right, brainfart from my side. Thanks for correcting. Still currentTarget is the way to go for OP to avoid problems when adding child elements. – Mark Baijens Jun 13 '23 at 13:58
1

Your reuse of the i variable within the event callback isn't referencing the element as you think it is, therefore causing the error.

This all stems from the way you are attempting to loop over the selected elements and how you are attempting to access them when you loop. getElementsByClassName() should rarely (if ever) be used and most certainly not if you are going to loop over the returned node list.

And, if you use the correct modern approach to getting the references, you can then use the modern .forEach() method to iterate over the node list.

Then, during each iteration, when you set up the event handler, you can refer to the current item with the argument specified within the callback function of the forEach() method.

Finally, within the event handler, you can refer to the element that triggered the event in the first place with the this identifier.

Here would be the modern way to do this:

// No need to set up a variable to reference the selected nodes if you 
// don't plan to reference the entire node list again. Instead, you can
// just "chain" the .forEach() method on the resulting node list. 

// Note that forEach() specifies (with the first argument) the identifier
// that each item in the node list should be accessible as (element in this case):
document.querySelectorAll(".item").forEach(function(element){
  // Then you can access each item during the loop with the variable from forEach():
  element.addEventListener("click", function(event){
     // And within the event handler, this will refer to the element that triggered
     // the event in the first place. Using this, you can access properties of the element
     // directly with "dot notation".
     this.classList.add("active");
     // Just for fun:
     console.log("You initiated the " + event.type + " event of the " + this + " element, which has the: " + this.className + " class(es).");
  });
});
.active { color:red; }
<ul>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
</ul>

And, if you are wanting only the most recently clicked item to have the active class, you need to loop over all the items upon the click of any of them and remove the class first. Then, you can apply it to only the one that was just clicked. That would look like this:

let items = document.querySelectorAll(".item");

items.forEach(function(element){

  element.addEventListener("click", function(event){
     // First, loop over all the node list elements and remove the active class
     items.forEach(function(item){
       item.classList.remove("active");
     });
     
     // The apply the active class to only the one that just got clicked:
     this.classList.add("active");
  });
  
});
.active { color:red; }
<ul>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
    <li class="item">test</li>
</ul>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
-1

You need to define i first.

for (let i of items)

hope this solve your issue

Niks Banna
  • 39
  • 4
  • 1
    I'd recommend to test an answer before posting. That way you would have noticed that it doesn't solve the OP issue. – Mark Baijens Jun 13 '23 at 12:57
  • Javascript *implicitly* declares variables as global if they have not been defined *explicitly*. Regardless, this is not the cause of OPs issue. – freedomn-m Jun 13 '23 at 13:29