I have a form field like below
<input type="text" :value="addressObj.name" v-model="dname">
It is not working. But below code is working
<input type="text" :value="addressObj.name">
I have data() like below
data() {
return {
dname: '',
}
},
Here I am iterating over addressObj. addressObj.name is a value of that addressObj object. When I add v-model="dname" it is creating the issue. I have no issue with addressObj.name.
Why v-model is not working here ?
So first things first : you cannot use :value and v-model at the same time, v-model will always have priority.
What you can do is something like this : https://jsfiddle.net/sx0yjge2/5/
Or if you don't need to have a model attached : https://jsfiddle.net/sx0yjge2/3/
Related
I don't know how to exactly name this, I have some sort of forms in Vue which consist of different input types or more complex wrapped elements or even custom components. They all have one thing in common though - their fieldName which is used for checking various stuff such as validation, class binding etc.
Example:
<div class="field">
<div class="label">Product name</div>
<input :value="productName" #input="valueChanged" :class="{ changed: isChanged('productName') }" data-field="productName">
</div>
As you can see, productName is repeated 3 times in just a single line. I use it in dataset so valueChanged method (a global mixin) knows that field name has changed, then in the class binding to check if value has changed to style it properly, and next for the value binding itself.
It grows bigger and bigger as I want to add for example another class binding like error: hasErrors('productName')
Is there any way to define the field name once and re-use it in other bindings? It would still require some repetition, but at least changing the field name in the future would be just one change instead of 4-5. Something like this:
<input :fName="productName" :value="fName" #input="valueChanged" :class="{changed: isChanged(fName), error: hasErrors(fName)" :data-field="fName">
I know that wrapping it in some custom component would probably be one way, but that would require a lot of different conditions to render things correctly as I'm using various field types with different structures. And I would need to re-write half of my app.
Here are two potential solutions:
Use v-bind and generate an object with all of the attributes you need
<input v-bind="getAttrs('productName')" #input="valueChanged" />
...
methods: {
getAttrs(fieldName) {
return {
value: this[fieldName],
class: {
changed: this.isChanged(fieldName),
error: this.hasErrors(fieldName)
},
'data-field': fieldName
}
}
}
Store all the fields in a variable and loop through them:
<div
v-for="field in fields"
class="field"
>
<div class="label">
Product name
// Or e.g. {{ field.label }} if you have an array of field objects
</div>
<input
:value="getValue(field)"
:class="{ changed: isChanged(field), error: hasErrors(field) }"
// Still need to use v-bind to generate the data attribute on the fly
v-bind="getDataAttr(field)"
#input="valueChanged"
/>
</div>
...
data() {
return {
fields: [
'fieldName1',
'fieldName2',
'fieldName3'
],
}
},
methods: {
getValue(fieldName) {
return this[fieldName];
},
getDataAttr(fieldName) {
return {
'data-field': fieldName
}
}
}
I believe that having custom component handling input fields is the smartest way. I don't think, there would be that much conditions and even if there is some workaround, you will still need to rewrite half of your app. You may use input type as prop, so you can use component for different field types and if there are much different structures, you can use slots, to add some custom structure.
Short question
The v-model which binds a string to an input field won't update in some cases.
Example
I am using Vue within a Laravel application. This is the main component which contains two other components:
<template>
<div>
<select-component
:items="items"
#selectedItem="updateSelectedItems"
/>
<basket-component
:selectedItems="selectedItems"
#clickedConfirm="confirm"
#clickedStopAll="stopAll"
/>
<form ref="chosenItemsForm" method="post">
<!-- Slot for CSRF token-->
<slot name="csrf-token"></slot>
<input type="text" name="chosenItems" v-model="selectedItemsPipedList" />
</form>
</div>
</template>
<script>
export default {
props: ["items"],
data: function() {
return {
selectedItems: [],
selectedItemsPipedList: ""
};
},
methods: {
updateSelectedItems: function(data) {
this.selectedItems = data;
this.selectedItemsPipedList = this.selectedItems
.map(item => item.id)
.join("|");
},
confirm() {
this.$refs.chosenItemsForm.submit();
},
stopAll() {
this.updateSelectedItems([]);
this.confirm();
}
}
};
</script>
The method updateSelectedItems is called from the select-component and it works fine. In the end, the selectedItemsPipedList contains the selected items from the select-component, which looks like "1|2|3" and this value is bound to the input field in the chosenItemsForm. When the method confirm is called from the basket-component, this form is posted to the Laravel backend and the post request contains the chosen items as piped list. So far, so good.
The method stopAll is called from the basket-component and it will remove all the selected items from the array. Therefore it will call the method updateSelectedItems with an empty array, which will clear the selectedItems array and then clear the selectedItemsPipedList. After that, confirm is called which will post the form again. But, the post value still contains the selected items (e.g. '1|2|3'), instead of "". It looks like the v-model in my form is not updated, which is strange because it does work when selecting items. Why is it working when adding items, and doesn't when removing all items?
I believe you have a timing issue here. The value of the properties haven't been propagated to the DOM yet, so the form submission is incorrect. Try this instead:
stopAll() {
this.updateSelectedItems([]);
//NextTick waits until after the next round of UI updates to execute the callback.
this.$nextTick(function() {this.confirm()});
}
Dynamic V-models created during an ajax request doesn't update when I try inputting a value
I'm using vue2.x and axios. I want to get the value set in generated input when user submit the form. I managed to set v-model on this input during ajax request
I receive this HTLM as response:
<input type="text" value="" v-model="generatedcode">. But after submitting the form the value is still empty. Looks like Vue ignore the v-model directive. How can I fix it ?
Here is my code :
VUE
var app = new Vue({
el: '#subcribtionform',
data: {
generatedform:'',
generatedcode:''
},
methods:{
OnSuccess(response){
this.generatedform = response.data;
},
OnclickSub(){
axios.post('/submitformURL',{
lastname: this.lastname,
generatedcode: this.generatedcode,
})
}
created: function () {
axios.get('/generate_inputURL').then(this.OnSuccess);
}
HTML
<div v-html="generatedform"></div>
GENERATED INPUT
<input type="text" value="" v-model="generatedcode"/>
Try:
created: function () {
axios.get('/generate_inputURL').then(res => this.OnSuccess(res));
}
Component data must be function not object. You should be seeing warning about this in console. I guess your component is not reactive data then, which means that is not redrawn after on request done.
data(): {
return {
generatedform:'',
generatedcode:''
}
}
As I can see, you want to change the vue-app template using v-html attr - I think this is not possible. While mounting the application, the template compiles into render function, so your trick does not make any sense. You can try to do the following:
Construct the template (using html recieved from server as you want ) as string or as hidden el in the DOM
Set it in your app object - template:your_html_template
Create vue app
<div class="input-wrapper" id="name" :data-text="name" :class="{ error: error.isErrorName }">
<input type="text" name="name" placeholder="Name…" #input="inputName($event.target.value)">
</div>
data () {
return {
name:'',
error:{
isErrorName:false,
isErrorEmail:false,
isErrorSubject:false,
isErrorMessage:false
},
}
},
methods:{
inputName(val){
this.name=val;
this.error.isErrorName = !val.trim();
}
}
[Vue warn]: Property or method "index" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Assuming that you are trying to avoid that warning, for the explanation of what's going on behind the scenes see this post.
And to remove the warning in your case, try it out like follows:
HTML template
Use v-model for passing the variable to the method and remove ($event.target.value) and #input for being concise (See Using-v-model-on-Components). For instance you bind it to the data property name and if you want to run that inputName method everytime a key is pressed by the user:
<input type="text" name="name" placeholder="Name…" v-model="name" #keyup="inputName">
Script
Then, you do not need to add it as a parameter for the
methods: {
inputName(){
// this.name=val;
this.error.isErrorName = !(this.name.trim());
}
}
P.S: I tried to read your mind, if you have a more obvious goal. Please share it in the question ;)
In VueJS there is some v-model modifies that pre-parse the binded value, for instance v-model.trim that removes whitespaces from the string.
How can I create my own modifier? for instance v-model.myparse
Today um using something like:
computed: {
name: {
get: function () { return parse(this._name);},
set: function (value) { _name = parse(value);}
}
What is very verbose.
I would it to be reusable to do something like:
<input v-model.myparse="name" name='n1'/>
<input v-model.myparse="name" name='n2'/>
<input v-model.myparse="name" name='n3'/>
<input v-model.myparse="name" name='n4'/>
computed properties with setters seems to do part of the work, but it is really useful with some few variables only, it becomes very verbose with a lot of properties.
First, adding adding a custom modified to v-model is under discussion but not yet implemented.
If it was implemented, you could extend the v-model and add a modifier to it.
Since that is not possible, you have a couple of options left, one of which is to use :value instead of v-model. Because v-model is just a syntactic sugar of following:
<input type="text" :value="message" #input="message = $event.target.value">
The above code is the same as:
<input type="text" v-model="message">
So, I suggest you replace the logic for the #input to something like this:
<input type="text" :value="message" #input="getModel">
Now, you can use a function to return a modified value as:
methods: {
getModel ($event) {
return $event.target.value.trim()
}
}
But all of what I mentioned can still be done with the v-model if you use a function.
Of course it goes without saying, you can create your own custom directive also.