Capax Capax - 1 month ago 15
Javascript Question

Push Array of List Item Elements to Array Outside of Loop (Plain JavaScript)

I'm trying to push a list of collected items (fruits) to an empty array outside of the loop.

Then I want to access each fruit from the array with addEventListener, and append them to a container (fruit basket) on click.

I got the function itself to work fine in a different way: https://jsfiddle.net/ec4hqas2/

However that's with the eventListener inside the loop, I want it outside, like this:

https://jsfiddle.net/ec4hqas2/8/

(function() {

var fruits = document.querySelectorAll('.list li');
var fruit_basket = document.querySelector('.fruit-basket');
var fruits_array = [];

for (var i = 0; i < fruits.length; i++) {
// Returns only numbers
fruits_array.push(i);

/* This returns the entire NodeList:
fruits_array.push(fruits[i]);
*/
}
console.log(fruits_array); // result: [0,1,2]

// Append each clicked fruit to fruit basket
fruits_array[i].addEventListener('click', function() {
fruit_basket.appendChild(this);

});

})();


Problem is that I can only manage to push the number of fruits from the loop to the array. How do I push the entire list item elements with their fruit name to the empty array?

The reason I'm trying to do this, is that I recall being told that keeping eventListener out of loop was a good practice, but I might have mixed things up, is this a bad idea in the first place?

I read these articles trying to wrap my head around NodeLists & Arrays, but I'm still confused:
https://davidwalsh.name/nodelist-array
https://toddmotto.com/a-comprehensive-dive-into-nodelists-arrays-converting-nodelists-and-understanding-the-dom/

Any help greatly appreciated! :-)

Answer

Adding the event-listener inside, or outside, a loop doesn't really matter what tends to be the problem is creating a function multiple times within the loop, as you are with your anonymous function:

for (var i = 0; i < fruits.length; i++) {
  var fruit = fruits[i];
  fruit.addEventListener('click', function() {
    fruit_basket.appendChild(this)
  });
}

The 'better' way, which avoids the recreation of the anonymous function on each loop would be to use a named function and assign that function as the event-handler within the loop:

// declaring the variables using let,
// first we retrieve all of the <li> elements held
// inside of an ancestor element with the class of
// 'list':
let fruits = document.querySelectorAll('.list li'),

// retrieving only the first - if any - element with
// the class of 'fruit-basket':
  fruit_basket = document.querySelector('.fruit-basket');

// creating a named-function, to avoid re-recreating
// an anonymous function, which will be bound as the
// event-handler later:
function addToBasket(event) {
  // the 'event' object, as well as the 'this'
  // is passed automatically from the
  // EventTarget.addEventListener() method,
  // the event.target is the element upon
  // which the listened-for event was first
  // triggered (here the <li> or a descendant
  // of that <li>):
  fruit_basket.appendChild(event.target);
}

for (let i = 0; i < fruits.length; i++) {

  // iterating through the NodeList returned
  // by document.querySelectorAll() and binding
  // the addToBasket function as the event-handler
  // for the 'click' event:
  fruits[i].addEventListener('click', addToBasket);
}

let fruits = document.querySelectorAll('.list li'),
  fruit_basket = document.querySelector('.fruit-basket');

function addToBasket(event) {
  fruit_basket.appendChild(event.target);
}

for (let i = 0; i < fruits.length; i++) {
  fruits[i].addEventListener('click', addToBasket);
}
ul {
  border: 1px solid #f90;
  width: 80%;
  margin: 0 auto 1em auto;
  border-radius: 0.5em;
  min-height: 6em;
  line-height: 2em;
}
<ul class="list">
  <li>Apple</li>
  <li>Banana</li>
  <li>Orange</li>
</ul>

<ul class="fruit-basket">

</ul>

JS Fiddle demo.

Incidentally there is an alternative means, using the <ul> elements:

// again, creating a named-function to act as the
// event-handler:
function addToBasket(event) {
  // finding the relevant element to which
  // the event.target should be appended as
  // a child, and appending the event.target:
  document.querySelector('.fruit-basket').appendChild(event.target);
}

// here we create an Array from the Array-like
// NodeList returned by document.querySelectorAll:    
Array.from(
  document.querySelectorAll('ul')

// iterating over that Array of nodes:
).forEach(

  // we use an Arrow function to bind the addToBasket
  // function as the event-handler for the 'click'
  // event on each of the <ul> elements:
  list => list.addEventListener('click', addToBasket)
);

let fruits = document.querySelectorAll('.list li'),
  fruit_basket = document.querySelector('.fruit-basket');

function addToBasket(event) {
  document.querySelector('.fruit-basket').appendChild(event.target);
}

Array.from(
  document.querySelectorAll('ul')
).forEach(
  list => list.addEventListener('click', addToBasket)
);
ul {
  border: 1px solid #f90;
  width: 80%;
  margin: 0 auto 1em auto;
  border-radius: 0.5em;
  min-height: 6em;
  line-height: 2em;
}
<ul class="list">
  <li>Apple</li>
  <li>Banana</li>
  <li>Orange</li>
</ul>

<ul class="fruit-basket">

</ul>

JS Fiddle demo.

References:

Comments