Load More Data On Scroll With Vue And Vuex - vue.js

I would like to ask how can I display more data by using Vue and vuex. all data stored in vuex-store management already. From State management now I want to load more data on scrolling.
I found online solution by ajax. but I need to loading form state management (Vuex).
This is my Vue template:
<template>
<div>
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<tr>
<th>Name - Number of Products: <span style="color: red"> {{products}} </span></th>
<th width="100"> </th>
</tr>
</tr>
</thead>
<tbody v-if="isLoaded">
<tr v-for="company, index in companies">
<td>{{ company.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return { }
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
products(){
return this.$store.getters['exa1Company/countProducts'];
}
},
mounted() {
this.$store.dispatch('exa1Company/indexResource');
}
}
</script>
My vuex store file is partial for simplicity
export const getters = {
countProducts(state) {
return state.list.data.length;
},
getProducts(state) {
return state.list.data;
},
getTodoById: (state) => (id) => {
return state.list.data.find(tod => tod.id === id)
}
};
export default {
namespaced: true,
state: customerState,
getters,
actions,
mutations,
};

something like this should work. use companiesLoaded in the template, and increase page when scrolled to bottom. I hope this helps.
data: function () {
return {
page: 1,
perPage: 20
}
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
companiesLoaded(){
return this.companies.slice(0, this.page * this.perPage)
},
...

Related

Strange behavior in Vue 3 without any error or warning

I have the following Vue 3 component that I am using in 2 pages that are setup like this in vue.config.js:
module.exports = {
// Put this in the ASP.NET Core directory
outputDir: "../wwwroot/app",
pages: {
vehicleDetails: "src/VehicleDetails.js",
driverDetails: "src/DriverDetails.js"
}
};
Both of these pages work and share the same underlying 'Certificates' component that looks like this:
<template>
<div>
<h3 id="Vue_Certificates">Certificates</h3>
<div>
objectId: {{objectId}} test
<table class="table table-striped table-hover table-condensed2" style="clear: both;">
<thead>
<tr>
<th><b>test</b></th>
<th style="text-align: right;">
<a href="#" #click="addCertificate">
<i class="fa fa-plus-square"></i> Add
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="certificate in certificates" v-bind:key="certificate">
<td>{{ certificate.CertificateTypeId }}</td>
<td>
<a href="#" #click="removeCertificate(index)" title="Delete" style="float: right;" class="btn btn-default">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { onMounted, ref } from "vue";
import $ from 'jquery';
import _ from 'lodash';
export default {
components: {
},
props: {
objectId: String,
ownerIsVehicle: Boolean
},
data() {
return {
certificates: [],
types: []
}
},
created() {
const requestOne = axios.get("/api/v1.0/CertificateType/GetCertificateTypes");
const requestTwo = axios.get("/api/v1.0/Certificate/GetCertificateByObjectId", { params: { objectId: this.objectId, ownerIsVehicle: this.ownerIsVehicle } });
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
alert("Axios calls completed 1");
this.types = responseOne.data;
var mappedData = responseTwo.data.map(x => ({
...x,
ValidFrom: new Date(x.ValidFrom),
ValidTill: new Date(x.ValidTill)
}));
this.certificates = mappedData;
alert("Axios calls completed 2");
console.log("succes");
})).catch(errors => {
console.log("fail");
});
},
methods: {
removeCertificate(index) {
this.certificates.splice(index, 1);
},
addCertificate() {
alert("Hello?");
}
}
}
</script>
The above code does NOT render the Vue component, but once I take out the two cells containing the anchors that call addCertificate & removeCertificate it all suddenly works.
For some reason I don't get any error in Chrome dev tools .. npm console .. visual studio console .. nowhere, so I have no clue at all what is going wrong.
Important: If I take out 1 of the 2 lines in vue.config.js it works on the page that is still added. The purpose of this component is that it can be reused.
FYI: I shrank down the code as much as possible to make sure other code bits aren't the cause.
For the record, the data property "certificates" was first a ref([]) but that doesn't seem to help at all.
Thanks in advance!

How to store array to Vuex in VueJs

Can someone tell me how to write an array of objects in Vuex
I will explain the essence
In this situation, I need to create an order management system
I have an array of products with different data
This array I get from the server using axios request
Via v-for, I displayed this array in the select option html tag
Next, by clicking on the html option tag, I need to add the product to the data table
Also, with the addition of a specific product to the table, it is necessary that the product for which a click is made is recorded in the Vuex store, but not with rewriting, but with addition to existing data
Next, in synchronization with the addition to the Vuex store, information is output from the Vuex store to the table
this is my code in Vue Component
<div class="row">
<div class="col-md-8">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>QTY</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4">
<form>
<div class="form-group">
<label for="products">Select Products</label>
<select v-model="orderData" #change="addOrderData" id="products" class="form-control">
<option v-for="product in products" multiple v-bind:value="{productId: product.id, name: product.name, price: product.price}">{{product.name}}</option>
</select>
<pre>{{orderData}}</pre>
</div>
</form>
</div>
</div>
data() {
return {
selected: '',
formData: {
userId: ''
},
orderData: {
productId: [],
price: [],
total: [],
quantity: []
}
}
},
methods: {
addOrderData()
{
let data = {
productId: this.orderData.productId,
name: this.orderData.name,
price: this.orderData.price,
}
this.$store.commit('orders/setOrderProduct', data)
}
},
this is my code in Vuex store
function initialState () {
const orderProducts = [];
return {
orderProducts
}}
const getters = {
orderProducts(state)
{
return state.orderProduct;
},};
const mutations = {
setOrderProduct(state, orderProduct)
{
state.orderProduct = orderProduct;
}};
If I understand you correctly, please check the following:
template:
<select v-model="orderData" #change="addOrderData">
<option v-for="(product) in products" :key="product.productId" :value="{productId: product.productId, name: product.name, price: product.price}">
{{product.productId}} - {{product.name}}</option>
</select>
<br /><br />
<table>
<thead>
<tr>
<th>name</th>
<th>price</th>
</tr>
</thead>
<tbody v-if="orderProducts.length > 0">
<tr v-for="(item, index) in orderProducts" :key="index">
<td>{{item.name}}</td>
<td>{{item.price}}</td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="2">No products to display</td>
</tr>
</tbody>
</table>
code:
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
orderProducts: []
},
mutations: {
addProduct(state, payload) {
state.orderProducts.push(payload);
}
}
})
new Vue({
store,
el: '#app',
data() {
return {
products: [{
productId: 1,
name: 'Product One',
price: 10
},
{
productId: 2,
name: 'Product Two',
price: 15
}
],
orderData: {
productId: [],
price: [],
total: [],
quantity: []
}
};
},
computed: {
orderProducts() {
return this.$store.state.orderProducts;
}
},
methods: {
addOrderData() {
this.$store.commit('addProduct', this.orderData);
}
}
});
The idea is when change event is triggered by selecting an option, you commit mutation to the store with the selected product as payload. The orderProducts computed property will refresh, and the table will get the latest data. You can check this jsfiddle.

:class not updating when clicked in vuejs v-for

I want to add a css class to a item in v-for when the td in clicked
<template>
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
</tr>
<th>Delete</th>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<td>
<router-link :to="{ name: 'ListForce', params: { name: force.id } }">Link</router-link>
</td>
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</template>
<script>
import {APIService} from '../APIService';
const apiService = new APIService();
const _ = require('lodash');
export default {
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
computed: {
/* forcesOrdered() {
return this.orderByName ? _.orderBy(this.forces, 'name', 'desc') : this.forces;
}*/
},
mounted() {
this.getForces();
},
}
</script>
<style>
.completed {
text-decoration: line-through;
}
</style>
I want a line to go through the items when clicked, but the dom doesn't update. If I go into the vue tab in chrome I can see the force.done changes for each item but only if I click out of the object and click back into it. I'm not to sure why the state isn't updating as it's done so when I have used an click and a css bind before.
I've tried to make minimal changes to get this working:
// import {APIService} from '../APIService';
// const apiService = new APIService();
// const _ = require('lodash');
const apiService = {
getForces () {
return Promise.resolve([
{ id: 1, name: 'Red' },
{ id: 2, name: 'Green' },
{ id: 3, name: 'Blue' }
])
}
}
new Vue({
el: '#app',
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
for (const force of data) {
force.done = false;
}
this.forces = data;
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
mounted() {
this.getForces();
}
})
.completed {
text-decoration: line-through;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</div>
The key problem was here:
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
The problem here is that the property done is being added to the data after it has been made reactive. The reactivity observers get added as soon as the line this.forces = data runs. Adding done after that counts as adding a new property, which won't work.
It's also a misuse of map so I've switched it to a for/of loop instead.
for (const force of data) {
force.done = false;
}
this.forces = data; // <- data becomes reactive here, including 'done'
On an unrelated note I've tweaked the template to move the Delete header inside the top row.
Make sure force.done is set to false in data before initialization so it is reactive.
data() {
return {
force:{
done: false;
}
}
}
If force exists but has no done member set, you can use Vue.set instead of = to create a reactive value after initialization.
Vue.set(this.force, 'done', true);

Is it possible to use a component property as a data variable?

I've copied my current code below. I'm trying to dynamically generate table headers depending on what type I pass as a prop to my tables component (standings and status are what I have as the data arrays in my example).
I've accomplished this by using the if statement in the header computed value to return the proper list.
However I'd like to not have to add additional if statements for every type.
Is there a way that I can leverage the type prop to bind directly to the matching data?
<div id="root" class="container">
<tables type="stats">
</tables>
</div>
Vue.component('tables', {
template: `
<table class="table">
<thead>
<tr>
<th v-for="headers in header">{{ headers }}</th>
</tr>
</thead>
<tfoot>
<tr>
<th v-for="footers in header">{{ footers }}</th>
</tr>
</tfoot>
<tbody>
<tr>
<th>1</th>
<td>Leicester City <strong>(C)</strong>
<slot></slot>
</tr>
</tbody>
</table>
`,
data () {
return {
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
};
},
computed: {
header() {
if (this.type == 'standings') {
return this.standings;
} else {
return this.stats;
}
}
},
props: {
type: { required: true }
}
});
If you change up your data structure slightly, you can remove your if statements.
data () {
return {
types:{
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
}
};
},
computed: {
header() {
return this.types[this.type]
}
},
props: {
type: { required: true }
}
Here is an example.
Vue.component('tables', {
template: `
<table class="table">
<thead>
<tr>
<th v-for="headers in header">{{ headers }}</th>
</tr>
</thead>
<tfoot>
<tr>
<th v-for="footers in header">{{ footers }}</th>
</tr>
</tfoot>
<tbody>
<tr>
<th>1</th>
<td>Leicester City <strong>(C)</strong>
<slot></slot>
</td>
</tr>
</tbody>
</table>
`,
data () {
return {
types:{
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
}
};
},
computed: {
header() {
return this.types[this.type]
}
},
props: {
type: { required: true }
}
});
new Vue({
el: "#root"
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="root" class="container">
<tables type="stats">
</tables>
</div>

Vue - axios - handling same requests

In my project I've got such structure:
Client page which has sidebar, general client's info and router-view for children views.
routes:
{ path: '/clients/:id', component: Client,
children: [
{
path: '/',
component: ClientReview,
name: 'client-review'
},
{
path: 'balances',
component: ClientBalances,
name: 'client-balances'
},
{
path: 'report',
component: MainReport,
name: 'client-report'
},
Client's component (Client.vue):
<template>
<el-row>
<client-menu></client-menu>
<el-col class="client-view" :md="{ span: 22, offset: 2}" :sm="{ span: 20, offset: 4}" :xs="{ span: 18, offset: 6}">
<client-bar></client-bar>
<transition name="el-zoom-in-center">
<router-view></router-view>
</transition>
</el-col>
</el-row>
</template>
<script>
import ClientMenu from './ClientMenu.vue'
import ClientBar from './ClientBar.vue'
export default {
data () {
return {
loading: false,
};
},
components: {
'client-menu': ClientMenu,
'client-bar': ClientBar,
}
}
</script>
ClientBar component (ClientBar.vue):
<template>
<div class="client-bar">
<el-col :span="18">
<h3>{{ client.Name }}</h3>
<h4>{{ client.Address }}</h4>
</el-col>
<el-col :span="6" style="text-align: right;">
<el-button-group>
<el-button icon="edit" size="small"></el-button>
<el-button icon="share" size="small"></el-button>
</el-button-group>
</el-col>
<div class="clrfx"></div>
</div>
</template>
<script>
export default {
data () {
return {
client: {}
}
},
mounted () {
this.loadClient()
},
methods: {
loadClient: function() {
self = this;
this.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.client = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
}
}
</script>
And I've got ClientReview component, which is root component for clients/:id route and use the same api to load clients information as ClientBar:
<template>
<div>
<el-row v-loading.body="loading">
<el-col :span="12">
<table class="el-table striped">
<tr>
<td class="cell">Полное наименование</td>
<td class="cell">{{ clientInfo.FullName }}</td>
</tr>
<tr>
<td class="cell">УНП</td>
<td class="cell">{{ clientInfo.UNP }}</td>
</tr>
<tr>
<td class="cell">ОКЭД</td>
<td class="cell">{{ clientInfo.Branch.code }}<br>{{ clientInfo.Branch.name }}</td>
</tr>
<tr>
<td class="cell">Адрес</td>
<td class="cell">{{ clientInfo.Address }}</td>
</tr>
<tr>
<td class="cell">Аналитик</td>
<td class="cell">{{ clientInfo.Analytic.first_name }} {{ clientInfo.Analytic.last_name }}</td>
</tr>
<tr>
<td class="cell">Менеджер</td>
<td class="cell">{{ clientInfo.Manager.first_name }} {{ clientInfo.Manager.last_name }}</td>
</tr>
</table>
</el-col>
<el-col :span="12">
<classification-report></classification-report>
</el-col>
</el-row>
</div>
</template>
<script>
import ClassificationReport from '../reports/ClassificationReport.vue'
export default {
data () {
return {
loading: false,
clientInfo: {}
}
},
created () {
this.Client();
},
methods: {
Client: function() {
self = this;
self.loading = true;
self.axios.get('http://127.0.0.1:8020/clients/'+self.$route.params.id)
.then(function(response) {
self.clientInfo = response.data;
self.loading = false;
})
.catch(function(error) {
console.log(error);
});
}
},
components: {
'classification-report': ClassificationReport
}
}
</script>
The problem is when I load page client/:id first time or refresh the page client's data in ClientReview doesn't load.
The component is rendered (as I see it in Vue Devtools), and both requests to server are sent, but clientInfo object in ClientReview still empty.
Than if I go to balances or report page and after that go to client-review page everything is loaded.
Hope someone could help me.
It's because self happens to be another reference to the window object, and is available in all modules.
Let's walk thru the steps and see why this bug is happening.
You load up client:/id.
The created method in ClientReview does ajax request, and assigns self to this.
The mounted method in ClientBar does ajax request, and reassigns self to this. Note that this also changed the self variable reference in ClientReview.
The ClientReview ajax finishes and assigns self.clientInfo = response.data;. Since self is a reference to ClientBar, and ClientBar does not declare clientInfo as a root data property, this assignment does nothing.
ClientBar ajax finishes and assigns self.client = response.data;, which works.
You route away from ClientReview.
You route back to ClientReview. Repeat step 2.
ClientReview successfully renders because ClientBar has already rendered, and does not reassign self.
The answer is to do let self = this instead of self = this.
The lesson is ALWAYS DECLARE YOUR VARIABLES with var, let or const.