I have 2 buttons. One adds a movie to local storage, the other removes it from there. I made a function that basically switches the button. If the movie is added it shows "remove", if the movie's not been added it shows the button "add".
The function works but it doesn't know when the boolean changes so the button doesn't change. Someone explained that i should use watch property, but how am I supposed to watch an output of a function?
here is the code
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!showButton(movie.id)" type="submit" #click="storeMovie(movie.id)" >
Aggiungi
</button>
<button v-show="showButton(movie.id)" type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
<div class="card" v-for="favourite in watchlist"
:key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomeComp',
data () {
return {
movies: [],
watchlist: [],
movie: null,
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false®ion=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
},
watch: {
switchButton(oldValue, newValue) {
if (oldValue != newValue) {
this.showButton(id) = true;
} //made an attempt here
}
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id )
this.watchlist.push(favouriteMovie);
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
removeMovie(id) {
const removedMovie = this.watchlist.find(movie => movie.id === id )
const indexMovie = this.watchlist.indexOf(removedMovie);
if (indexMovie > -1) {
this.watchlist.splice(indexMovie, 1);
}
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
showButton(id) {
const favouriteMovie = this.watchlist.find(movie => movie.id === id )
if (favouriteMovie && favouriteMovie.length > 0) {
return true
} else{
return false
}
}
},
}
</script>
<style scoped lang="scss">
</style>
A better approach would be to store the state of a movie being stored or not in the watchlist directly on the movie object.
Then use a computed to get the watchlist from the movie list instead of using two different arrays.
<template>
<div>
<div class="card" v-for="movie in movies" :key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!movie.toWatch" type="submit" #click="storeMovie(movie.id)">
{{ movie.toWatch ? 'Rimuovi' : 'Aggiungi' }}
</button>
</div>
<div class="card" v-for="favourite in watchList" :key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
export default {
name: 'HomeComp',
data() {
return {
movies: [],
}
},
computed: {
// Get the watchList from the movies list
watchList() {
return this.movies.filter(movie => movie.toWatch)
}
},
watch: {
watchList(newWatchList) {
// Update the localStorage whenever the list changes
localStorage.setItem("watchlist", JSON.stringify(newWatchList));
}
},
mounted() {
// your axios call
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id)
if (favouriteMovie) {
// just reverse the boolean
favouriteMovie.toWatch = !favouriteMovie.toWatch
}
},
},
}
</script>
Related
i try to build a little clothing web shop with nuxtjs. You can choose the color on the details page. The details page represents a pice of clothing. The ColorMenu is a component. If you choose something a color, it will emit it back to the details page and will send a new details request to my backend.
However, changing the color only works if you don't choose another piece of clothing. If you choose another piece of clothing (so the route parameters will change) and choose another color in the menu, there is a always an error that it cannot load anything. it seems that it sends repeated requests until the request is blocked.
The details routes are built according to this scheme: localhost/details/{sellableId}/{ideaId}/{appearanceId}
Details Page:
<template>
<section class="section">
<div v-if="details">
<div class="columns">
<div class="column">
<ImageCaroussel :images="details.images"></ImageCaroussel>
</div>
<div class="column">
<h3>Farben</h3>
<ColorMenu
:appearances="productType.appearances"
:appearanceIds="details.appearanceIds"
></ColorMenu>
</div>
</div>
</div>
</section>
</template>
<script>
import { mapState } from 'vuex'
import Dropdown from '~/components/details/Dropdown.vue'
import ColorMenu from '~/components/details/ColorMenu.vue'
import ImageCaroussel from '~/components/details/ImageCaroussel.vue'
export default {
created() {
this.$nuxt.$on('selected', ($event) => (this.selected = $event))
this.$nuxt.$on('selectedColor', ($event) => this.setSelectedColor($event))
},
data() {
return {
modal: false,
selected: '',
selectedColor: '',
}
},
async asyncData({ store, params }) {
console.log('asyncfirst')
if (params.sellableId && params.appearanceId && params.ideaId) {
await store.dispatch('details/get_details', {
sellableId: params.sellableId,
appearanceId: params.appearanceId,
ideaId: params.ideaId,
})
let sellableId = params.sellableId
let appearanceId = params.appearanceId
let ideaId = params.ideaId
console.log('asyncsecond!')
return { sellableId, appearanceId, ideaId }
}
},
mounted() {
this.sellableId = this.$route.params.sellableId
this.appearanceId = this.$route.params.appearanceId
this.ideaId = this.$route.params.ideaId
console.log('Mounted!')
},
components: {
Dropdown,
ColorMenu,
ImageCaroussel,
},
computed: {
...mapState({
details: (state) => {
return state.details.details
},
currency: (state) => {
return state.sellable.currency
},
productType: (state) => {
return state.details.productType
},
}),
},
methods: {
checkout: async function (sellableId, size, appearanceId) {
let link = await this.$backendrepositories.basket.checkout(
sellableId,
size,
appearanceId
)
if (link.status === 200 && link.data) {
this.modal = true
setTimeout(() => {
window.location.href = link.data.link
}, 3000)
}
},
setSelectedColor: async function (event) {
this.selectedColor = event
await this.$store.dispatch('details/get_details', {
sellableId: this.sellableId,
appearanceId: this.selectedColor,
ideaId: this.ideaId,
})
},
},
}
</script>
ColorMenu Component:
<template>
<div>
<div
v-for="(cell, index) in appearances"
:key="index"
style="display: inline-block"
>
<label v-if="appearanceIds.includes(cell.id)" class="self-container">
<input type="radio" checked="checked" name="color" />
<span
class="checkmark"
:style="`background-color: ${cell.colors[0].value}`"
#click="select(cell.id)"
></span>
</label>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: '',
}
},
props: ['appearances', 'appearanceIds'],
methods: {
select(select) {
this.selected = select
this.$nuxt.$emit('selectedColor', this.selected)
},
},
}
</script>
There is a live demo at https://akano-frontend.vercel.app/
I'm working with BootstrapVue and I've tried to have a photo preview in my v-for loop.
Everything works fine, except of :src="'fileRequestSrc'+countTitle" - In my developer tools I get following information:
This is how it looked before when I wrote it manually without the loop:
What is the mistake in my code respectively in this code line :src="'fileRequestSrc'+countTitle" - thanks in advance!
<template>
<div>
<div v-for="countTitle in 3" :key="countTitle">
<b-button v-b-toggle="'request'+countTitle" variant="danger btn-block mb-2">Upload {{countTitle}}</b-button>
<b-collapse :id="'request'+countTitle" class="mt-2">
<div class="m-2 mt-3">
<table class="table table-striped mt-2">
<tbody>
<div class="mt-3 mb-2 ml-1">Upload</div>
<b-form-file :v-model="'fileRequest'+countTitle" placeholder="Upload ..." class="mb-2"></b-form-file>
<b-img v-if="'hasfileRequest'+countTitle" :src="'fileRequestSrc'+countTitle" class="mb-3" fluid block rounded></b-img>
</tbody>
</table>
</div>
</b-collapse>
</div>
</div>
</template>
<script>
const base64Encode = data =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
export default {
data() {
return {
fileRequest1: null,
fileRequestSrc1: null,
fileRequest2: null,
fileRequestSrc2: null,
fileRequest3: null,
fileRequestSrc3: null,
}
},
computed: {
hasfileRequest1() {
return !!this.fileRequest1;
},
hasfileRequest2() {
return !!this.fileRequest2;
},
hasfileRequest3() {
return !!this.fileRequest3;
}
},
watch: {
fileRequest1(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc1 = value;
})
.catch(() => {
this.fileRequestSrc1 = null;
});
} else {
this.fileRequestSrc1 = null;
}
}
},
fileRequest2(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc2 = value;
})
.catch(() => {
this.fileRequestSrc2 = null;
});
} else {
this.fileRequestSrc2 = null;
}
}
},
fileRequest3(newValue, oldValue) {
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue)
.then(value => {
this.fileRequestSrc3 = value;
})
.catch(() => {
this.fileRequestSrc3 = null;
});
} else {
this.fileRequestSrc3 = null;
}
}
},
},
}
</script>
<style scoped>
</style>
The issue that you are encountering is a difference between what you are expecting vue to do and what vue is actually doing.
With this statement
:src="'fileRequestSrc'+countTitle"
You are expecting vue to concatenate the string and then get the contents of the variable fileRequestSrc1, fileRequestSrc1, etc. However, what Vue is actually doing is concatenating the string together and assigning that to the value of src.
Currently, the way your logic is structured there are a few ways to accomplish what you want to do. Personally, the method I would take is to create an array of your variables and only access the one you want within your template.
However, none of these methods are really great. I would suggest restructuring your component to iterate over an array of objects, that contain the source for your image, as well as the file request. For example
data(){
return{
files:[
{id:0, src:null, request:null}
]
}
}
then iterate over the files in the array instead of counting to 3.
Therefore your template would change to
<template>
<div>
<div v-for="item in files" :key="item.id">
<b-button v-b-toggle="item.request" variant="danger btn-block mb-2">Upload {{item.id}}</b-button>
<b-collapse :id="'request'+item" class="mt-2">
<div class="m-2 mt-3">
<table class="table table-striped mt-2">
<tbody>
<div class="mt-3 mb-2 ml-1">Upload</div>
<b-form-file :v-model="item.request" placeholder="Upload ..." class="mb-2"></b-form-file>
<b-img v-if="item.request" :src="item.src" class="mb-3" fluid block rounded></b-img>
</tbody>
</table>
</div>
</b-collapse>
</div>
</div>
</template>
Then you would need to add a watcher to encode the image as base 64. For example
watch: {
files: {
deep: true, //used to watch the object's properties
handler(newValue, oldValue){
if (newValue !== oldValue) {
if (newValue) {
base64Encode(newValue.src)
.then(value => {
newValue.src = value;
})
.catch(() => {
newValue.src = null;
});
} else {
newValue.src = null;
}
}
}
}
}
i'm new to Vue js in this code below , i wanted to hide button "Clear Filter" when nothing selected and show the button only when function " selectedAnswer(index)" called so that it will show only when filter applied otherwise it should be hided , is there a way to do it in my code?
and thanks in advance
<template>
<div class="container" width=800px>
<b-row>
<b-col cols="8">
<h1> Recently Asked </h1>
<ul class="container-question" v-for="(question1,index) in questions" :key="index">
<li>
{{question1.question}}
</li>
</ul>
</b-col>
<b-button class="outline-primaryy" style="margin:auto;" #click="ClearFilter" >Clear Filter</b-button>
</div>
<router-view />
</div>
</template>
<script>
export default {
data() {
return {
questions: [],
answered: null,
index: 0,
selectedIndex: null,
}
},
methods: {
selectedAnswer(index) {
this.selectedIndex = index;
this.questions = this.questions.filter((question) => question.incorrect_answers.includes(index))
console.log(index)
},
ClearFilter() {
this.questions = this.unmutated
},
watch: {
question1: {
handler() {
this.selectedIndex = null;
this.answered = false;
},
},
},
},
mounted: function() {
fetch('https://opentdb.com/api.php?amount=10&category=9&difficulty=medium&type=multiple', {
method: 'get'
})
.then((response) => {
return response.json()
})
.then((jsonData) => {
this.questions = jsonData.results
this.unmutated = jsonData.results;
})
}
}
</script>
You just need to add a v-if="selectedIndex" to your btn element.
ie
<b-button v-if="selectedIndex" class="outline-primaryy" style="margin:auto;" #click="ClearFilter" >Clear Filter</b-button
I'm a beginner programmer and I create a spa like trello. Creates boards. In the board creates lists, they are displayed different with different id, but the list items are displayed with the same id and they are duplicated in each list. Sorry for my english:) Help me, please and tell in detail what the problem is.. Thank you very much
vuex file router.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
boards: JSON.parse(localStorage.getItem('boards') || '[]'),
lists: [],
items: []
// items: JSON.parse(localStorage.getItem('items') || '[]')
// lists: JSON.parse(localStorage.getItem('lists') || '[]')
},
mutations: {
addBoard(state, board) {
state.boards.push(board)
localStorage.setItem('boards', JSON.stringify(state.boards))
},
addList(state, list) {
state.lists.push(list)
// localStorage.setItem('lists', JSON.stringify(state.lists))
},
createItemListt(state, item) {
state.items.push(item)
// localStorage.setItem('items', JSON.stringify(state.items))
}
},
actions: {
addBoard({commit}, board) {
commit('addBoard', board)
},
addList({commit}, list) {
commit('addList', list)
},
createItemListt({commit}, item) {
commit('createItemListt', item)
}
},
getters: {
boards: s => s.boards,
taskById: s => id => s.boards.find(t => t.id === id),
lists: d => d.lists,
items: a => a.items
},
modules: {
}
})
the page on which the lists are created MyBoard.vue
<template>
<div>
<div class="wrapper">
<div class="row">
<h1>{{board.title}}</h1>
<div class="list " v-for="list in lists" :key="list.idList">
<div class="list__title">
<h3>{{list.titleList}}</h3>
</div>
<div class="list__card" v-for="item in items" :key="item.idItemList">
<span class="list__item">{{item.itemList}}</span>
<a class="btn-floating btn-tiny btn-check" tag="button">
<i class="material-icons">check</i>
</a>
</div>
<createItemList />
</div>
<createList />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
board() {
return this.$store.getters.taskById(+this.$route.params.id);
},
lists() {
return this.$store.getters.lists;
},
items() {
return this.$store.getters.items;
}
},
components: {
createList: () => import("../components/createList"),
createItemList: () => import("../components/createItemList")
}
};
</script>
CreateList.Vue
<template>
<div>
<div class="row">
<div class="new-list" v-show="isCreating">
<div class="list__title input-field">
<input
type="text"
required
id="list-title"
class="none validate"
tag="button"
autofocus
v-model="titleList"
v-on:keyup.enter="createList"
/>
<label for="list-title">Enter Title List</label>
</div>
<a class="btn-floating transparent btn-close" tag="button" #click="closeList">
<i class="material-icons">close</i>
</a>
</div>
<div class="create-list z-depth-2" v-show="!isCreating">
<p>Create list</p>
<a
class="btn-floating btn-large waves-effect waves-light deep-purple lighten-2 pulse"
tag="button"
#click="addList"
v-on:keyup.enter="addList"
>
<i class="material-icons">add</i>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isCreating: false,
titleList: "",
idList: ""
}),
methods: {
addList() {
this.isCreating = true;
},
closeList() {
this.isCreating = false;
},
createList() {
if (this.titleList == "") {
return false;
}
const list = {
idList: Date.now(),
titleList: this.titleList
};
this.$store.dispatch("addList", list);
this.titleList = "";
this.isCreating = false;
console.log(list.titleList);
}
}
};
</script>
CreateItemList.vue
<template>
<div>
<div class="add-item">
<div class="textarea-item input-field" v-show="isAdding">
<input
type="text"
class="validate"
id="list-item"
v-model="itemList"
v-on:keyup.enter="createItemList"
autofocus
/>
<label for="list-item">Enter Item List</label>
</div>
<a class="waves-effect waves-light btn" v-show="!isAdding" #click="addCard">
<i class="material-icons right">add</i>Add Card
</a>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isAdding: false,
itemList: "",
}),
methods: {
addCard() {
this.isAdding = true;
},
createItemList() {
if (this.itemList == "") {
return false;
}
const item = {
idItemList: Date.now(),
itemList: this.itemList
};
this.$store.dispatch("createItemListt", item);
this.itemList = "";
this.isAdding = false;
}
}
};
</script>
attach photo
Tried to go with the basic idea of the structure you laid out. I added:
id to all items, so they can be identifed
children to appropriate items, so you can keep track of what's in them
const store = new Vuex.Store({
state: {
tables: [
{ id: 1, children: ['1.1', '1.2'] },
{ id: 2, children: ['2.1'] }
],
lists: [
{ id: '1.1', children: ['1.1.1'] },
{ id: '1.2', children: ['1.2.1'] },
{ id: '2.1', children: ['2.1.1', '2.1.2'] },
],
cards: [
{ id: '1.1.1' },
{ id: '1.2.1' },
{ id: '2.1.1' },
{ id: '2.1.2' },
]
},
mutations: {
ADD_CARD(state, listId) {
const list = state.lists.find(e => e.id === listId)
const cards = state.cards
const card = { id: Date.now() }
cards.push( card )
list.children.push( card.id )
},
ADD_LIST(state, tableId) {
const table = state.tables.find(e => e.id === tableId)
const lists = state.lists
const list = { id: Date.now(), children: [] }
lists.push( list )
table.children.push( list.id )
},
ADD_TABLE(state) {
const tables = state.tables
const table = { id: Date.now(), children: [] }
tables.push( table )
},
TRY_MOVING_LIST(state) {
const table1 = state.tables.find(e => e.id === 1)
const table2 = state.tables.find(e => e.id === 2)
const item = table1.children.pop() // remove the last item
table2.children.push(item)
}
},
actions: {
addCard({ commit }, listId) {
commit('ADD_CARD', listId)
},
addList({ commit }, tableId) {
commit('ADD_LIST', tableId)
},
addTable({ commit }) {
commit('ADD_TABLE')
},
tryMovingList({ commit }) {
commit('TRY_MOVING_LIST')
}
},
getters: {
getTables: s => s.tables,
getListById: s => id => s.lists.find(e => e.id === id),
getCardById: s => id => s.cards.find(e => e.id === id),
}
})
Vue.component('CustomCard', {
props: ['card'],
template: `<div>
card ID: {{ card.id }}<br />
</div>`
})
Vue.component('CustomList', {
props: ['list'],
template: `<div>
list ID: {{ list.id }}<br />
<custom-card
v-for="item in list.children"
:key="item"
:card="getCard(item)"
/>
<button #click="addCard">ADD CARD +</button>
<hr />
</div>`,
methods: {
getCard(id) {
return this.$store.getters.getCardById(id)
},
addCard() {
this.$store.dispatch('addCard', this.list.id)
}
}
})
Vue.component('CustomTable', {
props: ['cTable'],
template: `<div>
table ID: {{ cTable.id }}<br />
<custom-list
v-for="item in cTable.children"
:key="item"
:list="getList(item)"
/>
<button #click="addList">ADD LIST +</button>
<hr />
</div>`,
methods: {
getList(id) {
return this.$store.getters.getListById(id)
},
addList(id) {
this.$store.dispatch('addList', this.cTable.id)
}
}
})
new Vue({
el: "#app",
store,
computed: {
tables() {
return this.$store.state.tables
}
},
methods: {
addTable() {
this.$store.dispatch('addTable')
},
tryMovingList() {
// this function will move the last list in table ID 1
// to the end of table ID 2's lists
// NOT FOOLPROOF - you should add error handling logic!
this.$store.dispatch('tryMovingList')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<button #click="tryMovingList()">MOVE LIST</button><br />
<button #click="addTable()">ADD TABLE +</button>
<hr />
<custom-table v-for="item in tables" :key="'table-' + item.id" :c-table="item" />
</div>
With this setup you can change the hierarchy quite easily: just delete an ID from one Array of children and add it to another (e.g. remove '1.1' from table ID 1's children array and add it to table ID 2's children array - everything moved to table ID 2. tryMovingList() does exactly this - this method/action is not foolproof, just for you to try out moving a whole list)
There could be other patterns to solve this problem (like a real linked list data structure or the mediator pattern), but for smaller apps this is OK, I think (I would use it... :) ).
ONE PIECE OF ADVICE
If you want to store state in localStorage on mutations, don't do it by yourself - use Vuex's integrated subscribe mechanism: https://vuex.vuejs.org/api/#subscribe
I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});