Vue.js course Β· Module 5: Events & Forms

Project: Mission Control System

2 min read
In this lesson2

Final project of the Control Simulator! Build a complete form and event system for space mission control at NOVA LAB station.

Module Summary

In this module you learned the key interaction mechanisms in Vue.js, which are the foundation of every web application:

  • Event handling - directives @click, @input, @submit, @keydown and other DOM events that allow reacting to user actions in real time
  • Event modifiers - .prevent (blocking default action), .stop (stopping propagation), .once (one-time call), .self (only on target element) - precise control of event behavior
  • v-model - two-way data binding in forms that automatically synchronizes data between the template and component state
  • v-model modifiers - .lazy (update on blur), .number (automatic number conversion), .trim (whitespace removal) - customizing binding behavior
  • Advanced forms - checkboxes, radio buttons, selects, textarea and other controls that connect with Vue's reactive data

Each of these mechanisms corresponds to a control system at NOVA LAB station - events are buttons on the control panel, v-model is two-way communication with sensors, and modifiers are signal filters ensuring precision.

Project Requirements

Build a Mission Registration Panel - a complete interface for planning Martian expeditions that includes:

1. Mission data form

Create an extensive form with fields: mission name (text input), destination (select with options: Olympus Mons, Valles Marineris, Polar Cap, Jezero Crater), priority (radio: low/medium/high/critical), estimated duration (number input in days), mission description (textarea), and safety regulations consent (checkbox). Each field should be bound to reactive state via v-model.

2. v-model modifiers

Use .trim on text fields (mission name, description) to automatically remove unnecessary spaces. Apply .number on the duration field so the value is always a number. Use .lazy on the mission description field so the update happens only after leaving the field - this reduces the number of re-renders.

3. Form validation

Implement real-time validation: mission name minimum 3 characters, description minimum 20 characters, duration between 1 and 365 days, required safety regulations consent. Display error messages under each field. The submit button should be disabled when the form is invalid.

4. Event handling and summary

Add @submit.prevent on the form, Escape key handling (@keydown.escape) to clear the form with confirmation (confirm()), and display a summary of the registered mission after form submission. Also add a history of registered missions displayed below the form.

Expand the starter below!

Code for this lesson: App.vue
1<script setup>
2import { ref, reactive } from 'vue'
3
4// Mission Control System - Final project of module 5
5const missionForm = reactive({
6  name: '',
7  destination: 'mars',
8  priority: 'normal',
9  description: '',
10  crewSize: 4,
11  acceptTerms: false
12})
13
14const errors = ref({})
15const submitted = ref(false)
16const missions = ref([])
17
18function validate() {
19  const e = {}
20  if (!missionForm.name.trim()) e.name = 'Mission name is required'
21  if (!missionForm.description.trim()) e.description = 'Description is required'
22  if (missionForm.crewSize < 1 || missionForm.crewSize > 20) e.crewSize = 'Crew: 1-20 people'
23  if (!missionForm.acceptTerms) e.acceptTerms = 'You must accept the terms'
24  errors.value = e
25  return Object.keys(e).length === 0
26}
27
28function submitMission() {
29  if (!validate()) return
30  missions.value.push({ ...missionForm, id: Date.now() })
31  resetForm()
32  submitted.value = true
33  setTimeout(() => { submitted.value = false }, 3000)
34}
35
36function resetForm() {
37  missionForm.name = ''
38  missionForm.destination = 'mars'
39  missionForm.priority = 'normal'
40  missionForm.description = ''
41  missionForm.crewSize = 4
42  missionForm.acceptTerms = false
43  errors.value = {}
44}
45</script>
46
47<template>
48  <div class="mission-control">
49    <h1>NOVA LAB Mission Registration</h1>
50
51    <div v-if="submitted" class="success">Mission registered successfully!</div>
52
53    <form @submit.prevent="submitMission" @keyup.escape="resetForm">
54      <div class="field">
55        <label>Mission name</label>
56        <input v-model.trim="missionForm.name" placeholder="Enter a name..." />
57        <span class="error" v-if="errors.name">{{ errors.name }}</span>
58      </div>
59
60      <div class="field">
61        <label>Travel destination</label>
62        <select v-model="missionForm.destination">
63          <option value="mars">Mars</option>
64          <option value="europa">Europa (Jupiter)</option>
65          <option value="titan">Titan (Saturn)</option>
66          <option value="asteroid">Asteroid Belt</option>
67        </select>
68      </div>
69
70      <div class="field">
71        <label>Priority</label>
72        <div class="radio-group">
73          <label><input type="radio" v-model="missionForm.priority" value="low" /> Low</label>
74          <label><input type="radio" v-model="missionForm.priority" value="normal" /> Normal</label>
75          <label><input type="radio" v-model="missionForm.priority" value="high" /> High</label>
76        </div>
77      </div>
78
79      <div class="field">
80        <label>Crew size</label>
81        <input type="number" v-model.number="missionForm.crewSize" min="1" max="20" />
82        <span class="error" v-if="errors.crewSize">{{ errors.crewSize }}</span>
83      </div>
84
85      <div class="field">
86        <label>Mission description</label>
87        <textarea v-model.lazy="missionForm.description" rows="3" placeholder="Describe the mission goal..."></textarea>
88        <span class="error" v-if="errors.description">{{ errors.description }}</span>
89      </div>
90
91      <div class="field">
92        <label><input type="checkbox" v-model="missionForm.acceptTerms" /> I accept the NOVA LAB terms</label>
93        <span class="error" v-if="errors.acceptTerms">{{ errors.acceptTerms }}</span>
94      </div>
95
96      <div class="actions">
97        <button type="submit">Register mission</button>
98        <button type="button" @click="resetForm">Clear (Escape)</button>
99      </div>
100    </form>
101  </div>
102</template>
103
104<style scoped>
105.mission-control { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
106h1 { color: #00d4ff; text-align: center; }
107.success { background: #1a4a1a; color: #00ff88; padding: 1rem; border-radius: 4px; text-align: center; margin: 1rem 0; }
108.field { margin: 1rem 0; }
109label { display: block; margin-bottom: 0.3rem; color: #aaa; }
110input, select, textarea { width: 100%; background: #1a1a4e; border: 1px solid #333; color: white; padding: 0.5rem; border-radius: 4px; box-sizing: border-box; }
111.radio-group { display: flex; gap: 1rem; }
112.radio-group label { display: flex; align-items: center; gap: 0.3rem; }
113.error { color: #ff4444; font-size: 0.8rem; }
114.actions { display: flex; gap: 1rem; margin-top: 1rem; }
115button { background: #00d4ff; color: #0a0a2e; border: none; padding: 0.7rem 1.5rem; cursor: pointer; border-radius: 4px; font-weight: bold; }
116button[type="button"] { background: #333; color: white; }
117</style>

Spotted a mistake in this lesson?

Useful articles