I am working on a project where a user fills in a quiz and the results need to be passed to a new page. The quiz has the Quiz component, where the quiz is taken, and the QuizFinished component, where the user can go to the Results page. I have been able to pass the quiz answers to QuizFinished, but the problem lies in passing the answers from QuizFinished to QuizResults. I have copied some code from here which is from this stackoverflow question, but the code does not work.
Am I missing something here?
Any help would be much appreciated.
Quiz.vue
<template>
<div class="quiz-background py-5" id="mood-finder">
<b-container class="text-center">
<h1 class="">Mood finder</h1>
<component
<!-- This event is triggered when a question is answered in Question.vue, that works -->
v-on:click="_answerQuestion($event)"
<!-- Going from the startpage to the quiz -->
v-on:start="this.startQuiz"
v-bind:is="component"
<!-- Binds the answers to be read in QuizResult -->
:answers="answers"
ref="question"
/>
<b-row class="d-flex justify-content-center">
<b-col cols="12" md="8" class="pt-3">
<!-- A progress bar -->
<b-progress
variant="primary"
aria-label="Voortgang van de Moodfinder"
:value="progress"
:max="maxProgress"
></b-progress>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import quizStart from "./QuizStart.vue";
import question from "./Question";
import strawberry from "../assets/strawberry.webp";
import banana from "../assets/banana.webp";
import quizFinished from "./QuizFinished.vue";
export default {
name: "app",
components: {
quizStart,
question,
quizFinished,
},
data() {
return {
questionIndex: 0,
<!--The array with the answers. It is put in an object to circumvent the array limitation by Vue: https://github.com/vuejs/vue/issues/1798 -->
answers: {array: []},
component: "quizStart",
strawberry: strawberry,
banana: banana,
progress: 0,
maxProgress: 99,
};
},
methods: {
/**
* #description
* saved pressed answer text in answers array
*/
_answerQuestion(chosenItem) {
this.answers.array.push(chosenItem);
this.switchQuestion();
},
startQuiz(){
this.component= "question"
},
/**
* #description switches the questions when the user performs an action in the quiz
* it swaps out the quiz images, text and quiz question for each question.
*/
switchQuestion() {
/**
* questionIndex stands for the question the user would like to go to.
* So e.g questionIndex = 1 is going to the second question (counting from 0)
*/
this.questionIndex++;
switch (this.questionIndex) {
case 0:
this.progress = 0;
break;
// //For question 1, see Question.Vue data field
case 1:
this.$refs.question.setQuestion("Question 2");
this.$refs.question.setItems(
{ name: "strawberry", variety: "sweet" },
{ name: "strawberry", variety: "sweet" },
{ name: "strawberry", variety: "sweet" },
{ name: "strawberry", variety: "sweet" }
);
this.$refs.question.setImage(
this.strawberry,
this.strawberry,
this.strawberry,
this.strawberry
);
this.progress = 33;
break;
case 2:
console.log(this.questionIndex)
this.$refs.question.setQuestion("Question 3");
this.$refs.question.setItems(
{ name: "banana", variety: "sweet" },
{ name: "banana", variety: "sweet" },
{ name: "banana", variety: "sweet" },
{ name: "banana", variety: "sweet" }
);
this.$refs.question.setImage(
this.banana,
this.banana,
this.banana,
this.banana
);
this.progress = 66;
break;
case 3:
this.progress = 99;
//Goes to the quizFinished component
this.component = quizFinished;
}
},
},
};
</script>
//removed the styles
QuizFinished.vue:
<template>
<b-row class="d-flex justify-content-center py-3">
<b-col class="col-6 col-lg-2">
<p class="text-white">
De Mood finder is gemaakt. Je resultaten staan klaar!
</p>
<!-- <router-link :to="{ path: '/results/', params: { answers: answers } }"
><button class="btn btn-primary" type="button">
Verder naar resultaten
</button></router-link
> -->
<br />
<router-link :to="{ name: 'results', params: { params: answers} }"
>Go to results</router-link>
<!-- For testing purposes, the answers are shown here as: { "array": [{object1} etc.]} -->
{{ answers }}
</b-col>
</b-row>
</template>
<script>
export default {
name: "quizFinished",
props: {
//For reasoning behind type: Object, see previous codeblock
answers: { type: Object, required: false },
},
};
</script>
//removed styles
QuizResult.vue
<!-- Html here -->
<script>
export default {
props: {
params: { type: Object, required: false },
},
mounted() {
//returns undefined, adding a html object like this: {{params}} returns nothing
console.log(this.params);
}
};
</script>
//removed styles
main.js:
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import './registerServiceWorker';
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import '../src/scss/bootstrap.css';
// Import for fontAwesome : svg-core, brand icons and vue
import { library } from '#fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
import { fab } from '#fortawesome/free-brands-svg-icons';
import VueMeta from 'vue-meta';
library.add(fab);
// fontawesome component neccesary to call
Vue.component('font-awesome-icon', FontAwesomeIcon, fab);
// Make BootstrapVue available throughout your project
Vue.component('font-awesome-icon', FontAwesomeIcon, fab);
Vue.use(BootstrapVue);
Vue.use(IconsPlugin);
Vue.use(VueRouter);
Vue.use(VueMeta);
import results from './components/pages/Results';
import index from './components/pages/Index';
import pageNotFound from './components/pages/PageNotFound';
import webshop from './components/pages/Webshop';
const resultsRoute = { path: '/results', name: 'results', component: results, props: true, };
const indexRoute = { path: '/', name: 'index', component: index };
const pageNotFoundRoute = { path: '*', name: 'pageNotFound', component: pageNotFound };
const webshopRoute = { path: '/webshop', name: 'webshop', component: webshop };
const routes = [resultsRoute, indexRoute, pageNotFoundRoute, webshopRoute];
const router = new VueRouter({
routes,
mode: 'history',
});
Vue.config.productionTip = false;
new Vue({
router,
render: function(h) {
return h(App);
},
}).$mount('#app');
Here's a very quick snippet to show quiz using VueRouter:
const QuizStart = {
template: `
<div><router-link :to="'/question/0'">START</router-link></div>
`
}
const QuizQuestion = {
props: ['qidx', 'questions'],
computed: {
question() {
return this.questions[this.qidx]
},
},
methods: {
setAnswer(val) {
this.$emit('set-answer', {
id: this.qidx,
answer: val
})
let next = ''
if (Number(this.qidx) < this.questions.length - 1) {
next = `/question/${ Number(this.qidx) + 1 }`
} else {
next = '/result'
}
this.$router.push(next)
},
},
template: `
<div>
{{ question.text }}:<br />
<button #click="() => setAnswer(1)">YES</button> | <button #click="() => setAnswer(-1)">NO</button>
</div>
`
}
const QuizResult = {
props: ['questions'],
computed: {
computedResult() {
return this.questions.reduce((a, c) => a + c.answer, 0)
}
},
template: `
<div>
Your score is {{ computedResult }}
</div>
`
}
const routes = [{
path: '/',
component: QuizStart
},
{
path: '/question/:qidx',
component: QuizQuestion,
props: true
},
{
path: '/result',
component: QuizResult
},
]
const router = new VueRouter({
routes // short for `routes: routes`
})
new Vue({
el: "#app",
components: {
QuizStart,
QuizQuestion,
QuizResult,
},
router,
data() {
return {
questions: [{
text: 'Is this the first question?',
answer: null,
},
{
text: 'Is this the second question?',
answer: null,
},
{
text: 'Is this the third question?',
answer: null,
},
],
}
},
methods: {
setAnswer({
id,
answer
}) {
this.questions[id].answer = answer
},
},
template: `
<div>
<div>
QUESTIONS STATE: {{ questions }}
</div>
<hr />
<router-view
:questions="questions"
#set-answer="(data) => setAnswer(data)"
></router-view>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"></div>
You can see that
the whole quiz state is tracked in one place: the question components do nothing but display the question and emit the answer back to the parent to update the state;
the questions are passed down from the parent; the route param (:qidx) is used to choose which question to display;
when the last question is reached, the next step is automatically the result page; but, because ALL the pages in the router-view receive the state of the questions, it's easy to calculate a result based on any calculation rule you might need.
I hope this helps you in a "smooth quiz-flow" :)
What I ended up doing is pushing to the router directly like this:
In the button:
v-on:click="toResults()"
And in methods:
toResults() {
this.$router.push({
name: "resultsRoute",
params: {
items: this.answers,
}
I was then able to get the items on the other side like this:
vm.$route.params.items
Related
I am using Vue draggable to sort items from my Store.js by drag and drop (I've simplified my example here using only ['a', 'b', 'c'] as my store data).
I am also using a computed property made accessible from the setup()
<draggable v-model="myList" item-key="id" #start="drag=true" #end="drag=false" >
<template #item="{card}">
<p>{{ card }}</p>
</template>
</draggable>
import draggable from 'vuedraggable';
export default {
name: "Dashboard",
components: {
draggable
},
setup() {
const cards = computed(() => {
return ['a', 'b', 'c']
})
return {
cards
}
}
}
I know the template iterates through cards but no value is displayed or is neither accessible.
In Vue 3 - When setup is executed, the component instance has not been created yet. As a result, you will not have access to data, computed, methods component options.
Reference:
https://v3.vuejs.org/guide/composition-api-setup.html#accessing-component-properties
Also in Draggable component, the array item will be accessed by element variable.
Updated template code:
<draggable v-model="myList" item-key="id" #start="drag=true" #end="drag=false" >
<template #item="{element}">
<p>{{ element.value }}</p>
</template>
</draggable>
Try changing the component code as follows,
import draggable from 'vuedraggable';
export default {
name: "Dashboard",
components: {
draggable
},
data:function(){
return {
drag:false
}
},
computed:{
myList:function(){
return [{id:1,value:'Card A'},{id:2, value:'Card B'}];
}
}
}
yarn add vue-draggable-next
<draggable class="w-full mt-5 dragArea list-group" :list="list" #change="log">
<div
class="max-w-md p-2 mb-5 border cursor-pointer list-styles"
v-for="element in list"
:key="element.name"
>
{{ element.name }}
</div>
</draggable>
</template>
<script>
import { defineComponent } from "vue";
import { VueDraggableNext } from "vue-draggable-next";
export default defineComponent({
components: {
draggable: VueDraggableNext,
},
data() {
return {
enabled: true,
list: [
{ name: "Medical science", id: 1 },
{ name: "Allied Medicine", id: 2 },
{ name: "Defense Service", id: 3 },
{ name: "Education training", id: 4 },
{ name: "Economics & Commerce", id: 5 },
{ name: "Banking & Finance", id: 6 },
{ name: "Enginnering", id: 7 },
{ name: "science", id: 8 },
],
dragging: false,
};
},
methods: {
log(event) {
console.log(event);
},
},
});
</script>
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
I have an array in My store which i am looping through to get a list which will be their own pages and share a same paragraph. I have a button which hides the paragraph. How do i make it so that it hides only on the first page and not on the other pages? So the way it should work is that if hide the paragraph in the Apple page, it should still appear on the other four pages.Or for the matter, if i hide the paragraph on any of the pages, it should not affect on any other page. Thank You
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
show:true,
specialTypes:[
{
name: "Apple",
Id:1
},
{
name: "Banana",
Id:2
},
{
name: "Berries",
Id:3
},
{
name: "Mango",
Id:4
},
{
name: "Oranges",
Id:5
}
]
},
mutations: {
toggle : state => {
state.show = !state.show
}
},
actions: {
}
})
My app.vue file looks like this
<template>
<div>
<ul >
<li v-for="specialType in $store.state.specialTypes"
:key="specialType.specialTypeId" #click="setActiveType(specialType)"
:class="{'active': activeType === specialType}">
<a><span>{{ specialType.name }}</span></a>
</li>
</ul>
<router-view/>
<Home></Home>
<About></About>
</div>
</template>
<script>
import Home from "./views/Home"
import About from "./views/About"
export default {
components:{
Home,
About
},
data(){
return{
activeType:""
}
},
methods:{
setActiveType(type) {
this.activeType = type
}
}
}
</script>
This is the paragraph which is shared by each tab.
<template>
<p v-if="$store.state.show">This needs to disappear</p>
</template>
<script>
import {mapMutations} from "vuex"
export default {
data(){
return{
}
},
methods : {
...mapMutations ([
"toggle"
])
}
}
</script>
The button is on this file
<template>
<div>
<button #click="toggle">Click Me</button>
</div>
</template>
<script>
import {mapMutations} from "vuex"
export default {
methods : {
...mapMutations ([
"toggle"
])
}
}
</script>
In your code, you are comparing objects here.
:class="{'active': activeType === specialType}"
Javascript doesn't work like this.
Replace above line with.
:class="{'active': activeType.Id === specialType.Id}"
Example:
var jangoFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
var bobaFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
console.log(bobaFett === jangoFett); // Outputs: false
The properties of bobaFett and jangoFett are identical, yet the objects themselves aren't considered equal.
I hope that helps.
I'm writing a component that renders a text. When a word starts with '#' it's a user's reference (like in twitter), and I must create a tooltip with the user's info.
This is how I instantiate the user's info component (this works fine, I'm using it in other places of the app):
const AvatarCtor = Vue.extend(AvatarTooltip);
let avatarComponent = new AvatarCtor({
propsData: {
user: user
}
});
This is the TooltipWrapper component:
<template>
<el-tooltip>
<slot name="content" slot="content"></slot>
<span v-html="text"></span>
</el-tooltip>
</template>
<script>
import {Tooltip} from 'element-ui';
export default {
name: "TooltipWrapper",
components: {
'el-tooltip': Tooltip
},
props: {
text: String
}
}
</script>
And this is how I wire it up all together:
const TooltipCtor = Vue.extend(TooltipWrapper);
const tooltip = new TooltipCtor({
propsData: {
text: "whatever"
}
});
tooltip.$slots.content = [avatarComponent];
tooltip.$mount(link);
This doesn't work. But if I set some random text in the content slot, it works fine:
tooltip.$slots.content = ['some text'];
So my problem is that I don't know how to pass a component to the slot. What am I doing wrong?
this.$slots is VNodes, but you assign with one component instance.
Below is one approach (mount the component to one element then reference its vnode) to reach the goal.
Vue.config.productionTip = false
const parentComponent = Vue.component('parent', {
template: `<div>
<div>
<slot name="content"></slot>
<span v-html="text"></span>
</div>
</div>`,
props: {
text: {
type: String,
default: ''
},
}
})
const childComponent = Vue.component('child', {
template: `<div>
<button #click="printSomething()">#<span>{{user}}</span></button>
<h4>You Already # {{this.clickCount}} times!!!</h4>
</div>`,
props: {
user: {
type: String,
default: ''
},
},
data(){
return {
clickCount: 1
}
},
methods: {
printSomething: function () {
console.log(`already #${this.user} ${this.clickCount} times` )
this.clickCount ++
}
}
})
const TooltipCtor = Vue.extend(parentComponent)
const tooltip = new TooltipCtor({
propsData: {
text: "whatever"
}
})
const SlotContainer = Vue.extend(childComponent)
const slotInstance = new SlotContainer({
propsData: {
user: "one user"
}
})
slotInstance.$mount('#slot')
tooltip.$slots.content = slotInstance._vnode
tooltip.$mount('#link')
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="link">
</div>
<div style="display:none"><div id="slot"></div></div>
I read Reactivity in Depth but can't solve the issue.
I'm creating a small single page app that contains images and text.
When the user clicks a button I want the language to change.
Currently I am storing the content in two files that export an object.
export default {
projects: [
{
title: 'Project Title',
year: 2016,
...
},
]
}
and importing that
import contentEn from './assets/content.en.js'
import contentDe from './assets/content.de.js'
new Vue({
el: '#app',
components: { App },
data: {
mainContent: {
content: contentEn
}
},
methods: {
switchToGerman(){
this.mainContent.content = contentDe
}
},
template: '<App :mainData="mainContent"/>',
})
When I assign another object to mainContent.content the rendering is not triggered.
I understand that adding and deleting properties from object don't lead to change detection but I switch out a whole object. I tried assigning it with this.$set with no success.
I also tried this and googled a lot but can't get it work.
Or is my approach just wrong?
Thank you for helping,
best,
ccarstens
EDIT:
See below the code for the App component and the ProjectElement
// App.vue
<template>
<div id="app">
<button #click="switchGerman">Deutsch</button>
<ProjectElement v-for="(project, key) in fullData.content.projects" :key="key" :content="project"/>
</div>
</template>
<script>
import ProjectElement from './components/ProjectElement'
export default {
name: 'App',
props: [
'mainData'
],
data () {
return{
fullData: {}
}
},
methods: {
switchGerman(){
this.$root.switchToGerman()
}
},
created(){
this.fullData = this.$props.mainData
},
watch: {
mainData: {
handler: function(newData){
this.fullData = newData
},
deep: true
}
},
components: {
ProjectElement,
}
}
</script>
And the ProjectElement
//ProjectElement.vue
<template>
<article :class="classObject" v-observe-visibility="{
callback: visibilityChanged,
throttle,
intersection: {
threshold
}
}">
<header v-html="description"></header>
<div class="content">
<carousel :per-page="1" :pagination-enabled="false">
<slide v-for="(slide, index) in projectContent.media" :key="index">
<VisualElement :content="slide" ></VisualElement>
</slide>
</carousel>
</div>
</article>
</template>
<script>
import {Carousel, Slide} from 'vue-carousel'
import VisualElement from './VisualElement'
export default {
name: "ProjectElement",
components: {
Carousel,
Slide,
VisualElement
},
props: [
'content'
],
data () {
return {
projectContent: {},
isVisible: false,
throttle: 300,
threshold: 0.8
}
},
created(){
this.projectContent = this.content
},
methods: {
visibilityChanged(isVisible){
this.isVisible = isVisible
}
},
computed: {
description(){
return `
<p>${ this.projectContent.title } - ${this.projectContent.year}</p>
<p>${ this.projectContent.description }</p>
`
},
classObject(){
return {
visible: this.isVisible,
'project-element': true
}
}
}
}
</script>
Did you try doing deep copy:
switchToGerman () {
const copyContent = JSON.parse(JSON.stringify(this.mainContent))
copyContent.content = contentDe
this.mainContent = copyContent
}
I found the solution (thank you #EricGuan for pointing out that the mistake must lay somewhere else)
As you can see in the original post I created a watcher for the mainData property and expected that this would trigger the re render.
What was missing is, that I didn't watch the content property on the ProjectElement component, thus not triggering a re render there.
I added this to ProjectElement.vue and now it works like a charm:
watch: {
content(newContent){
this.projectContent = newContent
}
},
Thank you everybody for helping me! <3