1

I tried removing all class after 1 second but this did not work, the following code:

function copyLink() {
  var text = document.getElementsByClassName("text"),
      len = text.length;
  for(i = 0; i < len; i++) {
      text[i].classList.add("copied");
      setTimeout(function() {
        text[1].classList.remove("copied");
      },1000);
  }
}
.copied {color: red;}
<p class="link text">Text</p>

<p class="demo text">Some text</p>

<button onclick="copyLink(location.href)">Click me</button>
Asalan
  • 85
  • 2
  • 11
  • loop has completed long before setTimeout fires so `i` is at it's maximum then – charlietfl Nov 26 '17 at 23:10
  • @charlietfl this is not a duplicate, try to note, the code only works on one class, not working on all classes – Asalan Nov 26 '17 at 23:45
  • It certainly is duplicate ...the problem is the same and tht link is the canonocal answer for the lultitude of similar issues that come up using asynchronous code in a `for()` loop – charlietfl Nov 26 '17 at 23:50
  • @Asalan the edit made to your post removes use of the loop counter `i` in the timeout call back function, so the edited question doesn't match the duplicate. But the edited question works and doesn't have a problem to be answered. Catch 22. – traktor Nov 27 '17 at 06:44

1 Answers1

-1

Problem

The loop finishes executing before the timeouts callbacks have started, so when they are executed the i variable is len - 1 for all of them.

ES6 Approach

If you are working with ES6 you can just change the var to a let. So it would look like this:

for(let i = 0; i < len; i++) {
    setTimeout(function() {
        text[i].classList.remove("copied");
    }, 1000);
}

ES5 Approach

If your application is only using ES5 then the only to solve your problem is by creating a new scope and insert the setTimeout there.

Creating a new scope with a catch block

for(var i = 0; i < len; i++) {
    try { throw i; } catch(i) {
        setTimeout(function() {
            text[i].classList.remove("copied");
        }, 1000);
    }
}

Creating a new scope with an IIFE

for(var i = 0; i < len; i++) {
    (function(i) {
        setTimeout(function() {
            text[i].classList.remove("copied");
        }, 1000);
    })();
}

Creating a new scope with a function call

for(var i = 0; i < len; i++) {
    loopCallBack(i);
}

function loopCallBack(i) {
    setTimeout(function() {
        text[i].classList.remove("copied");
    }, 1000);
}

Full ES5 Example

function copyLink() {
  var text = document.getElementsByClassName("text"),
    len = text.length;
  for (var i = 0; i < len; i++) {
    (function(i) {
      text[i].classList.add("copied");
      setTimeout(function() {
        text[i].classList.remove("copied");
      }, 1000);
    })(i);
  }
}
.copied {
  color: red;
}
<p class="link text">Text</p>

<p class="demo text">Some text</p>

<a href="javascript:copyLink()">Click me</a>

A Different Approach

Instead of creating a new scope for each iteration you could just add the for loop in the setTimeout call-back

function copyLink() {
  var text = document.getElementsByClassName("text"),
    len = text.length;
  for (var i = 0; i < len; i++) {
    text[i].classList.add("copied");
  }
  setTimeout(function() {
    for (var i = 0; i < len; i++) {
      text[i].classList.remove("copied");
    }
  }, 1000);
}
.copied {
  color: red;
}
<p class="link text">Text</p>

<p class="demo text">Some text</p>

<a href="javascript:copyLink()">Click me</a>
nick zoum
  • 7,216
  • 7
  • 36
  • 80