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.
Related
I'm only a few months into vue coming from an angularjs background.
I built my first custom directive and it's acting a little odd to me.
Vue.directive('silly',{
componentUpdated: function (el, binding, vnode) {
console.log("it was called");
}
});
and I place it on my form like this:
<form id="opt-cpmt-form" method="post" class="mri-grid mri-p-none">
<label for="one">name<input id="one" type="text" v-model="local.name" v-silly class="form-control"></label><br/>
<label for="two">phone<input v-isnumeric id="two" type="text" v-model="local.phone" class="form-control "></label><br/>
<label for="two">zip<input id="three" type="text" v-model="local.zip" class="form-control" ></label><br/>
</form>
It kinda works...the part that I didn't understand is that my v-silly directive is called when any of the other fields are updated too. It seems to be related to the model but I only want my directive called when the field changes.
Any ideas??
It's an expected behaviour as the component updates whenever a piece of its data object is updated. To not trigger the logic too many times, you can create an event listener when the directive is bound to its parent and then run the logic when a desired event happens.
Vue.directive('silly', {
bind(el) {
this.updateCallback = function(event) {
// Your logic
};
el.addEventListener('input', this.updateCallback);
},
unbind(el) {
el.removeEventListener('input', this.updateCallback);
}
});
In case you plan to listen to the changes of v-model directive, bear in mind that it uses different events based on what element it's bound to. You can read more about that topic in v-model documentation.
v-model internally uses different properties and emits different events for different input elements:
text and textarea elements use value property and input event;
checkbox and radiobutton inputs use checked property and change event;
select fields use value as a prop and change as an event.
Also, from my experience when it comes to the form validation; I've done it using the directives and regretted it afterwards. I found it best to create reusable functions and create the custom form validation for every form. See custom form validation for more.
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.
From the documentation (https://v2.vuejs.org/v2/guide/reactivity.html), I was under the impression that all attributes of an object need to be in the Vue data object to be reactive, unless they're explicitly added using Vue.set(object, key, value), or this.$set(object, key, value).
However, I'm using Rails with Vue and any data attribute I collect in a form, whether it's initially in the data object or not, becomes reactive. I'm using Jbuiler to build JSON objects, but I don't think that's affecting the reactivity, since if I remove the attributes there, they're still reactive when collected in the form. I've tried attributes that are on the object in Rails and ones that aren't, in Jbuilder or not, added via the console or not. All become reactive. This is great, but not the behavior I expect, so I 'd like to understand it.
Here's an example...
# Product attributes: name, code (note: not 'location'!)
# Rails Controller
def new
#product = Product.new
end
# JS
var product = gon.product // using Gon gem to pass variables
var app = new Vue({
el: element,
data: function() {
return {
id: id,
product: product
}
}
)}
# HTML
<div class="col-sm-3">
<input type="text" v-model="product.code" class="form-control form-control-sm" />
</div>
<div class="col-sm-3">
<input type="text" v-model="product.location" class="form-control form-control-sm" />
</div>
<div>
Product Code: {{ product.code }}
Product Location: {{ product.location }}
</div>
When I start typing in the product.location field, the output immediately appears on the screen, so it appears to be reactive. Examining the object in the console reveals a reactive getter and reactive setter for the product.location. The attribute isn't initially in the Vue console devtool but it appears as soon as I start typing in the field.
So, what gives?
From the documentation link above:
When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty.
In other words, everything defined on component instance is reactive. This allows watcher instance to update all dependent values and virtual DOM.
$set method is used to ensure that reactivity works for deeply nested objects/arrays or previously not defined properties.
In addition, v-model directive uses $set method to update values, so even if value did not have getters and setter initially, those will be added after value has been updated.
In my Vue.js app, a child components emits an event in the following format:
this.$emit('done-editing', payload)
My parent component is designed in the following manner:
<child-component
v-on:done-editing="console.log(data)">
</child-component>
But when I execute this code, It throws an error saying
TypeError: Cannot read property 'log' of undefined
What I understood was the console object was not found in this scope. (It is originally defined on the window object). I want to know that what is the scope of JavaScript expressions inside v-on:event="…" and how to use console.log inside Vue template syntax.
I know I can do the same thing as below. But is there a way to do it inside a template expression?
<template>
<child-component
v-on:done-editing="logMethod(data)">
</child-component>
</template>
<script>
methods : {
logMethod(data) {
console.log(data)
}
}
</script>
Each handler in a v-on directive is "bound to this". That means, when you try to do:
v-on:some-event="console.log('test')"
You're actually doing:
this.console.log('test')
Which is not valid because this points to the Vue component instance. That's why you can do this:
v-on:some-event="someHandler"
…
methods: {
someHandler() { … }
}
Because the expression inside the v-on directive is automatically prefixed with this. It calls this.someHandler which exists. The same goes for expressions in v-bind directives. Specifically, in the documentation:
[...] all Vue handler functions and expressions are strictly bound to the ViewModel that’s handling the current view [...]
And:
These expressions will be evaluated as JavaScript in the data scope of the owner Vue instance.
I wrote a Vue plugin so you can use $window and $document in your templates.
https://www.npmjs.com/package/window-plugin
This will work after you install the plugin:
<child-component
v-on:done-editing="$window.console.log(data)">
</child-component>
I am attempting to write a Vue directive that updates the properties of a component before the component is evaluated.
For example, consider the following.
<b-modal v-i18n:title="'i18n.key'">
Hello
</b-modal>
b-modal is a Vue Component and it takes a property called 'title'. I would like to have a custom directive that can set the property title after translating the supplied key.
That is, I would like the above code to get rewritten by the directive to:
<b-modal title="Translated Text">
Hello
</b-modal>
So far I have read the following resources and found no reference on how to do this.
https://css-tricks.com/power-custom-directives-vue/
http://optimizely.github.io/vuejs.org/guide/directives.html
My current attempt looks like this:
Vue.directive('i18n', {
inserted: function (el,binding) {
const i18nKey = binding.value;
const attrName = binding.arg;
el.setAttribute(attrName, i18nKey);
}
})
This attempt sadly falls short. It results in a change to the final DOM element and has no affect on the property being past to the Vue component.
How can I can the above directive be modified to change the properties being past to the b-modal component?