Hello,
I have a component called AccountPanel. It's a simple component with no real logic. The component has two slots. Inside of the main slot, I slot in a component. In the named slot (footer), I slot in a button (later to be a component as well). Pretty simple stuff. Here's some code.
<AccountPanel
panelDescription="Update your personal information."
panelId="personalInformation"
panelTitle="Personal Information"
>
<PersonalInformation @update-personal-information="alert('update')" />
<template v-slot:footer>
<button class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-black rounded-md border border-transparent shadow-sm hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" type="button" @click="alert('emit the event'); $emit('updatePersonalInformation')">Save</button>
</template>
</AccountPanel>
š From the docs, it looks like I'm doing what is shown, although my issue might be related to way I am using it with slots.
https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events āļø
Originally the @update-personal-information was inside of the component, but I moved it here as a test. I assume either is possible.
Any help is greatly appreciated! š
Update:
It doesn't make a difference if I do it outside of the component slot. Even a dumbed down version with two elements doesn't work.
What exactly are you trying to do here? I assume PersonalInformation
is a form and you're using the button within the footer slot to submit this? You then want to fire an event that gets picked up to say the form has been submitted?
Yeah, pretty much. The logic is in PersonalInformation component. This is the form. I need to fire it when clicking the button in the footer slot. I can't get any event emitting to work though. It may seem silly but some other components follow this way for good reason. Like the Addresses component. It contains logic for Google Places API, adding, updating, as well as the markup. I would never want a button for saving as it isn't relevant on say the checkout page, but it is for account addresses. Having one component makes more sense to me.
Gotcha. Not disputing the way you've done it, and that makes sense now. Working on a solution for you now!
I'm open to a better approach! This one just made more sense to me. Though it does seem silly for PersonalInformation right now, but later down the line, that component could also be used elsewhere. I'm all ears!
Ok, so I don't think emitting events is the right way to go here, since you have a child component looking to receive an event to handle. Events should be submitted upwards to parents.
Here's a better solution that I hope will work for you.
I guess your AccountPanel
looks like this.
<template>
<slot />
<slot name="footer" />
</template>
You could use your AccountPanel
component the same way, but instead add a ref
to the PersonalInformation
form to call a method within that child component.
<template>
<AccountPanel>
<PersonalInformation ref="form" />
<template v-slot:footer>
<button v-on:click="form.submit()">Click me</button>
</template>
</AccountPanel>
</template>
<script setup>
import { ref } from 'vue'
import AccountPanel from './components/AccountPanel.vue'
import PersonalInformation from './components/PersonalInformation.vue'
const form = ref(null)
</script>
In your PersonalInformation
component, use defineExpose
to expose the submit method.
<template>
<form action=""></form>
</template>
<script setup>
const submit = () => {
console.log('submit the form')
}
defineExpose({ submit })
</script>
Let me know if that makes sense!
Hey,
Thanks for that. Very close, but not quite. I am getting a new error now (see below).
Notable changes:
const updatePersonalInformation = () => {
Inertia.patch...etc...
defineExpose({
updatePersonalInformation,
});
const personalInformationForm = ref(null);
<PersonalInformation ref="personalInformationForm" />
The form:
<form class="grid grid-cols-6 gap-6" method="POST" v-on:submit.prevent="updatePersonalInformation">
Input fields... No submit button.
</form>
The submit button (footer slot):
<button class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-black rounded-md border border-transparent shadow-sm hover:bg-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500" type="submit" v-on:click="personalInformationForm.submit()">Save</button>
I have tested using the original names you supplied as well, it made no difference. The error:
TypeError: $setup.personalInformationForm.submit is not a function
The problem here is that you're using an on-submit handler on the form when this isn't necessary.
Instead of calling personalInformationForm.submit()
you need personalInformationForm. updatePersonalInformation()
(the method you're exposing) and to remove the event handler from your form.
An alternative approach would be to do this (I've not tested this code locally):
<template>
<AccountPanel>
<PersonalInformation />
<template v-slot:footer>
<button type="submit" form="personal-information-form">Click me</button>
</template>
</AccountPanel>
</template>
And in PersonalInformation
:
<template>
<form action="" id="personal-information-form" v-on:submit.prevent="submit"></form>
</template>
<script setup>
const submit = () => {
console.log('submit the form')
}
</script>
This works because using the form
attribute on a button will submit the form with the same ID, even if it's not within the form itself.
<button form="personal-information-form">Click me</button>
Thanks @alex! Everything is working now. Here is the final dumbed down result in case anyone else stumbles upon this thread.
const personalInformationForm = ref(null);
<AccountPanel
panelDescription="Update your personal information."
panelId="personalInformation"
panelTitle="Personal Information"
>
<PersonalInformation ref="personalInformationForm" />
<template v-slot:footer>
<button v-on:click="personalInformationForm.updatePersonalInformation()">Save</button>
</template>
</AccountPanel>
<script setup>
import { reactive } from "vue";
const updatePersonalInformation = () => {
// logic...
};
defineExpose({
updatePersonalInformation,
});
</script>
<template>
<form method="POST" v-on:submit.prevent="updatePersonalInformation">
<div>
<label for="first_name">First name</label>
<input v-model="form.first_name">
</div>
<div>
<label for="last_name">Last name</label>
<input v-model="form.last_name">
</div>
<div>
<label for="email">Email address</label>
<input v-model="form.email">
</div>
<div>
<label for="phone">Phone</label>
<input v-model="form.phone">
</div>
</form>
</template>
Awesome!