Passing functions as props to child components - javascript

Assuming I have a component named Modal which I want to reuse across my application and in the meanwhile I also want to dynamically bind functions to its Yes button, how can I pass a function to the #click event of the Yes button in my Modal as a prop. For instance:
//data tags are used for fast markup here only
<tamplate>
<div>
<div :id="`${id}`" data-modal>
<div data-modal-title>
{{title}}
</div>
<div data-modal-body>
{{body}}
</div>
<div data-modal-footer>
<button #click="//event to be passed//" data-modal-button-ok>Yes</button>
<button data-modal-button-cancel>Yes</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'modal',
props: [
'id',
'title',
'body',
// event?
]
}
</script>
and when using this modal, how should the event be passed?
<Modal id="x" title="y" body="x" //event="????"//></Modal>

You should $emit an event (name, say, yes) at the modal:
<button #click="$emit('yes')" data-modal-button-ok>Yes</button>
Or, use a method:
<button #click="handleYesClick" data-modal-button-ok>Yes</button>
methods: {
handleYesClick() {
this.$emit('yes');
}
}
And listen to it in the parent using:
<modal ... v-on:yes="someCodeToExecute"></modal>
or (shorthand):
<modal ... #yes="someCodeToExecute"></modal>
Demo:
Vue.component('modal', {
template: "#modal",
name: 'modal',
props: ['id', 'title', 'body']
})
new Vue({
el: '#app',
data: {},
methods: {
methodAtParentYes() {
alert('methodAtParentYes!');
},
methodAtParentCancel() {
alert('methodAtParentCancel!');
}
}
})
<script src="https://unpkg.com/vue"></script>
<template id="modal">
<div>
<div :id="`${id}`" data-modal>
<div data-modal-title>
{{title}}
</div>
<div data-modal-body>
{{body}}
</div>
<div data-modal-footer>
<button #click="$emit('yes')" data-modal-button-ok>Yes</button>
<button #click="$emit('cancel')" data-modal-button-cancel>Cancel</button>
</div>
</div>
</div>
</template>
<div id="app">
<modal id="1" title="My Title" body="Body" #yes="methodAtParentYes" #cancel="methodAtParentCancel"></modal>
</div>

There are two ways to do this.
The first way is you could pass the method down as a prop, just like you would pass anything else and then in the Modal component just call that prop right in the click handler.
Parent.vue
<template>
<Modal id="x" title="y" body="x" :handleYes="handleYes"></Modal>
</template>
<script>
methods: {
handleYes () {
// do something
}
}
</script>
Modal.vue
<button #click="handleYes()">Yes</button>
The other way is to use $emit. So in Modal.vue you would define a method to emit an event and then listen for that event in the parent and do call the method there.
Modal.vue
<template>
<button #click="emitEvent">Yes</button>
</template>
<script>
methods: {
emitEvent () {
this.$emit('userClickedYes')
}
}
</script>
Parent.vue
<template>
<Modal id="x" title="y" body="x" #userClickedYes="handleYes"></Modal>
</template>
<script>
methods: {
handleYes () {
// do something
}
}
</script>

Related

Cannot read properties of undefined Nuxt.js

I am trying to call the showMenu method on the click event based on the value passed on "open" variable.
But I m getting cannot read properties of undefined while, open is a defined variable.
I have tried to console log something on the click events it works, but whenever I try to interact with the open variable which is defined to the best of my knowledge I am getting the undefined error message.
<template>
<nav>
<div class="header-one">
<div class="header">
<div class="logo">Logo</div>
<div class="navigation" id="nav">
<nuxt-link to="/">Home</nuxt-link>
<nuxt-link to="/">About</nuxt-link>
<nuxt-link to="/">Login</nuxt-link>
<nuxt-link to="/">Logout</nuxt-link>
<nuxt-link to="/">Profile</nuxt-link>
</div>
**<div class="burger" id="burger" #click="showMenu">**
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<MobileMenu v-if="this.open"/>
</nav>
</template>
<script>
import MobileMenu from "~/components/MobileMenu";
export default {
name: "HeaderMenu",
MobileMenu,
data: function(){
return{
**open: false,**
}
},
methods:{
showMenu:() =>{
**this.open = !this.open**
}
}
}
</script>
This happen because your showMenu function is an arrow function wich change the scope of this.
Here this refer to the scope of the function instead of the components
Use a classic function instead
Here is an example
new Vue({
el: '#app',
data: () => {
return {
open: false
}
},
methods: {
toggleOpen1(){
this.open = !this.open
},
toggleOpen2: () => {
this.open = !this.open
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>Open : {{ open }}</div>
<button #click="toggleOpen1">Toggle open 1</button>
<button #click="toggleOpen2">Toggle open 2</button>
</div>
As you can see, the toggleOpen1 (classic function) methods work whereas the toggleOpen2 (arrow function), doesn't work.

Bind element inside a for loop Vue not working properly

In the following Vue Component I want to loop through dwarfs array. And as long as I am in the current component, everything is fine (TEST) and also all the following properties are correct.
Currenct_Component.vue :
<template>
<div>
<h2>Stamm: {{ tribeName }}</h2>
<div class="card-container">
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
<!-- TEST -->
<p>{{dwarf}}</p>
<!-- CHILD COMPONENT -->
<app-modal
:showModal="showModal"
:targetDwarf="dwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<!-- <img class="card-img-top" src="" alt="Card image cap">-->
<div class="card-body">
<h3 class="card-title" ref="dwarfName">{{ dwarf.name }}</h3>
<hr>
<ul class="dwarf-details">
<li><strong>Alter:</strong> {{ dwarf.age }}</li>
<li><strong>Waffen:</strong>
<ul v-for="weapon in dwarf.weapons">
<li><span>Name: {{ weapon.name }} | Magischer Wert: {{ weapon.magicValue }}</span></li>
</ul>
</li>
<li><strong>Powerfactor:</strong> {{ dwarf.weapons.map(weapon => weapon.magicValue).reduce((accumulator, currentValue) => accumulator + currentValue) }}</li>
</ul>
<button class="card-button" #click="showModal = true"><span class="plus-sign">+</span> Waffe</button>
</div>
</div>
</div>
<button id="backBtn" #click="onClick">Zurück</button>
</div>
</template>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
onClick() {
this.$emit('backBtn')
},
notifyApp() {
this.showModal = false;
this.$emit('weaponAdded');
}
},
}
</script>
But when I bind the element dwarf to the Child Component <app-modal/> it changes to the next dwarf in the array dwarfs (TEST) - (So as the result when i add a new weapon in the modal-form it gets added to the second dwarf...):
Child_Component.vue :
<template>
<div>
<div class="myModal" v-show="showModal">
<div class="modal-content">
<span #click="$emit('close')" class="close">×</span>
<h3>Neue Waffe</h3>
<!-- TEST -->
<p>{{ targetDwarf }}</p>
<form>
<input
type="text"
placeholder="Name..."
v-model="weaponName"
required
/>
<input
type="number"
placeholder="Magischer Wert..."
v-model="magicValue"
required
/>
<button #click.prevent="onClick">bestätigen</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
weaponName: '',
magicValue: '',
}
},
props: ['showModal', 'targetDwarf'],
methods: {
onClick() {
if(this.weaponName !== '' &&
Number.isInteger(+this.magicValue)) {
let newData = {...this.dwarf};
newData['weapons'] = [
...this.dwarf['weapons'],
{
"name": this.weaponName,
"magicValue": Number.parseInt(this.magicValue)
},
];
this.$http.post("https://localhost:5019/api", newData)
.then(data => data.text())
.then(text => console.log(text))
.catch(err => console.log(err));
this.$emit('weaponAdded');
} else {
alert('You should fill all fields validly')
}
},
}
}
</script>
It looks like you have the <app-modal/> component inside of the v-for="dwarf in dwarfs" loop, but then the control for showing all of the modal components created by that loop is just in one variable: showModal. So when showModal is true, the modal will show each of the dwarfs, and I'm guessing the second dwarf's modal is just covering up the first one's.
To fix this, you could move the <app-modal /> outside of that v-for loop, so there's only one instance on the page, then as part of the logic that shows the modal, populate the props of the modal with the correct dwarf's info.
Something like this:
<div class="card-container">
<div class="card" v-for="dwarf in dwarfs" :key="dwarf.name">
<p>{{dwarf}}</p>
<div class="card-body">
<button
class="card-button"
#click="() => setModalDwarf(dwarf)"
>
Waffe
</button>
</div>
</div>
<!-- Move outside of v-for loop -->
<app-modal
:showModal="!!modalDwarfId"
:targetDwarf="modalDwarfId"
#close="modalDwarfId = null"
#weaponAdded="onDwarfWeaponAdd"
/>
</div>
export default {
//....
data: () => ({
modalDwarfId: null,
)},
methods: {
setModalDwarf(dwarf) {
this.modalDwarfId = drawf.id;
},
onDwarfWeaponAdd() {
//...
}
},
}
You could then grab the correct dwarf data within the modal, from the ID passed as a prop, or pass in more granular data to the modal so it's more "dumb", which is the better practice so that the component isn't dependent on a specific data structure. Hope that helps
Courtesy of #Joe Dalton's answer, a bit alternated for my case:
<div class="card" style="width: 18rem;" v-for="dwarf in dwarfs" :key="dwarf.name">
...
<button class="card-button" #click="setModalDwarf(dwarf)"><span class="plus-sign">+</span> Waffe</button>
<div>
<app-modal
:showModal="showModal"
:targetDwarf="currentDwarf"
#close="showModal = false"
#weaponAdded="notifyApp"
/>
<script>
import Modal from './NewWeaponModal.vue';
export default {
data() {
return {
showModal: false,
currentDwarf: null,
}
},
components: { appModal : Modal },
props: ['tribeName', 'dwarfs'],
methods: {
setModalDwarf(dwarf) {
this.currentDwarf = dwarf;
this.showModal = true;
},
...
}
</script>

VueJS independent components events

I have a component that emit event through a bus as indicated below. The same component need to be included twice on another component. I want the events emitted to populate different variables;
//component.vue
<template>
<div>
Hello there?
<a #click="changed">New</a>
<ol>
<li v-for="option in list">
<div class='row justify-content-start'>
<div class='col-sm-6'><input v-model="option.value" type='text' placeholder="key"/></div>
<div class='col-sm-6'><input v-model="option.name" type='text' placeholder="Name"/></div>
</div>
</li>
</ol>
</div>
</template>
<script>
export default{
props:['options','iscolumn'],
data(){
return {list:this.options,item:{name:'',value:''}}
},
methods:{
changed(){
$bus.$emit('add-option',this.item,this.iscolumn);
}
}
}
</script>
/** root.vue **/
<template>
<div>
<h3>Rows</h3>
<div><rows :options="rows" :iscolumn="false"/></div>
<h3>Columns</h3>
<div><rows :options="columns" :iscolumn="true" /></div>
</div>
</template>
<script>
export default{
components:{'rows':require('./component')},
data(){
return {
columns:[],rows:[]
}
},
created(){
this.$bus.$on('add-option',(option,iscolumn)=>{
if (is_column) {this.columns.push(option);}
else this.rows.push(option);
})
}
}
</script>
When I click on the New from root both columns and rows get populated.
Looking for case where each of the component are independent, can't understand how they are sharing variables.
Any assistance will be appreciated.
Assign unique key attributes to the rows components:
<template>
<div>
<h3>Rows</h3>
<div><rows key="rows1" :options="rows" :iscolumn="false"/></div>
<h3>Columns</h3>
<div><rows key="rows2" :options="columns" :iscolumn="true" /></div>
</div>
</template>

How to change the data value of the parent file from the sub file in vue.js?

I have a brand new app in vue.js. I want to change the data value of the app.vue using the checkbox checked used in the child component how I will do this the code I' using in both the files is:-
component file:-
<template>
<div>
<h3>Edit the user</h3>
<p>Local: {{ $route.query.local }}</p>
<p>Number is:- {{ $route.query.q }}</p>
<label><input type="checkbox" #click="change()">customizer</label>
</div>
</template>
<script>
export default{
props:["changeParentStatus"],
methods:{
change() {
this.active = !this.active
console.log(this.active)
this.changeParentStatus(this.active);
}
}
}
</script>
app.vue file
<template>
<div :class="[{ 'active': active }]">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<h1>Routing</h1>
<hr>
<app-header></app-header>
<router-view :changeParentStatus="changeStatus"></router-view>
</div>
</div>
</div>
</template>
<script>
import AppHeader from "./components/Header.vue";
export default {
data(){
return{
active: true
}
},
methods:{
changeStatus(active){
this.active=active
}
},
components:{
appHeader: AppHeader
}
}
</script>
<style>
.active{
background: black;
}
</style>
How will I change the value of the app.vue data active to true or false by changing the checkbox used in the component file. Can anybody please help me to do this.
Folder structure is:-
src---> app.vue
components---->users----->userEdit.vue (component file)
you can pass a function as an attribute to the child component
<app-header :changeParentStatus="changeStatus"></app-header>
parent
callback
<script>
import AppHeader from "./components/Header.vue";
export default {
data(){
return{
active: true
}
},
methods:{
changeStatus(active){
this.active=active
}
},
components:{
appHeader: AppHeader
}
}
</script>
now call "changeParentStatus" from child change function
<script>
export default{
props:["changeParentStatus"],
methods:{
change() {
this.active = !this.active
console.log(this.active)
this.changeParentStatus(this.active);
}
}
}
</script>
Looks like you want to pass the checkbox value from child to parent. For this define event on parent component and emit that event from child component.
In Parent handle event like this:
<router-view #changeParentStatus="changeStatus"></router-view>
The ChangeStatus is event/method so we defined using v-on: (or #shorthand)
Next, emit event from child component:
<label>
<input type="checkbox"
#click="$emit('changeParentStatus', $event.target.checked)">
customizer</label>
More info: Vue.JS Guide

Laravel+vue - Props is undefined

so I am new to vue, but getting up to speed. However, I cannot make the props thing to work - it always remains undefined in my child component.
The idea of the below is to create a app-wide notification modal window to display notifications.
This is my app.js
require('./bootstrap');
import ModalNotification from './components/Modal-Notification.vue';
const app = new Vue({
el: '#app',
data : {
formInputs: {},
formErrors: {},
showModal: false
},
components: {ModalNotification}
});
This is my Modal-Notification.vue
<template>
<transition name="modal">
<div class="modal-mask" #click="$emit('close')">
<div class="modal-wrapper">
<div class="modal-container" #click.stop>
<!-- <div class="modal-header">
<slot name="header">
NOTIFICATION
</slot>
</div> -->
<div class="modal-body">
<slot name="body">
Bla bla
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button btn btn-success" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'ModalNotification',
data: function() {
return {
};
},
props: ['showModal'],
mounted: function() {
console.log(this);
document.addEventListener("keydown", (e) => {
console.log(this.showModal);
if (this.showModal && e.keyCode == 27) {
this.$emit('close');
}
});
},
methods: {
}
}
</script>
And the relevant part of app.blade.php
<div class="container-fluid" id="app">
<button #click="showModal = true" class="btn btn-default">MODAL</button>
<modal-notification v-if="showModal" #close="showModal = false" :showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
<div id="wrapper">
#yield('sidebar')
#yield('content')
</div>
</div>
I've tried everything out there, except switching to Browserify, babel and such stuff, but I don't think it should be needed - webpack should work just fine.
Please help, if you can.
You have mistake in the following snippet of code:
<modal-notification v-if="showModal" #close="showModal = false" :showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
:showModal="false" is basically shortcut of v-bind:showModal="false", which tries to search vue instance variable in the value of attached HTML property(documentation). as you are passing false which is not a vue data variable, it is just passing null in showModal props.
If you want to pass only false, change the code to following:
<modal-notification v-if="showModal" #close="showModal = false" showModal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>
Edited
I think it is magic of camelCase-vs-kebab-case:
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents:
You need to pass : show-modal="false"
<modal-notification v-if="showModal" #close="showModal = false" show-modal="false">
<p slot="body" id="notification-message">hehe</p>
</modal-notification>

Categories

Resources