chanind / hanzi-writer

Chinese character stroke order animations and practice quizzes
https://hanziwriter.org
MIT License
3.44k stars 534 forks source link

How to get reference to writer? (generating writers programmatically) #298

Closed rodrigomorales1 closed 1 year ago

rodrigomorales1 commented 1 year ago

Table of contents

The context

Consider the following minimal simple web application with screenshot below (I think it is long but I tried to write it as simple as possible, this is the shortest I could write it). It takes a word (stored in the word variable) and create a quiz for each character in the word. Whenever a mistake occurs, a counter is incremented.

<body>
  <div>
    Number of mistakes: <span id="number-of-mistakes">0</span>
  </div>
  <div id="quizzes-container">
  </div>
  <style>
    .quiz-character {
      border: 1px solid black;
      display: inline-block;
    }
  </style>
  <script src="https://cdn.jsdelivr.net/npm/hanzi-writer@3.5/dist/hanzi-writer.min.js"></script>
  <script>
    var numberOfMistakes = 0
    var word = '动物园'
    function createDivForQuizForEachCharacter() {
      var quizContainer = document.getElementById('quizzes-container')
      for (var i = 0; i < word.length; i++) {
        var quizCharacter = document.createElement('div')
        quizCharacter.setAttribute('id', 'quiz-character-' + i.toString())
        quizCharacter.classList.add('quiz-character')
        quizContainer.appendChild(quizCharacter)
      }
    }
    function startQuizForEachCharacter() {
      for (var i = 0; i < word.length; i++) {
        var writer = HanziWriter.create('quiz-character-' + i.toString(), word[i], {
          width: 300,
          height: 300,
          showCharacter: false,
          showOutline: true,
          showHintAfterMisses: false,
          highlightOnComplete: false,
          padding: 5
        })
        writer.quiz({
          onMistake: function(strokeData) {
            incrementNumberOfMistakes()
            writer.cancelQuiz()
          }
        })
      }
    }
    function incrementNumberOfMistakes() {
      numberOfMistakes = numberOfMistakes + 1
      var a = document.getElementById('number-of-mistakes')
      a.innerText = numberOfMistakes
    }
    createDivForQuizForEachCharacter()
    startQuizForEachCharacter()
  </script>
</body>

image

The problem

As you can see, in the definition of onMistake, I'm trying to cancel the quiz whenever the user makes a mistake in that character. The problem is that regardless on which character a mistake occurs, the quiz for the last character is cancelled. I think this happens because in the last iteration of the for loop that creates the quiz for each character, the last value for writer is that of the last character, so whenever Javascript tries to access writer, it is accessing to the last quiz which is not what I want. I want each quiz to be able to reference itself in order to cancel the quiz.

        writer.quiz({
          onMistake: function(strokeData) {
            incrementNumberOfMistakes()
            writer.cancelQuiz()
          }
        })

The question

I think this is a clear example of the XY problem.

The X problem here is: Cancel a quiz when a mistake occurs The Y-solution is: Reference the writer from within the onMistake method.

chanind commented 1 year ago

It looks like the issue here is a case of variable scoping in javascript. This is a common gotcha in JS when using the var keyword, and is one of the reasons ES6 replaces var with let and const. It's confusing, but the var writer = ... in the for-loop is not actually creating a new variable, since var is function-scoped, so you're always getting the writer for the last character in the loop in the callback for writer rather than the writer you think you're getting. I think if you change var to const or let it should fix the issue, since those are block-scoped, and a for-loop is a block. TLDR; never use var in javascript, always use let and const.

rodrigomorales1 commented 1 year ago

@chanind Using const or let solved the problem. I'm not familiar with the differences between let, var and const. I'll read on that.

Thanks for helping out even though my issue is not related with the project but with Javascript itself!