v-model concatenate a string with a var? - vue.js

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.

Related

Vuejs 2 terminal directives or higher priority directives?

I'm trying to create an autovivification directive for my Vue app.
So basically if my data has a user object, e.g. {user:{}} and I create the following input:
<input type="text" v-model="user.info.name.first" v-autovivify>
My directive will automatically create the desired path inside the user object, e.g. user.info.name.first (similar to how it worked in Angular 1.0)
My problem is that before my v-autovivify directive is called, v-model already evaluates and throws an error:
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
But I do know this can be done, because here is a trick I'm using to autovivify objects right now:
The vivify methods just creates the nested object and returns true
methods: {
vivifyUser() {
//this should of course check if the path exist but removed for brevity.
this.user = {name: {first: {salut: ''}}};
return true;
}
// other methods, etc...
What this means is v-if is called before v-model is evaluated and so I'm able to create the path and no error is thrown.
So my question is how can I set the priority of my directive higher than v-model (just like v-if has a priority)?
Add a v-if to delay the rendering of the element.
<input v-if='user.info' type="text" v-model="user.info.name.first" v-autovivify>
or even more general
<input v-if='user && user.info' type="text" v-model="user.info.name.first" v-autovivify>
This is a common pattern used when working with async-computed properties.

How create a v-model modifier to a VueJS Component?

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.

Access v-for current item from v-bind directive function call

I need to loop over some tasks objects with a v-for directive.
<div v-for="(currentTask, taskName) in step.tasks">
<span>{{ currentTask.title }}</span>
<button :class="getTaskButtonProp(currentTask, 'class')" :disabled="getTaskButtonProp(currentTask, 'disabled')">{{ getTaskButtonProp(currentTask, 'caption') }}</button>
</div>
The vue instance method involved:
// …
,methods: {
getTaskButtonProp : function (task, key) {
let out = tasksStatusDescriptor[task.status][key];
// out variable manipulation …
return out;
}
}
The data involved:
Vue complains and says ReferenceError: currentTask is not defined., as if v-bind directive parsing did not grant access to the current loop scope.
Did I miss something here ? Is there some sort of special syntax here ? Or did anyone already spot a workaround ? Thank you.
EDIT
This code is perfectly fine. A missing attribute's ending double quote boundary, up in the dom tree, led to a set of errors that have now disappeared.

How to use v-model in place of v-bind?

here is a working custom component:
jsFiddle
<myselect :option="cnt" ></myselect>
above code works, now how to change it into v-model? following code won't work:
<myselect v-model="cnt"></myselect>
how to use v-model in this case? Thanks.
From the documentation:
<input v-model="something">
is just syntactic sugar for:
<input :value="something" #input="something = $event.target.value">
In your fiddle you are still referencing an option property, but the component no longer has one. You need to reference the value property to get the initial value of cnt and then emit an input event to update the cnt var being used as the model. https://jsfiddle.net/4yavj0en/2/

In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/
It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.
I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/
yourvar.__ob__.dep.notify()
works on objects and arrays
Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.