0
votes

I have a fill in the blank question. Each time an input is correct I want to emit back to the parent so that when all are correct I can display a success message.

I figured I could just use a simple counter to increment by one each time the $emit came to the parent but always on the last try I get an infinite loop. I don't understand what I am doing wrong. I tried to strip out my files as much as possible to show what I believe is relevant and you can get an idea of how it works. Is it something with how the parent loops of the question or something?

Thanks for any help you can give.

Parent Component

Parent component takes a JOSN string like "There are *_* letters in the *_* alphabet" and splits it into spans for the text and inputs for the *_* then reassembles it. The answers would be 26 and English

The parts of trouble in parent is the function questionSuccess()

<template>
  <div class="interactive interactive-vue-question interactive-vue-question-iv">
    <component
      :is="buildQuestion(index)"
      v-for="(item, index) in questionParts"
      ref="ivWrap"
      :key="index"
      :question-props="item.includes('*_*') ? matchedOptions[index] : item"
      @success="questionSuccess"
    />
  </div>
</template>

<script>
export default {
  name: 'IvV1',

  components: {
    ivInput,
    ivText,
  },

  props: ['cData'],

  data: function () {
    return {
      questionParts: [],
      matchedOptions: [],
      options: this.cData.body.options,
      optionsIndex: 0,
      answerBtnText: 'Show Answer(s)',
      showAnswers: false,
      showHints: false,
      showModal: false,
      counter: 0
    }
  },

  created () {
    const { questionText } = this.cData.body
    // Split the array at any whitespace character, and groups *_* an returns an array
    this.questionParts = questionText.split(/\s*(\*_\*)\s*/)
    // Filter out any empty array items so there's no extra unkown values
    this.questionParts = this.questionParts.filter(function (e) { return e })
    console.log(this.showHints)
  },

  beforeMount: function () {
    // Find all the questionParts
    for (let x = 0; x < this.questionParts.length; x++) {
      // If input placeholder is found push the corresponding answer from answers into matchedAnswers array
      if (this.questionParts[x].includes('*_*')) {
        this.matchedOptions.push(this.options[this.optionsIndex])
        this.optionsIndex++
      } else {
        // If not push an empty string
        this.matchedOptions.push('')
      }
    }
  },

  methods: {
    buildQuestion: function (index) {
      // Find the question parts that inlude *_* and push an input to array otherwise push the <span> text </span>
      if (this.questionParts[index].includes('*_*')) {
        return ivInput
      } else {
        return ivText
      }
    },

   // THIS IS WHERE I AM TRYING TO COUNT EACH TIME IT RUNS
    questionSuccess: function () {
      this.counter++
      console.log(this.counter)
    },
  }
}
</script>

Input Component

Each input is validated separately so when each input is correct I want to emit that success to parent. Then in parent count the times it has been emitted so I can show a message but I get infinite loop on last correct input

I emit 'success' after isCorrect()

<template>
  <div class="iv-input-wrap" :class="[{'is-error': isError, 'is-correct': isCorrect}]">
    <input
      ref="iv"
      v-model="userInput"
      type="text"
      class="iv-input"
      :class="[{'is-correct':isCorrect, 'is-error':isError}]"
      :disabled="isCorrect"
    >
  </div>
</template>

<script>
export default {
  name: 'IvInput',

  props: [
    'cData',
    'questionProps',
  ],

  data: function () {
    return {
      userInput: '',
      errorMessage: '',
      showPadding: this.questionProps.showPadding,
      displayErrorMessage: this.cData.config.displayErrorMessage,
    }
  },

  computed: {
    isCorrect: function () {
      if (this.isType === true && this.isMatch === true) {
        this.$emit('success')

        return true
      }
    },
    isError: function () {
      // If isType has failed and displayErrorMessages are turned on return
      if (this.isType === false && this.displayErrorMessage === true) {
        this.$nextTick(function () {
          new Popper(this.$refs.iv, this.$refs.errorPopper, {
            placement: 'bottom'
          })
        })

        return true
      }
    },

    hideErrorMessage: function () {
      // If Display error message has been turned off
      if (this.isType === false && this.displayErrorMessage === false) {
        return true
      }
    },

    isType: function () {
      // If the answer type is a string
      let charRegex
      if (this.questionProps.type === 'string') {
        // Check what the accent allowed is
        if (this.questionProps.accent === true) {
          // Allow alpha and accented characters
          charRegex = /^[A-Za-z\u00C0-\u024F]+$/
          this.errorMessage = 'Letters Only'
        } else {
          // Allow alpha characters only
          charRegex = /^[A-Za-z]+$/
          this.errorMessage = 'Letters only'
        }
        // Check if the user has typed anything, if so match input to char-regex
        if (this.userInput.length > 0) {
          if (this.userInput.match(charRegex)) {
            return true
          } else {
            return false
          }
        }
      } // End if is string
    },

    isMatch: function () {
      // If isType has not passed then we don't need to proceed
      let checkCase
      if (this.isType === true) {
        checkCase = this.userInput.localeCompare(
          this.questionProps.answer, { sensitivity: 'case', usage: 'search' })
      } // end isType true
    } // end isMatch
  },
}
</script>
1

1 Answers

0
votes

Answer was actually fairly simple.

On the parent component I added .once on the event and this stopped the loop and allowed it to increment as expected.

<template>
     <div class="interactive interactive-vue-question interactive-vue-question-iv">
     <component 
       :is="buildQuestion(index)"
        ref="ivWrap"
        v-for="(item, index) in questionParts"
        :key="index"
        :questionProps="item.includes('*_*') ? matchedOptions[index] : item"
         @success.once="questionSuccess">
     </component>
   </div>
</template>