0
votes

I have 2 components OperatorsList and OperatorButton. The OperatorsList contains of course my buttons and I simply want, when I click one button, to update some data :

  • I emit select with the operator.id
  • This event is captured by OperatorList component, who calls setSelectedOperator in the store

    1. First problem here, in Vue tools, I can see the store updated in real time on Vuex tab, but on the Components tab, the operator computed object is not updated until I click antoher node in the tree : I don't know if it's a display issue in Vue tools or a real data update issue.

    2. However, when it's done, I have another computed property on Vue root element called selectedOperator that should return... the selected operator : its value stays always null, I can't figure out why.

    3. Finally, on the button, I have a v-bind:class that should update when the operator.selected property is true : it never does, even though I can see the property set to true.

I just start using Vue, I'm pretty sure I do something wrong, but what ? I got the same problems before I used Vuex, using props.

Here is my OperatorList code :

<template>
    <div>
        <div class="conthdr">Operator</div>
        <div>
            <operator-button v-for="operator in operators" :op="operator.id" 
            :key="operator.id" @select="selectOp"></operator-button>
        </div>
    </div>
</template>
<script>
    import OperatorButton from './OperatorButton';
    export default {
        name: 'operators-list',
        components : {
            'operator-button': OperatorButton
        },
        computed : {
            operators() { return this.$store.getters.operators },
            selected() {
                this.operators.forEach(op =>{
                    if (op.selected) return op;
                });
                return null;
            },
        },
        methods : {
            selectOp(arg) {
                this.$store.commit('setSelectedOperator', arg);
            }            
        },
    }
</script>

OperatorButton code is

<template>
    <span>
        <button type="button" v-bind:class="{ sel: operator.selected }" 
         @click="$emit('select', {'id':operator.id})">
            {{ operateur.name }}
        </button>
    </span>
</template>
<script>
    export default {
        name: 'operator-button',
        props : ['op'],
        computed : {
            operator() {
                return this.$store.getters.operateurById(this.op);
            }
        },
    }
</script>
<style scoped>
    .sel{
        background-color : yellow;
    }
</style>

and finally my app.js look like that :

window.Vue = require('vue');
import Vuex from 'vuex';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';

const store = new Vuex.Store({
    state: {    
        periods : [],
    },
    mutations: {
        setInitialData (state, payload) {
            state.periods = payload;
        },
        setSelectedOperator(state, payload) {
            this.getters.operateurs.forEach( op => {
                op.selected = (op.id==payload.id)
            })
        },
    },
    getters : { 
       operators : (state) => { 
            if (Array.isArray(state.periods)) 
            {
                let ops = state.periods
                .map( item => {
                    return item.operators
                }).flat();
                ops.forEach(op => {
                    // op.selected=false; //replaced after Radu Diță answer by next line : 
                    if (ops.selected === undefined) op.selected=false;
                })
                return ops;
            }
        },
        operatorById : (state, getters) => (id) => {
            return getters.operators.find(operator => operator.id==id);
        },
    }
});

import Chrono from './components/Chrono.vue';
var app = new Vue({
            el: '#app',
            store,
            components : { Chrono },
            mounted () {
               this.$store.commit('setInitialData',
                    JSON.parse(this.$el.attributes.initialdata.value));
            },
            computed: {
                            ...mapState(['periods']),
                            ...mapGetters(['operators', 'operatorById']),
                            selectedOperator(){
                                this.$store.getters.operators.forEach(op =>{
                                    if (op.selected) return op;
                                });
                                return null;
                            }

            },
        });
2

2 Answers

2
votes

Your getter in vuex for operators is always setting selected to false.

operators : (state) => { 
        if (Array.isArray(state.periods)) 
        {
            let ops = state.periods
            .map( item => {
                return item.operators
            }).flat();
            ops.forEach(op => {
                op.selected=false;
            })
            return ops;
        }
    }

I'm guessing you do this for initialisation, but that's a bad place to put it, as you'll never get a selected operator from that getter. Just move it to the proper mutations. setInitialData seems like the right place.

0
votes

Finally I found where my problems came from : The $el.attributes.initialdata.value came from an API and the operator objects it contained didn't have a selected property, so I added it after data was set and it was not reactive. I just added this property on server side before converting to JSON and sending to Vue, removed the code pointed by Radu Diță since it was now useless, and it works.