Adding New Events

vue.js logo

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 .prevent event modifier
  • Emit a submit event from the EventForm component (form data is already accessible via v-model)
  • Handle the submit event in App.vue to add new events to the eventData array
  • Access form data directly from the bound v-model ref 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:

  1. Prevent the default form submission behavior
  2. Emit a submit event to notify the parent (form data is already bound via defineModel())
  3. Access the form data directly from the bound ref in the parent component
  4. Add the new event to the eventData array

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 name and date are 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 the v-model binding. 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 behavior
  • type="submit" - The button will trigger the form's submit event
  • type="button" - The cancel button won't trigger form submission

The .prevent modifier is equivalent to calling event.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 formData is bound via v-model with defineModel(), we can access it directly as formData.value in the parent
  • Unique ID: Uses Date.now() to generate a unique ID based on the current timestamp
  • Spread operator: Uses ...formData.value to copy all properties from the bound form data
  • Array push: Adds the new event to the eventData array using .push()
  • Close modal: Calls closeModal() to hide the form and reset the form data

Since eventData is a reactive ref(), adding a new event will automatically update the UI to show the new event in the list! Also, because we're using defineModel(), 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')">&times;</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:

  1. Click the "+ Add Event" button to open the form
  2. Fill in the event name, date, and optionally details and background color
  3. Click "Add Event" to submit the form
  4. See the new event appear in the list immediately
  5. 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.