VueJS sending v-model property from child to parent - javascript

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

Related

child component emit an custom event, but parent component's listener not triggered

I'm registered a child component in Vue, the child will emit an custom event, but the parent component's listener not triggered.
<template id="add-item-template">
<div class="input-group">
<input #keyup.enter="addItem" v-model="newItem" >
<span class="input-group-btn">
<button #click="addItem" type="button">Add!</button>
</span>
</div>
</template>
<div id="app" class="container">
<h2>{{ title }}</h2>
<add-item-component v-on:itemAdd="addAItem"></add-item-component>
</div>
Vue.component('add-item-component', {
template: '#add-item-template',
data: function () {
return {
newItem: ''
};
},
methods: {
addItem() {
this.$emit('itemAdd');
console.log("itemAdd In add-item-component");
}
}
});
new Vue({
el: '#app',
data: {
title: 'Welcome to Vue'
},
methods: {
addAItem: function () {
console.log("In #app, addAItem()!");
}
}
});
The "itemAdd In add-item-component" log show in console, but "In #app, addAItem()!" log not, the #app's method addAItem not invoked.
The problem is that custom events should not be named with camelCase. If you look at the error message in the console, it tells you:
Event "itemadd" is emitted in component but the handler is registered for "itemAdd"
The component is emitting a lowercase version even though you've used camelCase to name it. Renaming to all lowercase or kebab-case will fix it.
In the parent template:
<add-item-component #itemadd="addAItem">
Emitting from the child:
this.$emit('itemadd');
This is discussed a bit with Evan You (Vue creator) here

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.

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 binding overrides element attribute

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"

Vue instance inside Vue instance

Sup people!
I got this HTML code here:
// index.html
<div data-init="component-one">
<...>
<div data-init="component-two">
<button #click="doSomething($event)">
</div>
</div>
This basically references a Vue instance inside another Vue instance if I understood everything correctly. The respective JS code is split up in two files and looks like this:
// componentOne.js
new Vue(
el: '[data-init="component-one"]',
data: {...},
methods: {...}
);
// componentTwo.js
new Vue(
el: '[data-init="component-two"]'
data: {...}
methods: {
doSomething: function(event) {...}
}
);
Now, the problem with this is, that doSomething from componentTwo never gets called.
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something. And it also removes the #click element on page load.
I tried fiddling around with inline-template as well, but it doesn't really work as I'd expect it to in this situation. And I figured it isn't meant for this case anyway, so I dropped it again.
What would the correct approach be here? And how can I make this work the easiest way possible with how it's set up right now?
The Vue version we use is 2.1.8.
Cheers!
The problem is that you have two vue instances nested to each other.
If the elements are nested, then you should use the same instance or try components
https://jsfiddle.net/p16y2g16/1/
// componentTwo.js
var item = Vue.component('item',({
name:'item',
template:'<button #click="doSomething($event)">{{ message2 }</button>',
data: function(){
return{
message2: 'ddddddddddd!'
}},
methods: {
doSomething: function(event) {alert('s')}
}
}));
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
<div data-init="component-one">
<button >{{ message }}</button>
<item></item>
</div>
Separate instances work if they are independant of each other.
as follows:
https://jsfiddle.net/p16y2g16/
var app = new Vue({
el: '[data-init="component-one"]',
data: {
message: 'Hello Vue!'
}
});
// componentTwo.js
var ddd = new Vue({
el: '[data-init="component-two"]',
data: {
message: 'ddddddddddd!'
},
methods: {
doSomething: function(event) {alert('s')}
}
});
But when I do some inline stuff, like {{ 3 + 3 }}, it gets computed like it should. So Vue knows there is something.
Because you have parent instance 'componentOne'. It activated Vue for this template. If you need to set another instance inside, you have to separate part of template. Example (it can lag in snippet!) .
Alternative
https://jsfiddle.net/qh8a8ebg/2/
// componentOne.js
new Vue({
el: '[data-init="component-one"]',
data: {
text: 'first'
},
methods: {}
});
// componentTwo.js
new Vue({
el: '[data-init="component-two"]',
data: {
text: 'second'
},
template: `<button #click="doSomething($event)">{{text}}</button>`,
methods: {
doSomething: function(event) {
console.log(event);
}
}
});
<script src="https://vuejs.org/js/vue.min.js"></script>
<div data-init="component-one">
{{text}}
</div>
<div data-init="component-two">
</div>
The button element inside component-two is referenced as a slot in Vue.
The evaluation of the #click directive value happens in the parent component (component-one, which host component-two). Therefor, you need to declare the click handler over there (over component-one).
If you want the handler to be handled inside component-two, you should declare a click directive for the slot element in it's (component-two) template, and pass the handler function, for instance, as a pop.
good luck.
You're doing everything right except you've nested the 2nd Vue instance inside the 1st. Just put it to the side and it will work as expected.
Vue ignores binding more than once to the same element to avoid infinite loops, which is the only reason it doesn't work nested.
Use vue-cli to create a webpack starter app. vue init app --webpack
Then, try to structure your components this way. Read more: https://v2.vuejs.org/v2/guide/components.html#What-are-Components
This is main.js
import Vue from 'vue'
import ComponentOne from './ComponentOne.vue'
import ComponentTwo from './ComponentTwo.vue'
new Vue({
el: '#app',
template: '<App/>',
components: {
ComponentOne,
ComponentTwo
}
})
This is ComponentOne.vue
<template>
<div class="user">
<div v-for="user in users">
<p>Username: {{ user.username }}</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
users: [
{username: 'Bryan'},
{username: 'Gwen'},
{username: 'Gabriel'}
]
}
}
}
</script>
This is ComponentTwo.vue
<template>
<div class="two">
Hello World
</div>
</template>
<script>
export default {
}
</script>
<div th:if="${msg.replyFloor}">
<div class="msg-lists-item-left">
<span class="msg-left-edit"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">您在</span>
<span th:text="${msg.topic.title}"
class="msg-left-edit-res"
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">问题回答</span>
<span th:text="${msg.type.name}"
class="msg-left-edit "
th:classappend=" ${msg.unreadCount == 0} ? 'msg-all-read' ">帖子相关</span>
<span class="msg-left-edit-number" >
产生了<span th:text="${msg.unreadCount} ? : ${msg.unreadCount} + '条新' : ${msg.unreadCount} + '条' "
th:class="${msg.unreadCount} ? : 'number-inner':''">2132条</span>回复
</span>
</div>
<div class="msg-lists-item-right">
<span th:text="${msg.lastShowTime}">2017-8-10</span>
</div>
</div>

Categories