Can you dynamically define properties in a template string in Vuejs? - javascript

Can I pass in a template string and also dynamically pass in a property so that I can make it reactive? In the example below I would like message to be reactive, but I don't want to have to predefine it on the data option.
<div id="vue">
<component :is="string && {template:string}"/>
</div>
new Vue({
el:'#vue',
data(){
return {
string:undefined,
}
},
created(){
//setTimeout to simulate ajax call
setTimeout(()=> this.string = '<div><h1 v-for="n in 1">Hello! </h1><input v-model="message" placeholder="edit me"><p>Message is: {{ message }}</p> </div>', 1000)
}
})
https://jsfiddle.net/kxtsvtro/5/

You can specify the data in the same way you specify the template: just interpolate it into the component spec.
new Vue({
el: '#vue',
data() {
return {
string: undefined,
dataObj: undefined
}
},
created() {
//setTimeout to simulate ajax call
setTimeout(() => {
this.string = '<div><h1 v-for="n in 1">Hello!</h1><input v-model="message" placeholder="edit me"><p>Message is: {{ message }}</p></div>';
this.dataObj = {
message: 'initial'
};
}, 1000)
}
})
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="vue">
<component :is="string && {template:string, data: function() { return dataObj}}" />
</div>

It looks like what you want is a component with a <slot> that you can dump a custom component into. If you're trying to compose components that's probably the easiest way to do it.

You can use Props, but I am not sure if this is the best way to do it :) here is an example:
new Vue({
el:'#vue',
data(){
return {
string:undefined,
message:''
}
},
created(){
//setTimeout to simulate ajax call
setTimeout(()=> this.string = '<div><h1 v-for="n in 1">Hello!</h1><input v-model="message" placeholder="edit me"><p>Message is: {{ message }}</p></div>', 1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<div id="vue">
<component :is="string && {template:string, props:['message']}" :message="message"/>
</div>

Related

How do you update a vue interpolation using an onclick method?

I am struggling to update the interpolation of thing.
This simple function should change the value of the data but instead does nothing.
Chrome logs the change of thing in the console but the HTML does not update.
<template>
<div class="container-xl pb-5">
<button class="mb-2 btn-lg selected-dropdown" #click="dropSelect('B')">
{{ thing }}
</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
thing: "A",
};
},
methods: {
dropSelect(thing: any) {
console.log(thing);
return this.thing;
},
},
});
</script>
Try to update your data property instead of returning:
dropSelect(thing: any) {
this.thing = thing
},
One small observation : Use this.thing = thing instead of return this.thing
Demo :
new Vue({
el: '#app',
data: {
thing: "A"
},
methods: {
dropSelect(thing) {
this.thing = thing;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="dropSelect('B')">
{{ thing }}
</button>
</div>

How to use function that return value inside a template? Vuex, Vue

I'm using a template to get data of a json file, I use "v-for" to print all data, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ item.date }}</li>
<ul>
</template>
</div>
`,
But I need use functions, year() to modificate this information and return and result, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ year(item.date) }}</li>
<ul>
</template>
</div>
`,
The value {{ item.date }} print "2021-01-20" but I hope print "2021" using the function {{ year(item.date) }}
Code function year() using javascript:
year(date){
return String(date).substr(0, 4);
}
I tried use that code but is not working, appear this error:
That's my javascript code:
//VueEx
const store = new Vuex.Store({
state: {
actividades: [],
programas: [],
year: ""
},
mutations: {
llamarJsonMutation(state, llamarJsonAction){
state.actividades = llamarJsonAction.Nueva_estructura_proveedor;
state.programas = llamarJsonAction.BD_programas;
},
yearFunction(state, date){
state.year = String(date).substr(8, 2);
return state.year;
}
},
actions: {
llamarJson: async function({ commit }){
const data = await fetch('calendario-2021-prueba.json');
const dataJson = await data.json();
commit('llamarJsonMutation', dataJson);
}
}
});
//Vue
new Vue({
el: '#caja-vue',
store: store,
created() {
this.$store.dispatch('llamarJson');
}
});
Inside a template you can use functions defined as methods or computed. Technically, you can also use data to pass a function to the template, but I wouldn't recommend it. Not that it wouldn't work, but Vue makes anything declared in data reactive and there's no point in making a function (which is basically a constant) reactive. So, in your case:
new Vue({
el: '#app',
data: () => ({
actividades: [
{ date: '2021-01-20' },
{ date: '2020-01-20' },
{ date: '2019-01-20' },
{ date: '2018-01-20' },
{ date: '2017-01-20' }
]
}),
methods: {
year(date) { return date.substring(0, 4); }
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="(item, key) in actividades" :key="key">
{{ year(item.date) }}
</li>
</ul>
</div>
If, for some reason, you want to pass year as computed:
computed: {
year() { return date => date.substring(0, 4); }
}
But it's a convoluted construct (a getter function returning an inner arrow function) and this complexity doesn't serve any purpose. I'd recommend you use a method in your case, since it's the most straight-forward (easy to read/understand).
If you're importing the year function from another file:
import { year } from '../helpers'; // random example, replace with your import
// inside component's methods:
methods: {
year, // this provides `year` imported function to the template, as `year`
// it is equivalent to `year: year,`
// other methods here
}
Side notes:
there is no point in iterating through <template> tags which contain <ul>'s. You can place the v-for directly on the <ul> and lose the <template> (You should only use <template> when you want to apply some logic - i.e: a v-if - to a bunch of elements without actually wrapping them into a DOM wrapper; another use-case is when you want its children to be direct descendants of the its parent: for <ul>/<li> or <tbody>/<tr> relations, where you can't have intermediary wrappers between them). In your case, placing the v-for on the <ul> produces the exact same result with less code.
you should always key your v-for's: <ul v-for="(item, key) in actividades" :key="key">. Keys help Vue maintain the state of list elements, keep track of animations and update them correctly
I see you are trying to work with the Vuex store. And using mutation inside the template syntax.
Not sure if we can call mutation directly via HTML as the way you are doing. In the past when I tried to call a mutation, I would either:
Create an action which would commit that mutation and call that action wrapped inside a method through Vue, something like this:look for a method printSampleLog() that I defined here
Vue.component('followers', {
template: '<div>Followers: {{ computedFollowers }} {{printSampleLog()}}</div>',
data() {
return { followers: 0 }
},
created () {
this.$store.dispatch('getFollowers').then(res => {
this.followers = res.data.followers
})
},
computed: {
computedFollowers: function () {
return this.followers
}
},
methods:{
printSampleLog(){
this.$store.dispatch('sampleAction').then(res => {
this.followers = res.data.followers
})
}
}
});
const store = new Vuex.Store({
actions: {
getFollowers() {
return new Promise((resolve, reject) => {
axios.get('https://api.github.com/users/octocat')
.then(response => resolve(response))
.catch(err => reject(error))
});
},
sampleAction(context){
context.commit('sampleMutation');
}
},
mutations: {
sampleMutation(){
console.log("sample mutation")
}
}
})
const app = new Vue({
store,
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<followers></followers>
</div>
Else create method w/o action in your Vue component committing the mutation directly, using this.$store.commit()
PS: Would recommend creating action around the mutation first, as it is a much cleaner approach.

Is Vue's 'destroyed' method called on page refresh?

I am wondering if refreshing a page that runs a Vue app will trigger the Vue's .destroyed callback.
From what I observed in a Vue app that contains these simple lifecycle callbacks:
created() {
console.log(' created');
},
destroyed() {
console.log('destroyed');
}
only 'created' is logged (not 'destroyed'). How can I check if the .destroyed callback has been executed?
I found the similar question and answer to it on stackoverflow
Do something before reload or close in vue.js
He/she basically explains that nothing is destroyed on page reload, you need to define
window.onbeforeunload = function(){
return "Are you sure you want to close the window?";
}
If you want to do something before a page refresh
As your question was
Is Vue's 'destroyed' method called on page refresh?
No, destroyed method called if your component's controller lost or you manually destroy, above example is for manually destroy.
I have found very good example in vuejs forum which uses externally this.$destroy() method.
new Vue({
el: '#app',
data() {
return {
value: 'will work until destroy'
};
},
methods: {
destroy() {
this.$destroy();
}
},
beforeDestroy() {
console.log('Main Vue destroyed')
}
})
var tmp = Vue.extend({
template: `
<div>
<span>{{ value }}</span>
<input v-model="value" />
</div>
`,
data() {
return {
value: 'always bind and work'
};
},
beforeDestroy() {
console.log('Mounted destroyed')
}
});
new tmp().$mount('#mount-point');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app">
{{ value }}
<input v-model="value" />
<div id="mount-point"></div>
<button #click="destroy()">Destroy</button>
</div>
Reference
Another example. If component's control lost or removed then destroy method will be called of that component's
Vue.component('comp1', {
template: '<div>A custom component1!</div>',
destroyed(){
console.log('comp1 destroyed');
}
})
Vue.component('comp2', {
template: '<div>A custom component2!</div>',
destroyed(){
console.log('comp2 destroyed');
}
})
new Vue({
el: '#app',
data() {
return {
value: 1
};
},
methods: {
},
beforeDestroy() {
console.log('Main Vue destroyed')
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app">
<select v-model="value">
<option value="1">comp1</option>
<option value="2">comp2</option>
</select>
<comp1 v-if="value==1"></comp1>
<comp2 v-if="value==2"></comp2>
<button #click="destroy()">Destroy</button>
</div>

Passing custom emited events as props to a new created component in VueJs

My app consists of:
A component named
<consl :output="output" #submit-to-vue><consl>
which contains an input that calls a submit() method when enter key is pressed.
<div>
<output v-html="output"></output>
<div id="input-line" class="input-line">
<div class="prompt">{{ prompt }}</div>
<div>
<input class="cmdline" autofocus
v-model.trim="command"
#keyup.enter="submit"
:readonly="submited" />
</div>
</div>
Then the method submit() emits an event #submit-to-vue to parent method submitv() that create an instance of the same component and adds it to the DOM.
//........
methods: {
submit: function () {
this.$emit('submit-to-vue')
this.submited = true
}
},
and
//......
methods: {
submitv: function () {
var ComponentClass = Vue.extend(consl)
var instance = new ComponentClass({
propsData: { output: this.output }
})
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)
What I want to accomplish ?
I want to create a new consl component and add it to the DOM every time the old one is submited. (I want my app to emulate a terminal)
The problem
When submitted the new created component does not contain the #submit-to-vue event listener, which make it unable to recall the submitv() method.
Questions
How can I solve this problem ?
Is this the proper way to do things in VueJs or is there a more elegent way ?
In parent component, declare one data property=childs, it will includes all childs already created.
So once parent component receives the event=submit-to-vue, then add one new child to this.childs
Finally uses v-for to render these child components.
The trick: always consider the data-driven way, doesn't manipulate dom directly as possible.
below is one simple demo :
Vue.config.productionTip = false
Vue.component('child', {
template: `
<div>
<div>Label:<span>{{output}}</span></div>
<div>Value:<span>{{command}}</span></div>
<div id="input-line" class="input-line">
<div class="prompt">{{ prompt }}</div>
<div>
<input class="cmdline" autofocus
v-model.trim="command"
#keyup.enter="submit"
:readonly="submited" />
</div>
</div>
</div>`,
props: ['output'],
data() {
return {
submited: false,
command: ''
}
},
computed: {
prompt: function () {
return this.submited ? 'Already submitted, input is ready-only now' : ''
}
},
methods: {
submit: function () {
this.$emit('submit-to-vue')
this.submited = true
}
}
})
app = new Vue({
el: "#app",
data: {
childs: [{'output':'default:'}]
},
methods: {
addChild: function () {
this.childs.push({'output': this.childs.length})
}
}
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div>
<ul>
<li v-for="(child, index) in childs" :key="index">
<child :output="child.output" #submit-to-vue="addChild()"></child>
</li>
</ul>
</div>
</div>

vuejs prototype array not being watched

in my vuejs program i am trying to make a global instance of an alert/notification system. This would be at the rootmost instance of the app. and then my plan was to push to an array of objects and pass that through to the component.
This only half works.
in my app.vue i have
<template>
<div id="app">
<alert-queue :alerts="$alerts"></alert-queue>
<router-view></router-view>
</div>
</template>
in my main.js i have
exports.install = function (Vue, options) {
Vue.prototype.$alerts = []
}
and my alert_queue.vue is
<template>
<div id="alert-queue">
<div v-for="alert in alerts" >
<transition name="fade">
<div>
<div class="alert-card-close">
<span #click="dismissAlert(alert)"> × </span>
</div>
<div class="alert-card-message">
{{alert.message}}
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
export default {
name: 'alert',
props: {
alerts: {
default: []
}
},
data () {
return {
}
},
methods: {
dismissAlert (alert) {
for (let i = 0; i < this.alerts.length; i++) {
if (this.alerts[i].message === alert.message) {
this.alerts.splice([i], 1)
}
}
}
}
}
</script>
I can add to this list now by using this.$alerts.push({}) and i can see they are added by console.logging the results.
The problem is that the component doesn't recognise them unless i manually go in and force it to refresh by changing something in code and having webpack reload the results. as far as i can see, there is no way to do this programatically.... Is there a way to make prototype components be watched like the rest of the application?
I have tried making the root most file have a $alerts object but when i use $root.$alerts.push({}) it doesn't work because $root is readonly.
Is there another way i can go about this ?
You could make $alerts a Vue instance and use it as an event bus:
exports.install = function (Vue, options) {
Vue.prototype.$alerts = new Vue({
data: {alerts: []},
events: { ... },
methods: { ... }
})
}
Then in your components you might call a method this.$alerts.addAlert() which in turn pushes to the array and broadcasts an event alert-added. In other places you could use this.$alerts.on('alert-added', (alert) => { ... }
Other than that, I think this is a good use case for Vuex, which is pretty much designed for this: https://github.com/vuejs/vuex
Properties defined on Vue.prototype are not reactive like a Vue instance's data properties.
I agree that, in most cases, Jeff's method or using Vuex is the way to go.
However, you could simply set this.$alerts as a Vue instance's data property and then updating that property (which would be reactive) would, by association, update the global $alerts array:
Vue.prototype.$alerts = ['Alert #1'];
Vue.component('child', {
template: `<div><div v-for="i in items">{{ i }}</div></div>`,
props: ['items'],
})
new Vue({
el: '#app',
data() {
return {
globalAlerts: this.$alerts,
}
},
methods: {
addToArray() {
this.globalAlerts.push('Alert #' + (this.globalAlerts.length + 1));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<child :items="$alerts"></child>
<button #click="addToArray">Add alert</button>
</div>

Categories

Resources