use instance property as v-model - vue.js

I use a Form object to handle all my input fields and validation/errors etc.
new Vue({
el: '#root',
data: {
form: { trainer: '' }
}
And I can use it as v-model like this:
<input v-model='form.trainer' name='trainer'>
I would like to make the form an instance property, because I have to pass it to all my components at the moment.
Vue.prototype.$form = {trainer: ''}
new Vue({ el: '#root' });
However, its not reactive anymore:
<input v-model="$form.trainer">
<span v-text="$form.trainer"></span>
Changes to the input are not reflected on the span.
Is there a solution to use a instance property as v-model?

Using Global Mixin:
You can use Global Mixin and then add form: { trainer: '' } to it and then this option will be available in every Vue instance created afterwards like:
// inject a handler for `myOption` custom option
Vue.mixin({
data: function () {
return {
form: { trainer: '' }
}
}
})
and then you can use it like normal component data option:
<input v-model="form.trainer">
<span v-text="form.trainer"></span>
Demo:
// inject a handler for `myOption` custom option
Vue.mixin({
data: function() {
return {
form: { trainer: '' }
}
}
})
// Define a new component called trainer
Vue.component('trainer', {
template: `<div>
<input v-model="form.trainer">
<span v-text="form.trainer"></span>
</div>
`
})
new Vue({
el: "#myApp",
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="myApp">
<trainer></trainer>
<trainer></trainer>
</div>
Demo #2:
var trainer2Data = {
form: { trainer: '' }
}
Vue.mixin({
data: function() {
return trainer2Data;
}
})
// Define a new component called trainer2
Vue.component('trainer2', {
template: `<div>
<input v-model="form.trainer">
<span v-text="form.trainer"></span>
</div>
`
})
new Vue({
el: "#myApp",
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="myApp">
<trainer2></trainer2>
<trainer2></trainer2>
</div>

Related

VueJS: How to inject a variable to a text from API

I get some text from API then I display it. But before I display it, I need to inject or replace some variable in the text to value from variable.
Please check this out to understand what I mean:
https://jsfiddle.net/0q4ot5sw/
new Vue({
el: "#app",
data: {
text: 'Some very long text from API where I need to inject %variable%',
variable: 'some word' // How to inject this variable to the text?
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
{{ text }}
</div>
You can do that
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
{{ text.replace("%variable%", variable) }}
</div>
or that
new Vue({
el: "#app",
data: {
text: 'Some very long text from API where I need to inject %variable%',
variable: 'some word' // How to inject this variable to the text?
},
computed: {
resultText: function() {
return this.text.replace("%variable%", this.variable);
}
}
})
and use like this
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
{{ resultText }}
</div>
You can use computed value to do the task
new Vue({
el: '#app',
data: {
text: 'Some very long text from API where I need to inject',
variable: 'some word123' // How to inject this variable to the text?
},
computed : {
changeText : function() {
return this.text + this.variable
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ changeText }}</p>
</div>
You can create a function (or a computed property, this decision is actually yours) and combine those two strings. Something like this
new Vue({
el: "#app",
data: {
text: 'Some very long text from API where I need to inject',
variable: 'some word'
},
methods: {
getText: function(){
return this.text + this.variable;
},
toggle: function(todo){
todo.done = !todo.done
}
}
})
On your template, you just call the function you created
<div id="app">
{{ getText() }}
</div>

vuejs data doesn't change when property change

I am very new to Vuejs so although I can probably devise a solution myself by using a watcher or perhaps a lifecycle hook I would like to understand why the following does not work and what should be done instead.
The problem is that the mutated local data doesn't update whenever the component consumer changes the property cellContent. The parent owns cellContent so using the property directly is a no-no (Vue seems to agree).
<template>
<textarea
v-model="mutableCellContent"
#keyup.ctrl.enter="$emit('value-submit', mutableCellContent)"
#keyup.esc="$emit('cancel')">
</textarea>
</template>
<script>
export default {
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data () {
return {
mutableCellContent: this.cellContent
}
}
}
</script>
<style>
...
</style>
In data (mutableCellContent: this.cellContent) you are creating a copy of the prop, that's why when the parent changes, the local copy (mutableCellContent) is not updated. (If you must have a local copy, you'd have to watch the parent to update it.)
Instead, you should not keep a copy in the child component, just let the state be in the parent (and change it through events emitted in the child). This is a well known the best practice (and not only in Vue, but in other frameworks too, if I may say it).
Example:
Vue.component('cell-editor', {
template: '#celleditor',
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data () {
return {}
}
});
new Vue({
el: '#app',
data: {
message: "Hello, Vue.js!"
}
});
textarea { height: 50px; width: 300px; }
<script src="https://unpkg.com/vue"></script>
<template id="celleditor">
<textarea
:value="cellContent"
#keyup.ctrl.enter="$emit('value-submit', $event.currentTarget.value)"
#keyup.esc="$event.currentTarget.value = cellContent">
</textarea>
</template>
<div id="app">
{{ message }}
<br>
<cell-editor :cell-content="message" #value-submit="message = $event"></cell-editor>
<br>
<button #click="message += 'parent!'">Change message in parent</button>
</div>
You have to create a watcher to the prop cellContent.
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.config.debug = false
Vue.config.silent = true
Vue.component('component-1', {
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data() {
return {
mutableCellContent: this.cellContent
}
},
template: `
<textarea
v-model="mutableCellContent"
#keyup.ctrl.enter="$emit('value-submit', mutableCellContent)"
#keyup.esc="$emit('cancel')">
</textarea>
`,
watch: {
cellContent(value) {
this.mutableCellContent = value;
}
}
});
var vm = new Vue({
el: '#app',
data() {
return {
out: "",
cellContent: ""
}
},
methods: {
toOut(...args) {
this.out = JSON.stringify(args);
},
changeCellContent() {
this.cellContent = "changed at " + Date.now();
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<component-1 :cell-content="cellContent" #value-submit="toOut" #cancel="toOut"></component-1>
<p>{{out}}</p>
<button #click="changeCellContent">change prop</button>
</div>

VueJS process component or HTML using a function

I understand how this works from the VueJS Documentation:
<div id="components-demo">
<button-counter></button-counter>
</div>
And also this:
<div id="app">
<a v-on:click="loadElement(request, etc)">Load</a>
</div>
Is there any way to write the equivalent from a function and have Vue pick it up? For example as this:
<div id="app">
{{ writeComponent('component-name', etc) }}
or
{{ writeElement('loadElement') }} <!-- which then writes the component above -->
</div>
The reason for this is that in this context quite a few components might need to be written and writing it out in HTML would be cumbersome.
You could use the render function like below:
Vue.component('button-counter', {
template: '<span class="bc">bc</span>'
})
new Vue({
el: '#app',
data: {
request: 'req!',
etc: 'etc!',
buttonCounters: []
},
methods: {
loadElement(r, e) {
console.log('loadElement', r, e);
this.buttonCounters.push(r);
}
},
render(h) {
let bcs = this.buttonCounters.map(bc => h('button-counter'));
let loadLink = h('a', {on: {"click": ($event) => { this.loadElement(this.request, this.etc)}}}, ["Load"]);
return h('div', {attrs: {"id": "app"}}, [loadLink, " ", ...bcs])
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app"></div>
Though the same can be achieved via regular template and v-for:
Vue.component('button-counter', {
template: '<span class="bc">bc</span>'
})
new Vue({
el: '#app',
data: {
request: 'req!',
etc: 'etc!',
buttonCounters: []
},
methods: {
loadElement(r, e) {
console.log('loadElement', r, e);
this.buttonCounters.push(r);
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<a v-on:click="loadElement(request, etc)">Load</a>
<button-counter v-for="(bc, index) in buttonCounters" :key="index"></button-counter>
</div>

Is it possible to use inline an $emitted parameter?

In the following code, clicking on the component emits a signal to the parent, who modifies its state inline (in the sense - not via a handler):
Vue.component('my-component', {
template: '<div v-on:click="emitit">click on the component</div>',
methods: {
emitit: function() {
this.$emit('mysignal', 7)
}
}
})
new Vue({
el: "#root",
data: {
from: 0
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="root">
<my-component v-on:mysignal="from=5"></my-component>
from component: {{ from }}
</div>
Is it possible to access the parameter provided via the $emit directly in v-on:mysignal="..."?
I know that I can use a handler defined in the main Vue component but I would like to simplify my code and avoid to have several handlers in methods.
Yes, like this:
<my-component v-on:mysignal="value => from = value"></my-component>
Vue.component('my-component', {
template: '<div v-on:click="emitit">click on the component</div>',
methods: {
emitit: function() {
this.$emit('mysignal', 7)
}
}
})
new Vue({
el: "#root",
data: {
from: 0
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="root">
<my-component v-on:mysignal="value => from = value"></my-component>
from component: {{ from }}
</div>
#click="val => $emit('mysignal', val)"
<div v-on:click="val => $emit('mysignal', val)">click the component</div>'
or even
#click.prevent="e => $emit('mysignal', e.target.value)"

How to handle getting new data even?

I have got Vue-JS app. After use click variable rasters_previews_list get new data. I would like to generate list with them. I can't understand which directive I should use to handle this even.
Here is my code:
var userContent = Vue.extend({
template: `
<div class="LayersMenuSectionContent" v-if="rasters_previews_list.length">
<ul v-for="img in rasters_previews_list">
<li>{{img.id}}</li> // generate list here
<ul>
</div>
`,
data: function () {
return {
rasters_previews_list: [] // when come new data I should generate li with then
}
},
ready: function()
{
},
methods:
{
}
});
Should I use v-on or v-if?
When rasters_previews_list is changed, list is autorendered in v-for.
new Vue({
el: '#app',
template: `
<div class="LayersMenuSectionContent" v-show="rasters_previews_list.length">
<ul v-for="img in rasters_previews_list">
<li>{{img.id}}</li>
<ul>
</div>
<button #click="add">Add</button>
`,
data: function () {
return {
rasters_previews_list: [] // when come new data I should generate li with then
}
},
ready: function(){ },
methods: {
add(){
this.rasters_previews_list.push({id: 'hello'},{id: 'world'});
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app"></div>
Extra: You must use v-show instead of v-if for this case