I have a form which is saved using ajax after each change in one of the fields.
The response from the ajax request is used to update some data in that instance but does not update the data that is used in my form.
The problem is when i change one of the fields, switch fast to the next field and start typing, it changes back to the value that it had before the ajax call after the request is done.
Code sample:
var vueCheckout = new Vue({
el: document.getElementById('vue-container'),
data: {
billing_address: {
'postcode' : '',
'city' : ''
},
shipping_address: {
'postcode' : '',
'city' : ''
}
},
mounted: function () {
this.billing_address = {postcode: 'postcode', city: 'city'};
this.shipping_address = {postcode: '2', city: '2'};
},
methods: {
changeAddress: function(type, key, event) {
this[type][key] = event.target.value;
var $this = this;
setTimeout(function(){
$this.shipping_address = {postcode: '3', city: '3'};
}, 1000);
},
}
});
<div id="vue-container">
<div>
<input type="text" name="billing[postcode]" :value="billing_address.postcode" #change="changeAddress('billing_address','postcode',$event)" maxlength="5">
<input type="text" name="billing[city]" :value="billing_address.city" #change="changeAddress('billing_address','city',$event)">
</div>
<div>
<span>{{ shipping_address.postcode }}</span> - <span>{{ shipping_address.city }}</span>
</div>
</div>
How to reproduce:
https://jsfiddle.net/3au4m5qw/1/
Try changing the value of postcode and then switch fast to the next field (city) and change the value to something else.
You will see that the value is back to the original.
EDIT: jsfiddle with v-model.lazy: https://jsfiddle.net/hym63pL7/
Use
v-model="billing_address.city"
instead of :value. V-model is two way binding and fixes your issue.
I think even changing to v-model the data from the ajax request won't be displayed because the data values will change but they won't be rendered. You need to wait until the DOM is updated using nextTick.
https://v2.vuejs.org/v2/api/#Vue-nextTick
var vueCheckout = new Vue({
el: document.getElementById('vue-container'),
data: {
billing_address: {
'postcode' : '',
'city' : ''
},
shipping_address: {
'postcode' : '',
'city' : ''
}
},
mounted: function () {
this.billing_address = {postcode: 'postcode', city: 'city'};
this.shipping_address = {postcode: '2', city: '2'};
},
methods: {
changeAddress: function(type, key, event) {
this[type][key] = event.target.value;
var $this = this;
setTimeout(function(){
$this.$nextTick(function() {
$this.shipping_address = {postcode: '3', city: '3'};
})
}, 1000);
},
}
});
Similar issue here:
https://laracasts.com/discuss/channels/vue/vuejs-20-data-gotten-from-backend-via-ajax-can-not-be-shown-in-view
Related
I am calling an async function which loads the profile pic, the await call returns the value to the variable 'pf' as expected, but I couldn't return that from loadProfilePic. At least for the start I tried to return a static string to be displayed as [object Promise] in vue template.
But when I remove await/asnyc it returns the string though.
<div v-for="i in obj">
{{ loadProfilePic(i.id) }}
</div>
loadProfilePic: async function(id) {
var pf = await this.blockstack.lookupProfile(id)
return 'test data';
//return pf.image[0]['contentUrl']
},
That is because async function returns a native promise, so the loadProfilePic method actually returns a promise instead of a value. What you can do instead, is actually set an empty profile pic in obj, and then populate it in your loadProfilePic method. VueJS will automatically re-render when the obj.profilePic is updated.
<div v-for="i in obj">
{{ i.profilePic }}
</div>
loadProfilePic: async function(id) {
var pf = await this.blockstack.lookupProfile(id);
this.obj.filter(o => o.id).forEach(o => o.profilePic = pf);
}
See proof-of-concept below:
new Vue({
el: '#app',
data: {
obj: [{
id: 1,
profilePic: null
},
{
id: 2,
profilePic: null
},
{
id: 3,
profilePic: null
}]
},
methods: {
loadProfilePic: async function(id) {
var pf = await this.dummyFetch(id);
this.obj.filter(o => o.id === id).forEach(o => o.profilePic = pf.name);
},
dummyFetch: async function(id) {
return await fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then(r => r.json());
}
},
mounted: function() {
this.obj.forEach(o => this.loadProfilePic(o.id));
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="i in obj">
{{ i.profilePic }}
</div>
</div>
I am using Bootstrap Vue to render a select input. Everything is working great - I'm able to get the value and change the UI based on the option that was selected.
I am trying to change the headline text on my page - to be the text of the selected option. I am using an array of objects to render the options in my select input.
Here is what I'm using for my template:
<b-form-group
id="mySelect"
description="Make a choice."
label="Choose an option"
label-for="mySelect">
<b-form-select id="mySelect"
#change="handleChange($event)"
v-model="form.option"
:options="options"/>
</b-form-group>
Here is what my data/options look like that I'm passing to the template:
...
data: () => ({
form: {
option: '',
}
options: [
{text: 'Select', value: null},
{
text: 'Option One',
value: 'optionOne',
foo: {...}
},
{
text: 'Option Two',
value: 'optionTwo',
foo: {...}
},
}),
methods: {
handleChange: (event) => {
console.log('handleChange called');
console.log('event: ', event); // optionOne or optionTwo
},
},
...
I can get optionOne or optionTwo, what I'd like to get is Option One or Option Two (the text value) instead of the value value. Is there a way to do that without creating an additional array or something to map the selected option? I've also tried binding to the actual options object, but haven't had much luck yet that route either. Thank you for any suggestions!
Solution
Thanks to #Vuco, here's what I ended up with. Bootstrap Vue passes all of the select options in via :options. I was struggling to see how to access the complete object that was selected; not just the value.
Template:
<h1>{{ selectedOption }}</h1>
<b-form-group
id="mySelect"
description="Make a choice."
label="Choose an option"
label-for="mySelect">
<b-form-select id="mySelect"
v-model="form.option"
:options="options"/>
</b-form-group>
JS:
...
computed: {
selectedOption: function() {
const report = this.options.find(option => option.value === this.form.option);
return option.text; // Option One
},
methods: {
//
}
...
Now, when I select something the text value shows on my template.
I don't know Vue bootstrap select and its events and logic, but you can create a simple computed property that returns the info by the current form.option value :
let app = new Vue({
el: "#app",
data: {
form: {
option: null,
},
options: [{
text: 'Select',
value: null
},
{
text: 'Option One',
value: 'optionOne'
},
{
text: 'Option Two',
value: 'optionTwo'
}
]
},
computed: {
currentValue() {
return this.options.find(option => option.value === this.form.option)
}
}
});
<div id="app">
<b-form-group id="mySelect" description="Make a choice." label="Choose an option" label-for="mySelect">
<b-form-select id="mySelect" v-model="form.option" :options="options" />
</b-form-group>
<p>{{ currentValue.text }}</p>
</div>
Here's a working fiddle.
You have an error in your dictionary.
Text is showed as an option.
Value is what receive your variable when option is selected.
Is unneccesary to use computed property in this case.
let app = new Vue({
el: "#app",
data: {
form: {
option: null,
},
options: [{
value: null,
text: 'Select'
},
{
value: 'Option One',
text: 'Option One'
},
{
value: 'Option Two',
text: 'Option Two'
}
]
}
});
Fiddle with corrections
Documentation
I can't figure out how to get Vue.js to always evaluate a computed regardless of if I'm actually using it in the page. A simplified version of what I'm trying to accomplish is to have a couple input fields which I want to influence the value of another field when either has been updated. I also want this field to be manually editable too. Example jsfiddle.
html:
<div id="app">
<p v-if="updateUsername">Just here to get the darn thing to run</p>
<div>
yourName:<input v-model="yourName">
</div>
<div>
dogsName:<input v-model="dogName">
</div>
<div>
username:<input v-model="userName">
</div>
</div>
js:
var main = new Vue({
el: '#app',
data: {
yourName: 'Adam',
dogName: 'Barkster',
userName: ''
},
methods: {
},
computed: {
updateUsername: function(){
this.userName = this.yourName + this.dogName;
}
}
});
This works exactly as I want it to but requires I BS the use of "updateUsername" in the html. I'm sure there's a better way.
You could add a watch:
watch: { updateUsername() {} }
Fiddle: https://jsfiddle.net/acdcjunior/k6rknwqg/2/
But it seems what you want are two watchers instead:
var main = new Vue({
el: '#app',
data: {
yourName: 'Adam',
dogName: 'Barkster',
userName: ''
},
watch: {
yourName: {
handler() {
this.userName = this.yourName + this.dogName;
},
immediate: true
},
dogName() {
this.userName = this.yourName + this.dogName;
}
}
});
Fiddle: https://jsfiddle.net/acdcjunior/k6rknwqg/6/
Another option (watching two or more properties simultaneously):
var main = new Vue({
el: '#app',
data: {
yourName: 'Adam',
dogName: 'Barkster',
userName: ''
},
mounted() {
this.$watch(vm => [vm.yourName, vm.dogName].join(), val => {
this.userName = this.yourName + this.dogName;
}, {immediate: true})
}
});
Fiddle: https://jsfiddle.net/acdcjunior/k6rknwqg/11/
For Vue2, computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed.
In your example, computed property=updateUserName will be re-evaluate when either dogName or yourName is changed.
And I think it is not a good idea to update other data in computed property, you will remeber you update userName in computed property=updateUserName now, but after a long time, you may meet some problems why my username is updated accidentally, then you don't remember you update it in one of the computed properties.
Finally, based on your example, I think watch should be better.
You can define three watcher for userName, dogName, yourName, then execute your own logic at there.
var main = new Vue({
el: '#app',
data: {
yourName: 'Adam',
dogName: 'Barkster',
userName: 'Adam Barkster'
},
methods: {
},
computed: {
updateUsername: function(){
return this.yourName + this.dogName;
}
},
watch: {
dogName: function(newValue){
this.userName = this.yourName + ' ' + newValue
},
yourName: function(newValue){
this.userName = newValue + ' '+this.dogName
},
userName: function(newValue) {
// below is one sample, it will update dogName and yourName
// when end user type in something in the <input>.
let temp = newValue.split(' ')
this.yourName = temp[0]
this.dogName = temp[1]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div id="app">
<p v-if="updateUsername">Just here to get the darn thing to run</p>
<div>
yourName:<input v-model="yourName">
</div>
<div>
dogsName:<input v-model="dogName">
</div>
<div>
username:<input v-model="userName">
</div>
</div>
</div>
I am very new to VueJS.
From what I've seen there is probably an elegant answer to this. I have a table of records. Clicking on one of them opens a modal and loads that row/record. My code looks like this (made easier to read):
Javascript
app = new Vue({
el: '#app',
data: {
records: [], //keys have no significance
focusRecord: { //this object will in the modal to edit, initialize it
id: '',
firstname: '',
lastname: ''
},
focusRecordInitial: {}
},
created: function(){
//load values in app.records via ajax; this is working fine!
app.records = ajax.response.data; //this is pseudo code :)
},
methods: {
loadRecord: function(record){
app.focusRecord = record; // and this works
app.focusRecordInitial = record;
}
}
});
Html
<tr v-for="record in records">
<td v-on:click="loadRecord(record)">{{ record.id }}</td>
<td>{{ record.firstname }} {{ record.lastname }}</td>
</tr>
What I'm trying to do is really simple: detect if focusRecord has changed after it has been loaded into the modal from a row/record. Ideally another attribute like app.focusRecord.changed that I can reference. I'm thinking this might be a computed field which I'm learning about, but again with Vue there may be a more elegant way. How would I do this?
What you need is to use VueJS watchers : https://v2.vuejs.org/v2/guide/computed.html#Watchers
...
watch : {
focusRecord(newValue, oldValue) {
// Hey my value just changed
}
}
...
Here is another way to do it, however I didn't know what's refers "focusRecordInitial"
new Vue({
el: '#app',
data() {
return {
records: [],
focusRecordIndex: null
}
},
computed : {
focusRecord() {
if (this.focusRecordIndex == null) return null
if (typeof this.records[this.focusRecordIndex] === 'undefined') return null
return this.records[this.focusRecordIndex]
}
},
watch : {
focusRecord(newValue, oldValue) {
alert('Changed !')
}
},
created() {
this.records = [{id: 1, firstname: 'John', lastname: 'Doe'}, {id: 2, firstname: 'Jane', lastname: 'Doe'}, {id: 3, firstname: 'Frank', lastname: 'Doe'}]
},
methods : {
loadRecord(index) {
this.focusRecordIndex = index
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<table>
<tr v-for="(record, i) in records">
<td><button #click="loadRecord(i)">{{ record.id }}</button></td>
<td>{{ record.firstname }} {{ record.lastname }}</td>
</tr>
</table>
{{focusRecord}}
</div>
Vue Watchers
Vue provides a more generic way to react to data changes through the watch option. This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
Vue JS Watchers
You can do something like this:
app = new Vue({
el: '#app',
data: {
records: [], //keys have no significance
focusRecord: { //this object will in the modal to edit, initialize it
id: '',
firstname: '',
lastname: ''
},
focusRecordInitial: {}
},
created: function(){
//load values in app.records via ajax; this is working fine!
app.records = ajax.response.data; //this is pseudo code :)
},
methods: {
loadRecord: function(record){
app.focusRecord = record; // and this works
app.focusRecordInitial = record;
}
},
watch: {
loadRecord: function () {
alert('Record changed');
}
}
});
You can also check out: Vue JS Computed Properties
You can set up a watcher to react to data changes as:
watch: {
'focusRecord': function(newValue, oldValue) {
/* called whenever there is change in the focusRecord
property in the data option */
console.log(newValue); // this is the updated value
console.log(oldValue); // this is the value before changes
}
}
The key in the watch object is the expression you want to watch for the changes.
The expression is nothing but the dot-delimited paths of the property you want to watch.
Example:
watch: {
'focusRecord': function(newValue, oldValue) {
//do something
},
'focusRecord.firstname': function(newValue, oldValue){
//watch 'firstname' property of focusRecord object
}
}
I'm using vue.js 2.3 and element-ui. I'm facing reactivity issues with a select dropdown whose value is the item picked.
Problems
The select is not automatically filled with the initial object value
Notices
If I change :value="item" to :value="item.name"
and form: {option: {name:'blue', price: ''}} to form: {option:'blue'}
It is working
Questions
Is there a way to make the select dropdown fully reactive when its value is not just a string or a id but rather the whole object that has been selected
https://jsfiddle.net/LeoCoco/aqduobop/
<
div style='margin-bottom:50px;'>
My form object :
{{form}}
</div>
<el-button #click="autoFill">Auto fill</el-button>
<el-select v-model="form.option" placeholder="Select">
<el-option v-for="item in options" :key="item.name" :label="item.name" :value="item">
</el-option>
</el-select>
</div>
var Main = {
data() {
const options = [
{name: 'blue', price: '100$'},{name: 'red', price: '150$'},
]
return {
currentItem: 0,
options,
form: {
option: {name:'', price: ''},
},
testForm: {
option:{name:'red', price: '150$'}
},
}
},
methods: {
autoFill() {
this.form = Object.assign({}, this.testForm); // -> Does not work
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Your issue is that when the selected value is an object, then form.option needs to be the same object in order for it to be selected in the select list.
For example if I change the fiddle code to this, I think it works the way you expect.
var Main = {
data() {
const options = {
'color': [{name: 'blue', price: '100$'},{name: 'red', price: '150$'}],
'engine': [{name: '300hp', price: '700$'},{name: '600hp', price: '2000$'}],
}
let currentCategory = 'color'
return {
currentCategory,
currentItem: 0,
options,
form: {
option: options[currentCategory][0]
},
testForm: {
option:{name:'blue', price: '100$'}
},
}
},
methods: {
autoFill() {
this.form = Object.assign({}, this.testForm); // -> Does not work
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Here is your fiddle updated.
You said your values are coming from an ajax call. That means that when you set form.option you need to set it to one of the objects in options[currentCategory].