Vue - Array push within an array push - vue.js

I'm trying to achieve adding different rows for each genres array push. The problem is all the rows are simultaneously added for each genre and if I input the text, it will reflect in all rows. How do I separate the entity of each rows?
Here is my code.
Template
<table class="table">
<v-btn #click="addGenre()">Add Genre</v-btn>
<tbody>
<tr v-for="genre in genres" :key="genre.id">
<td>
<v-select/>
<v-btn #click="addRow()">Add Row</v-btn> // add row for each genre added
<tr v-for="(row, index) in rows" :key="row.id">
<td><v-textarea/></td>
</tr>
</td>
</tr>
</tbody>
</table>
export default {
data: ()=> ({
genres: [],
rows: [],
}),
methods: {
addGenre () {
this.genres.push({
genre: '',
});
},
addRow () {
this.rows.push({
row: '',
});
},
}

If I understand your issue, I think you should just store rows inside of the genre objects.
<tr v-for="(genre, genreIndex) in genres" :key="genre.id">
<td>
<v-select/>
<v-btn #click="addRow(genreIndex)">Add Row</v-btn> // add row for each genre added
<tr v-for="(row, index) in genre.rows" :key="row.id">
<td><v-textarea v-model="row.row"/></td>
</tr>
</td>
</tr>
data: ()=> ({
genres: [],
}),
methods: {
addGenre () {
this.genres.push({
genre: '',
rows: [],
});
},
addRow (genreIndex) {
this.genres[genreIndex].rows.push({
row: '',
});
},
}

Related

Jest: Quasar table is not rendering rows

I'm using Jest for testing a quasar table in VueJs 3. The table I have is like this:
<template>
<q-table id="myTable"
:rows="rows"
:columns="columns"
virtual-scroll
:rows-per-page-options="[0]"
hide-bottom
/>
</template>
<script lang="ts">
import {defineComponent, PropType} from 'vue'
export default defineComponent({
name: 'TableDemo',
props: {
rows: {
type: [] as PropType<[{ name: string, surname: string }]>,
default: []
},
},
setup() {
const columns = [
{
name: 'name',
required: true,
align: 'left',
label: 'name',
field: 'name',
sortable: true
},
{
name: 'surname',
label: 'surname',
align: 'left',
field: 'surname',
sortable: true
},
]
return {columns}
}
})
</script>
I'm trying to write a very simple test to understand how this works:
it('Should contain the word "John"', async () => {
)
const wrapper = mount(TableDemo, {
props: {rows: [{name: 'John', surname: 'Marston'}]},
}
)
expect(wrapper.find('#myTable').text()).toContain('John')
})
My problem is that it doesn't render the rows, but only the columns.
Here what Jest renders:
<div class="q-table__container q-table--horizontal-separator column no-wrap q-table__card q-table--no-wrap"
id="myTable"><!---->
<div class="q-table__middle q-virtual-scroll q-virtual-scroll--vertical scroll">
<table class="q-table">
<thead>
<tr>
<th class="text-left sortable">name<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
<th class="text-left sortable">surname<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
</tr>
</thead>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 0px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
<tbody class="q-virtual-scroll__content" id="qvs_1" tabindex="-1"/>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 48px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
</table>
</div><!----></div>
Where is my mistake? Why doesn't the table render the row?
UPDATE
I actually realized that it works without "virtual-scroll" option, so the problem is bound to that. Does anyone have experience with it?
It seems they have used some timer function inside virtual scroll implementation, this is my fix:
call jest.useFakeTimers(); method before describe block, then inside test call jest.runAllTimers(); and await wrapper.vm.$nextTick(); before assertion
it('renders correctly', async () => {
const wrapper: any = wrapperFactory();
jest.runAllTimers();
await wrapper.vm.$nextTick();
expect(wrapper.html()).toMatchSnapshot();
});

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.

Vue.js - Loop, fetch and display all data from array.push

I have a button that adds new field with the use of array.push. I was able to fetch the right data while using a REST client (Insomnia). I'm aware of how to display the data to a single form using response => this.item = response.data, but how do I loop the response.data to display all the data entered in the array.push fields?
Please see my code below.
Component.vue
<template>
<v-btn #click="addRow()">Add New</v-btn>
</template>
<table class="table">
<tbody>
<tr v-for="(row, index) in books" :key="row.id">
<td>
<base-select
v-model="row.book_id"
:items="books"
item-text="title"
item-value="id"
label="Sample Books"
/>
</td>
<td><v-textarea label="Title" v-model="row.title" /></td>
<td><v-textarea label="Genre" v-model="row.genre" /></td>
<td><v-text-field label="Pages" v-model="row.pages" /></td>
<td><a #click="removeRow(index);">Remove</a></td>
</tr>
</tbody>
</table>
<script>
export default {
data: ()=> ({
books: [],
}),
created () {
this.getBooks ()
},
methods: {
addRow () {
this.books.push({
book_id: '',
title: '',
genre: '',
pages: '',
});
},
getBooks () {
axios.get('/api/books', {
params: { id: this.$route.params.id }
})
.then(response => this.books = // Display all data to each field in row (example there are 3 records existing with the id of params.id) )
.catch(error => console.log(error))
},
removeRow (index) {
this.buttons.splice(index, 1);
},
}
}
</script>
Screnshot
I have 5 data in the database and it reflects the number of rows, but the fields are empty.

dynamically call an object property in a v-for loop

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.
You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

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>