I have this 3 components. One is for the Question component that consists of questions. The second is for QuestionOption for the list of choices. And the third one is my main component. How do I get the data from the QuestionOption component to the main component. Since the main component is not the parent of the QuestionOption I cannot use the $emit. Also I make use of bus but it's not working on my case. Can anyone help me on this.
Also how will I get all the chosen answers after the submission of the button? Here is my code.
Main.vue
<div class="quiz-background">
<question
v-for="(question, $indexParent) in randomQuestions"
:question="question['doc'].question"
:choices="question['doc']['choices']"
:indexParent="$indexParent"
:correctAnswer="question['doc'].correctAnswer"
>
</question>
<section style="margin:16px">
<v-ons-button
modifier="cta"
#click="submit"
v-show="!isDone"
>Submit
</v-ons-button>
</section>
</div>
<script>
submit() {
bus.$on('choosed', function(answer) {
console.log(answer);
});
},
</script>
Question.vue
<template>
<v-ons-list>
<v-ons-list-header>
{{indexParent + 1}}.{{ question }}
</v-ons-list-header>
<question-option
v-for="(choice, key, $index) in choices"
:choice="choice"
:letter="key"
:indexParent="indexParent"
:index="$index"
:correctAnswer="correctAnswer"
>
</question-option>
</v-ons-list>
</template>
<script>
import QuestionOption from './QuestionOption.vue';
export default {
props: ['question', 'indexParent', 'choices', 'correctAnswer'],
components: {
QuestionOption
}
}
</script>
QuestionOption.vue
<template>
<v-ons-list modifier="inset">
<v-ons-list-item
:name="'question-' + indexParent"
:modifier="longdivider"
:key="letter"
tappable
>
<label class="left">
<v-ons-radio
:name="'question-' + indexParent"
:input-id="indexParent + '-' + index"
:value="letter"
:key="letter"
v-model="chosenAnswer"
#change="appendAnswer($event)"
>
</v-ons-radio>
</label>
<label
class="center"
:for="indexParent + '-' + index"
:class="{'success' : isSuccess, 'danger' : isDanger}"
>
{{ letter }} . {{ choice }}
</label>
</v-ons-list-item>
</v-ons-list>
</template>
<script>
import Vue from 'vue';
var bus = new Vue();
export default {
data() {
return {
chosenAnswer: ''
}
},
props: ['letter', 'choice','correctAnswer', 'indexParent', 'index'],
computed: {
isSuccess () {
return this.chosenAnswer === this.correctAnswer;
},
isDanger () {
return this.chosenAnswer !== this.correctAnswer;
}
},
methods: {
appendAnswer (event) {
var answer = event.target.value;
bus.$emit('choosed', answer);
}
}
}
</script>
Like the article I mentioned in the comment, you can make a js file called event-bus.js, include code below.
// event-bus.js
import Vue from 'vue';
var EventBus = new Vue();
export default EventBus;
After that, import it both in QuestionOption.vue and Main.vue like this
import bus from './event-bus';
Then try it again. The reason why your code didn't work is because you generated two Vue instances, they've got no relationship with each other, so no communication would happen.
Related
Hi everyone and sorry for the title, I'm not really sure of how to describe my problem. If you have a better title feel free to edit !
A little bit of context
I'm working on a little personal project to help me learn headless & micro-services. So I have an API made with Node.js & Express that works pretty well. I then have my front project which is a simple one-page vue app that use vuex store.
On my single page I have several components and I want to add on each of them a possibility that when you're logged in as an Administrator you can click on every component to edit them.
I made it works well on static elements :
For example, here the plus button is shown as expected.
However, just bellow this one I have some components, that are loaded once the data are received. And in those components, I also have those buttons, but they're not shown. However, there's no data in this one except the title but that part is working very well, already tested and in production. It's just the "admin buttons" part that is not working as I expect it to be :
Sometimes when I edit some codes and the webpack watcher deal with my changes I have the result that appears :
And that's what I expect once the data are loaded.
There is something that I don't understand here and so I can't deal with the problem. Maybe a watch is missing or something ?
So and the code ?
First of all, we have a mixin for "Auth" that isn't implemented yet so for now it's just this :
Auth.js
export default {
computed: {
IsAdmin() {
return true;
}
},
}
Then we have a first component :
LCSkills.js
<template>
<div class="skills-container">
<h2 v-if="skills">{{ $t('skills') }}</h2>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addModal" />
<LCModal ref="addModal"></LCModal>
<div class="skills" v-if="skills">
<LCSkillCategory
v-for="category in skills"
:key="category"
:category="category"
/>
</div>
</div>
</template>
<script>
import LCSkillCategory from './LCSkillCategory.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkills',
components: {
LCSkillCategory,
LCAdmin,
LCModal,
},
computed: mapState({
skills: (state) => state.career.skills,
}),
mixins: [Auth],
};
</script>
<style scoped>
...
</style>
This component load each skills category with the LCSkillCategory component when the data is present in the store.
LCSkillCategory.js
<template>
<div class="skillsCategory">
<h2 v-if="category">{{ name }}</h2>
<LCAdmin
v-if="IsAdmin && category"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
<div v-if="category">
<LCSkill
v-for="skill in category.skills"
:key="skill"
:skill="skill"
/>
</div>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addSkillModal" />
<LCModal ref="addSkillModal"></LCModal>
</div>
</template>
<script>
import LCSkill from './LCSkill.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkillCategory',
components: { LCSkill, LCAdmin, LCModal },
props: ['category'],
mixins: [Auth],
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.category,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
And so each category load a LCSkill component for each skill of this category.
<template>
<div class="skill-item">
<img :src="img(skill.icon.hash, 30, 30)" />
<p>{{ name }}</p>
<LCAdmin
v-if="IsAdmin"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
</div>
</template>
<script>
import LCImageRendering from '../../mixins/LCImageRendering';
import { mapState } from 'vuex';
import Auth from '../../mixins/Auth';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
export default {
name: 'LCSkill',
mixins: [LCImageRendering, Auth],
props: ['skill'],
components: { LCAdmin, LCModal },
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.skill,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
Then, the component with the button that is added everywhere :
LCAdmin.js
<template>
<div class="lc-admin">
<button v-if="addModal" #click="addModal.openModal()">
<i class="fas fa-plus"></i>
</button>
<button v-if="editModal" #click="editModal.openModal()">
<i class="fas fa-edit"></i>
</button>
<button v-if="deleteModal" #click="deleteModal.openModal()">
<i class="fas fa-trash"></i>
</button>
</div>
</template>
<script>
export default {
name: 'LCAdmin',
props: ['addModal', 'editModal', 'deleteModal'],
};
</script>
Again and I'm sorry it's not that I haven't look for a solution by myself, it's just that I don't know what to lookup for... And I'm also sorry for the very long post...
By the way, if you have some advice about how it is done and how I can improve it, feel free, Really. That how I can learn to do better !
EDIT :: ADDED The Store Code
Store Career Module
import { getCareer, getSkills } from '../../services/CareerService';
const state = () => {
// eslint-disable-next-line no-unused-labels
careerPath: [];
// eslint-disable-next-line no-unused-labels
skills: [];
};
const actions = {
async getCareerPath ({commit}) {
getCareer().then(response => {
commit('setCareerPath', response);
}).catch(err => console.log(err));
},
async getSkills ({commit}) {
getSkills().then(response => {
commit('setSkills', response);
}).catch(err => console.log(err));
}
};
const mutations = {
async setCareerPath(state, careerPath) {
state.careerPath = careerPath;
},
async setSkills(state, skills) {
state.skills = skills;
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Career Service
export async function getCareer() {
const response = await fetch('/api/career');
return await response.json();
}
export async function getSkills() {
const response = await fetch('/api/career/skill');
return await response.json();
}
Then App.vue, created() :
created() {
this.$store.dispatch('config/getConfigurations');
this.$store.dispatch('certs/getCerts');
this.$store.dispatch('career/getSkills');
this.$store.dispatch('projects/getProjects');
},
Clues
It seems that if I remove the v-if on the buttons of the LCAdmin, the button are shown as expected except that they all show even when I don't want them to. (If no modal are associated)
Which give me this result :
Problem is that refs are not reactive
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
See simple demo below...
const vm = new Vue({
el: "#app",
components: {
MyComponent: {
props: ['modalRef'],
template: `
<div>
Hi!
<button v-if="modalRef">Click!</button>
</div>`
}
},
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component :modal-ref="$refs.modal"></my-component>
<div ref="modal">I'm modal placeholder</div>
</div>
The solution is to not pass $ref as prop at all. Pass simple true/false (which button to display). And on click event, $emit the event to the parent and pass the name of the ref as string...
I have a parent component and a childcomponent.
In my parent component I call a simple childcomponent-method to save an email to the email variable. But the variable email does not change.
My Parentcomponent:
import ChildComponent from "./ChildComponent";
export default {
components: {ChildComponent},
methods: {
openDocument(d) {
ChildComponent.methods.saveEmail('new#example.com');
}
}
My Childcomponent:
<template>
<div>
Email: {{ email }}
</div>
</template>
<script>
export default {
data: function () {
return {
email: ''
}
},
methods: {
saveEmail(email) {
this.email = email; // this does NOT change my email variable
}
}
}
</script>
Why my email variable does not change? How can I change this variable?
In vue it is not work like that. You have to use Probs:
Parent :
<template>
<div class="container">
<child-component :email ="email"></child-component> // NEW HERE
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
module.exports = {
data: function () {
return {
email:''
}
},
methods: {
openDocument(d) {
this.email = "example#gmil.com"
}
},
}
</script>
Child component:
<template>
<div class="container">
<h1>Profile Form Component</h1>
</div>
</template>
<script>
module.exports = {
module.exports = {
props: ['email'], //NEW HERE
created: function () {
console.log(this.email) //prints out an empty string
}
}
</script>
ATTENTION
As you I added 2 comment NEW HERE in the code , these 2 lines are really important for what you wanna do.
The code that I giving you is an example (not a complete answer) , Probs is the solution of what you asked for.
Hope it Helps <3.
The ChildComponent variable only holds the recipe for creating components of this type - but it does not hold your actual component. Your actual component lives inside your template - you have to add a ref attribute to it (e.g. <custom-component ref="custom" ... />) and then reference it like this.$refs.custom.saveEmail()
I have some global-registered base components to be listed & rendered on the UI as the user drag and drop it on the layout editor. Then, I want it to be processed on a <ComponentRenderer/> component.
Inside of <ComponentRenderer/>, I currently have this kind of logic:
<template>
<div class="component-renderer">
<radio-button
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'radio'"
></radio-button>
<check-box
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'checkbox'"
></check-box>
<standard-input
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'input'"
></standard-input>
...
...
</div>
</template>
Now instead of hard-coding & comparing it manually using v-if, I want it to dynamically compare itself and render it's element and properties so that I don't need to register the other one when a new component was added. Something that looks like this:
<template>
<div class="component-renderer" v-html="preRenderComponent"></div>
</template>
<script>
export default {
props: {
targetedComponent: {
type: Object,
required: true
}
},
computed: {
preRenderComponent() {
return this.$options.components.filter(
component =>
component.extendOptions.name.toLowerCase() == "base" + this.targetedComponent.type.toLowerCase() // E.g: 'input'
);
}
}
};
</script>
Is it possible? And if possible, how could I render the element and properties? Knowing that when I do console.log(Vue.options.components) and exploring it, it does not provide the element that gonna be rendered.
First thing first, I would say thanks for Steven Spungin & Phil for giving an information about this issue.
I tried to implements it on my code and it does work as I expected. For anyone who wondering what is it looks like, here I provides the code that have been implemented.
LayoutRenderer.vue :
<template>
<GridLayout
v-if="pageComponents.length != 0"
:layout.sync="pageComponents"
:col-num="12"
:row-height="30"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:vertical-compact="true"
:margin="[10, 10]"
:use-css-transforms="true"
:responsive="true"
:auto-size="true"
>
<GridItem
v-for="component in pageComponents"
:key="component.i"
:x="component.x"
:y="component.y"
:w="component.w"
:h="component.h"
:i="component.i"
>
<!-- <ComponentRenderer :component="component"></ComponentRenderer> -->
<Component :is="named(component)"></Component>
</GridItem>
</GridLayout>
</template>
<script>
import VueGridLayout from "vue-grid-layout";
export default {
components: {
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem
},
data() {
return {
pageComponents: []
};
},
created() {
this.fetchComponents();
},
methods: {
fetchComponents() {
let pageId = this.$route.params.component.pageId;
this.$store.dispatch("components/fetchComponents", pageId).then(() => {
this.pageComponents = this.$store.getters["components/components"];
});
},
named(component) {
let componentName = this.$options.filters.capitalize(component.type);
return `Base${componentName}`;
}
}
};
</script>
I have this code in file app.vue :
<template>
<div id="app">
<button v-on:click="component = 'login'">aa</button>
<component v-bind:is="component"></component>
</div>
</template>
<script>
import acceuil from './components/acceuil.vue'
import login from './components/login.vue'
export default {
name: 'app',
components: {
acceuil,
login
},
data(){
return {
component: 'acceuil'
}
}
}
</script>
How can I toggle between acceuil/login in component from a different vue file ?
You need to pass the imported dependency (the object or the name of the component as a string) to v-bind:is. You can do this by returning it in a computed function and pass it to a computed property, which you then can use in the template.
<template>
<div id="app">
<button v-on:click="isLogin = true">Show Login</button>
<component v-bind:is="currentComponent"></component>
</div>
</template>
<script>
import acceuil from './components/acceuil.vue';
import login from './components/login.vue';
export default {
name: 'app',
data () {
return {
isLogin: false
};
},
computed: {
currentComponent () {
return this.isLogin ? login : acceuil;
}
},
};
</script>
See also the documentation of dynamic components in the official docs.
I have an issue with the nuxt.js project. I used async the component but when it rending, async wasn't working. This is my code.
I have view document in https://nuxtjs.org/api/ but I don't know what is exactly my issue
Test.vue (component)
<template>
<div>
{{ project }}
</div>
</template>
<script>
export default {
data() {
return {
project : 'aaaa'
}
},
asyncData() {
return {
project : 'bbbb'
}
}
}
</script>
This is index.vue (page)
<template>
<test></test>
</template>
<script>
import Test from '~/components/Test.vue'
export default {
components : {
Test
}
}
</script>
My expected result is
bbbb
But when running on http://localhost:3000 this is actual result
aaaa
I try to search google many times but don't have expected solution for me. Someone help me, please.
Thanks for helping.
The components/ folder must contains only "pure" Vue.js components
So you can't use asyncData inside.
Read this FAQ: https://nuxtjs.org/faq/async-data-components#async-data-in-components-
components/Test.vue (component)
<template>
<div>
{{ project }}
</div>
</template>
<script>
export default {
props: ['project'] // to receive data from page/index.vue
}
</script>
pages/index.vue (page)
<template>
<test :project="project"></test>
</template>
<script>
import Test from '~/components/Test.vue'
export default {
components : {
Test
},
asyncData() {
return {
project : 'bbbb'
}
}
}
</script>
You cannot use asyncData in component.
You can choose to use the fetch method instead.
<template>
<div>
<p v-if="$fetchState.pending">Loading....</p>
<p v-else-if="$fetchState.error">Error while fetching mountains</p>
<ul v-else>
<li v-for="(mountain, index) in mountains" :key="index">
{{ mountain.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>