Is possible to generate vue component using for loop inside function? - vue.js

Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated.
https://jsfiddle.net/3ordn7sj/5/
https://jsfiddle.net/bt5dhqtf/973/
for (var key in app.html) {
Vue.component(key, {
template: `<div><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
}
Is possible to generate vue component using for loop. I am trying to generate and able to get but it's override by new component dynamically it's override component one schema also with component second with is at last generated. In above jsfiddel link my data is there inside created.
I am trying to generate vue component base on this data and I am using vue form generator.In above code what exactly I am trying to do is while my loop running form generated but I don't know how it's first component also getting second component schema and it;s showing on first step also overrides schema data.
I am very confused why this is happening I tried a lot but I am not getting any solution if you have please suggest what I can do for generate component using for loop inside function.
Please try to solve this issue or tell me id it;s not possible.
I did like this
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
Inside Data I have added for now this tabs option
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
generateNewForm.vue
<template>
<div class="app animated fadeIn">
<loading :active.sync="this.$store.state.isLoading"
:can-cancel="true"
:is-full-page="this.$store.state.fullPage"></loading>
<b-row>
<b-col cols="12" xl="12">
<transition name="slide">
<b-card>
<div slot="header">
<b-button variant="primary" #click="goBack"><i class="icon-arrow-left icons font-1xl"></i>Back</b-button>
</div>
<formcomponent :tree="this.$store.state.formData" />
</b-card>
</transition>
</b-col>
</b-row>
</div>
</template>
<script>
import {store} from '#/components/store'
import formcomponent from '#/components/formcomponent';
import Vue from 'vue'
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/vue-loading.css';
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
/*import VeeValidate from 'vee-validate';*/
Vue.use(VueFormGenerator);
Vue.use(Loading);
export default {
name: 'tables',
store: store,
data: () => {
return {
finalModel: {},
activeTabIndex: 0,
model: {},
count: 0,
}
},
components: {
'loading': Loading,
FormWizard,
TabContent,
formcomponent: formcomponent
},
created() {
},
beforeMount() {
this.$store.dispatch('loadFormData', this.$route.params.id);
},
methods: {
onComplete: function(){
alert('Yay. Done!');
},
goBack() {
this.$router.go(-1)
}
}
}
</script>
formcomponent.vue
<template>
<div>
<form-wizard #on-complete="onComplete"
#on-change="handleChange"
validate-on-back
ref="wizard"
:start-index.sync="activeTabIndex"
shape="circle" color="#20a0ff" error-color="#ff4949" v-if="html != 0">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</template>
<script>
import Vue from 'vue'
import {FormWizard, TabContent} from 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import VueFormGenerator from "vue-form-generator";
//console.log(Vue.options);
Vue.use(VueFormGenerator);
export default {
components: {
FormWizard,
TabContent,
},
data() {
return {
loadingWizard: false,
error: null,
count: 0,
dash: '-',
firstTime: 0,
model: {},
html: '',
index: '',
activeTabIndex: 0,
tabs: [{title: 'Personal details', icon: 'ti-user', component: 'firstTabSchema'},
{title: 'Is Logged In?', icon: 'ti-settings', component: 'secondTabSchema', hide: false},
],
formOptions: {
validationErrorClass: "has-error",
validationSuccessClass: "has-success",
validateAfterLoad: true,
validateAfterChanged: true,
},
}
},
created() {
this.html = this.tree;
this.index = this.ind;
},
props: ['tree', 'ind'],
methods: {
onComplete: function () {
alert('Yay. Done!');
},
setLoading(value) {
this.loadingWizard = value
},
handleChange(prevIndex, nextIndex) {
console.log(`Changing from ${prevIndex} to ${nextIndex}`)
},
setError(error) {
this.error = error
},
validateFunction: function () {
return new Promise((resolve, reject) => {
console.log(this.$refs.firstTabSchema);
setTimeout(() => {
if (this.count % 2 === 0) {
reject('Some custom error')
} else {
resolve(true)
}
this.count++
}, 100)
})
},
validate() {
return true
},
buildTree(tree, rep = 1) {
var html = '';
var app = this;
var dash = "--";
app.html = tree;
var test = this.formOptions;
for (var key in app.html) {
var isComponentExists = key in Vue.options.components
if(!isComponentExists) {
Vue.component(key, {
template: `<div :class="key"><vue-form-generator :model="model"
:schema="schema"
:options="formOptions"
ref="key"></vue-form-generator>{{ key }}</div>`,
mounted() {
this.schema = app.html[key]
this.key = key
},
data: function () {
return {
schema: app.html[key],
key: '',
formOptions: this.formOptions,
model: this.model,
}
},
}
)
//console.log(Vue.$options);
this.$emit('init')
}
}
}
},
watch: {
tree: {
handler() {
this.html = '';
this.buildTree(this.tree)
},
deep: true
}
}
}
</script>

So if I understand you correctly you are trying to use a list of some kind app.html to dynamically register a set of identical components under different names (key). I think it is possible, but i cannot tell from the code you provided what is going wrong.
I can tell you that this approach to code reuse/abstraction is probably not the right way to go. The whole point of components is that you can reuse functionality with the use of binding props. What you are trying to do is probably better achieved like this:
Vue.component('my-custom-form', {
props: ['key', 'schema', 'formOptions', 'model'],
template: `
<div>
<vue-form-generator
:model="model"
:schema="schema"
:options="formOptions"
:ref="key"
></vue-form-generator>{{ key }}
</div>`,
})
Then in your vue template:
<my-custom-form
v-for="(key, value) in app.html"
:key="key"
:schema="value"
:formOptions="formOptions"
:model="model"
/>
Let me know if that helps. Otherwise, if you are sure you want to stick with your original approach give me some more context for the code and I will see what i can do. Best of luck!

I think i understand a little bit better where you are getting stuck. I see this piece of code in your jsfiddle:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="tab in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<component :is="tab.component"></component>
</tab-content>
</form-wizard>
</div>
</div>
You don't need to use the component :is syntax to solve this problem. You could also write is as follows:
<div id="app">
<div>
<form-wizard #on-complete="onComplete">
<tab-content v-for="(tab, tabindex) in tabs"
v-if="!tab.hide"
:key="tab.title"
:title="tab.title"
:icon="tab.icon">
<my-custom-form v-if="tabindex == 1" :key="'the first key'" :schema="app.html['the first key']"/>
<my-custom-form v-else-if="tabindex == 2" :key="'the second key'" :schema="app.html['the second key']"/>
</tab-content>
</form-wizard>
</div>
</div>
etc. Let me know if that example is clear.
best

Related

How do have unique variables for each dynamically created buttons/text fields?

I'm trying to create buttons and vue element inputs for each item on the page. I'm iterating through the items and rendering them with v-for and so I decided to expand on that and do it for both the rest as well. The problem i'm having is that I need to to bind textInput as well as displayTextbox to each one and i'm not sure how to achieve that.
currently all the input text in the el-inputs are bound to the same variable, and clicking to display the inputs will display them all at once.
<template>
<div class="container">
<div v-for="(item, index) in items" :key="index">
<icon #click="showTextbox"/>
<el-input v-if="displayTextbox" v-model="textInput" />
<el-button v-if="displayTextbox" type="primary" #click="confirm" />
<ItemDisplay :data-id="item.id" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
displayTextbox = false,
textInput = '',
},
methods: {
confirm() {
// todo send request here
this.displayTextbox = false;
},
showTextbox() {
this.displayTextbox = true;
}
}
}
</script>
EDIT: with the help of #kissu here's the updated and working version
<template>
<div class="container">
<div v-for="(item, index) in itemDataList" :key="itemDataList.id">
<icon #click="showTextbox(item.id)"/>
<El-Input v-if="item.displayTextbox" v-model="item.textInput" />
<El-Button v-if="item.displayTextbox" type="primary" #click="confirm(item.id)" />
<ItemDisplay :data-id="item.item.uuid" />
</div>
</div>
</template>
<script>
import ItemDisplay from '#/components/ItemDisplay';
export default {
name: 'ItemList',
components: {
ItemDisplay,
},
props: {
items: {
type: Array,
required: true,
},
}
data() {
itemDataList = [],
},
methods: {
confirm(id) {
const selected = this.itemDataList.find(
(item) => item.id === id,
)
selected.displayTextbox = false;
console.log(selected.textInput);
// todo send request here
},
showTextbox(id) {
this.itemDataList.find(
(item) => item.id === id,
).displayTextbox = true;
},
populateItemData() {
this.items.forEach((item, index) => {
this.itemDataList.push({
id: item.uuid + index,
displayTextbox: false,
textInput: '',
item: item,
});
});
}
},
created() {
// items prop is obtained from parent component vuex
// generate itemDataList before DOM is rendered so we can render it correctly
this.populateItemData();
},
}
</script>
[assuming you're using Vue2]
If you want to interact with multiple displayTextbox + textInput state, you will need to have an array of objects with a specific key tied to each one of them like in this example.
As of right now, you do have only 1 state for them all, meaning that as you can see: you can toggle it for all or none only.
You'll need to refactor it with an object as in my above example to allow a case-per-case iteration on each state individually.
PS: :key="index" is not a valid solution, you should never use the index of a v-for as explained here.
PS2: please follow the conventions in terms of component naming in your template.
Also, I'm not sure how deep you were planning to go with your components since we don't know the internals of <ItemDisplay :data-id="item.id" />.
But if you also want to manage the labels for each of your inputs, you can do that with nanoid, that way you will be able to have unique UUIDs for each one of your inputs, quite useful.
Use an array to store the values, like this:
<template>
<div v-for="(item, index) in items" :key="index">
<el-input v-model="textInputs[index]" />
</div>
<template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
data() {
textInputs: []
}
}
</script>

vuejs2 vee-validate 3 dynamic radio

I am building a Form Wizard with multiple questions, each question has multiple radio options, but i not able to validate each question, always receive and error, heres is the source code
In the variable poll are stored the questions and each one has the possible answers
The Form Wizard hasta a multiple Tabs, each one with a question and each one with a radio button for each answer
I have tried with one question in order to avoid the possible duplicated data, but i was no be able to validate correctly using the vee-validate
I am using VueJS 2.6.x and vee-validate 3.4.14 more specifically using the Vuexy Template and JavaScript not TypeScript
<template>
<div v-if="poll">
<form-wizard
color="#7367F0"
:title="null"
:subtitle="null"
layout="horizontal"
shape="square"
finish-button-text="Submit"
back-button-text="Previous"
class="wizard-horizontal mb-3"
#on-complete="formSubmitted"
>
<!-- Question/Answers details tab -->
<template v-for="question in poll.questions">
<tab-content
:key="question.id"
:before-change="validationForm"
>
<validation-observer
ref="questionRules"
tag="form"
>
<b-card-text>
<h4>
{{ question.question }}
</h4>
</b-card-text>
<validation-provider
#default="{ errors }"
:name="'question-' + question.id"
rules="required|numeric"
>
<b-form-radio
v-for="answer in question.answers"
:id="'question-' + question.id + '-answer-' + answer.id"
:key="answer.id"
v-model="Selected"
:value="answer.id"
:name="'question-' + question.id"
class="mt-2 mb-25"
>
{{ answer.answer }}
</b-form-radio>
<div class="mt-2">
<small class="text-danger">{{ errors[0] }}</small>
</div>
</validation-provider>
</validation-observer>
</tab-content>
</template>
</form-wizard>
</div>
</template>
<script>
import { FormWizard, TabContent } from 'vue-form-wizard'
import { ValidationObserver, ValidationProvider, extend } from 'vee-validate'
import ToastificationContent from '#core/components/toastification/ToastificationContent.vue'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
import { required, numeric } from 'vee-validate/dist/rules'
import {
BFormRadio,
BCardText,
} from 'bootstrap-vue'
import index from 'vue-prism-component'
import { title } from '#core/utils/filter'
import { codeIcon } from './code'
extend('required', required)
extend('numeric', numeric)
export default {
components: {
ValidationObserver,
FormWizard,
TabContent,
BFormRadio,
BCardText,
ValidationProvider,
},
props: {
// eslint-disable-next-line vue/require-default-prop
poll: null,
},
data() {
return {
required,
numeric,
codeIcon,
Selected: null,
}
},
methods: {
formSubmitted() {
this.$toast({
component: ToastificationContent,
props: {
title: 'Form Submitted',
icon: 'EditIcon',
variant: 'success',
},
})
},
validationForm() {
return new Promise((resolve, reject) => {
this.$refs.questionRules.validate().then(success => {
if (success) {
resolve(true)
} else {
reject()
}
})
})
},
},
}
</script>
The view is loaded correctly, at least as is expected
Exist a walkaround in case not be possible as i implemented?
Thanks in advance
Here is the solution
Is necessary use the [0] index, thanks a lot!
validationForm() {
return new Promise((resolve, reject) => {
this.$refs.questionRules[0].validate().then(success => {
if (success) {
resolve(true)
} else {
reject()
}
})
})
},

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

How to share ajax results between two components with the first one using the second?

First of all, I'm a beginner with VueJS, so I may presenting you a bunch of non-sens. :-) I read all the beginner doc, but I'm still stuck for this case.
I have 2 template component managed by a functionnal component:
<template>
<h2>PageSpeed performance score: {{ results.score }}.</h2>
</template>
The second one, using the first one (the first one is needed to be used elsewhere to display score only:
<template>
<div>
<template v-if="results">
<hosting-performance-score :results="results"/>
<div
v-for="(result, rule) in results.rules"
v-if="result.ruleImpact > 0"
:key="rule"
class="panel panel-default"
>
<div class="panel-heading">{{ result.localizedRuleName }}</div>
<div class="panel-body">
<p>
{{ result.summary.format }}
<b>{{ result.ruleImpact }}</b>
</p>
</div>
</div>
</template>
<i
v-else
class="fa fa-spin fa-spinner"
/>
</div>
</template>
<script>
import HostingPerformanceScore from './HostingPerformanceScore';
export default {
components: {
HostingPerformanceScore,
},
};
</script>
And then, the functional one with the AJAX logic:
<script>
import axios from 'axios';
import axiosRetry from 'axios-retry';
import HostingPerformanceScore from './HostingPerformanceScore';
import HostingPerformancePage from './HostingPerformancePage';
axiosRetry(axios);
export default {
functional: true,
props: {
scoreOnly: {
default: false,
type: Boolean,
},
slug: {
required: true,
type: String,
},
},
data: () => ({
results: null,
}),
created() {
axios.get(Routing.generate('hosting_performance_pagespeed', {
slug: this.slug,
})).then((response) => {
this.results = {
rules: Object.entries(response.data.formattedResults.ruleResults).map((entry) => {
const result = entry[1];
result.ruleName = entry[0];
return result;
}).sort((result1, result2) => result1.ruleImpact < result2.ruleImpact),
score: response.data.ruleGroups.SPEED.score,
};
});
},
render: (createElement, context) => {
return createElement(
context.props.scoreOnly ? HostingPerformanceScore : HostingPerformancePage,
context.data,
context.children
);
},
};
</script>
The issue is: I can't access the result and I don't know how to pass it properly: Property or method "results" is not defined on the instance but referenced during render.
Or maybe functional components are not designed for this, but I don't know how to achieve it otherway. How would you do it?
Thanks! :-)
You appear to have this a little backwards in terms of which components can be functional and which not.
Since your HostingPerformanceScore and HostingPerformancePage components are really only rendering data, they can be functional components by just rendering props they accept.
Your other component has to maintain state, and so it cannot be a functional component.
I put together an example of how this might work.
HostingPerformanceScore
<template functional>
<h2 v-if="props.results">
PageSpeed performance score: {{ props.results.score }}.
</h2>
</template>
HostingPerformancePage
<template functional>
<div>
<h2>Hosting Performance Page</h2>
<HostingPerformanceScore :results="props.results"/>
</div>
</template>
<script>
import HostingPerformanceScore from "./HostingPerformanceScore.vue";
export default {
components: {
HostingPerformanceScore
}
};
</script>
PerformanceResults.vue
<template>
<HostingPerformanceScore :results="results" v-if="scoreOnly" />
<HostingPerformancePage :results="results" v-else />
</template>
<script>
import HostingPerformancePage from "./HostingPerformancePage.vue";
import HostingPerformanceScore from "./HostingPerformanceScore.vue";
export default {
props: {
scoreOnly: Boolean
},
data() {
return {
results: null
};
},
created() {
setTimeout(() => {
this.results = {
score: Math.random()
};
}, 1000);
},
components: {
HostingPerformancePage,
HostingPerformanceScore
}
};
</script>
And here is a working example.