Hi I was trying to use a v-model an input to a value in object in an array of object in Vue 3. The complexity lies in the fact the object is first processed by a function. And that it need to be processed every time when a change is made to an input. Here is my code (and a sandbox link) :
<template>
<div id="app">
<div v-for="param in process(parameters)" :key="param">
Name : {{param.name}} Value : <input v-model="param.value">
</div>
{{parameters}}
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
parameters :[
{'name':'Richard Stallman','value':'cool dude'},
{'name':'Linus Torvalds','value':'very cool dude'},
{'name':'Dennis Ritchie','value':'very very cool dude'}
]
}
},
methods:{
process(parameters){
const results = parameters.map( param =>{
return {name:param.name+' extra text',
value:param.value+' extra text',
}
})
return results
}
}
};
</script>
I just want the original parameters to change when something is types in the inputs. Maybe #change could be of use. But I didn't find a fix with #change. Does anyone know a solution to my problem? Thanks in advance.
Use computed property to get reactive state of the data.
Working Demo :
new Vue({
el: '#app',
data: {
parameters :[
{'name':'Richard Stallman','value':'cool dude'},
{'name':'Linus Torvalds','value':'very cool dude'},
{'name':'Dennis Ritchie','value':'very very cool dude'}
]
},
computed: {
process() {
const results = this.parameters.map((param) => {
return {
name: param.name + ' extra text',
value: param.value + ' extra text'
}
});
return results;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="param in process" :key="param">
Name : {{param.name}}
Value : <input v-model="param.value">
</div><br>
<strong>Orinigal Data :</strong> {{parameters}}
</div>
I am not entirely sure I understood whether the person should be able to see/edit the text you added within you processing method.
Anyway, I think this sample of code should solve you problem :
<template>
<div id="app">
<div v-for="param in parameters" :key="param.name">
Name : {{ param.name }} Value : <input v-model="param.value" />
</div>
{{ process }}
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
parameters: [
{ name: "Richard Stallman", value: "cool dude" },
{ name: "Linus Torvalds", value: "very cool dude" },
{ name: "Dennis Ritchie", value: "very very cool dude" },
],
};
},
computed: {
process: function() {
const results = this.parameters.map((param) => {
return {
name: param.name + " extra text",
value: param.value + " extra text",
};
});
return results;
},
},
};
</script>
So, we're iterating through the parameters array directly, adding an input on the value just like you did.
When you type in the input, you update the parameter linked to it, in live.
I just switched the method you made into a computed method.
This way, every time parameters is updated, "process" is also updated because it's depending on it directly.
I also removed passing the "parameters" argument, it's in the component data, you can just access it directly.
This way, using "process" just like any variable, you'll always have the updated parameters + what you added to em.
Related
I'm wondering if I am able to get a components data, the count property in this instance and console.log it into normal javascript, is this possible? I'm wanting to do console.log(btn.data.count) in this case
<div id="app" v-cloak>
<h1>{{greeting}}</h1>
<button-counter></button-counter>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script>
let app = Vue.createApp({
data: function(){
return {
greeting: "hi"
}
}
})
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
console.log(btn.data.count) // doesn't work
app.mount("#app")
</script>
There might be multiple instances of the button-counter component, so you cannot ask for the count.
You can only access the data from within the component itself. For example from within a method that handles the click event:
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
methods: {
onClick() {
console.log(this.count)
this.count++
}
},
template: '<button v-on:click="onClick">You clicked me {{ count }} times.</button>'
})
You can use console.log normally you just need to use it where it makes sense. It does not make sense using it before #app is mounted.
Try writing a method for your count++ and add console log there you will see that it gets executed every time.
My Greeting.
To put in context, my purpose of asking this question is to be able to render a child component inside a form based on the selected option of the <app-selector> Vue component as simple and silly as that.
For the sake of simplicity. I've made a snippet down here to expose what I'm trying to figure out.
Basically, the aim is to get the component name to be rendered by using the computed property cardTypeComponent. However, I want to fathom the way cardTypeComponent is working, since I cannot see why, in one hand, the first return (return this.form) is giving the object (this.form) with the property I want (card_type) but on the other hand the second return (return this.form.card_type ? this.form.card_type + 'Compose' : '') is giving me an empty string, assuming this.form.card_type is undefined when it is clear looking at the first return that, in fact, is not taking it as undefined.
There is way more context, since once the option is selected there is a validation process from the server before setting the value inside this.form object. Moreover, the form interaction is through steps, so once the user select the option he has to click a button to reach the form fields that corresponds to that type card selected, therefore the component is not going to be rendered the very first moment the user selects an option as in the snippet approach. However, it would entangle what I'm asking. Thanks beforehand.
It is better to use the Fiddle link below.
Snippet
var appSelector = Vue.component('app-selector', {
name: 'AppSelector',
template: `<div>
<label for="card_type">Card Type:</label>
<select :name="name" value="" #change="sendSelectedValue">
<option v-for="option in options" :value="option.value">
{{ option.name }}
</option>
</select>
</div>`,
props: {
name: {
required: false,
type: String,
},
options: {
required: false,
type: Array,
}
},
methods: {
sendSelectedValue: function(ev) {
this.$emit('selected', ev.target.value, this.name)
}
}
});
var guessByImageCompose = Vue.component({
name: 'GuessByImageComponse',
template: `<p>Guess By Image Compose Form</p>`
});
var guessByQuoteCompose = Vue.component({
name: 'GuessByQuoteComponse',
template: `<p>Guess By Quote Compose Form</p>`
});
new Vue({
el: '#app',
components: {
appSelector: appSelector,
guessByImageCompose: guessByImageCompose,
guessByQuoteCompose: guessByQuoteCompose,
},
data() {
return {
form: {},
card_types: [
{
name: 'Guess By Quote',
value: 'GuessByQuote'
},
{
name: 'Guess By Image',
value: 'GuessByImage'
}
],
}
},
computed: {
cardTypeComponent: function() {
return this.form; // return { card_type: "GuessByImage" || "GuessByQuote" }
return this.form.card_type ? this.form.card_type + 'Compose' : ''; // return empty string ("") Why?
}
},
methods: {
setCardType: function(selectedValue, field) {
this.form[field] = selectedValue;
console.log(this.form.card_type); // GuessByImage || GuessByQuote
console.log(this.cardTypeComponent); // empty string ("") Why?
}
},
mounted() {
console.log(this.cardTypeComponent); // empty string ("")
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form action="#" method="post">
<app-selector
:name="'card_type'"
:options="card_types"
#selected="setCardType"
>
</app-selector>
{{ cardTypeComponent }} <!-- Always empty string !-->
<component v-if="cardTypeComponent !== ''" :is="cardTypeComponent">
</component>
</form>
</div>
https://jsfiddle.net/k7gnouty/2/
You're setting a property on this.form which is not initialized first in data. This means you have run into Vue's change detection caveat. Use Vue.set when setting it:
methods: {
setCardType: function(selectedValue, field) {
Vue.set(this.form, field, selectedValue);
}
}
Alternatively, you could declare the properties first if that works better for you.
In my Drupal 7 site's html I have this
<script>$L = $L.wait(function() {
(function($) {
Drupal.behaviors.related_products = {
attach: function (context, settings) {
artiklar = Drupal.settings.related_products.artiklar;
console.log(artiklar);
}
};
})(jQuery);
});</script>
In the variable artiklar above I have some data that I have passed from the server side using Drupal behaviors. Now, on the client side I need to access the variable artiklar in a Vue component, like so:
Vue.component('artikel-lista', {
template:`
<ul>
<artikel v-for="artikel in artiklar">{{ artikel.title }} Pris: {{artikel.price}} <a :href="artikel.link" class="button tiny" target="_blank">Läs mer</a></artikel>
</ul>
`,
data(){
return {
artiklar: "",
};
},
mounted: function(){
this.artiklar = artiklar // how can I access the variable "artiklar" here
},
});
The data in the variable consists of an array of items, that I need in my Vue component. But how can I pass the variable from within the script tags to the Vue instance, that lives in a separate file, inserted just before the ending body tag. Anyone?
If you have data in the globally visible Drupal.settings.related_products.artiklar object then you can refer to it practically the same way in Vue.js. or if you must use this function, assign data to global scope window.*.
new Vue({
template: `<div>{{foo}} / {{bar}}</div>`,
data() {
return {
foo: Drupal.settings.related_products.artiklar,
bar: window.artiklarData
};
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">Vue App</div>
<script>
// simulate global variable
var Drupal = {
settings: {
related_products: {
artiklar: ['fus', 'ro', 'dah']
}
}
};
(function() {
window.artiklarData = Drupal.settings.related_products.artiklar;
})();
</script>
If you assign the value to Drupal.settings.related_products.artiklar after creating the Vue object, you can try to use the solutions described in the documentation, e.g.
const vm = new Vue({
template: `<div>{{foobar}}</div>`,
data() {
return {
foobar: 'Initial value'
};
}
}).$mount("#app");
setTimeout(() => {
// simulate global variable
var Drupal = {
settings: {
related_products: {
artiklar: 'Changed value'
}
}
};
(function() {
vm.foobar = Drupal.settings.related_products.artiklar;
})();
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">Vue App</div>
Maybe you could use RxJS but I don't have enough knowledge to tell if it's true and give an example.
Just in case anyone else is struggling with the same thing, I post this answer to my own question (I accidentally posted the question with the wrong account). In the end it turns out that the answer from Gander was correct and that I could access the variable directly in the Vue component, w/o first stashing it an a global variable. The viewed result was kind of weird though and after some trialling I found out that I had to parse the result with JSON.parse(). This is the working code now:
Vue.component('artikel-lista', {
template:`
<ul>
<artikel v-for="artikel in artiklar">{{ artikel.title }} Pris: {{artikel.price}} <a :href="artikel.link" class="button tiny" target="_blank">Läs mer</a></artikel>
</ul>
`,
data(){
return{
artiklar:""
}
},
mounted:function(){
this.artiklar = JSON.parse(Drupal.settings.related_products.artiklar);
console.log(this.artiklar);
}
});
Basically I have a couple data strings in my data object, what I want, is to concatenate one the the strings, into the other. I want the user to be able to see the date of the last server update.
lastUpdate: "10/30/3033",
message: 'Servers last updated: ',
So ideally it would display "message + lastUpdate"
Also I cant just string it together in HTMl because I need to be able to swap out the message for other strings. I could seperate out my messages in html, but i want to learn if there is a more dynamic way to do this.
Putting it into the context of my code we have the following parent componenet:
<template>
<div id="main-container" class="col-sm-12">
<h1>Server Display</h1>
<p>{{message}}</p>
<div id="mini-container" class="col-sm-3" v-for="(server, index) in servers">
<h3>({{index+1}}): {{server}}</h3>
<mi-single-server :servers="servers"
#serversReset="message = $event"></mi-single-server>
</div>
</div>
</template>
<script type="text/javascript">
import SingleServer from './SingleServer.vue';
export default{
data: function(){
return{
lastUpdate: "10/30/3033",
servers: ['Blue', 'Aphex', 'Maxamillion', 'T180', 'T190', 'NW0'],
message: 'Servers last updated: '
};
},
components: {
'mi-single-server': SingleServer
}
}
</script>
What I would love to be able to do is to add something like the following to my data table
message: 'Servers last updated: ' + this.lastUpdate
You could use a computed property so it would automatically adjust when lastUpdate changes:
export default{
data: function(){
return{
lastUpdate: "10/30/3033",
servers: ['Blue', 'Aphex', 'Maxamillion', 'T180', 'T190', 'NW0']
};
},
components: {
'mi-single-server': SingleServer
},
computed: {
message: function(){
return 'Servers last updated: ' + this.lastUpdate
}
}
}
Then you can use it like you would as if it was in data, but you would have to change your event to update lastUpdate instead of message.
You can update data property using created hook
export default{
data: function(){
return{
lastUpdate: "10/30/3033",
servers: ['Blue', 'Aphex', 'Maxamillion', 'T180', 'T190', 'NW0'],
message: 'Servers last updated: '
};
},
components: {
'mi-single-server': SingleServer
},
created: function(){
this.message = 'Servers last updated: ' + this.lastUpdate
}
}
Or you can also use mounted hook instead of created hook
I am trying to make a Vue2 component to all the select of my app so would be easier later to change it if necessary!
I've based my research on the example given by the docs and I am breaking my head to figure out why should I speficy all the object on the data attr to make it work!
The following code is working properly, but if we change:
data: { record: { category_id: null } } by data: { record: {} } it stop to work!
Must be said the $data.record is loaded by ajax... would I always specify the whole object even knowing that after the ajax request I am going to replace all with something like this.record = response.data?
If somebody need there is FIDDLE [ https://jsfiddle.net/gustavobissolli/4xrfy54e/1/ ]
EDIT: SORRY GUYS JUST FIXED FIDDLE LINK
Vue.component('select2', {
props: ['options', 'value'],
template: '#select2-template',
data() {
return {
model: ''
}
},
mounted: function() {
this.model = this.value
},
watch: {
value: function(value) {
this.model = value
},
model: function(value) {
this.$emit('input', value)
},
}
})
var vm = new Vue({
el: '#el',
template: '#demo-template',
data: {
record: {
category_id: null
},
options: [{
id: 1,
text: 'Hello'
}, {
id: 2,
text: 'World'
}]
}
})
<div id="el"></div>
<!-- using string template here to work around HTML <option> placement restriction -->
<script type="text/x-template" id="demo-template">
<div>
<pre>{{ $data | json }}</pre>
<select2 :options="options" v-model="record.category_id" value="record.category_id"></select2>
</div>
</script>
<script type="text/x-template" id="select2-template">
<select v-model="model">
<option disabled>Select...</option>
<option v-for="opt in options" :value="opt.id">{{ opt.text }}</option>
</select>
</script>
So you are trying to edit a value which didn't arrive yet? :-)
The thing is: at the moment v-model="record.category_id" is "executed", you have nothing there, ie, there is no "category_id" at the "record" object. So, it binds to nothing. This is why the select won't work if you omit the "category_id" at data initialization.
But your assumption that when data arrives from server (ajax call) the component will not work, is wrong.
I have updated your fiddle: https://jsfiddle.net/4xrfy54e/4/
First, use the dropdown before clicking the button: since it is binded to nothing, it will not update anything. This is correct.
Now, click the button. The button is simulating that data arrived from the server, and is assigned to this.record of the vm.
Play with the dropdown again: since record.category_id exists now, the binding is working fine.
Please, read the "Reactivity in Depth" documentation page, and you will stop breaking your head :-)