How do I trigger another binding to a Vue component - javascript

I'm using vue-apollo to fetch data from backend and will bind the value into the components.
When the first assignment of the local data, vue will send the data immediately to the following components and generate the template.
But I will need to put an additional property to that object after that, but seems like the component won't generate new html although the data had changed.
new Vue({
el: '#app',
apolloProvider,
components: {
Category, Chart
},
data () {
return {
categories: null,
},
apollo: {
getDatafromBackend() {
...
...,
async result (result) {
this.categories = await result.data.entriesWithinCategories.categories;
this.total = result.data.entriesWithinCategories.total;
return this.categories = await this.categories.map((category, index) => {
category.color = randomColors[index];
return category;
});
}
},
})
v-bind in index.js
<Category v-for="category in categories" :key="category._id" :category="category" :total="total"></Category>
Category.vue
<template>
<div>
<h4 :style="{ color: category.color }" #click="toggle = !toggle">
{{ category.name }} - {{ category.percentage }}% {{ category.color }}
</h4>
</div>
</template>
It won't print the category.color and console.log shows the value is undefined.
My question is how do I trigger another binding when the data changed?

Related

Vue (2.6.14) - v-for not displaying data with an api import (axios)

The idea
I'm trying to build a display where activities are shown with some filters. The data comes from an API generated by the CMS. The filters are not shown in the code since its not relevant.
Problem
When manually defining the 'items' in the data property the v-for list rendering displays fine and gives the desired output. When pulling the data from the api and assigning them to items the v-for is not displaying anything.
My Thoughts
I think that the v-for is run before the api request is finished putting the data into the 'items' value. Currently I'm using 'created' property to fire the function, also used 'Mounted()' before, this also didn't work.
Versions Vue 2.6.14, Axios 0.21.1
Vue Code
var vue = new Vue({
el: '#app',
data: {
items: null,
},
created: function () {
this.fetchData()
},
methods: {
fetchData: function() {
axios
.get('/api/activities.json')
.then(response => (this.items = response.data.data))
}
}
})
Templating
<div id="app">
<ul class="example">
<li v-for="item in items">
{{ item }}
</li>
</ul>
</div>
Your code seems to be working just fine. Look below, I just replaced your api call with a dummy REST api call and it's working just fine. Please console out the data from response.data.data and see if you are really receiving an array there.
Vue.config.productionTip = false
Vue.config.devtools = false
let vue = new Vue({
el: '#app',
data: {
items: null,
},
created() {
this.fetchData()
},
methods: {
fetchData: function() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(response => { this.items = response.data })}
}
})
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="app">
<ul class="example">
<li v-for="item in items">
{{ item.id }} - {{ item.name }}
</li>
</ul>
</div>
</div>

How to use function that return value inside a template? Vuex, Vue

I'm using a template to get data of a json file, I use "v-for" to print all data, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ item.date }}</li>
<ul>
</template>
</div>
`,
But I need use functions, year() to modificate this information and return and result, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ year(item.date) }}</li>
<ul>
</template>
</div>
`,
The value {{ item.date }} print "2021-01-20" but I hope print "2021" using the function {{ year(item.date) }}
Code function year() using javascript:
year(date){
return String(date).substr(0, 4);
}
I tried use that code but is not working, appear this error:
That's my javascript code:
//VueEx
const store = new Vuex.Store({
state: {
actividades: [],
programas: [],
year: ""
},
mutations: {
llamarJsonMutation(state, llamarJsonAction){
state.actividades = llamarJsonAction.Nueva_estructura_proveedor;
state.programas = llamarJsonAction.BD_programas;
},
yearFunction(state, date){
state.year = String(date).substr(8, 2);
return state.year;
}
},
actions: {
llamarJson: async function({ commit }){
const data = await fetch('calendario-2021-prueba.json');
const dataJson = await data.json();
commit('llamarJsonMutation', dataJson);
}
}
});
//Vue
new Vue({
el: '#caja-vue',
store: store,
created() {
this.$store.dispatch('llamarJson');
}
});
Inside a template you can use functions defined as methods or computed. Technically, you can also use data to pass a function to the template, but I wouldn't recommend it. Not that it wouldn't work, but Vue makes anything declared in data reactive and there's no point in making a function (which is basically a constant) reactive. So, in your case:
new Vue({
el: '#app',
data: () => ({
actividades: [
{ date: '2021-01-20' },
{ date: '2020-01-20' },
{ date: '2019-01-20' },
{ date: '2018-01-20' },
{ date: '2017-01-20' }
]
}),
methods: {
year(date) { return date.substring(0, 4); }
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="(item, key) in actividades" :key="key">
{{ year(item.date) }}
</li>
</ul>
</div>
If, for some reason, you want to pass year as computed:
computed: {
year() { return date => date.substring(0, 4); }
}
But it's a convoluted construct (a getter function returning an inner arrow function) and this complexity doesn't serve any purpose. I'd recommend you use a method in your case, since it's the most straight-forward (easy to read/understand).
If you're importing the year function from another file:
import { year } from '../helpers'; // random example, replace with your import
// inside component's methods:
methods: {
year, // this provides `year` imported function to the template, as `year`
// it is equivalent to `year: year,`
// other methods here
}
Side notes:
there is no point in iterating through <template> tags which contain <ul>'s. You can place the v-for directly on the <ul> and lose the <template> (You should only use <template> when you want to apply some logic - i.e: a v-if - to a bunch of elements without actually wrapping them into a DOM wrapper; another use-case is when you want its children to be direct descendants of the its parent: for <ul>/<li> or <tbody>/<tr> relations, where you can't have intermediary wrappers between them). In your case, placing the v-for on the <ul> produces the exact same result with less code.
you should always key your v-for's: <ul v-for="(item, key) in actividades" :key="key">. Keys help Vue maintain the state of list elements, keep track of animations and update them correctly
I see you are trying to work with the Vuex store. And using mutation inside the template syntax.
Not sure if we can call mutation directly via HTML as the way you are doing. In the past when I tried to call a mutation, I would either:
Create an action which would commit that mutation and call that action wrapped inside a method through Vue, something like this:look for a method printSampleLog() that I defined here
Vue.component('followers', {
template: '<div>Followers: {{ computedFollowers }} {{printSampleLog()}}</div>',
data() {
return { followers: 0 }
},
created () {
this.$store.dispatch('getFollowers').then(res => {
this.followers = res.data.followers
})
},
computed: {
computedFollowers: function () {
return this.followers
}
},
methods:{
printSampleLog(){
this.$store.dispatch('sampleAction').then(res => {
this.followers = res.data.followers
})
}
}
});
const store = new Vuex.Store({
actions: {
getFollowers() {
return new Promise((resolve, reject) => {
axios.get('https://api.github.com/users/octocat')
.then(response => resolve(response))
.catch(err => reject(error))
});
},
sampleAction(context){
context.commit('sampleMutation');
}
},
mutations: {
sampleMutation(){
console.log("sample mutation")
}
}
})
const app = new Vue({
store,
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<followers></followers>
</div>
Else create method w/o action in your Vue component committing the mutation directly, using this.$store.commit()
PS: Would recommend creating action around the mutation first, as it is a much cleaner approach.

Vue Bootstrap Pagination Define :Total-rows

I am learning to paginate data returned from an API using AXIOS. I have a working set of code, but there is a place in the code defined by bootstrap for :Total-rows, this is currently hardcoded but this creates extra rows based on the value rather than a computed value. I want to calculate the number of rows dynamically.
I know that I can count the response data from the api using: this.variable = response.data.length, but the way I am calling the data is using page variable to paginate.
Any suggestions on an efficient way to accomplish this somewhat seemingly simple call?
<template>
<div id="app">
<div class="row">
<div class="col-md-12">
<li v-for="item in todos" :key="item.id">
{{ item.name }} : {{ item.type }}
</li>
</div>
</div>
<b-pagination size="md" :total-rows="54" v-model="currentPage" :per-page="10" #input="getPostData(currentPage)">
</b-pagination>
</div>
</template>
VUE
<script>
//Import axios for REST API calls
import axios from 'axios'
import 'regenerator-runtime/runtime';
//Import bootstrap CSS
import 'bootstrap/dist/css/bootstrap.css'
//Import bootstrap vue CSS
import 'bootstrap-vue/dist/bootstrap-vue.css'
const baseURL = 'http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit;
export default {
name: 'app',
data () {
return {
title: 'Vue.js Pagination Example With Bootstrap',
currentPage: 1,
limit: 5,
todos: [],
todoName: "",
todoType: "",
}
},
methods: {
// Fetches todos when the component is created.
getPostData (currentPage) {
axios.get('http://localhost:3000/todos?_page='+this.currentPage+'&_limit='+this.limit)
.then(response => {
//console.log(response)
// JSON responses are automatically parsed.
this.todos = response.data
})
.catch(e => {
this.errors.push(e)
})
},
async addTodo() {
const res = await axios.post(baseURL, {
name: this.todoName,
type: this.todoType,
});
this.todos = [...this.todos, res.data];
//resets the input field
this.todoName = "";
this.todoType = "";
},
}, //end of methods
//detects the current page on load
mounted(currentPage){
this.getPostData(currentPage)
}
}
</script>
You will need the API to return the total amount of rows, otherwise your frontend have no way of knowing how many pages to show.
You can find an example of this below, which use a dummy/testing API called reqres. This API returns various information, like the current page, total amount of rows and per page and of course the data for the requested page.
new Vue({
el: "#app",
data() {
return {
currentPage: 1,
totalRows: 0,
perPage: 0,
users: [],
request: null
}
},
methods: {
async getData(page) {
const response = await fetch(`https://reqres.in/api/users?page=${page}&per_page=3`).then(resp => resp.json())
this.perPage = response.per_page;
this.users = response.data;
this.totalRows = response.total;
// Only for testing purposes
this.request = response
}
},
created() {
this.getData(this.currentPage)
}
})
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.css" />
<script src="//cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.18.1/bootstrap-vue.min.js"></script>
<div id="app">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
#change="getData">
</b-pagination>
<ul>
<li v-for="{ first_name, last_name } in users">
{{ first_name }} {{ last_name }}
</li>
</ul>
Request
<pre>{{ request }}</pre>
</div>

Vuejs: data list not accessible from template in another template

So I have this set-up, which crashes on v-for construct of table-component. It shows an error: "Property or method "tablesList" is not defined on the instance but referenced during render". If I omit v-for table-component renders. If I access this data from "container" component all is fine. So the problem is in accessing data from child template in parent template.
What am I doing wrong?
let container = Vue.component("container", {
props: ["item"],
template: `<div class="container">
<table-component v-for="item in tablesList"></table-component>
</div>`
});
let table = Vue.component("table-component", {
props: ["item"],
template: `<div class="table">
this is a table
</div>`
});
let app = new Vue({
el: "#app",
data() {
return {
containersList: [],
tablesList: [{item:'item'}]
};
},
methods: {
anyMethod() {
}
}
});
</script>
You are using tablesList in container component But you defined it in app.
You need to add tablesList in container like below,
let container = Vue.component("container", {
props: ["item"],
data: () => {
return {
tablesList: [{item:'item'}]
}
},
template: `<div class="container">
<table-component v-for="item in tablesList"></table-component>
</div>`
});
NOTE: Use v-bind:key when use v-for.
You need to define tablesList in props => https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props

VueJS - How to call event on child component from parent v-for

PARENT VIEW I have the following template code:
<template>
<employee-card
v-for="employee in employees"
:key="employee.id"
:employee="employee"
>
</employee-card>
</template>
<script>
import EmployeeCard from '#/components/employee-card';
export default {
components: {EmployeeCard},
computed: mapGetters({
employees: 'employees'
}),
methods: {
init() {
this.fetchEmployees();
},
fetchEmployees() {
// here get employees from store
},
validateServerEmployeeStatus() {
// here call ajax to get all employees status
// loop for each employee card and update the status
},
},
mounted() {
this.init();
// here I guess I should add a setInterval function that runs
// every 60 seconds and call validateServerEmployeeStatus() function
}
};
</script>
CHILD COMPONENT Employee card template is:
<template>
<div>
{{ employee.name }}
<br><br>
Status {{ employee.status }} (updated every 60 seconds)
</div>
</template>
<script>
export default {
name: 'EmployeeCard',
props: {
employee: {type: Object}
},
data() {
return {};
},
methods: {}
};
</script>
What I need is to call an API every 60 seconds, this will return me the status of all the employees I have in my child component. So then I have to loop for all the employees and update the status label in each employee card. I think this is the best approach because I save API calls if I do it inside employeecard.
My question is: once the view is rendered in the browser how can I loop through all employee card elements and update a value within a setInterval function thats going to live in the parent template.
Here is an example of what I was explaining in comments above.
Vue is data driven. If you change the data, the DOM will automatically be updated.
Below the status of each employee is updated every second. Notice that changes are only made in the EmployeeList component, not in the EmployeeCard, but the DOM is automatically updated to reflect the new status.
console.clear()
Vue.component("EmployeeCard",{
props: ["employee"],
template: `
<div>
<strong>{{ employee.name }}</strong>
<br><br>
Status {{ employee.status }}
<hr>
</div>
`
})
Vue.component("EmployeeList",{
template: `
<div>
<employee-card v-for="employee in employees" :employee="employee" :key="employee.id"/>
</div>
`,
data(){
return {
employees: []
}
},
created(){
axios.get("https://jsonplaceholder.typicode.com/users")
.then(response => this.employees = response.data)
// simulated employee validation
setInterval(() => {
let status = ["good", "bad", "meh"]
for (let employee of this.employees){
// set a random status
this.$set(employee, "status", status[Math.floor(Math.random()*status.length)])
}
}, 1000)
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.1/axios.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<employee-list></employee-list>
</div>

Categories

Resources