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

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.

Related

Embeded template in vue

I would like to use a local component in VueJS:
My component file (cleaned up a bit):
<template id="heroValuePair">
<td class="inner label">{{label}}</td>
<td class="inner value">{{c}}</td>
<td class="inner value">
{{t}}
<span v-if="c < t" class="more">(+{{t-c}})</span>
<span v-if="c > t" class="less">({{t-c}})</span>
</td>
</template>
<template id="hero">
<table class="hero card" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>other data...</td>
</tr>
<tr>
<hvp label="Label" v-bind:c="current.level" :t="target.level" :key="hero.id"/>
</tr>
</table>
</template>
<script>
var HeroValuePair = {
template: "#heroValuePair",
props: {
label : String,
c : Number,
t : Number
},
created() {
console.log("HVP: "+this.c+" "+this.t);
}
};
Vue.component("Hero", {
template: "#hero",
props: {
heroId : String
},
components: {
"hvp" : HeroValuePair
},
data: () => ({
hero: {},
current: {},
target: {}
}),
computed: {
},
created() {
fetch("/api/hero/"+this.heroId)
.then(res => res.json())
.then(res => {
this.hero = res.hero
this.current = res.current
this.target = res.target
})
}
});
</script>
<style>
</style>
This outer Hero template is used in a list iterator:
<template id="card-list">
<table>
Card list
<div id="">
<div v-for="card in cards" class="entry">
<Hero :hero-id="card.hero.id" :key="card.hero.id"/>
</div>
</div>
</table>
</template>
<script>
Vue.component("card-list", {
template: "#card-list",
data: () => ({
cards: [],
}),
created() {
fetch("/api/cards")
.then(res => res.json())
.then(res => {
this.cards = res.heroes
})
.catch((e) => alert("Error while fetching cards: "+e));
}
});
</script>
<style>
</style>
However, when I render the card list, it only produces the list of the first td in hvp template:
When I comment out the call of hpv the page is rendered correctly with all the HTML code from Hero template.
I tried to figure out what step I left out, but can't find the clue.
One last info: I used JavalinVue to support the server side, not nodejs-based Vue CLI. I don't know if it has any impact, but may be important.
UPDATE 1
After IVO GELOV spot the problem with multiple root tags, and because I can't move to Vue3, I tried to make it as a functional template, as he suggested. I removed the template and created the render function:
var HeroValuePair = {
template: "#heroValuePair",
functional: true,
props: {
label : String,
c : Number,
t : Number
},
render(createElement, context) {
console.log("HVP: "+context.props.c+" "+context.props.t);
if (typeof context.props.c === undefined) return createElement("td" )
else return [
createElement("td", context.props.label ),
createElement("td", context.props.c ),
createElement("td", context.props.t )
]
}
}
Although the console indicated the render is called correctly, the result is the same: there is neither the rendered nodes, nor the parent Hero component displayed. I tried to move into different file, tried the functional template format, but none worked.

Vue - Array push within an array push

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: '',
});
},
}

Vuetify data table creation

I'm looking for some simple tutorial to make data table with Vuetify.
I need first get my data from the JSON file, display it with first, middle, last name, email.
I want to use props.
Can you give me an idea of how to do this?
You can create a table child component with props and use that component by passing props.
Please check below working code snippet
new Vue({
el: '#app',
data: {
tableData: []
},
methods:{
onLoadDataClick(){
let self = this;
document.querySelector('.lds-roller').style.display="block";
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
document.querySelector('.lds-roller').style.display="none";
self.$data.tableData = json
})
}
},
components: {
'child' : {
template: `
<table style="width:100%;border-collapse: collapse;">
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
</tr>
<tr v-for="(item,key) in data" :key="key">
<td>{{item.id}}</td>
<td>{{item.title}}</td>
<td>{{item.body}}</td>
</tr></table>`,
props: ['data'],
watch: {
data: function(newVal, oldVal) { // watch it
console.log('Prop value changed: ', newVal, ' | was: ', oldVal)
}
}
}
}
});
.lds-roller{width:64px;height:64px;background-color:#00000075;position:absolute;border-radius:50%;z-index:9999;display:none}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:32px 32px}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#fff;margin:-3px 0 0 -3px}.lds-roller div:nth-child(1){animation-delay:-36ms}.lds-roller div:nth-child(1):after{top:50px;left:50px}.lds-roller div:nth-child(2){animation-delay:-72ms}.lds-roller div:nth-child(2):after{top:54px;left:45px}.lds-roller div:nth-child(3){animation-delay:-108ms}.lds-roller div:nth-child(3):after{top:57px;left:39px}.lds-roller div:nth-child(4){animation-delay:-144ms}.lds-roller div:nth-child(4):after{top:58px;left:32px}.lds-roller div:nth-child(5){animation-delay:-.18s}.lds-roller div:nth-child(5):after{top:57px;left:25px}.lds-roller div:nth-child(6){animation-delay:-216ms}.lds-roller div:nth-child(6):after{top:54px;left:19px}.lds-roller div:nth-child(7){animation-delay:-252ms}.lds-roller div:nth-child(7):after{top:50px;left:14px}.lds-roller div:nth-child(8){animation-delay:-288ms}.lds-roller div:nth-child(8):after{top:45px;left:10px}#keyframes lds-roller{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.as-console-wrapper{display:none!important}.btn{font-weight:700;cursor:pointer}td{border:1px solid #ccc}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div class="lds-roller" stypl="display:none;"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
<button #click="onLoadDataClick" class="btn">Load data</button>
<br/> <br/>
<child :data="tableData"></child>
</div>

Update data after change without page refreshing using vue.js

I wrote such code:
<template>
<div class="home">
<HelloWorld tableTitle="Goods:">
<table class="table table-striped">
<tbody>
<tr v-for="(i, index) in items.data" :key="index">
<td>{{ i.id }}</td>
<td>{{ i.name }}</td>
<td>{{ i.producer }}</td>
<td><font-awesome-icon v-if="i.received" icon="check" /><font-awesome-icon v-else icon="times" /><td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import HelloWorld from "#/components/HelloWorld.vue";
import axios from "axios";
export default {
name: "home",
components: {
HelloWorld
},
data: () => ({
items: {},
errors: []
}),
beforeMount() {
axios
.get("http://40.115.119.247/AgileSelection/tnt/status")
.then(response => {
this.items = response.data;
console.log(this.items);
})
.catch(e => {
this.error.push(e);
});
}
};
<script>
Now, for refreshing information I just use the refresh button.
What and where should I add some lines of code to update information but without refreshing the page? Because, I am updating data every 5 seconds. So I think that manually updating is not so good idea.
do something like this
// data property
data () {
return {
...
interval: null
}
},
// in your created hook
created () {
this.interval = setInterval(this.refreshData, 5000)
},
beforeDestroy () {
clearInterval(this.interval)
},
// in methods property
methods: {
refreshData () {
// fetch data
axios.get("http://40.115.119.247/AgileSelection/tnt/status")
.then(response => {
this.items = response.data
})
}
}
this will fetch your data from your API and update the list automatically. this will update your UI as well.
you can try using location.reload() after the code where you register the update
for example
handleSubmit() {
this.registerServices(this.organization)
location.reload();
}

Vuetify Using datatable with external data from an API with Vuex

I want to use the vuetify framework with Vuex , but there is limited documentation about using it with Vuex.
I want to:
Get data from an external API ( but only the data needed )
Then Save the data in state and edit or whatever
Then push any changes back to the api
I have tried some of the external pagination and sorting examples with vuetify , but I can't get it to show all record count unless I hard code it.
I am quite new to Vue and Vuetify , so maybe I am misunderstanding something.
<template>
<div>
<v-data-table
:headers='headers'
:items='items'
:length='pages'
:search='search'
:pagination.sync='pagination'
:total-items='totalItemCount'
class='elevation-1'
>
<template slot='items' slot-scope='props'>
<td class='text-xs-right'>{{ props.item.id }}</td>
<td class='text-xs-right'>{{ props.item.first_name }}</td>
<td class='text-xs-right'>{{ props.item.last_name }}</td>
<td class='text-xs-right'>{{ props.item.avatar }}</td>
</template>
</v-data-table>
</div>
</template>
<script>
import moment from 'moment'
import axios from 'axios'
export default {
name: 'test-table',
watch: {
pagination: {
async handler () {
const rowsPerPage = this.pagination.rowsPerPage
// const skip = (this.pagination.page - 1) * rowsPerPage
const pageNumber = this.pagination.page
const res = await axios.get(`https://reqres.in/api/users?page=${pageNumber}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
deep: true
}
},
computed: {
pages () {
return 171
},
totalItemCount () {
return 400
}
},
async mounted () {
const rowsPerPage = this.pagination.rowsPerPage
const skip = (this.pagination.page - 1) * rowsPerPage
const res = await axios.get(`https://reqres.in/api/users?page=${skip}&per_page=${rowsPerPage}`)
this.items = res.data.data
this.$store.commit('saveTableData', this.items)
},
methods: {
nzDate: function (dt) {
return moment(dt).format('DD/MM/YYYY')
}
},
data: () => ({
search: '',
// totalItems: 0,
items: [],
pagination: {
sortBy: 'Date'
},
headers: [
{ text: 'ID', value: 'id' },
{ text: 'First Name', value: 'first_name' },
{ text: 'Last Name', value: 'last_name' },
{ text: 'Avatar', value: 'avatar' }
]
})
}
This is my working setup:
<template>
<v-data-table
:total-items="pagination.totalItems"
:pagination.sync="pagination"
:items="rows"
:headers="columns">
<template slot="headers" slot-scope="props">
<tr :active="props.selected">
<th v-for="column in props.headers">
{{ column.value }}
</th>
</tr>
</template>
<template slot="items" slot-scope="props">
<tr>
<td v-for="cell in props.item.row">
<v-edit-dialog lazy>
{{ cell.value }}
<v-text-field
:value="cell.value"
single-line
counter>
</v-text-field>
</v-edit-dialog>
</td>
</tr>
</template>
</v-data-table>
</template>
<script>
export default {
data: () => ({
pagination: {
page: 1,
rowsPerPage: 10,
totalItems: 0
},
selected: []
}),
computed: {
columns: {
get () {
return this.$store.state.columns
}
},
rows: {
get () {
return this.$store.state.rows
}
}
},
methods: {
async getRowsHandler () {
try {
const {total} = await this.$store.dispatch('getRows', {
tableIdentifier: this.$route.params.tableIdentifier,
page: this.pagination.page,
size: this.pagination.rowsPerPage
})
this.pagination.totalItems = total
} catch (error) {
// Error
}
}
}
}
</script>
I didn't implement everything. If you miss a specific part ask again and I will update my example. One more tip: You should avoid watch deep wherever possible. It can result in heavy calculations.
Assuming this is Vuetify v1.5, the documentation on the total-items prop on data-tables states:
Caution: Binding this to a blank string or using in conjunction with
search will yield unexpected behaviours
If you remove the 'search' prop from your table the record count will show again. If you're doing external stuff anyway, you'll won't want the default search functionality.