Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to sync Vuejs components displayed multiple times on the same page?

I have a web page that displays items. For each items there is a button (vuejs component) which allow user to toggle (add/remove) this item to his collection.

Here is the component:

<template lang="html">
        <button type="button" @click="toggle" name="button" class="btn" :class="{'btn-danger': dAdded, 'btn-primary': !dAdded}">{{ dText }}</button>
    </template>

<script>
export default {
    props: {
        added: Boolean,
        text: String,
        id: Number,
    },
    data() {
        return {
            dAdded: this.added,
            dText: this.text,
            dId: this.id
        }
    },
    watch: {
        added: function(newVal, oldVal) { // watch it
            this.dAdded = this.added
        },
        text: function(newVal, oldVal) { // watch it
            this.dText = this.text
        },
        id: function(newVal, oldVal) { // watch it
            this.dId = this.id
        }
    },
    methods: {
        toggle: function(event) {
            axios.post(route('frontend.user.profile.pop.toggle', {
                    pop_id: this.dId
                }))
                .then(response => {
                    this.dText = response.data.message
                    let success = response.data.success
                    this.dText = response.data.new_text
                    if (success) {
                        this.dAdded = success.attached.length
                        let cardPop = document.getElementById('card-pop-'+this.dId);
                        if(cardPop)
                            cardPop.classList.toggle('owned')
                    }
                })
                .catch(e => {
                    console.log(e)
                })
        }
    }
}
</script>

For each item, the user can also open a modal, loaded by a click on this link:

<a href="#" data-toggle="modal" data-target="#popModal" @click="id = {{$pop->id}}">
    <figure>
        <img class="card-img-top" src="{{ URL::asset($pop->img_path) }}" alt="Card image cap">
    </figure>
</a>

The modal is also a Vuejs component:

<template>
    <section id="pop" class="h-100">
        <div class="card">
            <div class="container-fluid">
                <div class="row">

                    <div class="col-12 col-lg-1 flex-column others d-none d-xl-block">
                        <div class="row flex-column h-100">

                            <div v-for="other_pop in pop.other_pops" class="col">
                                <a :href="route('frontend.pop.collection.detail', {collection: pop.collection.slug, pop: other_pop.slug})">
                                        <img :src="other_pop.img_path" :alt="'{{ other_pop.name }}'" class="img-fluid">
                                    </a>
                            </div>

                            <div class="col active order-3">
                                <img :src="pop.img_path" :alt="pop.name" class="img-fluid">
                            </div>

                        </div>
                    </div>

                    <div class="col-12 col-lg-6 content text-center">
                        <div class="row">
                            <div class="col-12">
                                <img :src="pop.img_path" :alt="pop.name" class="img-fluid">
                            </div>

                            <div class="col-6 text-right">
                                <toggle-pop :id="pop.id" :added="pop.in_user_collection" :text="pop.in_user_collection ? 'Supprimer' : 'Ajouter'"></toggle-pop>
                            </div>
                            <div class="col-6 text-left">
                                <!-- <btnaddpopwhishlist :pop_id="propid" :added="pop.in_user_whishlist" :text="pop.in_user_whishlist ? 'Supprimer' : 'Ajouter'"></btnaddpopwhishlist> -->
                            </div>
                        </div>


                    </div>
                    <div class="col-12 col-lg-5 infos">
                        <div class="header">
                            <h1 class="h-100">{{ pop.name }}</h1>
                        </div>
                        <div class="card yellow">
                            <div class="card p-0">
                                <div class="container-fluid">
                                    <div class="row">
                                        <div class="col-3 py-2">

                                        </div>
                                        <div class="col-6 py-2 bg-lightgray">
                                            <h4>Collection:</h4>
                                            <h3>{{ pop.collection ? pop.collection.name : '' }}</h3>
                                        </div>
                                        <div class="col-3 py-2 bg-lightgray text-center">
                                            <a :href="route('frontend.index') + 'collections/' + pop.collection.slug" class="btn-round right white"></a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
</template>

<script>
export default {
    props: {
        id: Number
    },
    data() {
        return {
            pop: {
                collection: {

                }
            }
        }
    },
    ready: function() {
        if (this.propid != -1)
            this.fetchData()
    },
    watch: {
        id: function(newVal, oldVal) { // watch it
            // console.log('Prop changed: ', newVal, ' | was: ', oldVal)
            this.fetchData()
        }
    },
    computed: {
        imgSrc: function() {
            if (this.pop.img_path)
                return 'storage/images/pops/' + this.pop.img_path
            else
                return ''
        }
    },
    methods: {
        fetchData() {
            axios.get(route('frontend.api.v1.pops.show', this.id))
                .then(response => {
                    // JSON responses are automatically parsed.
                    // console.log(response.data.data.collection)
                    this.pop = response.data.data
                })
                .catch(e => {
                    this.errors.push(e)
                })
            // console.log('fetchData')
        }
    }
}
</script>

Here is my app.js script :

window.Vue = require('vue');

Vue.component('pop-modal', require('./components/PopModal.vue'));
Vue.component('toggle-pop', require('./components/TogglePop.vue'));

const app = new Vue({
    el: '#app',
    props: {
        id: Number
    }
});

I would like to sync the states of the component named toggle-pop, how can I achieve this ? One is rendered by Blade template (laravel) and the other one by pop-modal component. But they are just the same, displayed at different places.

Thanks.

like image 216
Jérémy Halin Avatar asked Apr 09 '18 19:04

Jérémy Halin


2 Answers

You could pass a state object as a property to the toggle-pop components. They could use this property to store/modify their state. In this way you can have multiple sets of components sharing state.

Your component could become:

<template lang="html">
  <button type="button" @click="toggle" name="button" class="btn" :class="{'btn-danger': sstate.added, 'btn-primary': !sstate.added}">{{ sstate.text }}</button>
</template>

<script>
export default {
  props: {
    sstate: {
      type: Object,
      default: function() {
        return { added: false, text: "", id: -1 };
      }
    }
  },
  data() {
    return {};
  },
  methods: {
    toggle: function(event) {
      axios.post(route('frontend.user.profile.pop.toggle', {
        pop_id: this.sstate.id
      }))
        .then(response => {
            this.sstate.text = response.data.message
            let success = response.data.success
            this.sstate.text = response.data.new_text
            if (success) {
                this.sstate.ddded = success.attached.length
                let cardPop = document.getElementById('card-pop-'+this.sstate.id);
                if(cardPop)
                    cardPop.classList.toggle('owned')
            }
        })
        .catch(e => {
          console.log(e)
        })
  }
};
</script>

Live demo

https://codesandbox.io/s/vq8r33o1w7

like image 89
Marco Pantaleoni Avatar answered Oct 06 '22 09:10

Marco Pantaleoni


If you are 100% sure that all toggle-pop components should always have the same state, you can choose to not define data as a function. Just declare it as an object.

  data: {
    dAdded: this.added,
    dText: this.text,
    dId: this.id
  }

In https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, it mentions

a component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object

If Vue didn’t have this rule, clicking on one button would affect the data of all other instances

Since you want to sync the data of all toggle-pop component instances, you don't have to follow the data option must be a function rule.

like image 30
Jacob Goh Avatar answered Oct 06 '22 08:10

Jacob Goh