Adding New Events
Lesson objectives
In the previous lesson, we set up two-way binding for our form data using defineModel(). Now we need to:
- Handle form submission using the
.preventevent modifier - Emit a
submitevent from theEventFormcomponent (form data is already accessible viav-model) - Handle the submit event in
App.vueto add new events to theeventDataarray - Access form data directly from the bound
v-modelref in the parent component - Add validation to check required fields are filled in
- Generate unique IDs for new events
- Reset the form after successful submission
The current state
Currently, our form has a submit button, but clicking it doesn't do anything:
<button type="submit" class="submit-button">Add Event</button>
We need to:
- Prevent the default form submission behavior
- Emit a submit event to notify the parent (form data is already bound via
defineModel()) - Access the form data directly from the bound ref in the parent component
- Add the new event to the
eventDataarray
Handle form submission in EventForm
First, we'll update the EventForm component to handle form submission and emit the data.
Add the submit event to defineEmits
Update the defineEmits() call to include a submit event:
<script setup>
const props = defineProps({
show: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(["close", "submit"]);
// ... rest of component
</script>
Create a submit handler function
Add a function to handle form submission:
<script setup>
// ... existing code ...
const handleSubmit = () => {
// Validate required fields
if (!formData.value.name || !formData.value.date) {
alert("Please fill in the name and date fields");
return;
}
// Emit submit event (parent can read formData directly via v-model)
emit("submit");
// Reset form after submission
formData.value = {
name: "",
details: "",
date: "",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
};
// Close the modal
emit("close");
};
</script>
Key points:
- Validation: Checks that
nameanddateare filled (required fields) - Emit submit: Notifies the parent that the form was submitted (no need to pass data since it's already bound via
defineModel()) - Reset form: Clears all form fields after successful submission
- Close modal: Closes the modal after submission
Since we're using
defineModel(), the parent component already has access to the form data through thev-modelbinding. We only need to emit the submit event to notify the parent that submission occurred.
Update the form template
Add the submit handler to the form using the .prevent modifier:
<form class="modal-form" @submit.prevent="handleSubmit">
<!-- form inputs -->
<div class="form-actions">
<button type="button" class="cancel-button" @click="emit('close')">
Cancel
</button>
<button type="submit" class="submit-button">Add Event</button>
</div>
</form>
@submit.prevent- Listens for form submission and prevents the default page reload behaviortype="submit"- The button will trigger the form's submit eventtype="button"- The cancel button won't trigger form submission
The
.preventmodifier is equivalent to callingevent.preventDefault()in JavaScript. You can find out more in the Event Modifiers lesson.
Handle submit in App.vue
Now we need to listen for the submit event in the parent component and add the new event to the eventData array.
Listen for the submit event
Update the EventForm component in App.vue to listen for the submit event:
<template>
<div class="events-container">
<!-- ... existing content ... -->
<EventForm
:show="showModal"
v-model="formData"
@close="closeModal"
@submit="handleSubmit"
/>
</div>
</template>
Create the handleSubmit function
Add a function to handle the form submission in App.vue:
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import Event from "./components/Event.vue";
import EventForm from "./components/EventForm.vue";
// ... existing code ...
const handleSubmit = () => {
// Since formData is bound via v-model with defineModel, we can access it directly
// Create a new event object with a unique ID
const newEvent = {
id: Date.now(), // Simple ID generation using timestamp
...formData.value, // Spread the form data from the bound ref
};
// Add the new event to the eventData array
eventData.value.push(newEvent);
// Close the modal (formData will be reset in EventForm)
closeModal();
};
</script>
Key points:
- Direct access: Since
formDatais bound viav-modelwithdefineModel(), we can access it directly asformData.valuein the parent - Unique ID: Uses
Date.now()to generate a unique ID based on the current timestamp - Spread operator: Uses
...formData.valueto copy all properties from the bound form data - Array push: Adds the new event to the
eventDataarray using.push() - Close modal: Calls
closeModal()to hide the form and reset the form data
Since
eventDatais a reactiveref(), adding a new event will automatically update the UI to show the new event in the list! Also, because we're usingdefineModel(), the form data is automatically synchronized between parent and child components.
Complete EventForm component
Here's the complete EventForm.vue component with form submission:
<script setup>
const props = defineProps({
show: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(["close", "submit"]);
// Form data using defineModel
const formData = defineModel({
type: Object,
default: () => ({
name: "",
details: "",
date: "",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
}),
});
const backgroundOptions = [
"linear-gradient(135deg, #FF416C, #FF4B2B)",
"linear-gradient(135deg, #11998e, #38ef7d)",
"linear-gradient(135deg, #4E65FF, #92EFFD)",
"linear-gradient(135deg, #FC466B, #3F5EFB)",
"linear-gradient(135deg, #009FFF, #ec2F4B)",
"linear-gradient(135deg, #654ea3, #eaafc8)",
"linear-gradient(135deg, #667eea, #764ba2)",
"linear-gradient(135deg, #f093fb, #f5576c)",
];
const handleSubmit = () => {
// Validate required fields
if (!formData.value.name || !formData.value.date) {
alert("Please fill in the name and date fields");
return;
}
// Emit submit event (parent can read formData directly via v-model)
emit("submit");
// Reset form after submission
formData.value = {
name: "",
details: "",
date: "",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
};
// Close the modal
emit("close");
};
</script>
<template>
<div v-if="show" class="modal-overlay" @click="emit('close')">
<div class="modal" @click.stop>
<div class="modal-header">
<h2>Add New Event</h2>
<button class="close-button" @click="emit('close')">×</button>
</div>
<form class="modal-form" @submit.prevent="handleSubmit">
<div class="form-group">
<label for="name">Event Name *</label>
<input
id="name"
v-model="formData.name"
type="text"
required
placeholder="Enter event name"
/>
</div>
<div class="form-group">
<label for="details">Details</label>
<textarea
id="details"
v-model="formData.details"
placeholder="Enter event details"
rows="3"
></textarea>
</div>
<div class="form-group">
<label for="date">Date *</label>
<input id="date" v-model="formData.date" type="date" required />
</div>
<div class="form-group">
<label>Background Color</label>
<div class="color-options">
<div
v-for="(bg, index) in backgroundOptions"
:key="index"
class="color-option"
:class="{ active: formData.background === bg }"
:style="{ background: bg }"
@click="formData.background = bg"
></div>
</div>
</div>
<div class="form-actions">
<button type="button" class="cancel-button" @click="emit('close')">
Cancel
</button>
<button type="submit" class="submit-button">Add Event</button>
</div>
</form>
</div>
</div>
</template>
Complete App.vue updates
Here's the relevant parts of App.vue with form submission handling:
<script setup>
import { ref, computed } from "vue";
import Event from "./components/Event.vue";
import EventForm from "./components/EventForm.vue";
const showPastEvents = ref(true);
const showModal = ref(false);
// Form data for new event
const formData = ref({
name: "",
details: "",
date: "",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
});
// Event data array
const eventData = ref([
{
id: 1,
name: "Graduation",
details: "wooohoo!!!",
date: "2025-06-12",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
},
// ... other events
]);
// ... existing functions (calculateDaysLeft, filteredEvents, etc.) ...
const openAddModal = () => {
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
resetForm();
};
const resetForm = () => {
formData.value = {
name: "",
details: "",
date: "",
background: "linear-gradient(135deg, #FF416C, #FF4B2B)",
};
};
const handleSubmit = () => {
// Since formData is bound via v-model with defineModel, we can access it directly
// Create a new event object with a unique ID
const newEvent = {
id: Date.now(),
...formData.value, // Access the bound form data
};
// Add the new event to the eventData array
eventData.value.push(newEvent);
// Close the modal (formData will be reset in EventForm)
closeModal();
};
</script>
<template>
<div class="events-container">
<div class="header">
<h1 class="title">Vue Countdown</h1>
<div class="controls">
<button @click="openAddModal" class="add-button">+ Add Event</button>
<label class="checkbox-label">
<input type="checkbox" v-model="showPastEvents" />
Show past events
</label>
</div>
</div>
<Event
v-for="event in filteredEvents"
:key="event.id"
:name="event.name"
:details="event.details"
:background="event.background"
:days-left="calculateDaysLeft(event.date)"
/>
<EventForm
:show="showModal"
v-model="formData"
@close="closeModal"
@submit="handleSubmit"
/>
</div>
</template>
Testing the functionality
Now you should be able to:
- Click the "+ Add Event" button to open the form
- Fill in the event name, date, and optionally details and background color
- Click "Add Event" to submit the form
- See the new event appear in the list immediately
- The form should close and reset after submission
Try adding a new event! The new event should appear in your events list with the correct styling and days calculation.
Form validation
The current validation is basic - it only checks if required fields are filled. You could enhance this by:
- Validating date format
- Ensuring the date is in the future (or allowing past dates)
- Checking name length
- Showing validation errors in the UI instead of using
alert()
For now, the basic validation ensures users can't submit empty required fields.
The form is now fully functional! Users can add new events that will appear in the event list with proper styling and date calculations. By using defineModel(), we've simplified the data flow. The parent component can directly access the form data without needing it passed through the submit event.