Parent-child communication in VueJS - javascript

I have two Vue components. The parent-component:
Vue.component('parent-component',{
methods: {
test: function(){
alert('Option Selected');
}
},
template: `
<div><slot></slot></div>
`
});
And the animals component:
Vue.component('animals',{
data: function(){
return {
selected: ''
}
},
template: `
<select #change="selectionChanged" v-model="selected">
<slot></slot>
</select>
`,
methods: {
selectionChanged: function(){
this.$emit('optionselected', this.selected);
}
}
});
And here is my HTML:
<div id="app">
<parent-component #optionselected="test()">
<animals>
<option>Aardvark</option>
<option>Bear</option>
<option>Cat</option>
</animals>
</parent-component>
</div>
I am trying to get the selected option from child component (animals) to the parent component (parent-component). I am emitting the optionselected event from the child, but it looks like the parent component is not responding to that event, I mean the method test() is not being called at all. What am I doing wrong here?
Here is the JSFiddle Demo

First, you need to add the listener to the animals component in your template.
<animals #optionselected="test">
Second, it's important to remember that because you are using slots, the elements inside the slots are going to be evaluated in the scope of the component in which they are defined. In this case, that scope is the Vue's scope, not the parent-component scope. In order to allow the elements defined inside a slot to use the containing components data, methods, etc, you need to define a scoped slot. That being the case, your parent component would end up looking like this:
<div><slot :test="test"></slot></div>
And your Vue template becomes
<parent-component>
<template scope="{test}">
<animals #optionselected="test">
<option>Aardvark</option>
<option>Bear</option>
<option>Cat</option>
</animals>
</template>
</parent-component>
Here is the updated code.
console.clear()
Vue.component('parent-component', {
methods: {
test: function(option) {
alert('Option Selected ' + option);
}
},
template: `
<div><slot :test="test"></slot></div>
`
});
Vue.component('animals', {
data: function() {
return {
selected: ''
}
},
template: `
<select #change="selectionChanged" v-model="selected">
<slot></slot>
</select>
`,
methods: {
selectionChanged: function() {
this.$emit('optionselected', this.selected);
}
}
});
new Vue({
el: "#app",
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<parent-component>
<template scope="{test}">
<animals #optionselected="test">
<option>Aardvark</option>
<option>Bear</option>
<option>Cat</option>
</animals>
</template>
</parent-component>
</div>

Related

v-for with model vuejs

I need to execute a v-for, however I do not know how to add a v-model for each different component inside of v-for;
<template>
<ProfilePasswordField
v-for="(item, index) in profilePasswordItems"
:key="index"
:profilePasswordItem="item"
v-model="???"
>
</template>
This v-for will always be three items and I want to name the v-model's as: ['passaword', 'newPassword', confirmNewPassword']
How can I add those names dinamically for the v-model inside v-for?
I tried to do a list inside data() but it did not work. Something like that:
data (){
return{
listPassword: ['passaword', 'newPassword', 'confirmNewPassword']
}
},
methods: {
method1 () {
console.log(this.passaword)
console.log(this.newPassword)
console.log(this.confirmNewPassword)
}
}
The v-model directives cannot update the iteration variable itself therefore we should not use a linear array item in for-loop as the v-model variable.
There is another method you can try like this-
In the parent component-
<template>
<div>
<ProfilePasswordField
v-for="(item, index) in listPassword"
:key="index"
:profilePasswordItem="item"
v-model="item.model"
/>
<button #click="method1()">Click to see changes</button>
</div>
</template>
<script>
export default {
name: "SomeParentComponent",
data() {
return {
listPassword: [
{ model: "passaword" },
{ model: "newPassword" },
{ model: "confirmNewPassword" },
],
};
},
methods: {
method1() {
this.listPassword.forEach((item) => {
console.log(item.model);
});
},
},
}
</script>
And in your ProfilePasswordField you can emit the input event to listen to the respected changes in v-model binding. For example, the ProfilePasswordField component could be like this-
<template>
<v-text-field :value="value" #input="$emit('input', $event)"/>
</template>
<script>
export default {
name: "ProfilePasswordField",
props: {
value: {
required: true
}
}
</script>
In the parent component's console, you should see the changes made by the child component.

problematically generated tree of components in vue2

I have a scenario which i need to build a nested menu and the menu can have an infinite level of nested layers (even thought this will not happen) and I'm wanting to know what is the best way of building the list of child components dynamically?
This is some test code that I put together to try some dynamic code from a function that would essentially give me the list of components in an array etc. What is the best way of problematically building up the child components tree?
<template>
<a-row>
<div v-html="getContent()"></div>
</a-row>
</template>
<script>
export default {
methods: {
getContent() {
return `<div #click="sayHello()">RYAN</div>`
},
sayHello() {
console.log('Hello there');
}
}
}
</script>
You can try with :is and pass component:
new Vue({
el: '#demo',
data() {
return {
component: '',
contents: ['RYAN', 'DJURO', 'SPIRO']
}
},
methods: {
getContent() {
this.component = 'comp'
},
}
})
Vue.component('comp', {
template: `<div #click="sayHello">{{ content }}</div>`,
props: ['content'],
methods: {
sayHello() {
console.log('Hello there ' + this.content);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="getContent">getContent</button>
<ul v-if="component">
<li v-for="(cont, i) in contents" :key="i">
<component :is="component" :content="cont"> </component>
</li>
</ul>
</div>

vue api $slots.default is not evaluated again when rendered inside slot with v-if

Vue slot provides the developers the excellent flexibility.
To communicate parent and child inside slot, to use eventbus is a good way.
But I faced some issues when using $slots api in the parent.
The $slots api is not evaluated again when slot content is rendered by v-if directive.
For example,
Vue.component('parent', {
template:'<div v-if="visible"> <slot></slot></div>',
data(){
return{
visible:false
}
},
mounted(){
this.$on("triggerVisible", ()=>{
this.visible = !this.visible;
//emit event to children in slot
this.emitEventToChildren();
})
},
methods:{
emitEventToChildren(){
this.$nextTick(()=>{
this.$slots.default.forEach(item=>{
if(item.child)
item.child.$emit("eventFromParent", true);
})
})
}
}
});
Vue.component('child', {
template:'<div> <slot></slot></div>',
data(){
return{};
},
mounted(){
this.$on("eventFromParent", ()=>{
this.$el.classList.add("event");
})
},
});
var app = new Vue({
el:'#app',
methods:{
triggerParent(){
this.$refs.parent.$emit("triggerVisible");
}
}
})
div.event{
background:#999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent ref="parent">
<child>
<div >
Child1
</div>
</child>
<child>
<div >
Child2
</div>
</child>
<child>
<div>
Child3
</div>
</child>
</parent>
<button #click="triggerParent()">
Trigger
</button>
</div>
As you can see in the example, when clicking the button at the first time, it works as expected.
Css style class event is added to each element.
But when clicking the trigger button 2 or more times, this.$slots.default is not evaluated again.
If v-show is used instead of v-if, it works fine.
Please let me know what the reason is.
You should avoid directly manipulating the DOM with this.$el.classList. For God sake, you are using Vue! Change the template to dynamically render different class depending on the component state and either use a global event bus for the events or simply directly call the corresponding component's method (not a good practice but acceptable). Also, do not forget to unsubscribe from the event(s) when the component is unmounted - otherwise you will end up with duplicate handlers in development mode with hot-reloading:
Vue.component('parent', {
template:'<div v-if="visible"> <slot></slot></div>',
data(){
return{
visible:false
}
},
mounted(){
this.$root.$on("triggerVisible", ()=>{
this.visible = !this.visible;
//emit event to children in slot
this.emitEventToChildren();
})
},
beforeDestroy()
{
this.$root.$off('triggerVisible');
},
methods:
{
emitEventToChildren()
{
this.$nextTick(()=>
{
this.$root.$emit('eventFromParent');
})
}
}
});
Vue.component('child', {
template:'<div :class="classList"> <slot></slot></div>',
data(){
return {classList: ''};
},
mounted(){
this.$root.$on("eventFromParent", ()=>{
this.classList = "event";
})
},
beforeDestroy()
{
this.$root.$off('eventFromParent');
}
});
var app = new Vue({
el:'#app',
methods:{
triggerParent(){
this.$emit("triggerVisible");
}
}
})
div.event{
background:#999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent ref="parent">
<child>
<div >
Child1
</div>
</child>
<child>
<div >
Child2
</div>
</child>
<child>
<div>
Child3
</div>
</child>
</parent>
<button #click="triggerParent()">
Trigger
</button>
</div>

How to reference a child component from the parent in Vue

I'm trying to figure out the Vue-way of referencing children from the parent handler.
Parent
<div>
<MyDropDown ref="dd0" #dd-global-click="ddClicked"></MyDropDown>
<MyDropDown ref="dd1" #dd-global-click="ddClicked"></MyDropDown>
<MyDropDown ref="dd2" #dd-global-click="ddClicked"></MyDropDown>
</div>
export default {
methods: {
ddClicked: function(id) {
console.log("I need to have MyDropDown id here")
}
}
}
Child
<template>
<h1>dropdown</h1>
<Button #click="bclick"></Button>
</template>
export default {
methods: {
bclick: function() {
this.$emit('dd-global-click')
}
}
}
In the parent component I need to see which dropdown was clicked.
What I've tried so far
I tried to set "ref" attribute in the parent. But I can't refer to this prop within the child component. Is there a way to do it? There is nothing like this.ref or this.$ref property.
I tried to use $event.targetElement in the parent, but it looks like I'm mixing Real DOM and Vue Components together. $event.targetElement is a DOM like . So in the parent I have to go over the tree until I find my dropdown. It is ugly I guess.
I set an additional :id property for the dropdown making it the copy of the 'ref' property. In the blick and I called this.$emit('dd-global-click', this.id). Later in the parent I check this.$refs[id]. I kind of works, but I'm not really content with it, because I have to mirror attributes.
Using the _uid property didn't work out either. On top of that, I think, that since it starts with an underscore it is not a recommended way to go.
It seems like a very basic task, so there must be a simplier way to achieve this.
If this custom dropdown element is the top level one (the root element) in the component, you could access the native DOM attributes (like id, class, etc) via this.$el, once it's mounted.
Vue.component('MyDropdown', {
template: '#my-dropdown',
props: {
items: Array
},
methods: {
changed() {
this.$emit('dd-global-click', this.$el.id);
}
}
})
new Vue({
el: '#app',
data: () => ({
items: [
{
id: 'dropdown-1',
options: ['abc', 'def', 'ghi']
},
{
id: 'dropdown-2',
options: ['jkl', 'lmn', 'opq']
},
{
id: 'dropdown-3',
options: ['rst', 'uvw', 'xyz']
}
]
}),
methods: {
ddClicked(id) {
console.log(`Clicked ID: ${id}`);
}
}
})
Vue.config.devtools = false;
Vue.config.productionTip = false;
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11"></script>
<div id="app">
<my-dropdown
v-for="item of items" :key="item.id"
:id="item.id"
:items="item.options"
#dd-global-click="ddClicked">
</my-dropdown>
</div>
<script id="my-dropdown" type="text/x-template">
<select #input="changed">
<option v-for="item of items" :key="item" :value="item">
{{item}}
</option>
</select>
</script>

Property or method "value" is not defined on the instance in vue js 2 nested component

I wrote a basic vue js 2 basic example to test nested components.
Below is components and template structure.
Vue.component('form-com', {
template: '#form',
props: ['value'],
methods: {
onInput: function (event) {
this.$emit('input', event.target.value);
}
}
});
Vue.component('message-com', {
template: '#message',
data: function () {
return {
msg: 'Hello'
}
},
props: ['user']
});
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
user: 'ahmad'
}
}
});
new Vue({
el: '#container'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<!--Form Template-->
<template id="form">
<div>
<div class="form-control">
<label>Enter Your Name:</label>
<input type="text" v-bind:value="value" :input="onInput">
</div>
</div>
</template>
<!--Hello Message Template-->
<template id="message">
<div>
<h3>{{msg}} {{user}}</h3>
</div>
</template>
<template id="welcome">
<div>
<form-com :value="value"></form-com>
<br>
<message-com :user="user"></message-com>
</div>
</template>
<div id="container">
<welcome-com></welcome-com>
</div>
But when run app in browser this error is shown:
[Vue warn]: Property or method "value" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
found in
---> <WelcomeCom>
<Root>
what is problem?
Update:
I just Rewrite this Fiddle from one of chapters of Learning Vue.js 2. I just rename some parameters and component and templates names. but when I copy main fiddle to my code all things worked.
In your form-com component you can set up a v-model which binds the input value and set up a watcher to observer the changes in the input which in turn emits an custom-event which telss the parent comonent that a change has taken place.
Vue.component('form-com', {
template: '#form',
data(){
return{
myInput:''
}
},
watch: {
myInput: function (inputVal) {
this.$emit('input', inputVal);
}
}
});
Vue.component('message-com', {
template: '#message',
data: function () {
return {
msg: 'Hello'
}
},
props: ['user']
});
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
user: 'ahmad'
}
},
methods:{
updateUser(value){
this.user = value;
}
}
});
new Vue({
el: '#container'
})
You can listen to the events emitted from the child **form-com ** component using v-on:input or shorthand #input directly in the parent template (welcome component) where the child component is used.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<!--Form Template-->
<template id="form">
<div>
<div class="form-control">
<label>Enter Your Name:</label>
<input type="text" v-model="myInput">
</div>
</div>
</template>
<!--Hello Message Template-->
<template id="message">
<div>
<h3>{{msg}} {{user}}</h3>
</div>
</template>
<template id="welcome">
<div>
<form-com #input="updateUser($event)" ></form-com>
<br>
<message-com :user="user"></message-com>
</div>
</template>
<div id="container">
<welcome-com></welcome-com>
</div>
Here is the jsFiddle
If you don't want to use a watcher then you can do it using computed setter . Have a look at the fiddle which is using a computed-setter
You are missing in your Component 'welcome-com' the value object:
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
value: '',
user: 'ahmad'
}
}
});

Categories

Resources