vuejs2 how to get v-for items to display at specific intervals? - vuejs2

Say I have a group of cars and I want to display each row...3 seconds at a time. How can I do this in Vuejs2?
<tbody>
<tr v-for="(car) in cars">
<td><img v-bind:src="car.photo" width="40px" height="40px" alt=""></td>
<td><router-link :to="{path:'/car/' + car.id}" >{{ car.name }}</router-link></td>
<td>{{ car.make }}</td>
<td></td>
<td>{{ car.created }}</td>
</tr>
</tbody>

something like this.
stored what to show currently in currentCarIndex.
use setInterval to change currentCarIndex every 3 seconds
btw, v-for and v-if shouldn't be used together, so I add a <template> tag as an empty wrapper to execute v-for
<template>
<tbody>
<template v-for="(car,i) in cars">
<tr :key="i" v-if="i<=currentCarIndex">
<td><img v-bind:src="car.photo" width="40px" height="40px" alt=""></td>
<td>
<router-link :to="{path:'/car/' + car.id}">{{ car.name }}</router-link>
</td>
<td>{{ car.make }}</td>
<td></td>
<td>{{ car.created }}</td>
</tr>
</template>
</tbody>
</template>
<script>
export default {
data() {
return {
currentCarIndex: 0,
cars: "..."
};
},
mounted() {
const interval = setInterval(() => {
if (this.currentCarIndex + 1 < this.cars.length) this.currentCarIndex++;
else clearInterval(interval);
}, 3000);
}
};
</script>

I was having this exact problem a couple of hours ago on an app I'm working on. I have a list of reviews and I wanted the reviews to display at interval so that it looks like the list is 'filled in' top down so that I can create a cascading effect. Something like this:
The documentations points out that you can use transition-group but personally I wasn't able to get them working for me so what I did is I created a wrapper component with a delay property on it and I passed in the time the component should wait before rendering. I did this using a simple v-if in the component's template.
What you could do is add a show-in and visible-for prop to a wrapper component like this:
<flashing-row v-for="(car, i) in cars" :show-in="i * 3000" :visible-for="2900">
// Stuff inside my row here....
</flashing-row>
and then define flashing-row like this:
Vue.component('flashing-row', {
props: {
showIn: {
type: Number,
required: true,
},
visibleFor: {
type: Number,
required: true,
},
},
data() {
return {
isVisible: false,
};
},
created() {
setTimeout(() => {
// Make component visible
this.isVisible = true;
// Create timer to hide component after 'visibleFor' milliseconds
setTimeout(() => this.isVisible = false, this.visibleFor);
}, this.showIn);
},
template: '<tr v-if="isVisible"><slot></slot></tr>'
});
You can see an example of the code in JSFiddle. This approach is especially good because:
You don't repeat yourself if you're going to be doing this at more than one place.
Makes your code more maintainable and easier to browse, read, and thus understand and modify later on.
And of course you can play around with the props and expand on it depending on what you need. The possibilities are really endless.

Related

Vue.js Displaying data from websocket into a table and sending data to other component

Version: Vue CLI 2.6.x
I am trying to resolve two issues:
Issue 1:
My Vue app has subscribed to updates via a websocket. I am getting the data continuously and need to keep the table updated with the received data. However the table remains empty even when the list (aqiDataList) has content in it.
Issue 2:
I also need to pass the aqiDataList to the AQITableComponent (where the actual table was originally suppose to be) but having this same issue
App.vue
<template>
<v-container>
<AQITableComponent :aqiList="aqiDataList" />
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">
Name
</th>
<th class="text-left">
Age
</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in aqiDataList"
:key="item.id"
>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.location }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-container>
</template>
<script>
import AQITableComponent from './AQITableComponent';
export default {
name: 'AQIComponent',
components: {
AQITableComponent
},
data: function () {
return {
connection: null,
aqiDataList: []
};
},
mounted() {
},
methods: {
},
created: function () {
console.log("Starting connection to WebSocket Server");
this.connection = new WebSocket("wss://my-websocket.com");
this.connection.onmessage = function (event) {
//console.log(event.data);
let aqiDataString = event.data;
this.aqiDataList = [];
let parsedData = JSON.parse(aqiDataString);
parsedData.forEach((aqiData) => {
this.aqiDataList.push({
... object
});
});
console.log(this.aqiDataList);
};
this.connection.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
},
}
</script>
AQITableComponent.vue
<template>
<v-container>
<v-simple-table>
<template v-slot:default>
.. same table as shown above
</template>
</v-simple-table>
</v-container>
</template>
<script>
export default {
name: 'AQITableComponent',
props: ['aqiList'],
data: function () {
},
}
</script>
(1) Try using the arrow function for the onmessage event:
from: this.connection.onmessage = function (event) {...}
to: this.connection.onmessage = (event) => {...}
or: this.connection.addEventListener("message", (event) => {...});
This way the this.aqiDataList will be available on your component. Inside the event callback.
This should also solve the problem (2) since your array is not being updated on the first place.

V-model populated in a method not updating the DOM

I am newbie in VueJs.(vue 2). I have a problem here. I have a table where I am dynamically populating data like this.
<tbody>
<tr v-bind:key="queProduct.value" v-for="queProduct in queueProducts">
<td class="has-text-centered">
<figure class="image is-48x48">
<img :src="queProduct.image" alt="Placeholder image">
</figure>
</td>
<td><span>{{queProduct.title}}</span></td>
<td class="has-text-centered"><a class="has-text-link">
<span class="icon is-size-4" #click="openModalPopup(queProduct.id)">
<i class="fa fa-edit" />
</span>
</a>
</td>
<td class="has-text-centered"><a class="has-text-link">
<span class="icon is-size-4" #click="openModalPopup(queProduct.id)">
<img :src="queProduct.indicatorImg" />
</span>
</a>
</td>
<td class="has-text-centered"><a class="delete is-large has-background-link" #click="removeFromQueue(queProduct.id)"></a></td>
</tr>
</tbody>
methods:{
loadQueue(){
const indicators = store.get('productIndicators');
if(indicators === undefined){
store.set('productIndicators', []);
} else {
this.savedProprogressIndicators = indicators;
}
this.queueProducts.forEach(product => {
product.indicatorImg = indicatorImgBaseUrl +'Level-0.png';
this.savedProprogressIndicators.forEach(indicator => {
if(indicator.id === product.id){
product.indicatorImg = indicatorImgBaseUrl +indicator.image;
}
})
})
}
}
When I console.log the product, I see the product object being updated with the new value. But the dom isnt getting updated. Like,
this.product looks like this.
{
id: "d6dd8228-e0a6-4cb7-ab83-50ca5a937d45"
image: "https://zuomod.ca/image/cache/catalog/products/2018/Single/medium/50105-1-800x800.jpg"
inQueue: false
indicatorImg: "https://cdn.shopify.com/s/files/1/0003/9252/7936/files/Level-2.png"
saved: false
sku: "50105"
title: "Interstellar Ceiling Lamp"
}
But in the DOM, it looks like this
{
id: "d6dd8228-e0a6-4cb7-ab83-50ca5a937d45"
image: "https://zuomod.ca/image/cache/catalog/products/2018/Single/medium/50105-1-800x800.jpg"
inQueue: false
indicatorImg: "https://cdn.shopify.com/s/files/1/0003/9252/7936/files/Level-0.png"
saved: false
sku: "50105"
title: "Interstellar Ceiling Lamp"
}
Can you please help me resolve this?
Thanks,
Vandanaa
As you use Vuex, you should get your products directly from you store like in computed property in your vue definition. This will refresh the data directly from store without any action from vue side.
{
...
computed:{
...mapGetters({
queueProducts : 'queueProducts'
})
}
...
}
Furthermore, if your are using vuex, try to keep your logic inside your store. You vue should only display data.
Hava a look to vuex documentation to know when and where you should use
Getters, Mutations and Actions.
Hope this help.
this.queueProducts.forEach(product => {
...
...
...
}
this.$forceUpdate(); // Trying to add this code
I guessed your product.indicatorImg was not been watch by Vue, so it will not update the DOM. Trying to add this.$forceUpdate() in the end. It will force Vue to update DOM.

Calling a method into another method in vue

I'm trying to call a method from inside another method in vue.
What I get is an undefined in my console, but what I really want is the id that is called in the getId function
In a whole what I'm tring to do is use the addEvent function to get the checkbox events so that I can get a true or false from it and then send that to the saveCheckbox function and from the saveCheckbox function call the getId function to get the ID of that specific checkbox.
I hope I was able to explain it properly. If it's still unclear please let me know.
This is what I have
<template>
<div class="card-body">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Active</th>
<th scope="col">Title</th>
</tr>
</thead>
<tbody>
<tr v-for="(category, index) in categories" >
<td>
<input name="active" type="checkbox" v-model="category.active" #change="getId(category.id)" #click="addEvent">
</td>
<td>
{{ category.title }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: [
'attributes'
],
data(){
return {
categories: this.attributes,
}
},
methods: {
getId(id){
console.log(id);
return id
},
saveCheckbox(event){
console.log(this.getId());
},
addEvent ({ type, target }) {
const event = {
type,
isCheckbox: target.type === 'checkbox',
target: {
value: target.value,
checked: target.checked
}
}
this.saveCheckbox(event.target.checked)
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
You have to pass argument (Id) to getId method
Having a sort overview, you are not passing any Id to the method, and it trys to return that id. so maybe, that is what is not defined ?
The method calling is done well. with the this. keyword before it

vuejs, how to use v-bind to bind class within a v-for loop

The table looks like:
| platform | description | remark | button |
When I click a Forward button, which set this row's clicked=true, why the button's class is still btn-primary(color: blue) instead of btn-danger(color: red)?
var app = new Vue({
el: "#app",
data: {
grid_data: [], //ajax
},
methods: {
"forward": function (url, index) {
this.grid_data[index].clicked = true;
openWindow(url);
}
}
})
<tr v-for="(item,index) in grid_data">
<td>{{ item.platform }}</td>
<td>{{ item.description }}</td>
<td>{{ item.remark }}</td>
<td>
<button v-bind:class="[item.clicked ? 'btn-danger' : 'btn-primary', 'btn', 'btn-sm' ]" v-on:click="forward(item.url, index)">Forward</button>
</td>
</tr>
The issue here is likely that the clicked attribute does not exist on your data until you add it via your method, and Vue cannot detect that you added it.
Change your code to use $set.
methods: {
"forward": function (url, index) {
this.$set(this.grid_data[index], 'clicked',true);
openWindow(url);
}
},
Working example.
Additionally, you can avoid passing indexes around. Just pass the item itself.
v-on:click="forward(item)
And in your method,
"forward": function (item) {
this.$set(item, 'clicked',true);
openWindow(item.url);
}

Vue data referencing issue

This is strange because it was just working last night, but basically I have a Vue app that's pulling JSON from my backend. Code below. The strange part is that while the loadData function is running and I see the 'Loaded Data' message in console along with the list of items from the JSON, I then get a console error saying 'items is not defined'. I must have made a subtle typo or some dumb change but I can't find it anywhere!! Any ideas?
HTML snippet:
<div id="app">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Query</th>
<th>Initiated By</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<tr>
<div v-for="item in items">
<td>{{ item.id }}</td>
<td>{{ item.query }}</td>
<td>{{ item.user }}</td>
<td>{{ item.type }}</td>
</div>
</tr>
</tbody>
</table>
</div>
</div>
(And then <script src="app.js"></script> right before </body>)
JS code:
new Vue({
el: '#app',
data: {
items: [],
interval: null
},
methods: {
loadData: function () {
$.get('http://localhost:4567/getQueue', function (response) {
this.items = response.results;
console.log("Loaded data.")
console.log(response.results)
}.bind(this));
}
},
created: function () {
console.log("Loading data...")
this.loadData();
console.log(items)
this.interval = setInterval(function () {
this.loadData();
}.bind(this), 3000);
},
beforeDestroy: function(){
clearInterval(this.interval);
}
});
You are getting the error
items is not defined
because of following line:
created: function () {
console.log("Loading data...")
this.loadData();
console.log(items) <== this should be console.log(this.items)
Turns out there was a few issues in my code.
1) As was pointed out by Saurabh, I forgot to put this.items instead of items.
2) this can't be referenced inside of the function I defined as I have it... instead, the function has to be defined with =>, for example:
$.get('http://localhost:4567/getQueue').then((response) => {
this.items = response.data.results;
console.log("loadData finished - items length is: "+this.items.length)
})
3) The big error I had was that my div bind with items was inside the table tag, which apparently isn't okay to do. Instead I applied the Vue binds to the existing tags (table, tr).