vuejs - How to show the number of likes for each post - javascript

My Home.vue file:
<template>
<div>
<my-post
v-for="(post, index) in posts"
:post="post"
:index="index"
:key="post.id"
></my-post>
</div>
</template>
<script>
import Post from './Post.vue';
export default {
data() {
return {
posts: []
}
},
mounted() {
axios.get('http://localhost/mine/test')
.then(response => {
this.posts = response.data.posts;
})
.catch(error => {
// console.log(error);
})
},
components: {'my-post': Post}
}
</script>
My Post.vue file:
<template>
<div class="post">
<!-- The content of the post...
I want to count the number of likes for each post here something like this:
<p>{{likes.length}}</p> -->
</div>
</template>
<script>
export default {
props: ['post'],
data() {
return {}
}
}
</script>
The data that is got by axios.get('http://localhost/mine/test') is like this:
posts: Array [
{0:first_name:'example123',post_id:1},
{1:first_name:'example456',post_id:2},
{2:first_name:'example789',post_id:3},
],
likes: Array [
{0:first_name:'example1',post_id:1},
{1:first_name:'example2',post_id:1},
{2:first_name:'example3',post_id:1},
]
Note that they are separate. The likes are not the children of posts.
I set likes in props as posts but the issue is that it shows the number of likes exactly the same.
How can I get the number of likes for each post?
Thanks

Ideally you need to update your schema, such that each post have a likes object defining them separately.
Incase its not possible you can modify your code by doing this.
Add a likes field having all likes.
export default {
data() {
return {
posts: [],
likes:0
}
},
mounted() {
axios.get('http://localhost/mine/test')
.then(response => {
this.posts = response.data.posts;
this.likes = response.data.likes;
})
.catch(error => {
// console.log(error);
})
},
components: {'my-post': Post}
}
Use filter to add/pass [likes] prop with likes specific to each post.
<my-post
v-for="(post, index) in posts"
:post="post"
:likes="likes.filter(x => x.post_id==post.post_id)"
:index="index"
:key="post.id">
</my-post>
CODE SNIPPET
function callMe() {
var post = Vue.component("post", {
template: "<p>PostId={{post.post_id}} . Number of likes: {{likes.length}}</p>",
props: {
likes: Array,
post: Object
},
data() {
return{
numberOfLikes:0
}
},
methods: {
}
})
var vm = new Vue({
el: '#app',
template: '<p><post v-for="(postObj,index) in post.posts" :post="postObj" :likes="post.likes.filter(x => x.post_id==postObj.post_id)"></post></p>',
data(){
return {
likes:0,
post:{
posts: [
{first_name:'example123',post_id:1},
{first_name:'example456',post_id:2},
{first_name:'example789',post_id:3},
],
likes: [
{first_name:'example1',post_id:1},
{first_name:'example2',post_id:1},
{first_name:'example3',post_id:1},
{first_name:'example4',post_id:2},
]
}
}
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js" type="text/javascript"></script>
<div id="app">
</div>

First you should add a "like_count" (or whatever you wish) to your db scheema. I assume you are using some kinda REST service that supports basic http methods ie GET,POST,PUT,DELETE.
Then just do a simple api call in your post component.
<template>
<div class="post">
<!-- Whatever you wish to stylize -->
<button class="add-like" #click="addLike"> +1 </button>
<p>{{currentLikes}}</p>
</div>
</template>
<script>
export default {
props: ['post'],
computed: {
currentLikes () {
return parseInt(this.post.like_count) + 1
}
},
methods: {
addLike () {
axios.put('/yourlink/' + this.post.post_id, {
like_count: this.currentLikes
})
}
}
}
</script>

Related

How to make correct pagination in Vue.js 2

I'm having trouble creating pagination with vue. My task is to make sure that when you click on the numbers of the buttons, new tasks from jsonplaceholder are loaded.
I have successfully loaded the first and second page. I assume this is related directly to my this.fetchTodos() action. I'm just learning vue and I need help figuring out how to update the data when moving to a new page without loading.
In this case, it is necessary that the url of the page changes (get request). My page state is changing, but posts are not loading when clicking on the third page.
Below is the code of four files that I think will help you understand the situation.
Maybe you will easier with GitHub, please check pagination branch
Thanks in advance for your help! If you have questions or need more information, write in the comments
TodoListView.vue - is starting page, where is todos fetching and rendered on page.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage v-if="todos" text="Список задач"/>
<TitlePage v-else text="Список задач пуст, создайте новую!"/>
<button-bootstrap data-bs-toggle="modal" data-bs-target="#createTodo" css-class="btn-lg btn-primary mt-2 mb-4">Создать задачу</button-bootstrap>
<ModalBootstrap #create="createTodo" :todos="todos" css-id="createTodo"/>
<SearchBootstrap v-if="todos" #search="searchTodo"/>
<div v-if="todos" class="d-flex justify-content-end mt-2">
<button-bootstrap #click.native="setCompletedToAllTodo()" css-class="btn-lg btn-success">Отменить всё как "Выполненные"</button-bootstrap>
</div>
</div>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import TodoList from "#/components/TodoList";
import {mapState, mapActions, mapMutations, mapGetters} from 'vuex'
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import ModalBootstrap from "#/components/UI/ModalBootstrap";
import SearchBootstrap from "#/components/UI/SearchBootstrap";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListView",
components: {
PaginationBootstrap,
SearchBootstrap, ModalBootstrap, TodoList , ButtonBootstrap, TitlePage, ContainerBootstrap},
data: function() {
return {
isShow: false,
}
},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos"
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
}),
createTodo(todo) {
this.$store.commit('todos/addTodo', todo);
},
setCompletedToAllTodo() {
console.log('hello')
this.$store.commit('todos/setCompletedToAllTodo')
},
searchTodo(query) {
this.$store.state.todos.searchQuery = query;
}
},
mounted() {
this.fetchTodos()
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
}
}
</script>
TodoListPaginationView - is second file, where is loading second page and another when click on pagination.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage :text="'Страница №'+ page"/>
<router-link to="/todolist">
<button-bootstrap css-class="btn-lg btn-primary mt-2 mb-4">Вернуться к началу</button-bootstrap>
</router-link>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</div>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import TodoList from "#/components/TodoList";
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListPaginationView",
components: {PaginationBootstrap, TodoList, ButtonBootstrap, TitlePage, ContainerBootstrap},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos",
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
})
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
},
mounted() {
this.fetchTodos()
},
}
</script>
PaginationBootstrap.vue - third file, where is logic for pagination. Ui bootstrap 5 file.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item"><a class="page-link" href="#">Предыдущая</a></li>
<li v-for="pageNumber in totalPages" :key="pageNumber" :class="{'active' : page === pageNumber}" class="page-item">
<span #click="changePage(pageNumber)" class="page-link">{{pageNumber}}</span>
</li>
<li class="page-item"><a class="page-link" href="#">Далее</a></li>
</ul>
</nav>
</template>
<script>
export default {
name: "PaginationBootstrap",
props: {
page: Number,
totalPages: Number
},
methods: {
changePage(pageNumber) {
this.$store.commit('todos/setPage', pageNumber);
if (pageNumber === 1) {
this.$router.push('/todolist')
}
else {
this.$router.push({name: 'todolistPagination', params: {page: pageNumber}})
}
}
}
}
</script>
<style lang="scss" scoped>
.pagination {
.page-item {
.page-link {
cursor: pointer;
}
}
}
</style>
todosModule.js - last file, where is vuex logic for todos.
import axios from "axios";
export const todosModule = {
state: () => ({
todos: [],
page: 1,
limit: 10,
totalPages: 0,
isTodosLoading: false,
searchQuery: '',
}),
mutations: {
setTodos(state, todos) {
state.todos = todos
},
setPage(state, page) {
state.page = page
},
setTotalPages(state, totalPages) {
state.totalPages = totalPages
},
setLoadingTodos(state, bool) {
state.isTodosLoading = bool
},
setCompleted(state, completed) {
const index = state.todos.findIndex(todo => todo.id === completed.id);
state.todos[index].completed = completed.completed
},
setCompletedToAllTodo(state) {
state.todos.map(obj => {
obj.completed = true
})
},
removeTodo(state, id) {
const index = state.todos.findIndex(todo => todo.id === id)
state.todos.splice(index, 1)
},
addTodo(state, todo) {
state.todos.unshift(todo);
},
setTitle(state, tusk) {
const index = state.todos.findIndex(todo => todo.id === tusk.id);
state.todos[index].title = tusk.title
},
setSearchQuery(state, searchQuery) {
state.searchQuery = searchQuery;
}
},
actions: {
async fetchTodos({state, commit}) {
try {
commit('setLoadingTodos' , true)
const response = await axios.get('https://jsonplaceholder.typicode.com/todos', {
params: {
_page: state.page,
_limit: state.limit
}
})
commit('setTotalPages', Math.ceil(response.headers['x-total-count'] / state.limit))
commit('setTodos', response.data)
}
catch (e) {
console.log(e)
}
finally {
commit('setLoadingTodos', false)
}
},
async getCurrentPage({commit}, currentPage) {
try {
commit('setPage', currentPage)
}
catch (e) {
console.log(e);
}
}
},
getters: {
searchedTodos(state) {
return [...state.todos].filter(todo => todo.title.toLowerCase().includes(state.searchQuery.toLowerCase()))
},
},
namespaced: true
}
Okey, I found solution for myself.
Most importent thing is watcher. I added to TodoListPaginationView.vue next code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="todolist">
<ContainerBootstrap>
<div class="row">
<div class="col-12 text-center">
<TitlePage :text="'Страница №'+ page"/>
<router-link to="/todolist">
<button-bootstrap css-class="btn-lg btn-primary mt-2 mb-4">Вернуться к началу</button-bootstrap>
</router-link>
</div>
<TodoList v-if="todos" :todos="searchedTodos"/>
<PaginationBootstrap :page="page" :total-pages="totalPages" class="mt-4"/>
</div>
</ContainerBootstrap>
</div>
</template>
<script>
import ContainerBootstrap from "#/components/UI/ContainerBootstrap";
import TitlePage from "#/components/TitlePage";
import ButtonBootstrap from "#/components/UI/ButtonBootstrap";
import TodoList from "#/components/TodoList";
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import PaginationBootstrap from "#/components/UI/PaginationBootstrap";
export default {
name: "TodoListPaginationView",
components: {PaginationBootstrap, TodoList, ButtonBootstrap, TitlePage, ContainerBootstrap},
methods: {
...mapActions({
fetchTodos: "todos/fetchTodos",
}),
...mapMutations({
setSearchQuery: 'todos/setSearchQuery'
})
},
computed: {
...mapState({
todos: state => state.todos.todos,
isTodosLoading: state => state.todos.isTodosLoading,
page: state => state.todos.page,
limit: state => state.todos.limit,
totalPages: state => state.todos.totalPages,
searchQuery: state => state.todos.searchQuery
}),
...mapGetters({
searchedTodos: 'todos/searchedTodos'
})
},
watch: {
page: function (val) {
if (val) {
this.fetchTodos()
}
},
},
mounted() {
this.fetchTodos();
},
}
</script>

How to pass dynamically props from one child component to another on the parent page

I have two child components I have to pass dynamically props from first child to parent and from parent to second child.
Parent
<script>
data: () => ({
model: {}
}),
methods: {
changeData(payload) {
this.model.personalData = {...payload}
}
}
</script>
<template>
<first-child #changeData="(payload) => changeData(payload)"/>
<second-child :enter-object="model" />
</template>
Child one
<script>
data: () => ({
model: {}
}),
methods: {
changeData() {
this.$emit("changeData", this.model);
}
}
</script>
<template>
<v-text-field v-model="model.name" #input="changeData()">
<v-text-field v-model="model.email" #input="changeData()">
</template>
Child two
<script>
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {}
}),
watch: {
enterObject: {
immediate: true,
handler() {
Object.assign(this.model.personalData, this.enterObject.personalData);
}
}
</script>
<template>
<div>
<div v-if="model.personalData.name || model.personalData.email">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>
No data
</div>
</div>
</template>
I get data in parent component with no problem, but this data doesn't pass to second child, why I have always "No data" ?
I tested your code and found a few things:
You need to create "personalData" inside the model in "childTwo".
<template>
<div>
// I changed the validation for personalData
<div v-if="model.personalData">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>No data</div>
</div>
</template>
export default {
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {
personalData: {}
}
}),
watch: {
enterObject: {
deep: true,
handler() {
// Add a validation in the handler, you can use Object assign inside the validation.
if(this.enterObject) {
Object.assign(this.model.personalData, this.enterObject.personalData)
}
}
}
}
It's worked for me.I hope it helps you.
You have to assign the value of the object using this.$set for more about object reactivity click here
your Parent component should be like this:-
here is the working example
<template>
<div>
<first-child #change-data="(payload) => changeData(payload)" />
<second-child :enter-object="model" />
</div>
</template>
<script>
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
export default {
data: () => ({
model: {},
compKey: 0,
}),
components: {
FirstChild,
SecondChild,
},
methods: {
changeData(payload) {
this.$set(this.model, "test", payload);
//this.model.test = payload;
},
},
};
</script>

Nuxtjs: Axios Request does not work when switching to another route

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/

VueJs watching deep changes in object

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
});

How to correctly set value for v-if in Vue.js

I am trying to get familiar with Vue.js by making this tasks app. When I try to update the v-if="!task.deleted" value to show some transition on delete, although the task is successfully deleted from the database, it is not removed from the page.
I tried retrieving the tasks again once a task is deleted, but I don't think that's the right way to update the list (maybe I am wrong). I can't get the transition effects to work with this method either.
Here's the code so far in my single file component Tasks.vue:
<template>
...
<table class="table">
<tbody>
<tr :tasks="tasks" v-for="task in tasks" :key="task.id">
<transition name="fade">
<task-item v-if="!task.deleted" v-on:singleTaskDeleted="taskDeleted(task)" :task="task"></task-item>
</transition>
</tr>
</tbody>
</table>
...
</template>
<script>
import TaskInput from './TaskInput.vue';
import TaskItem from './TaskItem.vue';
export default {
data : function (){
return {
dbTasks : {}
}
},
computed : {
tasks : function (){
return this.dbTasks;
}
},
components: {
TaskItem, TaskInput
},
methods: {
getTasks(){
axios.get('tasks')
.then( response => {
this.dbTasks = response.data;
})
.catch(function (error) {
console.log(error);
});
},
/* Is this the right way to set `deleted`?? */
taskDeleted(task){
task.deleted = 1;
}
},
created() {
this.getTasks();
},
mounted() {
console.log('Component mounted.')
}
}
</script>
The TaskItem.vue component is set up like this:
<template>
<td class="task-item" v-if="!task.deleted"
v-on:task-deleted="taskDeleted(task)" >
{{singleTask.id}} - {{singleTask.text}}
<button type="button" class="close" aria-label="Close"
v-on:click="deleteTaskItem(singleTask.id)">
<span aria-hidden="true">×</span>
</button>
</td>
</template>
<script>
export default {
props: ['task'],
data : function () {
return {
singleTask : this.task,
deleted : false,
};
},
mounted() {
console.log('Component TaskItem mounted.')
},
methods: {
deleteTaskItem : function (tid) {
axios.delete('tasks/'+tid, {
csrf : this.csrf,
id : this.singleTask.id
})
.then( response => {
this.$emit('singleTaskDeleted');
console.log('Delete event emitted');
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
}
</script>

Categories