vue.jsvuejs2vue-class-components

vue js class binding on repetitious element


I wanna create a simple game helps kids to read, in fact it is a language learning approach named 'silent mode', anyway this is the jsfiddle link

https://jsfiddle.net/raminSafari/b7zhc98q/19/

for example if I want a student to read 'pen' first I point to p(1) then e(2) and finally n(3), the code works fine with words which have unique letters, but when the word is something like 'dad' it is not working the way i want, i want it to show d(1)(3), a(2)

here is the complete simplified code(i know it's not robust)

<template>
    <div class = "container pt-5 mt-5">
            <h1 class="text-center pb-5"><span style="color: red;"> {{ answer }} </span> just to clarify</h1> <!-- just to clarify -->
    
   

        
        <div class="text-center">
        <template id="keyboard" v-for="alphabet in alphabets" >
            <template v-if = "alphabet == word.first ">
                    <span :class="{ 'active': firstActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num1 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.second ">
                   <span :class="{ 'active': secondActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num2 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.third ">
                   <span :class="{ 'active': thirdActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num3 }}</strong>&nbsp;
            </template>
           
             <template v-else-if = "alphabet == word.forth ">
                   <span :class="{ 'active': forthActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num4 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.fifth ">
                   <span :class="{ 'active': forthActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num5 }}</strong>&nbsp;
            </template>
            <template v-else>
                <span class="alphabet"> {{ alphabet }} </span>&nbsp;
            </template>
        </template>
       

                 <div><button class = "btn btn-info mt-3" @click = "again">again</button></div>
        </div>
           
            
     </div>
</template>

<script>
export default {
     data(){
        return{
         
           alphabets: ["p", "e", "m", "n", "d", "a", "s"],
           firstActive: false,
           secondActive: false,
           thirdActive: false,
           forthActive: false,
           fifthActive: false,
           index: 0,
           words:[
                {
                    first: 'p',
                    second: 'e',
                    third: 'n',
                    forth: '',
                    fifth: '',
                    answer : 'pen'
                },
                {
                    first: 'm',
                    second: 'a',
                    third: 'd',
                    forth: 'e',
                    fifth: '',
                    answer : 'made'
                },
                {
                    first: 'd',
                    second: 'a',
                    third: 'd',
                    forth: '',
                    fifth: '',
                    answer : 'dad'
                },

           ],
           word: [],
            answer: '', 
            myVar1: null,
            myVar2: null,
            myVar3: null,
            myVar4: null,
            myVar5: null,
            num1: '',
            num2: '',
            num3: '',
            num4: '',
            num5: ''
                     
        }
    },

    methods: {

        shuffle(a) {
                for (let i = a.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [a[i], a[j]] = [a[j], a[i]];
                }
        
                return a;
            },

            getWord(){
                this.word = this.words[this.index];
                this.answer = this.word.answer;
            },

            again(){
                clearTimeout(this.myVar1);
                clearTimeout(this.myVar2);
                clearTimeout(this.myVar3);
                clearTimeout(this.myVar4);
                clearTimeout(this.myVar5);
                this.firstActive = false;
                this.secondActive = false;
                this.thirdActive = false;
                this.forthActive = false;
                this.fifthActive = false;
                this.num1 = '';
            this.num2 = '';
            this.num3 = '';
            this.num4 = '';
            this.num5 = '';
                if(this.index == this.words.length){
                    this.index = 0;
                }else{
                    this.index++;
                }
                this.getWord();
                this.showBorder();
            },


            showBorder(){
                     this.myVar1 =  setTimeout(() => {
                         this.firstActive = true;
                         this.num1 = 1;
                    }, 2000);

                     this.myVar2 =  setTimeout(() => {
                         this.secondActive = true;
                         this.num2 = 2;
                    }, 4000);

                    this.myVar3 =  setTimeout(() => {
                         this.thirdActive = true;
                         this.num3 = 3;
                    }, 6000);

                    this.myVar4 =  setTimeout(() => {
                         this.forthActive = true;
                         this.num4 = 4;
                    }, 8000);

                    this.myVar5 =  setTimeout(() => {
                         this.fifthActive = true;
                         this.num5 = 5;
                    }, 10000);
            }
       


    },
    

   
     created(){
            
         this.words = this.shuffle(this.words);
         this.getWord();
         this.showBorder();
                                
      }

      



}
</script>

<style>
    span.alphabet{
         display: inline-block;
         width: 70px;
         height: 70px;
        
        font-size: 30px;
        font-weight: 600;
    }
    .active{
        border: 2px solid red;
        border-radius: 50%;
    }
</style>

thank you


Solution

  • The answer is not just a copy-and-paste code and it is a bit long. If you don't want to read that you may just skip to the conclusion.

    I read the jsfiddle and here are some suggestions to solve the problem you are facing:

    1. Treat the numbers as a string, not number

    After that, you will know that the d(1)(3) issue is not showing 2 numbers, it's indeed how to append the string "1" and "3" and display it.

    2. Divide-and-conquer

    Divide your whole task into 3 parts:

    1. Create functions to control which alphabet should be highlighted and showing corresponding sequence.
    2. Create a component, just to display the alphabet and the sequence e.g. d(1)(3).
    3. Define functions to trigger the rendering process and reset the app state.

    App.vue

    This is what the data looks like in App.vue

    data: function() {
        return {
            word: "dad",
            alphabets: ["p", "e", "m", "n", "d", "a", "s"],
            hits: ["", "", "", "", "", "", ""],
            handlers: [],
            delay: 2000,
        }
    },
    

    this.hits will store the corresponding sequences of the hit. For example, for the case of dad, the hits will eventually become: ["", "", "", "", "13", "2", ""].

    These are the functions you should have in App.vue

    renderAnswer: function() {
        var self = this;
        for (var i = 0; i < this.word.length; i++) {
            self.doSetTimeout(i, self);
        }
    }
    
    doSetTimeout: function (i, self) {
        self.handlers.push(setTimeout(function () { 
            self.updateHits(i, self) 
        }, self.delay * i));
    }
    
    updateHits: function(i, self) {
        let char = self.word[i];
        let index = self.alphabets.indexOf(char);
        console.debug(char, index)
        self.hits[index] += (i + 1);
        self.hits = self.hits.filter(x => true); // force refresh list for binding
    }
    

    Divide your big function into some smaller functions and let each of them just doing 1 thing.

    Note: Here, you will see renderAnswer() calls doSetTimeout() and doSetTimeout() calls updateHits(). In order to get the data and functions correctly, we need to define var self = this and pass self to each function call.

    Sub-component for the alphabet

    In the template of App.vue, you need to define a <alphabet> sub-component. This component is solely responsible for render a particular alphabet and the sequence related to that alphabet.

    <alphabet 
        v-for="(char, index) in alphabets" 
        v-bind:key="char" 
        :alphabet="char" 
        v-bind:hits="hits[index]"
    />
    

    You need to pass the alphabet and the corresponding hit value to the sub-component in order to render the component properly. The code of this sub-component should be straight-forward so I will skip it here. You may need to know how to pass and use props from the official documentaion when doing this part.

    Run the app

    When you set up all the functions and the sub-component, you should be able to render dad's case when you trigger renderAnswer().

    Reset the status

    You may need to reset the app status for each new word. Therefore, you should also define a reset() function to update this.word into a new word, and reset each item in this.hits as empty string.

    Read this gist if you need more details of the above steps.

    Conclusion

    When you are facing some problem on coding, you can try to re-phasing the problem you met. Asking the right questions will lead you to solve them using a better approach.

    On the other hand, break down the problem into smaller problems and tackle them one by one. In this case, try to split the big function into some smaller and simpler functions. And, try to create a sub-component to do the rendering part while leave all the logic to its parent.

    Hope you can solve the problem you met and help more kids :)