0
votes

I have a list of components called client-row and when I select one I want to change the style. I run into an issue when trying to remove the styling from the previously selected row, when selecting a new row.

Vue.component('client-row', {
    template: '#client-row',
    props: {
        client: Object,
    },
    data: function() {
        return {
            selected: false
        }
    },
    methods: {
        select: function() {

            // Does not work properly
            el = document.querySelector('.chosen_row')
            console.log(el)

            if ( el ) {
                el.className = el.className - "chosen_row"
            }
            this.selected = true
            this.$emit('selected', this.client)
        }
    }
})

<script type="text/x-template" id="client-row">
    <tr v-bind:class="{ 'chosen_row': selected }">
        <td>{{ client.name }}</td>
        <td>{{ client.location_name || 'no location found' }}</td>
        <td>{{ client.email || 'no email found' }}</td>
        <td><button class="btn btn-sm btn-awaken" @click="select()">Select</button></td>
    </tr>
</script>

I can properly set the selected property, but cannot seem to remove it reliably.

1

1 Answers

1
votes

It is generally bad practice to manually modify DOM elements in components. Instead, I recommend that you change the parent component to have a data field to keep track of which row is selected, and to pass that value into the rows. The row would then check whether its value matches the parent's selected row and apply style if true.

DOM manipulation in components is a sign you are doing things very wrong in vue.

In your case, vue and your manually DOM manipulation are battling each other. Vue is tracking whether to add the chosen_row class on the tr based on whether the child's data field selected is true or not. In your code, you are only ever setting it to true. Vue will always try to include the class for any row you have clicked. Then you are manually removing the class from all rows that were previously clicked, however Vue will still try to add the class because selected is still true in the child components that have been clicked.

You need to do a data oriented approach rather than a DOM manipulation based approach.

Child:

Vue.component('client-row', {
    template: '#client-row',
    props: {
        client: Object,
        selectedClient: Object
    },
    methods: {
        select: function() {
            this.$emit('selected', this.client);
        }
    }
})

<script type="text/x-template" id="client-row">
    <tr v-bind:class="{ 'chosen_row': client === selectedClient }">
        <!-- td's removed for brevity -->
        <td><button class="btn btn-sm btn-awaken" @click="select">Select</button></td>
    </tr>
</script>

Parent:

Vue.component('parent', {
    template: '#parent',
    data() {
        return {
            clients: [],
            selectedClient: null
        };
    },
    methods: {
        clientSelected(client) {
            this.selectedClient = client;
        }
    }
})

<script type="text/x-template" id="parent">
    <!-- i dont know what your parent looks like, so this is as simple as i can make it -->
    <div v-for="client in clients">
        <client-row :client="client" :selected-client="selectedClient" @selected="clientSelected"></client-row>
    </div>
</script>

In addition:

Your click event handler on the button can be shortened to @click="select" which is the recommended way of binding methods.