Vue emit will not be important in other component - vue.js

I am making a vue history quiz app, I have my questions retrived from an API. I want to show the questions, the correct answers and the user’s answers at the ResultScreen view. The way I have gone about it is to push the question into an array and $emit it. I can see it being stored in my vue tools, but for some reason it does not show in the component I want to show it in and consequently in on the resultScreeen.
The currentQuestion is an object that holds the question and the answers.
[the code that emits the array.]
storeQuestion: function (value) {
value = this.submitData.questionArray1
this.$emit('storeData', value)
console.log(value, 'my pants are on fire')
}
shows vue tools at it has been stored.
[the component where I want to project it and the function that will not show the question.]
<template>
<div>
<QuizQuestion
v-if="questions.length"
:currentQuestion="questions[index]"
#storeData="storeQuestion"
/>
<p v-if="questions">{{ questions }}</p>
<p v-else=""> loading.....</p>
</div>
</template>
<script>
// import { mapGetters } from 'vuex'
import QuizQuestion from '#/components/QuizQuestion.vue'
// se på gameplay hvor vi returner numcorrect, numtotal og numpoints, vi må se på hvordan vi kan importere verde
export default {
name: 'GameOver',
components: {
QuizQuestion
},
data () {
return {
questions: ''
}
},
methods: {
storeQuestion: function (value) {
this.questions = value
alert(value)
}
}
}
</script>
this.submitData.questionArray1.push(this.currentQuestion.question)

The problem lies in your template, specifically, this line:
<p v-if="questions">{{ storeQuestion() }}</p>
Because the method storeQuestion does not return a string value, it returns undefined regardless of what the value of questions is.
Without knowing the shape of the data being stored in questions is, it's hard to know exactly what code you want, but I suspect you want something like:
<p v-if="questions">{{ questions.text }}</p>
...or perhaps...
<p v-if="questions">{{ questions[index].text }}</p>
...or the like.
However, ancillary to your question about how to display something, your data structure is confusing me. I would expect a data variable called questions to be an Array, not at Object (especially since you're using Array access methods (such as .length and questions[index] on it. I suspect you'll have some additional changes to make, but that's beyond of the scope of your question about displaying info in the v-if statement.

Related

vue v-for: translate with switch in vue i18n

I have a problem to translate the information with vue i18n that is called through "v-for", all the data from the template is translated without problem, but those that I export through arrays and scripts do not render
i am using vue-i18n: version 7.8.1
You're only ever setting the helpTitles property when your component is created.
I would suggest using $t() in your templates instead of within data(). Then it will automatically react to changes.
I honestly don't think using an array from the translation file is a great idea. I'd be more inclined to add them with their own keys, just like your question and info translation keys, eg
helpStartedTitle: "GETTING STARTED - MODEL",
helpMembersTitle: "MEMBERS",
helpAccountTitle: "ACCOUNT",
//etc
You could then set up the keys in your data like this
data: () => {
const keys = [
"helpStarted",
"helpMembers",
"helpAccount",
"helpPayment",
"helpSocial",
"helpFraud",
"helpSupport",
"helpStudio"
]
return {
helpInfo: keys.map((key, id) => ({
id,
title: `general.help.${key}Title`,
question: `general.help.${key}`,
answer: `general.help.${key}Info`
}))
}
}
then in your template
<div v-for="help in helpInfo" :key="help.id">
<div :id="help.id" class="help-subtitle">{{ $t(help.title) }}:</div>
<HelpItem
:question="$t(help.question)"
:answer="$t(help.answer)"
:num="help.id"
/>
</div>
Even better would be to just pass the translation keys through to your HelpItem component and use $t() with it.
<HelpItem
:question="help.question"
:answer="help.answer"
:num="help.id"
/>
and in the HelpItem component
export default {
name: "HelpItem",
props: {
question: String,
answer: String,
num: Number
},
// etc
}
<!-- just guessing here -->
<h1>{{ $t(question) }}</h1>
<p>{{ $t(answer) }}</p>
FYI, I've corrected answear to answer throughout.

How can I rerender my vue template after changing v-for list?

So I'm trying to change a list based on a whether the elements are considered active or not. I do this through a computed data array. Basically a Search Function. However my template does not rerender and update automatically, even though I try to force it with this.forceUpdate().
This is my v-for in template:
<ion-list>
<div v-for="project in activeProjects" :key="project">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>
This is my computed array. The Log returns the correct things.
computed: {
activeProjects: function() {
return this.myprojects.filter(function(u){
console.log(u);
return u.active
})
}
}
And this is where I update the activity. The Log also returns the correct things.
search: function(){
for(var i=0; i<this.myprojects.length; i++){
if(this.myprojects[i].name.includes(this.searchinput)){
this.myprojects[i].active=true;
console.log(this.myprojects[i])
}
}
this.$forceUpdate();
}
Grateful for any help
I understand what you're attempting with the $forceUpdate, but I'm not certain that's the intended behavior here. In particular, by directly modifying the property of an Object in an Array, I believe Vue is missing the changes completely, so it doesn't know what to forceUpdate.
(See these links to read more on when Vue does / doesn't recognize mutations to Objects and Arrays)
TBH I've never attempted to use forceUpdate in this way, but I have done some Array mutation in a spreadsheet-like scenario before and it was a pain... I would avoid it if at all possible.
Rather than modifying a property in the array, I'd compute the filter on-the-fly using a method. You should get the reactivity you want because you're calculating, not mutating, the properties of the list of projects.
<script>
export default {
props: ['myprojects'],
data() {
return {
searchinput: ''
}
},
computed: {
activeProjects() {
return this.myprojects.filter(this.isInSearch)
}
},
methods: {
isInSearch(project) {
return project.name.includes(this.searchinput)
}
}
}
</script>
Vue caches nodes based on :key value. You're passing the entire object, you should be using a unique property on your project.
Try yo use name or an unique id if you have one.
<ion-list>
<div v-for="project in activeProjects" :key="project.name">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>```

Why my code is receiving an empty or blank value form my input?

Currently I am learning Ionic using Vue (with previous knowledge od Vue), I am doing some basic activities and was trying to pass the value from an input to the javascript side and show the input text or value inside an alert or in the console, but when I call the method that does it, I only get an empty or blank string as it the input field was empty.
I am using Ionic 4 and Vue.js, in the past I used Vue alone and had no problems with this kind of things.
<template>
<div>
<ion-item>
<ion-label position="floating">Floating Label</ion-label>
<ion-input v-model="input" #keyup.enter="show"></ion-input>
</ion-item>
</div>
</template>
<script>
export default {
data() {
return {
input: ""
}
},
methods: {
show() {
alert(this.input)
}
}
}
</script>
I am expecting the output to be the same value as the input, but in the console I get a blank or empty string.
Put the alert in a $nextTick block to make sure the model updates. The keyup event is fired before the update happens.
In vue you can also add a watch. Much better than using key bindings.
watch:{
input(value){
alert(value);
}
}
And if you only want the value on enter or blur, use the lazy modifier on v-model.

Combining v-for with v-show on same element in template

I want to display a list of entries, and I have it working up through retrieving JSON from a server, parsing it, storing it in a Vuex.Store and iterating through it with v-for-"entry in this.$store.state.entries".
When a user first visits the page all entries will be visible. The next step is to filter the entries so that only matching entries remain visible. Since this filtering will be changing a lot, I want to use v-show. I have a separate component that lets users enter search terms, the server is queried, and an array of numbers—matching IDs—is returned. I want to only show entries with IDs that match the numbers in the array, queriedEntries. My template is below:
<template>
<div id="entries">
<div v-for="entry in this.$store.state.entries"
v-html="entry.content"
v-show="this.$store.state.queriedEntries.includes(entry.id)">
</div>
</div>
</template>
I get an error that I don't understand, and searching for answers hasn't yielded anything because it doesn't match the problem others have had.
[Vue warn]: Error in render: "TypeError: this is undefined"
It's the this in the v-show, but every other this works. What's up?
Your problem is occurring because you are referencing this inside your template. This is not necessary.
The first thing I recommend you do is have a read into Vuex' Getters. Further down on the same page, you'll find information about mapGetters. This will help to prevent you from directly targeting/modifying data within your state. Modification of data should be left only to Mutations and Actions.
For example, your code may look like the below:
// in your component script
...
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
allEntries: 'entries', // map state.entries to the name 'allEntries'
queriedEntries, // your other state value. You may want to convert this to a getter
// other state values if necessary
})
}
}
...
// in your component template
<template>
<div id="entries">
<div v-for="entry in allEntries"
v-html="entry.content"
v-show="queriedEntries.includes(entry.id)">
</div>
</div>
</template>
...
Here you can see that we have used mapState which helpfully generates computed getter functions from our data in the store. We can then use the property name we have assigned it to within our template.
I ended up removing this from everything but the v-for, as suggested, and the code worked. Why this causes an error in v-show and v-html is still a mystery.
Final, working code:
<div v-for="(entry, entryindex) in this.$store.state.entries"
v-bind="{id:entryindex}"
v-bind:key="entryindex"
v-show="$store.state.queryMatchedEntries[0] == -1 || $store.state.queryMatchedEntries.indexOf(parseInt(entryindex)) != -1">

Dynamically insert child components inside vuejs2 data (without $compile or abusing v-html)

I'd like to insert new vuejs components on the fly, at arbitrary points within a block of not-necessarily-predefined HTML.
Here's a slightly contrived example that demonstrates the sort of thing I'm trying to do:
Vue.component('child', {
// pretend I do something useful
template: '<span>--><slot></slot><--</span>'
})
Vue.component('parent', {
data() {
return {
input: 'lorem',
text: '<p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p>'
}
},
template: `<div>
Search: <input type='text' v-model="input"><br>
<hr>
This inserts the child component but doesn't render it
or the HTML:
<div>{{output}}</div>
<hr>
This renders the HTML but of course strips out the child component:
<div v-html="output"></div>
<hr>
(This is the child component, just to show that it's usable here:
<child>hello</child>)
<hr>
This is the goal: it renders both the input html
and the inserted child components:
TODO ¯\_(ツ)_/¯
</div>`,
computed: {
output() {
/* This is the wrong approach; what do I replace it with? */
var out = this.text;
if (this.input) {
this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
var regex = new RegExp(this.input, "gi");
out = out.replace(regex, '<child><b>' + this.input + '</b></child>');
}
return out;
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<parent></parent>
</div>
In the above snippet, assume data.text is sanitized HTML. <child> is some sub-component that does something useful, which I want to wrap around chunks of data.text that aren't known ahead of time. (input is just for demo here. This MCVE doesn't really resemble the code I'm building, it's just an example that shows the sort of situation I'm stuck on.)
So: how would I change either the output function or the parent component's template, such that both the HTML from input and the inserted <child> templates are rendered properly?
What I've tried
In Vue 1, the answer to this would be a straightforward $compile. I'm using vuejs2 which removed $compile (out of justifiable concern that it made it too easy to naively introduce XSS vulnerabilities.)
v-html sanitizes what you feed it, which strips the child component out. Obviously this is not the way to do this. (That page suggests using partials instead, but I'm not sure how that could be applied to this situation; in any case partials have also been removed from vue2.)
I've tried passing the results of output() into another component which would then use it as its template. This seems like a promising approach, but I can't figure out how to change that secondary component's template. template only accepts a string, not a function like many of the other component properties, so I can't pass the template html in, say, a prop. Something like rewriting this.template inside beforeMount() or bind() would have been nice, but no joy there either. Is there some other way to replace a component's template string before it's mounted?
Unlike template, I can pass data to a component's render() function... but then I'm still stuck having to parse that html string into nested createElement functions. Which is exactly what Vue is doing internally in the first place; is there some way to hook into that here short of reinventing it myself?
Vue.component('foo', {
props: ['myInput'],
render(createElement) {
console.log(this.myInput); // this works...
// ...but how to parse the html in this.myInput into a usable render function?
// return createElement('div', this.myInput);
},
})
I wasn't able to cheat my around this with inline-template, either: <foo inline-template>{{$parent.output}}</foo> does exactly the same thing as a plain old {{output}}. In retrospect that should have been obvious, but it was worth a shot.
Maybe constructing an async component on the fly is the answer? This could clearly generate a component with an arbitrary template, but how would I reasonably call that from the parent component, and feed output to the constructor? (It would need to be reusable with different input, with multiple instances potentially visible simultaneously; no globals or singletons.)
I've even considered ridiculous stuff like having output() split the input into an array at the points where it would have inserted <child>, and then doing something like this in the main template:
...
<template v-for="chunk in output">
<span v-html="chunk"></span>
<child>...</child>
</template>
....
That would be doable, if laborious -- I'd have to split out what goes in the child's slot into a separate array too and get it by index during the v-for, but that could be done... if input were plain text instead of HTML. In splitting HTML I'll often wind up with unbalanced tags in each chunk, which can mess up the formatting when v-html rebalances it for me. And anyway this whole strategy feels like a bad hack; there must be a better way.
Maybe I just drop the whole input into a v-html and then (somehow) insert the child components at the proper positions through after-the-fact DOM manipulation? I haven't explored this option too deeply because it, too, feels like a hack, and the reverse of the data-driven strategy, but maybe it's a way to go if all else fails?
A couple of pre-emptive disclaimers
I'm very well aware of the XSS risks involved in $compile-like operations. Please be assured that none of what I'm doing involves unsanitized user input in any way; the user isn't inserting arbitrary component code, instead a component needs to insert child components at user-defined positions.
I'm reasonably confident that this is not an XY problem, that I really do need to insert components on the fly. (I hope it's obvious from the number of failed attempts and blind alleys I've run down that I've put more than a little thought into this one!) That said, if there's a different approach that leads to similar results, I'm all ears. The salient point is that I know which component I need to add, but I can't know ahead of time where to add it; that decision happens at run time.
If it's relevant, in real life I'm using the single-file component structure from vue-cli webpack template, not Vue.component() as in the samples above. Answers that don't stray too far from that structure are preferred, though anything that works will work.
Progress!
#BertEvans points out in comments that Vue.compile() is a thing that exists, which is an I-can't-believe-I-missed-that if ever there was one.
But I'm still having trouble using it without resorting to global variables as in that documentation. This renders, but hardcodes the template in a global:
var precompiled = Vue.compile('<span><child>test</child></span>');
Vue.component('test', {
render: precompiled.render,
staticRenderFns: precompiled.staticRenderFns
});
But various attempts to rejigger that into something that can accept an input property have been unsuccessful (the following for example throws "Error in render function: ReferenceError: _c is not defined", I assume because the staticRenderFns aren't ready to go when render needs them?
Vue.component('test', {
props: ['input'],
render() { return Vue.compile(this.input).render()},
staticRenderFns() {return Vue.compile(this.input).staticRenderFns()}
});
(It's not because there are two separate compile()s -- doing the precompile inside beforeMount() and then returning its render and staticRenderFns throws the same error.)
This really feels like it's on the right track but I'm just stuck on a dumb syntax error or the like...
As mentioned in the my comment above, $compile was removed, but Vue.compile is available in certain builds. Using that below works as I believe you intend except in a couple cases.
Vue.component('child', {
// pretend I do something useful
template: '<span>--><slot></slot><--</span>'
})
Vue.component('parent', {
data() {
return {
input: 'lorem',
text: '<div><p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p></div>'
}
},
template: `<div>
Search: <input type='text' v-model="input"><br>
<hr>
<div><component :is="output"></component></div>
</div>`,
computed: {
output() {
if (!this.input)
return Vue.compile(this.text)
/* This is the wrong approach; what do I replace it with? */
var out = this.text;
if (this.input) {
this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
var regex = new RegExp(this.input, "gi");
out = out.replace(regex, '<child><b>' + this.input + '</b></child>');
out = Vue.compile(out)
}
return out;
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<parent></parent>
</div>
You mentioned you are building with webpack and I believe the default for that build is Vue without the compiler, so you would need to modify it to use a different build.
I added a dynamic component to accept the results of the compiled output.
The sample text is not a valid template because it has more than one root. I added a wrapping div to make it a valid template.
One note: this will fail if the search term matches all or part of any of the HTML tags in the text. For example, if you enter "i", or "di" or "p" the results will not be what you expect and certain combinations will throw an error on compilation.
I'm posting this as a supplement to Bert Evans's answer, for the benefit of vue-cli webpack users who want to use .vue files instead of Vue.component(). (Which is to say, I'm mostly posting this so I'll be able to find this information when I inevitably forget it...)
Getting the right Vue build
In vue-cli 2 (and possibly 1?), to ensure Vue.compile will be available in the distribution build, confirm webpack.base.conf.js contains this line:
'vue$': 'vue/dist/vue.esm.js' // or vue/dist/vue.common.js for webpack1
instead of 'vue/dist/vue.runtime.esm.js'. (If you accepted the defaults when running vue init webpack you will already have the full standalone build. The "webpack-simple" template also sets the full standalone build.)
Vue-cli 3 works somewhat differently, and does not have Vue.compile available by default; here you'll need to add the runtimeCompiler rule to vue.config.js:
module.exports = {
/* (other config here) */
runtimeCompiler: true
};
The component
The "child" component can be a normal .vue file, nothing special about that.
A bare-bones version of the "parent" component would be:
<template>
<component :is="output"></component>
</template>
<script>
import Vue from 'vue';
import Child from './Child'; // normal .vue component import
export default {
name: 'Parent',
computed: {
output() {
var input = "<span>Arbitrary single-root HTML string that depends on <child></child>. This can come from anywhere; don't use unsanitized user input though...</span>";
var ret = Vue.compile(input);
ret.components = { Child }; // add any other necessary properties similarly
ret.methods = { /* ... */ } // like so
return ret;
}
}
};
</script>
(The only significant difference between this and the non-webpack version is importing the child, then declaring the component dependencies as ret.components: {Child} before returning it.)