3

Newbie here. Probably because I'm already sleepy and can't find a solution. I'm trying to stop a function when I click on another button. Here's the code so far:

When I click on a button (.start), it runs a function that messes up the text randomly without finish. But then, I want to click on button #stop and stop that specific function from running. I've tried toggles, preventDefault, event.stopPropagation(), setInterval, etc... None of them works. I've tried putting inside my function and outside as well.

Here's the current js code:

$(".start").click(function(){
    var getTextNodesIn = function(el) {
    // Look at all the page elements and returns the text nodes
    return $(el)
      .find(":not(iframe,script)")
      .addBack()
      .contents()
      .filter(function() {
        return this.nodeType == 3; // Text node types are type 3
      });
  };

  // var textNodes = getTextNodesIn($("p, h1, h2, h3","*"));
  var textNodes = getTextNodesIn($("p"));

  function isLetter(char) {
    return /^[\d]$/.test(char);
  }

  var wordsInTextNodes = [];
  for (var i = 0; i < textNodes.length; i++) {
    var node = textNodes[i];

    var words = [];

    var re = /\w+/g;
    var match;
    while ((match = re.exec(node.nodeValue)) != null) {
      var word = match[0];
      var position = match.index;

      words.push({
        length: word.length,
        position: position
      });
    }

    wordsInTextNodes[i] = words;
  }

  function messUpWords() {
    for (var i = 0; i < textNodes.length; i++) {
      var node = textNodes[i];

      for (var j = 0; j < wordsInTextNodes[i].length; j++) {
        // Only change a tenth of the words each round.
        if (Math.random() > 1 / 10) {
          continue;
        }

        var wordMeta = wordsInTextNodes[i][j];

        var word = node.nodeValue.slice(
          wordMeta.position,
          wordMeta.position + wordMeta.length
        );
        var before = node.nodeValue.slice(0, wordMeta.position);
        var after = node.nodeValue.slice(wordMeta.position + wordMeta.length);

        node.nodeValue = before + messUpWord(word) + after;
      }
    }
  }

  function messUpWord(word) {
    if (word.length < 3) {
      return word;
    }

    return word[0] + messUpMessyPart(word.slice(1, -1)) + word[word.length - 1];
  }

  function messUpMessyPart(messyPart) {
    if (messyPart.length < 2) {
      return messyPart;
    }

    var a, b;
    while (!(a < b)) {
      a = getRandomInt(0, messyPart.length - 1);
      b = getRandomInt(0, messyPart.length - 1);
    }

    return (
      messyPart.slice(0, a) +
      messyPart[b] +
      messyPart.slice(a + 1, b) +
      messyPart[a] +
      messyPart.slice(b + 1)
    );
  }

  // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
  function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  setInterval(messUpWords, 50);

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="controls pt-5">
<button class="start">Simulate</button>
<button id="stop">Stop</button>
</div>
<br>
<br>
<br>
<div>
  <h1>Trying</h1>

  <p>Friends who have dyslexia described to me how they experience reading. She <em>can</em> read, but it takes a lot of concentration, and the letters seems to "jump around".</p>
</div>

The function works pretty well. I'm just having a hard time making another button stop this function from running... Any help would be appreciated and I wish y'all a nice week.

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
Sara Silva
  • 85
  • 2
  • 8

2 Answers2

4

You cannot stop a function from running because only one function can ever run at a time within the JavaScript runtime environment (single-threaded). When you click the button to try to stop the first function, what actually happens is that the first function finishes and then the click event associated with the button runs. And, by then, the first function has completed, so it's too late to stop it.

You can look into asynchronous operations, which run outside of the JavaScript runtime. In your case, using setInterval() to run a function repeatedly at regular intervals would probably be the best way to go. This would allow you to check between invocations to see if something else has occurred, which should stop the function from running at the next interval.

Now, you are using the interval timer, but you aren't setting up a reference to it, so you can't tell it to stop when you need it to. The solution is to associate a variable with the timer and then to call clearInterval() and pass it the timer reference as shown here:

let timer = null; // This will hold a reference to the timer so you can stop it later

$("#stop").on("click", function(){
  clearInterval(timer); // Stop the timer
});

$(".start").click(function(){
   var getTextNodesIn = function(el) {
   // Look at all the page elements and returns the text nodes
   return $(el).find(":not(iframe,script)")
               .addBack()
               .contents()
               .filter(function() {
                  return this.nodeType == 3; // Text node types are type 3
                });
  };

  // var textNodes = getTextNodesIn($("p, h1, h2, h3","*"));
  var textNodes = getTextNodesIn($("p"));

  function isLetter(char) {
    return /^[\d]$/.test(char);
  }

  var wordsInTextNodes = [];
  for (var i = 0; i < textNodes.length; i++) {
    var node = textNodes[i];

    var words = [];

    var re = /\w+/g;
    var match;
    while ((match = re.exec(node.nodeValue)) != null) {
      var word = match[0];
      var position = match.index;

      words.push({
        length: word.length,
        position: position
      });
    }

    wordsInTextNodes[i] = words;
  }

  function messUpWords() {
    for (var i = 0; i < textNodes.length; i++) {
      var node = textNodes[i];

      for (var j = 0; j < wordsInTextNodes[i].length; j++) {
        // Only change a tenth of the words each round.
        if (Math.random() > 1 / 10) {
          continue;
        }

        var wordMeta = wordsInTextNodes[i][j];

        var word = node.nodeValue.slice(
          wordMeta.position,
          wordMeta.position + wordMeta.length
        );
        var before = node.nodeValue.slice(0, wordMeta.position);
        var after = node.nodeValue.slice(wordMeta.position + wordMeta.length);

        node.nodeValue = before + messUpWord(word) + after;
      }
    }
  }

  function messUpWord(word) {
    if (word.length < 3) {
      return word;
    }

    return word[0] + messUpMessyPart(word.slice(1, -1)) + word[word.length - 1];
  }

  function messUpMessyPart(messyPart) {
    if (messyPart.length < 2) {
      return messyPart;
    }

    var a, b;
    while (!(a < b)) {
      a = getRandomInt(0, messyPart.length - 1);
      b = getRandomInt(0, messyPart.length - 1);
    }

    return (
      messyPart.slice(0, a) +
      messyPart[b] +
      messyPart.slice(a + 1, b) +
      messyPart[a] +
      messyPart.slice(b + 1)
    );
  }

  // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
  function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  timer = setInterval(messUpWords, 50); // Set interval reference to variable

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="controls pt-5">
<button class="start">Simulate</button>
<button id="stop">Stop</button>
</div>
<br>
<br>
<br>
<div>
  <h1>Trying</h1>

  <p>Friends who have dyslexia described to me how they experience reading. She <em>can</em> read, but it takes a lot of concentration, and the letters seems to "jump around".</p>
</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Thank you so much for your nice input! It was a very clear and helpful explanation =) I'll be sure to take a look at it. – Sara Silva Jun 12 '19 at 00:47
  • @SaraSilva See my updated answer with the solution. – Scott Marcus Jun 12 '19 at 00:55
  • 1
    There's people on this planet that deserve the best, oh my. I feel bad that I wasted your time to make a solution for me. I was reading about the comments you made earlier and I now came across the answer by you. And it works! I'm eternally grateful for both the solution and the excellent explanation which, for a newbie like me, made sense and is even quite logical! I need to improve my logical thinking because there were some things typed without references or any background though, it was just a trial and error. Thank you again for your time and have a nice week! – Sara Silva Jun 12 '19 at 01:05
0

You can check at the top of the messUpWords function to see if the stop button has been clicked. If it has, you can clear the interval, reset the the stop button flag, and escape the routine.

var stopBtnClicked = false;
var stopBtn = document.getElementById("stop");
stopBtn.addEventListener("click", function() {
  stopBtnClicked = true;
}, false);

$(".start").click(function() {
  var getTextNodesIn = function(el) {
    // Look at all the page elements and returns the text nodes
    return $(el)
      .find(":not(iframe,script)")
      .addBack()
      .contents()
      .filter(function() {
        return this.nodeType == 3; // Text node types are type 3
      });
  };

  // var textNodes = getTextNodesIn($("p, h1, h2, h3","*"));
  var textNodes = getTextNodesIn($("p"));

  function isLetter(char) {
    return /^[\d]$/.test(char);
  }

  var wordsInTextNodes = [];
  for (var i = 0; i < textNodes.length; i++) {
    var node = textNodes[i];

    var words = [];

    var re = /\w+/g;
    var match;
    while ((match = re.exec(node.nodeValue)) != null) {    
      var word = match[0];
      var position = match.index;

      words.push({
        length: word.length,
        position: position
      });
    }

    wordsInTextNodes[i] = words;
  }

  function messUpWords() {
    if (stopBtnClicked) {
      clearInterval(jumbleTimer);
      stopBtnClicked = false;
      return;
    }  
  
    for (var i = 0; i < textNodes.length; i++) {
      var node = textNodes[i];

      for (var j = 0; j < wordsInTextNodes[i].length; j++) {
        // Only change a tenth of the words each round.
        if (Math.random() > 1 / 10) {
          continue;
        }

        var wordMeta = wordsInTextNodes[i][j];

        var word = node.nodeValue.slice(
          wordMeta.position,
          wordMeta.position + wordMeta.length
        );
        var before = node.nodeValue.slice(0, wordMeta.position);
        var after = node.nodeValue.slice(wordMeta.position + wordMeta.length);

        node.nodeValue = before + messUpWord(word) + after;
      }
    }
  }

  function messUpWord(word) {
    if (word.length < 3) {
      return word;
    }

    return word[0] + messUpMessyPart(word.slice(1, -1)) + word[word.length - 1];
  }

  function messUpMessyPart(messyPart) {
    if (messyPart.length < 2) {
      return messyPart;
    }

    var a, b;
    while (!(a < b)) {
      a = getRandomInt(0, messyPart.length - 1);
      b = getRandomInt(0, messyPart.length - 1);
    }

    return (
      messyPart.slice(0, a) +
      messyPart[b] +
      messyPart.slice(a + 1, b) +
      messyPart[a] +
      messyPart.slice(b + 1)
    );
  }

  // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
  function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  var jumbleTimer = setInterval(messUpWords, 50);

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="controls pt-5">
  <button class="start">Simulate</button>
  <button id="stop">Stop</button>
</div>
<br>
<br>
<br>
<div>
  <h1>Trying</h1>

  <p>Friends who have dyslexia described to me how they experience reading. She <em>can</em> read, but it takes a lot of concentration, and the letters seems to "jump around".</p>
</div>
MichaelvE
  • 2,558
  • 2
  • 9
  • 18