How create a v-model modifier to a VueJS Component? - vue.js

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.

Related

Vue : Custom parameter [duplicate]

I pass a parameter in v-on:input directives. If I don't pass it, I can access the event in the method. Is there any way I can still access the event when passing the parameter to the function? Note that I am using vue-router.
This is without passing the parameter. I can access the event object:
HTML
<input type="number" v-on:input="addToCart" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (event) {
// I need to access the element by using event.target
console.log('In addToCart')
console.log(event.target)
}
}
This is when passing the parameter. I can't access the event object:
HTML
<input type="number" v-on:input="addToCart(ticket.id)" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (id) {
// How can I access the element by using event
console.log('In addToCart')
console.log(id)
}
}
Here is a fiddle of the code, it should be good enough to replicate the problem that I am having:
https://jsfiddle.net/lookman/vdhwkrmq/
If you want to access event object as well as data passed, you have to pass event and ticket.id both as parameters, like following:
HTML
<input type="number" v-on:input="addToCart($event, ticket.id)" min="0" placeholder="0">
Javascript
methods: {
addToCart: function (event, id) {
// use event here as well as id
console.log('In addToCart')
console.log(id)
}
}
See working fiddle: https://jsfiddle.net/nee5nszL/
Edited: case with vue-router
In case you are using vue-router, you may have to use $event in your v-on:input method like following:
<input type="number" v-on:input="addToCart($event, num)" min="0" placeholder="0">
Here is working fiddle.
You can also do something like this...
<input #input="myHandler('foo', 'bar', ...arguments)">
Evan You himself recommended this technique in one post on Vue forum. In general some events may emit more than one argument. Also as documentation states internal variable $event is meant for passing original DOM event.
Depending on what arguments you need to pass, especially for custom event handlers, you can do something like this:
<div #customEvent='(arg1) => myCallback(arg1, arg2)'>Hello!</div>
I like this way of passing parameters because we can pass events and parameters both using an anonymous callback function.
<button v-on:click="(e)=>deleteHandler(e, args1)" > Click me </button>

VueJS - reusing prop (field name) at definition of other bindigns

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.

v-model concatenate a string with a var?

I've read answers such as this, but I can't get my template to compile.
I need to concatenate a string with a variable in v-model to bind to an array inside an object:
<li v-for="name in names">
<input type="checkbox" v-model="'checked.'+name">
....
I just get a compile error though.
Also when I do this:
:data-test="'checked.'+name"
It compiles fine, so it's something with v-model.
The compile error is:
Syntax Error: SyntaxError: Unexpected token (1:1161)
Just in case a slightly different perspective helps: Whether you use it in a v-model or :data-test directive
'checked.'+name
results in a string. Although it probably isn't what one would normally want to do, that is syntactically legal for an arbitrary v-bind (e.g. :data-test). That is not, however, syntactically legal for a v-model. As other have pointed out, v-model attempts to assign a value on "change" events. It would be equivalent, for example, to
'checked.foo' = true;
when what I think you want is
checked.foo = true;
It's hard to say for sure without seeing more of your code, but it may be the case that
<input type="checkbox" v-model="checked[name]">
is sufficient for you.
The v-model="name" helps you do two things.
:value="name"
#input="name = $event
However, in your case, you're doing v-model="'checked.'+name", which means:
:value="'checked.'+name" // Which is perfectly fine
#input="'checked.'+name = $event" // This would cause an error.
Documentation can be found here.
Below is some solution of mine: JsFiddle
computed: {
checkedName: {
// getter
get: function () {
return `${this.prefix}${this.name}`;
},
// setter
set: function (newValue) {
this.name = newValue.replace(this.prefix, '');
}
}
},
then
<input type="checkbox" v-model="checkedName">
You can't do that.
v-model is used for two-way data binding and is a syntactic sugar for :checked="myField" + #change="evt => myField = evt.target.checked in the case of a checkbox.
As you can see, myField must be a valid left-hand side expression in JS, this is by the way one of the rules precised by Vue to have a valid v-model:
The directive does not have the attribute value which is valid as LHS. E.g. <input v-model="foo() + bar()">
And that is exactly why your template doesn't compile. Vue can understand how to bind the data in one way because it can assign 'checked.'+name to a variable, but it can't assign a variable to 'checked.'+name - that is not a valid left-hand sign expression.

[Vue warn]:Property or method "index" is not defined on the instance but referenced during render

<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 ;)

v-model is not working in vuejs2

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/