Vue binding overrides element attribute - javascript

I have a component that renders a HTML input:
<input
:placeholder="placeholder"
v-model="value"
type="text"
:disabled="disabled"
:readOnly="readOnly"
#focus="onFocus"
/>
Note that the type is not binded/reactive.
When I put this component inside another, and bind a object to it, the type gets overrided.
<my-input v-bind="{type: 'foobar'}"></my-input>
Is this a by design or a bug?
Example (check the input[type] in the HTML):
const Input = {
template: '<input type="text"/>'
// ^^^^ "text" gets overriden to "foobar"
}
new Vue({
el: '#demo',
components: {
'my-input': Input
}
});
<script src="http://vuejs.org/js/vue.min.js"></script>
<div id="demo">
<my-input v-bind="{type: 'foobar'}"></my-input>
</div>

I answered this in an issue, this is expected
https://github.com/vuejs/vue/issues/5846#issuecomment-307098682
You can, however, disregard attrs by adding them as props and ignore them
const Input = {
props: ['type'],
template: '<input type="text"/>'
// ^^^^ "text" won't get overriden
}
new Vue({
el: '#demo',
components: {
'my-input': Input
}
});
Other attributes like class get merged but type can only be overriden

VueJS adds the component attributes to the first child node of the component template.
Look this fiddle
http://jsfiddle.net/8hzhvrng/
The my-input has a inputroot child and then it gets the type="password"
The my-input2 has a div root child which gets the type="number"

Related

this.$refs points to a vue component in a class styled component

Using package vue-property-decorator.
Component contents:
<template>
<div>
<label ref="label">asd</label>
</div>
</template>
<script lang="ts">
class Someclass extends Vue {
public $refs!: {
label: HTMLElement
}
private mounted(): void {
console.log(this.$refs.label) // returns a Vue component, instead of just html
}
}
</script>
It's because of this pointing to a class itself probably. But, how do i access this.$refs.label?
If you put a ref on a Vue component, its DOM element can be accessed like this:
this.$refs.label.$el
this.$refs.label is a Vue instance and its API is described here.
Regarding the ref attribute:
ref is used to register a reference to an element or a child component. The reference will be registered under the parent component’s $refs object. If used on a plain DOM element, the reference will be that element; if used on a child component, the reference will be component instance.
Update
Indeed, your example uses a label element, not a component. I assumed that you didn't show the whole code in the question and quoted the relevant parts of the doc. As you can see below, your code doesn't output what you say it does.
Vue.component('my-component', {
template: '<div><label ref="label">asd</label></div>',
mounted() {
const ref = this.$refs.label;
console.log(ref);
console.log("typeof(ref): " + typeof(ref));
console.log("Is component: " + (ref instanceof Vue));
console.log("Is HTMLElement: " + (ref instanceof HTMLElement));
}
})
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component />
</div>

shared v-model value between child and parent

I have a component which shares v-model same as parent component. The code is like below:
Vue.component('greeting', {
template: '<input type="text" :name="name" v-on:input="updateSearch($event.target.value)"> ' ,
props: ['name'],
methods: {
updateSearch: function(value) {
this.$emit('input', value);
}
}
});
const app = new Vue({
el: '#app',
data: {
name: ''
}
});
<script src="https://unpkg.com/vue#2.6.9/dist/vue.js"></script>
<div id="app">
Child: <greeting v-model="name"></greeting>
<br><br><br>
Main: <input type="text" v-model="name" placeholder="" />
</div>
I want to update both input boxes if the user enters text in either of them. Any suggestions would be helpful.
If you pass in a reference like an object as prop, you can bind a property of that object on both your parent and child
Vue.component('greeting', {
template: '<input type="text" v-model="name.value" />' ,
props: ['name']
});
const app = new Vue({
el: '#app',
data: {
name: { value: '' }
}
});
<script src="https://unpkg.com/vue#2.6.9/dist/vue.js"></script>
<div id="app">
Child: <greeting v-bind:name="name"></greeting>
<br><br><br>
Main: <input type="text" v-model="name.value" placeholder="" />
</div>
Usually is a bad practice change props inside child component. In this case, you can create two different variables and update the other one when some of them changes it value (via events and props).
So, greeting component would $emit some event which you will catch inside main component and update main's name
On the other hand, main component would pass a prop to greeting which will be reactive considering changes inside main and will update variable name inside greeting's data.
If you get more cases like that, think about using vuex
I think, what you are looking for is .sync modifier for events in Vue.js 2.3.0+.
You can find a sample implementation of the same in my article here.

VueJS sending v-model property from child to parent

Let's say I have a Vue component like this one:
Vue.component('child-comp',{
props:['name', 'val'],
data: function(){
return {
picked: ''
}
},
template: '<div><input type="radio" :name="name" :value="val" v-model="picked"><slot></slot></div>'
});
And The Parent vue instance:
new Vue({
el: '#app',
data: {
message: 'Hello'
}
});
And in the HTML:
<div id="app">
<child-comp name="fruits" val="apple">Apple</child-comp>
<child-comp name="fruits" val="banana">Banana</child-comp>
<child-comp name="fruits" val="cherry">Cherry</child-comp>
<p>{{ picked }}</p> <!-- this throws an error -->
</div>
How can I pass the v-model property picked from the child component to the root instance. Only way I know of is $emitting an event from the child component and later capturing the passed data in root instance. But as you can see, to access a simple property, triggering an event is overkill. How can I access {{ picked }} within <p>{{ picked }}</p>
If your child-comp is an input component you could use two-way props. These work similar to v-model but you can use them with your custom components.
<custom-input
:value="something"
#input="value => { something = value }">
</custom-input>
You can find out more about this here and there. See this thread also. Good luck!
That's easy:
new Vue({
el: '#app',
data: {
message: 'Hello',
picked: 'apple'
}
});
And in child :
Vue.component('child-comp',{
props:['name'],
template: '<div><input type="radio" :name="name" v-model="picked"><slot></slot></div>'
});

Setting a 'default' v-on event for a component within Vue

I have a set of 'text-input' custom components which house some boilerplate markup and an 'input' element.
One way of getting the value of the 'text-input' in to it's parent is to $emit an event when the value has changed.
I need to capture and handle the $emit with a v-on for every text-input component:
<text-input v-on:valueUpdated="storeInputValue" name='name'></text-input>
<text-input v-on:valueUpdated="storeInputValue" name='email'></text-input>
<text-input v-on:valueUpdated="storeInputValue" name='phone'></text-input>
I feel that this introduces too much repetition in the code, and I was wondering if there were a way to have the v-on listener on the component template itself:
<template v-on:valueUpdated="storeInputValue">
...
</template>
So that there is a 'default' v-on listener for this component, every time it is used.
You can use v-model on custom components.
html
<div id="app>
<text-input v-model="user.name" name="'name'"></text-input>
<text-input v-model="user.email" name="'email'"></text-input>
<text-input v-model="user.phone" name="'phone'"></text-input>
<h4>{{user}}</h4>
</div>
script
Vue.component('text-input', {
name: 'text-input',
template: `
<div>
<label>{{name}}</label>
<input type="text" :value="value" #input="storeInputValue($event.target.value)" />
</div>
`,
props: ['value', 'name'],
methods: {
storeInputValue(value){
this.$emit('input', value);
}
}
});
//parent component
new Vue({
el: '#app',
data: {
user: {
name: '',
email: '',
phone: ''
}
}
});
here is the example fiddle

Vue js dynamically added property not reactive

I have a component which displays has a prop called obj. obj has two properties: obj.title and obj.body. Each is bound to a textfield so as to be reactive and editable.
<div id="app">
<controller :obj="{title: 'TITLE'}"></controller>
</div>
<template id="controller">
<input type="text" v-model="obj.title">
<p>{{ obj.title }}</p>
<input type="text" v-model="obj.body">
<p>{{ obj.body }}</p>
</template>
The title property is part of the prop which is bound to the component. But the body property has been added dynamically during the created callback. Here is the js:
Vue.component('controller', {
template: '#controller',
props: ['obj'],
created: function() {
this.obj.body = "BODY";
},
});
new Vue({
el: '#app',
});
The problem is that the body property isn't behaving reactively. Changes to the body textfield are not reflected by {{ obj.body }}.
The vue website has a section about Adding and Deleting Properties, but I couldn't get their suggestions to work.
Here is a jsfiddle demonstrating the problem.
Note: it has been suggested that I declare the body property at the same time as the title property. This would work, but for my use-case the property needs to be added dynamically.
Try to declare the body property when passing the prop:
<controller :obj="{title: 'TITLE', body: null}"></controller>
Or in your created method:
created: function() {
this.obj = {
title: this.obj.title,
body: 'some body
}
},
https://jsfiddle.net/crabbly/33721g9w/
I have selected this as the Accepted solution because it is what I would recommend to someone else with the same problem.
As user crabbly noticed, the reactivity can only be re-established if the prop's reference is updated.
I think the nicest way to do this is by making a shallow copy:
created: function() {
this.obj.body = 'BODY'
/**
* ... other code that adds other properties and messes around with obj...
*/
this.obj = Object.assign({}, this.obj);
}
... here, Object.assign is responsible for updating the reference.
This solution is currently not supported by IE, though any "clone" function will do (e.g. this.obj = jQuery.extend({}, this.obj); also works).

Categories