How can I loop through indexes that rely on multiple functions?

Viewed 71

I am using two types of elements, .element and .summary. The index of both classes correspond to each other, so when a user has a mouseover event on .element, a class should be added to its corresponding .summary. Similarly, that same class should be removed on mouseout.

In other words, I'm looking to have the .styling class only applied to .summary when .element is on mouseover

Without declaring so many variables, I was wondering the most efficient way to loop through these elements.

var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");

for (let i = 1; i < xyz.length; i++) {
  abc[i].addEventListener("mouseover", movein(i), false);
  abc[i].addEventListener("mouseout", moveout(i), false);
}

function movein(i) {
  xyz[i].classList.add("styling");
}

function moveout(i) {
  xyz[i].classList.remove("styling");
}
.element {
  cursor: pointer;
}

.styling {
  background: red;
}
<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>

<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>

From what I can tell, these functions run but it doesn't work on mouseover.

3 Answers

You could do it like this, and not have to update your html. We programmatically set up an attribute so we can get it's index when we mouse over it. Then we use that index to find the associated summary. Note that you don't pass the i through the event listener - you test for the event.target and pick up that shiny new attribute with event.target.dataset per the code below.

Another bonus here is that we've combined the event listener logic which you can see makes sense. This answer leverages your existing structure, uses less code and is easy to understand.

var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");
for (let i = 0; i < xyz.length; i++) {
  abc[i].setAttribute('data-index', i)
  abc[i].addEventListener("mouseover", mousemove);
  abc[i].addEventListener("mouseout", mousemove);

}

function mousemove(event) {
  let element = xyz[event.target.dataset.index];
  if (event.type == 'mouseover') element.classList.add("styling");
  else element.classList.remove("styling");
}
.element {
  cursor: pointer;
}

.styling {
  background: red;
}
<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>

<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>

You don't actually have to loop, in fact it's probably best you don't. If you have containers for each section of your HTML: elements, and summaries, it's much more efficient to use event delegation to catch element events as they bubble up the DOM, and use one handler to decide how to style your summaries depending on their data attributes.

// Cache the containers
const elements = document.querySelector('#elements');
const summaries = document.querySelector('#summaries');

// Add listeners to the containers
elements.addEventListener('mouseover', handleMouse, false);
elements.addEventListener('mouseout', handleMouse, false);

function handleMouse(e) {

  // Destructure the type and id from the element
  const { type, target: { dataset: { id } } } = e;

  // Find the corresponding summary - this uses
  // a template literal to create the query
  const summary = summaries.querySelector(`[data-id="${id}"]`);

  // And then, depending on the event type, add or remove the style
  if (type === 'mouseover') {
    summary.classList.add("styling");
  } else {
    summary.classList.remove("styling");
  }
}
.element { cursor: pointer; }
.styling { background: red; }
<div id="elements">
  <div data-id="1" class="element">Element 1</div>
  <div data-id="2" class="element">Element 2</div>
  <div data-id="3" class="element">Element 3</div>
</div>

<div id="summaries">
  <div data-id="1" class="summary">Summary 1</div>
  <div data-id="2" class="summary">Summary 2</div>
  <div data-id="3" class="summary">Summary 3</div>
</div>

Additional documentation

Fixing your code to just refer to the functions (not call them) in addEventListener, to loop starting at 0 (not 1), and to use ids to get the corresponding elements...

There's nothing wrong with a short loop.

var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");

// fixed, was i=1
for (let i = 0; i < xyz.length; i++) {
  // fixed, was movein(i), moveout(i)
  abc[i].addEventListener("mouseover", movein, false);
  abc[i].addEventListener("mouseout", moveout, false);
}

// these get events as params, and can use the event.target.id
// to distinguish which element was triggered
function movein(event) {
  let id = event.target.id
  xyz[id].classList.add("styling");
}

function moveout(event) {
  let id = event.target.id
  xyz[id].classList.remove("styling");
}
.element {
  cursor: pointer;
}

.styling {
  background: red;
}
<div class="element" id="0">Element 1</div>
<div class="element" id="1">Element 2</div>
<div class="element" id="2">Element 3</div>

<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>

Related