I trying to create component that render more elements while scroling a page. I use Quasar framework for vue. This is code needed to render a single block that contains a information from json file
https://quasar.dev/vue-components/infinite-scroll
Quasar has built in infinite-scroll component, but in these examples, they're adding only empty elements to render. In my case, I need to render elements in order from object. I tried already adding q-infinite-scroll component inside v-for but this in most cases created an infinity loop
Can someone explain to me how can I adapt this example to render more complex objects ?
<div v-for="(p,k) in returnProductsBySelectedType()" class='caption' :key="p.name">
<q-card class="my-card" flat bordered v-if="p.name !== undefined">
<q-item class="" >
<q-item-section >
<div class="text-body1 q-mt-sm q-mb-xs" >{{ p.name }}</div>
</q-item-section>
<q-btn flat class="bg-primary" style="width:50px; height: 50px;" color="white" :icon='includeProduct(p,k) ? "remove" : "add"' #click=' includeProduct(p) ? removeProducts(p) : addProductToList(p)' :key="k"></q-btn>
</q-item>
<q-separator />
<q-item-section class="q-pa-sm bg-grey-3">
<div class="text-subtitle2 flex ">Kalorie: (Kcal) Tłuszcze: (g) Weglowodany: (g) Bialko: (g)</div>
</q-item-section>
</q-card>
</div>
data(){
return{
products:json,
dropdownType:'zboża'
}
},
methods:{
returnProductsBySelectedType(){
let arr=[]
for(const {type,products} of this.products){
if(this.dropdownType === type ) return products
if(this.dropdownType === 'wszystko') arr.push(...products)
}
return arr
},
}
Json file
[
{
"type":"zboża",
"products":[
{
"id":1,
"name":"Bagietki francuskie"
},
{
"id":2,
"name":"Bułeczki do hot-dogów"
},
{
"id":3,
"name":"Bułka tarta"
},
{
"id":4,
"name":"Bułki grahamki"
},
{
"id":5,
"name":"Bułki i rogale maślane"
},
{
"id":6,
"name":"Bułki mieszane, z cebulą"
},
{
"id":7,
"name":"Bułki mleczne"
},
{
"id":8,
"name":"Bułki owsiane"
},
{
"id":9,
"name":"Bułki pszenne zwykłe"
},
{
"id":10,
"name":"Bułki pszenne zwykłe, z serwatką"
},
{
"id":11,
"name":"Bułki sojowe"
},
{
"id":12,
"name":"Bułki szwedki"
},
{
"id":13,
"name":"Bułki wrocławskie"
},
{
"id":14,
"name":"Chałki zdobne"
},
{
"id":15,
"name":"Chleb baltonowski"
},
{
"id":16,
"name":"Chleb beskidzki"
},
{
"id":17,
"name":"Chleb chrupki"
},
]
}
]
Check out the Infinite Scroll component APi: https://next.quasar.dev/vue-components/infinite-scroll#qinfinitescroll-api
You need to listen to the #load event which is fired when the component needs more data. There you can inject more data from the JSON file.
Related
I have a array with a nested parent-child structure. I need to recursively go through this array and find wanted element. I wrote this function:
component child, this component calls itself, and displays on behalf of each child:
List.vue
<template>
<div>
<div v-for="(item, index) in items" :key="index" >
<div v-if="item.data != null " >
<template v-if="Object.keys(item.data).length > 0">
<template v-for="(item, index) in item.data" >
<v-simple-table :key="index">
<thead>
<tr class="d-flex flex-row">
{{ '░'.repeat(count) }} {{count.length}} {{ item['name'] }}
</tr>
</thead>
<tbody>
</tbody>
</v-simple-table>
</template>
</template>
</div>
<div v-if="item.children != null " >
<template v-if="Object.keys(item.children).length > 0">
<template v-for="(itemD, indexD) in item.children" >
<List :key="indexD" name="List" :items="itemD" :count="count + 1" />
</template>
</template>
</div>
<div v-else></div>
</div>
</div>
</template>
<script>
export default {
name: 'List',
props: {
items: {
type: [Array, Object],
default: [],
},
count: {
type: [Number]
}
},
}
</script>
component parent, this component call component List:
<template>
<div>
<v-row >
<v-col>
<v-card>
<v-card-title>
Estruct
</v-card-title>
<v-row>
<List :items="desserts" :count="count + 1" />
</v-row>
</v-card>
</v-col>
</v-row>
</div>
</template>
<script>
import axios from "axios";
import List from "./List.vue";
export default {
components: { List },
data: (vm) => ({
desserts: [],
count: 0
}),
watch: {
config() {
this.getData();
},
},
methods: {
getData() {
let vm = this;
//
//
axios["get"](process.env.api_url + "/estruct", {
})
.then((response) => {
console.log("response", response.data);
vm.desserts = response.data;
})
.catch((error) => {
console.log(error);
});
},
}
</script>
struct array is like, I am looking at this structure from console.log()
[
{
"data":[{}],
"children":[]
},
{
"data":[
{
"id":64,
"name":"YSVERIS"
}
],
"children":[
{
"data":[
{
"id":85,
"name":"MERCEDES"
}
],
"children":[]
},
{}
]
},
{
"data":[
{
"id":9,
"name":"YUDITH"
}
],
"children":[]
},
{
"data":[
{
"id":10,
"name":"YERNY DEL SOCORRO"
}
],
"children":[]
},
{
"data":[
{
"id":11,
"name":"NIDIA"
}
],
"children":[
{
"data":[
{
"id":21,
"name":"VIVIKEY"
}
],
"children":[]
},
{
"data":[
{
"id":18,
"name":"RODIMIRA "
}
],
"children":[]
}
]
},
{
"data":[],
"children":[]
},
{
"data":[
{
"id":78,
"name":"LUCRECIA"
}
],
"children":[]
},
{
"data":[
{
"id":22,
"name":"DAXI XIOMARA"
}
],
"children":[]
}
]
currently the parent data is being displayed, but not the child data, it should work correct as far as I understand. what do you think??? ....... recursive function is a function that calls itself until it doesn’t. And this technique is called recursion. A recursive function always has a condition to stop calling itself. Otherwise, it will call itself indefinitely. so I don't understand what I'm doing wrong....
I tested in component List.vue, before recursive in <List key="indexD" name="List"....... I added this {{ items }}, so I could see the data object, something like: [ { "data": [ { "id": 85, "name": "MERCEDES"} ] }]
Here is the code in codesandbox code in codesanbox
Hello Community
I have a parent component that includes a child component, in which a form with different controls is dynamically rendered from a JSON object (obtained through a Get request from Axios).
My objective is to read / loop through all form fields values found in child component, from parent component. How would I do it with Vue JS?
Here is part of the code, if someone has proposals for improvements they will be well received. For example, better structure my code so that it is more organized and clean or use best programming practices with Vue. Thank you.
Parent Component
<template>
<div id="app">
<h1>{{msg}}</h1>
<b-container>
<b-card>
<b-card-title>Formulario Dinámico</b-card-title>
<b-card-body>
<FormControls :fields="fields"></FormControls>
</b-card-body>
<b-card-footer>
<button class="btn btn-primary" #click="send">Enviar</button>
</b-card-footer>
</b-card>
</b-container>
</div>
</template>
<script>
import FormControls from "./FormControls.vue";
import ComponentTest from "./ComponentTest.vue";
import axios from 'axios';
export default {
name: 'app',
components: {
FormControls,
ComponentTest
},
created() {
axios.get('./src/form.json').then(response => this.fields = response.data);
},
data () {
return {
msg: 'Bienvenido',
fields: [] // Array que almacenará el json proveniente de la petición get
}
},
methods: {
send: () => {
alert('Enviar Formulario');
}
}
}
</script>
JSON
[
{
"name": "fechaRegistro",
"label": "Fecha de Registro",
"type": "date",
"placeholder": "Ingresa Fecha"
},
{
"name": "nombreDeUsuario",
"label": "Nombre de Usuario",
"type": "text",
"placeholder": "Ingresa Usuario"
},
{
"name": "passwordUsuario",
"label": "Password",
"type": "password",
"placeholder": "Contraseña"
},
{
"name": "adjuntarArchivo",
"label": "Adjuntar",
"type": "file"
},
{
"name": "roles",
"label": "Roles",
"type": "select",
"sortedByKey": false,
"options": [{
"name": "admin",
"label": "Administrador"
},
{
"name": "user",
"label": "Usuario"
},
{
"name": "guest",
"label": "Invitado"
}
]
},
{
"name": "description",
"label": "Descripción",
"type": "textarea"
},
{
"name": "multiSelect",
"label": "Selección Multiple",
"type": "multiselect",
"options": [{
"name": "op1",
"label": "Opcion1"
},
{
"name": "op2",
"label": "Opcion2"
},
{
"name": "op3",
"label": "Opcion3"
}
]
}
]
Child Component
<template>
<div>
<form>
<ul>
<li v-for="field in fields" :key="field">
<label :for="field.name">{{field.label}}</label>
<input v-if="isInput(field.type)"
:id="field.name" :type="field.type" :placeholder="field.placeholder" >
<select v-else-if="field.type === 'select'" :name="field.name">
<option v-for="opt in field.options" :key="opt" :value="opt.name">
{{opt.label}}
</option>
</select>
<textarea v-else-if="field.type === 'textarea'" :id="field.name" />
<div v-else-if="field.type === 'multiselect'" class="multi-select">
<multiselect v-model="values" tag-placeholder="Agregar etiqueta" :placeholder="field.placeholder" label="label" track-by="name" :options="field.options" :multiple="true" :taggable="true" #tag="agregarEtiqueta"></multiselect>
</div>
</li>
</ul>
</form>
</div>
</template>
<script>
export default {
props: ['fields'],
name: 'FormControls',
data () {
return {
titulo: 'Formulario Dinámico',
// Aqui va lo del MultiSelect
values: [],
options: []
}
},
methods: {
isInput(type) {
return ['text', 'password', 'checkbox', 'file', 'date'].includes(type);
},
// metodo multiselect
agregarEtiqueta (newTag) {
const tag = {
name: newTag,
label: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.options.push(tag)
this.value.push(tag)
},
devolverControl(){
}
}
}
</script>
You shouldn't be meddling with children's data from the parent. That would be a tight coupling. Try the "dumb component" style, where your inputs are only display components & accept props & emit data to the parent. This way you can validate the data in the parent, in any way you want.
A bit more complex solution would be to create the error-handlers inside your "dumb components", and only emit back "error" if they detect errors in themselves. Another style could be to create the validation functions in your fields dataset & just pass down the functions themselves (more of a React than a Vue approach - totally working & valid JS, just not really Vue-style).
In the snippet below I just added value & error to the field items, so they are fully present in the parent component.
Vue.component("InputPassword", {
props: ['field'],
computed: {
value: {
get() {
return this.field.value
},
set(value) {
this.$emit("update:value", {
value
})
}
},
},
template: `
<div>
<label>{{ field.name }}: <input type="password" v-model="value" /></label>
<span
v-if="field.error"
>
THIS FIELD HAS ERRORS
</span>
</div>
`
})
Vue.component("InputText", {
props: ['field'],
computed: {
value: {
get() {
return this.field.value
},
set(value) {
this.$emit("update:value", {
value
})
}
},
},
template: `
<div>
<label>{{ field.name }}: <input type="text" v-model="value" /></label>
<span
v-if="field.error"
>
THIS FIELD HAS ERRORS
</span>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
fields: [{
"name": "nombreDeUsuario",
"label": "Nombre de Usuario",
"type": "text",
"placeholder": "Ingresa Usuario",
"value": null,
"error": null,
},
{
"name": "passwordUsuario",
"label": "Password",
"type": "password",
"placeholder": "Contraseña",
"value": null,
"error": null,
},
]
}
},
computed: {
isFormValid() {
return this.fields.every(({
error
}) => error == false) ? "YES" : "NO"
}
},
methods: {
handleValidateForm() {
this.fields = this.fields.map(field => {
return {
...field,
error: !(!!field.value) // valid if not empty
}
})
},
},
template: `
<div>
<form>
<component
v-for="field in fields"
v-bind:is="'input-' + field.type"
v-bind="{
field
}"
v-on="{
'update:value': ({ value }) => {
field.value = value
},
}"
></component>
</form>
<div
v-for="(field, i) in fields"
:key="'display-field-' + i"
>
field name: {{ field.name }}<br />
field value: {{ field.value }}
</div>
<hr />
<button
#click="handleValidateForm"
>
VALIDATE
</button>
IS FORM VALID? {{ isFormValid }}
</div>
`
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<div id="app"></div>
I am creating a product web-app by using vue-2.6.11, axios-0.21.1, vuetify-2.4.3
I am fetching categories from local array then I am passing fetchUrl as Props it into Row component by using v-for . Then in Row component i am fetching the fetchUrl by using axios after getting API response I'm simply mounting it. It working fine but the problem is categories object means Row component loads in random order cause the Row component mounted as it got axios response from API.
So I want Next row await till upper fully-mounted or any thing else to make it orderly loaded.
My Components :
Home.vue -
<template>
<div>
<div v-for="(categories,index) in categories" :key="`${index}`">
<ItemsCarousel
:title="categories.title"
:fetch-url="categories.fetchUrl"
/>
</div>
</div>
</template>
<script>
import categoriesList from '#/local-database/Categories.json';
import ItemsCarousel from '#/components/carousel/ItemsCarousel';
export default {
name: 'Home',
components: {
ItemsCarousel
},
data: () => ({
categories: categoriesList.filter( categories => (catalogue.for==true || categories.for=="home"))
})
}
</script>
ItemsCarousel.vue -
<template>
<div class="items-carousel">
<v-lazy v-model="isActive" :options="{threshold: 0.5}">
<h1>{{title}}</h1>
<div class="items-carousel" v-for="product in products" :key="product.id">
<Card v-bind="{...product}">/>
</div>
</v-lazy>
</div>
</template>
<script>
import ProductManger from '#/mixins/ProductManger';
import Card from '#/components/Card';
export default {
name: 'ItemsCarousel',
mixins: [ProductManger], // Axios Setup
components: {
Card
},
props: ['title','params'],
data: () => ({
isActive: false,
cards: []
}),
methods: {
async loadCard() {
this.contentMangerCore(this.params) // Function code inside mixins
.then(res => {
this.cards = res.data;
})
}
},
mounted() {
this.loadCard();
}
};
</script>
DataSample :-
categoriesList.json-
[{
"id": 1,
"name": "Adventure",
"params": {
"categories": "Adventure",
"sort": "ASC"
}
}, {
"id": 2,
"name": "Art",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 3,
"name": "Beauty",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 4,
"name": "Business",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 5,
"name": "Craft",
"params": {
"categories": "Art",
"sort": "DESC"
}
},...]
products.json-
[{
"name": "AdventureIRC",
"img": "..."
},
{
"name": "Adventie",
"img": "..."
},...]
I Hope you guys will help me to resolve this...
Thank You :smile:
You could make a computed method that determines how many categories to actually display at any given time, incremented by successful axios requests.
get categoriesForDisplay() {
return this.categories.slice(0, this.successfulCarouselFetches + 1)
}
Then define successfulCarouselFetches :
data() {
return {
//
successfulCarouselFetches : 0
}
}
listen for successful axios requests in your Item-Carousel component:
<ItemsCarousel
:title="categories.title"
:fetch-url="categories.fetchUrl"
#success="successfulCarouselFetches = successfulCarouselFetches + 1"
/>
Now broadcast the success whenever your xhr is done working:
methods: {
async loadCard() {
this.contentMangerCore(this.params) // Function code inside mixins
.then(res => {
this.cards = res.data;
this.$emit('success');
})
}
},
When the page loads you'll have a single Item-Carousel component on the page which will perform the first XHR request. When the component $emit's the event, the parent component containing the v-for will increment the successfulCarouselFetches which will allow the getter categoriesForDisplay to add another Item-Carousel within the v-for.
This essentially performs what you're asking for, I believe.
I have this array:
data() {
return {
landingInfo: null,
slides: [
{
title: `this`,
},
{
title: "that",
},
{
title: "those",
},
],
};
},
Which is being displayed this way:
<div
class="slide"
v-for="(slide, index) in slides"
:key="index",
}"
>
<div>
<h1 class="text-bold">{{ slide.title }}</h1>
</div>
The problem is that I'm fetching info from and api and once I try to do:
slides: [
{
title: {{ landingInfo.data.subtitle }},
},
{
title: {{ landingInfo.data.subtitle2 }},
},
{
title: {{ landingInfo.data.subtitle3 }},
},
],
Everything explodes, I am new in vue using Nuxt.js and I cannot find any solution in how to achieve that.
Can someone show me how to include the fetched info inside the array property?
PD: I already tried using "{{thefetchedinfo}}" but it takes it literally that way and displays "{{thefetchedinfo}}"
The OP doesn't provide much info on the fetch, like when it is performed or what the returned data looks like, but the common pattern goes like this...
// <template> as in the OP, then...
data() {
return {
landingInfo: null,
slides: [], // empty before the fetch
};
},
mounted () {
fetchFromTheAPI().then(landingInfo => {
// More commonly, you'd map over the returned data to form slides,
// or, trying to match the OP...
this.slides = [
{ title: landingInfo.data.subtitle },
{ title: landingInfo.data.subtitle2 }, // ... and so on
]
});
},
i'm setting up vuetify's treeview component for my project.
i clicked treeview's folder objects, every objects opened.
but i did not set 'open-all' option.
my project is based vue cli 3 and my ESLint configs is 'airbnb'.
TEMPLATE :
<v-card class="mx-auto">
<v-sheet class="pa-1 tree__resources lighten-2">
<v-treeview
:active.sync="active"
:open.sync="open"
:items="items"
item-key="id"
activatable
active-class="primary--text"
open-on-click
return-object>
<template v-slot:prepend="{ item, open }">
<v-icon v-if="item.type == 'directory'">
{{ open ? 'mdi-folder-open' : 'mdi-folder' }}
</v-icon>
<v-icon v-else>
{{ files[item.type] }}
</v-icon>
</template>
</v-treeview>
</v-sheet>
</v-card>
Scripts :
export default {
data: () => ({
active: [],
open: [],
items: ["JSON DATA"],
files: {
html: 'mdi-language-html5',
js: 'mdi-nodejs',
json: 'mdi-json',
md: 'mdi-markdown',
pdf: 'mdi-file-pdf',
png: 'mdi-file-image',
txt: 'mdi-file-document-outline',
jpg: 'mdi-file-image',
jpeg: 'mdi-file-image',
gif: 'mdi-file-image',
arsc: 'mdi-file-document',
RSA: 'mdi-key-variant',
SF: 'mdi-file-document',
MF: 'mdi-file-document',
xml: 'mdi-file-xml',
dex: 'mdi-android',
java: 'mdi-code-braces',
kt: 'mdi-android',
css: 'mdi-language-css3',
android: 'mdi-android',
properties: 'mdi-file-document',
version: 'mdi-file-document',
so: 'mdi-file-document',
provider: 'mdi-file-document',
providers: 'mdi-file-document',
},
filename: '',
filepath: '',
filetype: '',
},
}),
computed: {
selected() {
if( !this.active.length ) return undefined;
const selected = this.active[0];
console.log(selected);
},
},
methods: {
.
.
.
JSON DATA SAMPLE :
[
{
"name":"sources",
"type":"directory",
"children":[
{
"name":"android",
"type":"directory",
"children":[
{
"name":"support",
"type":"directory",
"children":[
{
"name":"fragment",
"type":"directory",
"children":[
{
"name":"R.java",
"id":662,
"type":"java",
"path":"sources/android/support/fragment/R.java"
},
{
"name":"BuildConfig.java",
"id":663,
"type":"java",
"path":"sources/android/support/fragment/BuildConfig.java"
}
]
},
{
"name":"mediacompat",
"type":"directory",
"children":[
{
"name":"R.java",
"id":664,
"type":"java",
"path":"sources/android/support/mediacompat/R.java"
},
{
"name":"BuildConfig.java",
"id":665,
"type":"java",
"path":"sources/android/support/mediacompat/BuildConfig.java"
}
]
}
]
}
]
},
{
"name":"ntouch",
"type":"directory",
"children":[
{
"name":"ntouch_apk_01",
"type":"directory",
"children":[
{
"name":"R.java",
"id":666,
"type":"java",
"path":"sources/ntouch/ntouch_apk_01/R.java"
},
{
"name":"BuildConfig.java",
"id":667,
"type":"java",
"path":"sources/ntouch/ntouch_apk_01/BuildConfig.java"
},
{
"name":"Ntouch_apk_01.java",
"id":668,
"type":"java",
"path":"sources/ntouch/ntouch_apk_01/Ntouch_apk_01.java"
},
{
"name":"SendDateToServer.java",
"id":669,
"type":"java",
"path":"sources/ntouch/ntouch_apk_01/SendDateToServer.java"
}
]
}
]
}
]
},
{
"name":"resources",
"type":"directory",
"children":[
{
"name":"META-INF",
"type":"directory",
"children":[
{
"name":"MANIFEST.MF",
"id":670,
"type":"MF",
"path":"resources/META-INF/MANIFEST.MF"
},
{
"name":"CERT.RSA",
"id":671,
"type":"RSA",
"path":"resources/META-INF/CERT.RSA"
},
{
"name":"CERT.SF",
"id":672,
"type":"SF",
"path":"resources/META-INF/CERT.SF"
}
]
},
{
"name":"classes.dex",
"id":673,
"type":"dex",
"path":"resources/classes.dex"
},
{
"name":"AndroidManifest.xml",
"id":674,
"type":"xml",
"path":"resources/AndroidManifest.xml"
},
]
}
]
when i clicked "resources" folder object, expect the output this:
https://imgur.com/OjU4hjx
but the actual output this: EVERYTHING OPEN IMMEDIATELY
https://imgur.com/7ytQ3mX
Your item-key is id, which is undefined in some node. You should have id for each node includes parent node, something like:
[
{
"name":"sources",
"id": 1,
"type":"directory",
"children":[
{
"name":"android",
"id": 2
"type":"directory",
"children":[]
}
....
}
]
or change item-key to name. I expected name is unique in your structure (which might not be possible)
<v-treeview
:active.sync="active"
:open.sync="open"
:items="items"
item-key="name"
activatable
active-class="primary--text"
open-on-click
return-object>
<template v-slot:prepend="{ item, open }">
<v-icon v-if="item.type == 'directory'">
{{ open ? 'mdi-folder-open' : 'mdi-folder' }}
</v-icon>
<v-icon v-else>
{{ files[item.type] }}
</v-icon>
</template>
</v-treeview>