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.
Related
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.
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>
I am using vue.js and axios within visualstudiocode I'm very new at coding in principal and so I'm sorry if my question is worded poorly. I have two arrays I have assembled from this API:
'https://docs.openaq.org/#api-_'
One is a list of countries, with data of how many cities is in the region etc, and another is a list of cities within those countries with corresponding data. I display a list of countries, in a table followed by a corresponding button in the same row using a v-for loop.
What I want to happen, is when this button is clicked for each corresponding country in the array, all the cities within that country will be displayed. I've been told this needs seperate v-for loops, but I don't know how I'd write it out.
<template>
<div>
<table>
<tr>
<th>Countries</th>
<th>Cities</th>
</tr>
<tr>
<td>
<ul>
<li v-for="countries in listofcountries" :key="countries.name">
{{countries.name}}
</li>
</ul>
</td>
<ul v-for="countries in listofcountries" :key="countries.name">
<li v-for="cities in lifeofcities" :key="cities.city"></li>
<button>{{countries.name}}</button>
</li>
</ul>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
listofcountries: [],
listofcities: [],
}
},
mounted () {
const axios = require('axios');
var self = this;
// Make a request for a user with a given ID
axios.get('https://api.openaq.org/v1/cities')
.then(function (response) {
// handle success
console.log(response.data.results);
self.listofcities=response.data.results;
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
axios.get('https://api.openaq.org/v1/countries')
.then(function (response) {
// handle success
console.log(response.data.results) ;
self.listofcountries=response.data.results;
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
},
methods: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
ul {
list-style-type: none;
}
table,th,td,tr {
border: 1px solid black;
}
</style>
I'm not completely certain this output is what you want but it's a place to start:
<template>
<table>
<tr>
<th>Countries</th>
<th>Cities</th>
</tr>
<tr v-for="country in countries" :key="country.key">
<td>{{ country.name }}</td>
<td v-if="areCitiesVisible(country.code)">{{ citiesInCountry(country.code) }}</td>
<td v-else>
<button #click="onClickCountry(country.code)">show cities</button>
</td>
</tr>
</table>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
cities: [],
countries: [],
countriesWithVisibleCities: [],
};
},
mounted() {
axios
.get('https://api.openaq.org/v1/cities')
.then(response => (this.cities = response.data.results))
.catch(console.log);
axios
.get('https://api.openaq.org/v1/countries')
.then(response => (this.countries = response.data.results))
.catch(console.log);
},
methods: {
citiesInCountry(code) {
return this.cities
.filter(c => c.country === code)
.map(c => c.city)
.join(', ');
},
onClickCountry(code) {
this.countriesWithVisibleCities.push(code);
},
areCitiesVisible(code) {
return this.countriesWithVisibleCities.includes(code);
},
},
};
</script>
It renders a table with one row per country. Each row has a name and a button. If you press the button, you'll get a list of cities instead of the button. Note that the API call seems to return only the cities for the first few counties in the list.
Other notes:
Your original post had several typos that could have been caught with eslint. I recommend installing it along with prettier. I promise that will save you a lot of time in the future.
I simplified the axios calls - self isn't necessary here.
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.
I am having a problem updating my shown class when the data changes.
I have a servers array that calls to get the server status every 10 seconds. If the data changes, the data changes, but the class doesn't
The part that isn't changing is showing the font-awesome icon based on the status
'fas fa-exclamation-triangle critical' : 'fas fa-check ok'">
The text does change {{server.status}} just not the font-awesome class in the if statement.
Any ideas on what I need to change to get it to show correctly?
<tr v-for="server in servers">
<td>
{{server.name}}
<a v-bind:href="server.url" target="_blank">{{server.url}}</a>
</td>
<td style="min-width: 125px">
<i :class="server.status === 'CRITICAL' ? 'fas fa-exclamation-triangle critical' : 'fas fa-check ok'"></i>
{{server.status}}
</td>
<td>{{server.revision}}</td>
<td>{{server.notify}}</td>
<td>{{server.count}}</td>
</tr>
<script>
import axios from 'axios'
export default {
name: 'ServerMonitor',
data() {
return {
servers: []
}
},
created() {
this.fetchData();
},
mounted: function () {
setInterval(function () {
this.fetchData();
}.bind(this), 10000)
},
methods: {
fetchData() {
axios.get('https://SERVER/serverinfo')
.then((resp) => {
this.servers = resp.data[0].servers;
})
.catch((err) => {
console.log(err);
})
}
}
}
</script>
Also I have tried it without the :class like this:
<i v-if="server.status === 'CRITICAL'" class="fas fa-exclamation-triangle critical"></i>
<i v-if="server.status === 'OK'" class="fas fa-check ok"></i>
Vue's v-bind:class takes an object or an Array and not a string, which is probably your issue.
<td style="min-width: 125px">
<i :class="['fas', server.status === 'CRITICAL' ? 'fa-exclamation-triangle critical' : 'fa-check ok']"></i>
{{server.status}}
</td>
Updating my answer based on comments below:
You need to use the font-awesome Vue component. What's happening is that FontAwesome is converting the <i> icons to SVG once, and doesn't rerender them at any future point.
Edit 2
Alternatively you can use the v4 upgrade shim:
<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/v4-shims.js"></script>
https://jsfiddle.net/6tfqp4nb/12/
If you are using font-awesome in js way, you can try this:
FontAwesomeConfig = { autoReplaceSvg: 'nest' }
doc: https://fontawesome.com/how-to-use/svg-with-js#auto-replace-svg-nest