I'm working on a component in Vue3/TypeScript. I'm relatively new to Vue.
I'm dynamically creating a div. I want to be able to delete it when a user clicks on the "x" button. This is the "closeStatus" method.
In JavaScript I can pass this, but Vue hates it.
If anyone can help.
Thanks.
Here is the code:
<template>
<div>
<button #click="addStatus()" >Add Status</button>
<div id="slide" class="slide-modal">
<div class="clear-notifications" #click='clearAllNotifications()'>Clear all notifications</div>
<div class="status-div">
<div>11:00 Booking requested</div>
<div #click="closeStatus(this)"><img src="../assets/cross.svg" /></div>
</div>
<div class="status-div">
<div>11:30 Booking requested</div>
<div #click="closeStatus(this)"><img src="../assets/cross.svg" /></div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'SlideModal',
methods: {
clearAllNotifications() {
const allNotifications = document.querySelectorAll(".status-div");
for(let x=0;x<allNotifications.length;x++) {
allNotifications[x].remove(); //removes all the child elements.
}
document.getElementById('slide').style.visibility='hidden'; //This should be Vue reactive.
},
addStatus() {
document.getElementById('slide').style.visibility='visible'; //This should be Vue reactive.
const statusMessage = "Another Message"
var div = document.createElement('div');
div.setAttribute('class', 'status-div');
div.innerHTML = '<div>' + statusMessage + '</div><div onclick="closeStatus(this)"><img src="/src/assets/cross.svg" /></div>'
document.getElementById('slide').appendChild(div);
},
closeStatus(elm: any) {
elm.parentNode.remove();
const allNotifications = document.querySelectorAll(".status-div");
if(allNotifications.length == 0 ) {
document.getElementById('slide').style.visibility='hidden'; //This should be Vue reactive.
}
}
}
}
</script>
Code is javascript DOM management stuff and far from Vue style.
Vue manages DOM with variables and that is what makes javascript frameworks awesome.
Let me first share the modified code.
<template>
<div>
<button #click="addStatus()" >Add Status</button>
<div id="slide" class="slide-modal" v-show="visibleSlide">
<div class="clear-notifications" #click='clearAllNotifications()'>Clear all notifications</div>
<div class="status-div">
<div>11:00 Booking requested</div>
<div #click="closeStatus(this)"><img src="../assets/cross.svg" /></div>
</div>
<div class="status-div">
<div>11:30 Booking requested</div>
<div #click="closeStatus(this)"><img src="../assets/cross.svg" /></div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'SlideModal',
data() {
return {
visibleSlide: true
}
},
methods: {
clearAllNotifications() {
const allNotifications = document.querySelectorAll(".status-div");
for(let x = 0; x < allNotifications.length; x++) {
allNotifications[x].remove(); //removes all the child elements.
}
this.visibleSlide = false
},
addStatus() {
this.visibleSlide = true;
const statusMessage = "Another Message";
var div = document.createElement('div');
div.setAttribute('class', 'status-div');
div.innerHTML = '<div>' + statusMessage + '</div><div onclick="closeStatus(this)"><img src="/src/assets/cross.svg" /></div>'
document.getElementById('slide').appendChild(div);
},
closeStatus(elm: any) {
elm.parentNode.remove();
const allNotifications = document.querySelectorAll(".status-div");
if(allNotifications.length == 0 ) {
this.visibleSlide = false
}
}
}
}
</script>
I have just updated what is commented: "//This should be Vue reactive."
But it is still not benefiting from Vue framework.
Here is my whole idea of Vue code
<template>
<div>
<button #click="addStatus()" >Add Status</button>
<div id="slide" class="slide-modal" v-if="statuses.length">
<div class="clear-notifications" #click='clearAllNotifications()'>Clear all notifications</div>
<div v-for="status in statuses" :key="status.id" class="status-div">
<div>{{ status.text }}</div>
<div #click="closeStatus(status)"><img src="../assets/cross.svg" /></div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'SlideModal',
data() {
return {
statuses: [
{
id: 1,
text: '11:00 Booking requested'
}, {
id: 2,
text: '11:30 Booking requested'
}
]
}
},
methods: {
clearAllNotifications() {
this.statuses = []
},
addStatus() {
const statusMessage = "Another Message";
this.statuses.push({
id: this.statuses.length + 1,
text: statusMessage
})
},
closeStatus(elm: any) {
const index = this.statuses.map((status) => status.id).indexOf(elm.id);
this.statuses.splice(index, 1);
}
}
}
</script>
As you can see, DOM elements are managed by variables and their operations instead of DOM management methods.
Hope this is helpful
Related
I have a very simple Vue application I am building. I am new with using Vue.js. I have 2 vue components and I am trying to toggle between them with a button click. Can I use script code on the main page to toggle between the 2 vue components?
Here is my code. I assume I have to use v-show or v-if on the components or a div/span around the components to show and hide them.
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<Display v-if="show" />
<button v-on:click="toggleDisplay()">toggle btn</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import Display from './components/Display.vue'
export default {
name: 'App',
components: {
HelloWorld,
Display
},
data: {
return {
selectedComp: "HelloWorld"
}
},
methods: {
toggleDisplay(comp: string) {
this.selectedComp = comp;
}
}
}
</script>
Display.vue
<template>
<div class="post">
<div v-if="loading" class="loading">
Loading...
</div>
<div class="titleBlock text-center">
<div class="row justify-content-center" style="width:100%;">
<div class="col-6">
Service Title for Page
</div>
</div>
</div>
<div v-if="byteArray" class="content">
<img :src="byteArray" />
<div>{{ createImage }}</div>
<p>{{ byteArray }}</p>
</div>
</div>
</template>
<script lang="js">
import Vue from 'vue';
export default Vue.extend({
data() {
return {
loading: false,
post: null,
byteArray: null
};
},
created() {
this.fetchByteArray();
// Hide DisplayVue after 5 seconds
//setTimeout(() => this.byteArray = false, 5000)
//.then(
// setTimeout(() => this.byteArray = true, 2000)
// );
setTimeout(() => this.img = false, 5000)
.then(
setTimeout(() => this.img = true, 2000)
);
},
//watch: {
//}
methods: {
fetchByteArray() {
this.post = true;
this.byteArray = true;
this.loading = null;
fetch('https://localhost:5001/api/Doc/image001.png')
.then(response => response.json())
.then(bytes => {
this.byteArray = "data:image/png;base64," + bytes;
this.loading = false;
return;
})
.catch(console.log("Error"));
}
},
computed: {
createImage() {
return `<img class="img-fluid" src="data:image/png;base64,` + this.byteArray + `" />`;
}
}
});
</script>
Give a try to that one: https://vuejs.org/guide/essentials/component-basics.html#dynamic-components
Mainly like here: https://stackoverflow.com/a/73029625/8816585
<script setup>
import { ref } from "vue"
import Hehe from "#/components/Hehe.vue"
import Nice from "#/components/Nice.vue"
const boolean = ref(true)
</script>
<template>
<component :is="boolean ? Hehe : Nice" />
<button #click="boolean = !boolean">toggle</button>
</template>
I'm trying to make every single element created with the v-for the ability to toggle between 'face' class, but it happens that it toggles everything created with the v-for.
<div class="deckPairs">
<div class="deckBoxOne" v-for="base in cards" :key="base.id" >
<div class="deckBoxBlanc" :class="{face : face}" #click="face = !face">
<img class="deckPairsImage" style="z-index: 3" alt="Vue logo" :src="require('../assets/rightCardSide.png')">
</div>
<div class="deckBoxImg" :class="{face : !face}" #click="face = !face">
<img class="deckPairsImages" style="z-index: 2" :src="require(`../assets/images/${base.url}`)">
</div>
</div>
</div>
Script:
export default {
setup() {
const face = ref(false)
return { face }
},
data(){
return {
face: false,
}
},
methods: {
}
}
Create a component to encapsulate the v-for content.
// new-component.vue
<template>
<div>
<div class="deckBoxBlanc" :class="{face : face}" #click="face = !face">
<img class="deckPairsImage" style="z-index: 3" alt="Vue logo" :src="require('../assets/rightCardSide.png')">
</div>
<div class="deckBoxImg" :class="{face : !face}" #click="face = !face">
<img class="deckPairsImages" style="z-index: 2" :src="require(`../assets/images/${base.url}`)">
</div>
</div>
</template>
export default {
data(){
return {
face: false
}
}
}
Now update v-for to use the new component.
<div class="deckPairs">
<div class="deckBoxOne" v-for="base in cards" >
<new-component :key="base.id"/>
</div>
</div>
You can use base.id instead boolean and v-show directive:
const { ref } = Vue
const app = Vue.createApp({
el: "#demo",
setup() {
const face = ref(false)
const cards = ref([{id: 1, url: "https://picsum.photos/100"}, {id: 2, url: "https://picsum.photos/101"}, {id: 3, url: "https://picsum.photos/102"}])
const rightCardSide = ref("https://picsum.photos/103")
const setFace = (val) => {
face.value = val
}
return { cards, face, rightCardSide, setFace }
},
})
app.mount('#demo')
.deckPairs {
display: flex;
}
.deckBoxOne {
cursor: pointer;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="deckPairs">
<div class="deckBoxOne" v-for="base in cards" :key="base.id" >
<div class="deckBoxBlanc" v-show=" face !== base.id" #click="setFace(base.id)">
<img class="deckPairsImage" alt="Vue logo" :src="rightCardSide">
</div>
<div class="deckBoxImg" v-show="face === base.id" #click="setFace(false)">
<img class="deckPairsImages" :src="base.url">
</div>
</div>
</div>
</div>
I developed one page which is responsible for Displaying Books and the response is coming from backend , now i want to update my book card by clicking on ADD TO BAG button for that i write one handleCart() method for updating it updates based on book id , How to pass particular clickedCard Book id to the url path ,please help me to fix this .
DisplayBooks.vue
<template>
<div class="carddisplay-section">
<div v-for="book in books" :key="book.id" class="card book">
<div class="image-section">
<div class="image-container">
<img v-bind:src="book.file" />
</div>
</div>
<div class="title-section">
{{book.name}}
</div>
<div class="author-section">
by {{book.author}}
</div>
<div class="price-section">
Rs. {{book.price}}<label class="default">(2000)</label>
<button v-if="flag" class="btn-grp" type="submit" #click="handlesubmit();Togglebtn();">close</button>
</div>
<div class="buttons">
<div class="button-groups">
<button type="submit" #click="handleCart();" class="AddBag">Add to Bag</button>
<!-- v-if="state==true" -->
<button class="wishlist">wishlist</button>
</div>
<div class="AddedBag">
<button class="big-btn">Added to Bag</button>
</div>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
data() {
return {
isActive:true,
result: 0,
authorPrefix: 'by',
pricePrefix: 'Rs.',
defaultStrikePrice: '(2000)',
buttonValue: 'close',
flag: true,
state: true,
clickedCard: '',
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Dont Make me think',
author: 'Sai',
price: '1500'
}, ]
}
},
methods: {
toggleClass: function(event){
this.isActive = !this.isActive;
return event;
},
toggle(id) {
this.clickedCard = id;
console.log(this.clickedCard);
},
flip() {
this.state = !this.state;
},
Togglebtn() {
this.flag = !this.flag;
},
handlesubmit() {
service.userDisplayBooks().then(response => {
this.books.push(...response.data);
})
},
handleCart(){
let userData={
id:this.clickedCard,
}
service.userUpdateCart(userData).then(response=>{
alert("item added to cart");
return response;
})
}
}
}
</script>
User.js
userUpdateCart(data){
return axios.updateData(`/addtocart/${data.id}`,data);
},
axios.js
updateData(url,data){
return axios.put(url,data).then(response=>{
localStorage.getItem('token', response.data.token);
return response;
})
}
it's not taking book id
Pass the book ID as an argument to the handleCart method.
#click="handleCart(book.id)"
handleCart(bookId){
let userData={
id: bookId,
}
service.userUpdateCart(userData).then(response=>{
alert("item added to cart");
return response;
})
}
The toggle method is also never being called.
In my Vue.js project, I have two separate components are Country and States. I have merged them in one page. So now if I select one country it will display related states. How to do this?
<template>
<div>
<div style=margin-left:355px><country-index></country-index></div>
<div style=margin-left:710px><state-index></state-index></div>
</div>
</template>
<script>
import { ROAST_CONFIG } from '../../../config/config.js';
import CountryIndex from './components/country/Index';
import StateIndex from './components/state/Index';
import { listen } from '../../../util/history.js';
import axios from 'axios'
let baseUrl = ROAST_CONFIG.API_URL;
export default {
name: 'LocationsView',
layout: 'admin/layouts/default/defaultLayout',
middleware: 'auth',
components: {
'country-index' : CountryIndex,
'state-index' : StateIndex,
},
data() {
return { currentComponent:'','countryId':''}
},
methods: {
updateCurrentComponent(){
console.log(this.$route.name);
let vm = this;
let route = vm.$route;
if(this.$route.name == "Locations"){
this.currentComponent = "country-index";
}
}
},
mounted() {
let vm = this;
let route = this.$route;
window.addEventListener('popstate',this.updateCurrentComponent);
},
created() {
this.updateCurrentComponent();
}
}
Country Component
<template>
<div style="display:flex;height:100%">
<d-dotloader v-if="componentLoading" />
<div id="parent" class="list-manager" v-if="!componentLoading">
<div class="list-header">
<div class="bulk-action" :class="{'hide': showTop}" >
<div class="pull-left">
Countries
</div>
<!-- /pull-left -->
<div class="pull-right">
<d-button #click.native = "addCountry();"><i class="icon icon-sm"></i><span>New</span></i></d-button>
</div>
</div>
<!-- /bulk-action -->
<div class="bulk-action" :style ="{display:(showTop)?'block!important':'none!important'}" >
<div class="btn-toolbar">
<d-check field-class="check" v-model="selectAll" wrapper-class="field-check field-check-inline" label-position="right" label="" value="sel" #click.native = "toggleSelectAll();"/>
<d-button :is-loading="isLoading" #click.native = "deleteCountry();">Delete<i class="icon icon-sm" name="trash-2"></i></d-button>
<!-- <div class="pull-right mt5"><div class="green-bubble"></div>{{SelectedItems}}</div> -->
<d-button #click.native = "closeBulkToolBar();">close<i class="icon icon-sm" name="x"></i></d-button>
</div>
</div>
<!-- /bulk-action -->
</div>
<d-dotloader v-if="subListComponentLoading" />
<d-list-items :data="fetchData" #rowClick="changeCountryView" ref="itemsTable">
<d-list-cell column-class="list-item-check" :column-styles="{width: '40px'}" type="selectAll">
<template scope="row">
<div class="field-check field-check-inline" #click.stop="toggleSelect(row.rowIndex)" >
<input type="checkbox" class="check" :id="row.id" :value="row.id" :checked="row.selectAll">
<label></label>
</div>
<d-button #click.native = "editCountry(row.id);">Edit</d-button>
</template>
</d-list-cell>
<d-list-cell column-class="list-item-content">
<template scope="row">
<div class="list-item-content">
<div class="list-item-title">
<div class="pull-right">{{row.ISO_Code}}</div>
<div title="" class="pull-left">{{row.country_name}}</div>
</div>
<div class="list-item-meta">
<div class="pull-right">{{row.Default_Currency}} | {{row.Call_prefix}} </div>
<div class="pull-left">{{row.Zone}}</div>
</div>
<span class="list-item-status enabled"></span>
</div>
</template>
</d-list-cell >
</d-list-items>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { ROAST_CONFIG } from '../../../../../config/config.js';
var baseUrl = ROAST_CONFIG.API_URL;
export default {
data () {
return {
SelectedItems:"",
isLoading:false,
show:true,
searchBy: '',
activeSearch: '',
showTop: false,
selectAll : false,
componentLoading:true,
subListComponentLoading:false,
showModal: false,
form :{
country_name: '',
isCountryEnabled: true,
}
}
},
methods: {
async fetchData ({search, page, filter, sort,rows}) {
let resData;
let vm = this;
axios.defaults.headers.common['Authorization'] = "Bearer "+localStorage.getItem('token');
const res = await axios.post(baseUrl+'/country/fetch',{search, page, filter, sort,rows})
.then((response) => {
if( (typeof(response) != 'undefined') && (typeof(response.data) != 'undefined') && (typeof(response.data.fetch) != 'undefined')){
return response.data.fetch;
}
});
return res;
},
toggleSelect(rowId){
if(typeof(this.$refs.itemsTable.rows[rowId]) != 'undefined'){
this.$refs.itemsTable.rows[rowId].selectAll = !this.$refs.itemsTable.rows[rowId].selectAll;
let data = this.$refs.itemsTable.rows;
let status = false;
let selectAllStatus = true;
let items = 0;
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')&&(data[i].selectAll)){
items++;
this.SelectedItems = items +" Selected Items";
status = true;
}
if((typeof(data[i])!= 'undefined')&&(!data[i].selectAll)){
selectAllStatus = false;
}
this.showTop = status
}
}
},
toggleSelectAll(){
this.selectAll = !this.selectAll;
let items = 0;
let data = this.$refs.itemsTable.rows;
let status = false;
let rowId = '1'
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')){
items++;
this.SelectedItems = items +" Selected Items";
status = this.selectAll;
data[i].selectAll = status;
}
}
this.showTop = status
},
closeBulkToolBar(){
this.SelectedItems = "";
this.showTop = false;
},
}
}
State Component
<template>
<div style="display:flex;height:100%">
<d-dotloader v-if="componentLoading" />
<div id="parent" class="list-manager" v-if="!componentLoading">
<div class="list-header">
<div class="bulk-action" :class="{'hide': showTop}" >
<div class="pull-left">
States
</div>
<!-- /pull-left -->
<div class="pull-right">
<d-button #click.native = "addState();"><i class="icon icon-sm"></i><span>New</span></i></d-button>
</div>
</div>
<!-- /bulk-action -->
<div class="bulk-action" :style ="{display:(showTop)?'block!important':'none!important'}" >
<div class="btn-toolbar">
<d-check field-class="check" v-model="selectAll" wrapper-class="field-check field-check-inline" label-position="right" label="" value="sel" #click.native = "toggleSelectAll();"/>
<d-button :is-loading="isLoading" #click.native = "deleteState();">Delete<i class="icon icon-sm" name="trash-2"></i></d-button>
<!-- <div class="pull-right mt5"><div class="green-bubble"></div>{{SelectedItems}}</div> -->
<d-button #click.native = "closeBulkToolBar();">close<i class="icon icon-sm" name="x"></i></d-button>
</div>
</div>
<!-- /bulk-action -->
</div>
<d-dotloader v-if="subListComponentLoading" />
<d-list-items :data="fetchData" #rowClick="changeStateView" ref="itemsTable">
<d-list-cell column-class="list-item-check" :column-styles="{width: '40px'}" type="selectAll">
<template scope="row">
<div class="field-check field-check-inline" #click.stop="toggleSelect(row.rowIndex)" >
<input type="checkbox" class="check" :id="row.id" :value="row.id" :checked="row.selectAll">
<label></label>
</div>
<d-button #click.native = "editState(row.id);">Edit</d-button>
</template>
</d-list-cell>
<d-list-cell column-class="list-item-content">
<template scope="row">
<div class="list-item-content">
<div class="list-item-title">
<div class="pull-right">{{row.ISO_Code}}</div>
<div title="" class="pull-left">{{row.state_name}}</div>
</div>
<div class="list-item-meta">
<div class="pull-left">{{row.country_name}} </div>
<div class="pull-right">{{row.Zone}}</div>
</div>
<span class="list-item-status enabled"></span>
</div>
</template>
</d-list-cell >
</d-list-items>
</div>
<state-add></state-add>
<state-edit></state-edit>
</div>
</template>
<script>
import axios from 'axios'
import { ROAST_CONFIG } from '../../../../../config/config.js';
var baseUrl = ROAST_CONFIG.API_URL;
export default {
data () {
return {
SelectedItems:"",
isLoading:false,
show:true,
searchBy: '',
activeSearch: '',
showTop: false,
selectAll : false,
componentLoading:true,
subListComponentLoading:false,
showModal: false,
form :{
country_name: '',
isCountryEnabled: true,
}
}
},
methods: {
async fetchData ({search, page, filter, sort,rows}) {
let resData;
let vm = this;
axios.defaults.headers.common['Authorization'] = "Bearer "+localStorage.getItem('token');
const res = await axios.post(baseUrl+'/state/fetch',{search, page, filter, sort,rows})
.then((response) => {
if( (typeof(response) != 'undefined') && (typeof(response.data) != 'undefined') && (typeof(response.data.fetch) != 'undefined')){
return response.data.fetch;
}
});
return res;
},
changeStateView(row){
if(typeof(this.$children[7]) != 'undefined'){
this.$parent.stateId = row.id;
this.viewComponent = "state-main";
this.$children[7].readState(this.$parent.stateId);
this.$router.push({name:"StatesView", params: {id:row.id}});
}
},
toggleSelect(rowId){
if(typeof(this.$refs.itemsTable.rows[rowId]) != 'undefined'){
this.$refs.itemsTable.rows[rowId].selectAll = !this.$refs.itemsTable.rows[rowId].selectAll;
let data = this.$refs.itemsTable.rows;
let status = false;
let selectAllStatus = true;
let items = 0;
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')&&(data[i].selectAll)){
items++;
this.SelectedItems = items +" Selected Items";
status = true;
}
if((typeof(data[i])!= 'undefined')&&(!data[i].selectAll)){
selectAllStatus = false;
}
this.showTop = status
}
}
},
toggleSelectAll(){
this.selectAll = !this.selectAll;
let items = 0;
let data = this.$refs.itemsTable.rows;
let status = false;
let rowId = '1'
for(var i=0;i <= data.length;i++){
if((typeof(data[i])!= 'undefined')){
items++;
this.SelectedItems = items +" Selected Items";
status = this.selectAll;
data[i].selectAll = status;
}
}
this.showTop = status
},
closeBulkToolBar(){
this.SelectedItems = "";
this.showTop = false;
},
}
}
Without your component codes it will be difficult to accuratly answer but I can give a try. To communicate between your two components that don't have parent/child relationship you can use an EventBus. You have several choices on how to set up your EventBus; you can pass your event through your Vue root instance using $root, or you can create a dedicated Vue component like in this example.
Considering that you already have binded the event countrySelected($event) on each of your country checkbox, you could achieve to display the related states using something like this:
./components/country/Index
The CountryIndex trigger an event while a country is selected
methods: {
countrySelected(event) {
let currentTarget = event.currentTarget
this.$root.$emit("display-states",currentTarget.countryId);
}
}
./components/state/Index
The stateIndex component listen to the event and display the related state
mounted() {
/**
* Event listener
*/
this.$root.$on("display-states", countryId => {
this.diplayStates(countryId);
});
},
methods: {
displayStates(countryId) {
//your method selecting the states to be diplayed
}
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.
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
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.