Von Katharina „Kat“ Schmidt & Nova Trent | Java Fleet Systems Consulting
Kat: Frontend-Spezialistin & Mentorin 🎨
Nova: Junior Developer & Lernende 🌟
📚 Was bisher geschah – Vue.js für Anfänger
Bereits veröffentlicht:
- ✅ Teil 1 : Setup & Erste API-Anbindung – Du hast Vue.js installiert, deine erste Component geschrieben und Tasks von der API geladen
- ✅ Teil 2 : Styling & Responsive Design – Du hast Tailwind CSS integriert und deine App schön gemacht
Heute: Teil 3 fokussiert auf Interaktivität – endlich nicht nur anschauen, sondern auch ERSTELLEN!
Neu in der Serie? Kein Problem – du kannst hier einsteigen, aber Teil 1 & 2 geben dir den vollen Kontext.
⚡ Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden
Dein Problem: Deine Task-App zeigt nur Tasks an. Du willst endlich eigene Tasks ERSTELLEN! 🎯
Die Lösung: Formulare mit Vue.js bauen – Two-Way Data Binding mit v-model!
Heute lernst du:
- ✅ v-model für Two-Way Data Binding
- ✅ Formulare in Vue.js bauen
- ✅ Input-Validierung (Browser + Custom)
- ✅ Submit-Events richtig handhaben
- ✅ POST-Requests zur API senden
- ✅ Liste nach Submit automatisch updaten
Dein größter Gewinn: Deine App wird interaktiv! Du kannst Tasks erstellen, nicht nur anschauen! 🚀
Zeit-Investment: 45-60 Minuten | Schwierigkeit: Anfänger-freundlich
👋 Nova: „Ich will Tasks ERSTELLEN!“
Hi Leute! Nova hier – und ich hab eine Mission! 💪
Letzte Woche haben wir meine Task-App schön gemacht. Tailwind CSS, responsive Design, alles super!
ABER…
Ich kann nur Tasks SEHEN. Nicht erstellen. Nicht editieren. Nur… anschauen.
Das ist wie Instagram nur lesen, aber nie posten. LANGWEILIG! 😴
Also bin ich zu Kat gegangen:
Nova: „Kat! Ich will nicht nur Tasks SEHEN – ich will welche ERSTELLEN!“
Kat: [schaut von ihrem Screen auf] „Perfect timing! Formulare sind der nächste logische Schritt.“
Nova: „Formulare? Das ist doch nur <input> und <button>, oder?“
Kat: [lacht] „Oh, Nova… wenn es nur so einfach wäre! Formulare sind die Schnittstelle zwischen User und Daten. Da kann SO viel schiefgehen!“
Nova: „Was kann denn schiefgehen bei einem simplen Input-Feld?!“
Kat: „Setz dich. Ich zeig’s dir.“ 😄
🎨 Kat: Formulare – Die unterschätzte Herausforderung
Hey! Kat hier! 👋
Nova denkt, Formulare sind easy. Das dachte ich auch… vor 5 Jahren. 😅
Die Wahrheit: Formulare sind das, wo die meisten Bugs passieren:
- User gibt falsche Daten ein
- Seite lädt neu (obwohl sie nicht sollte)
- Daten kommen nicht bei der API an
- Validation fehlt
- UX ist frustrierend
Aber! Vue.js macht Formulare eigentlich ziemlich elegant. Wenn du verstehst, was v-model macht.
🤔 Was ist v-model? (Kat’s Erklärung)
Nova fragt: „Ich hab schon von v-model gehört. Was ist das?“
Kat erklärt:
Traditionell (mit plain JavaScript):
<!-- HTML -->
<input id="title" type="text">
<script>
// Input lesen
const input = document.getElementById('title');
const value = input.value;
// Input schreiben
input.value = 'Neuer Wert';
// Auf Änderungen reagieren
input.addEventListener('input', (e) => {
console.log(e.target.value);
});
</script>
Mit Vue + v-model:
<template>
<input v-model="title" type="text">
<p>Du hast geschrieben: {{ title }}</p>
</template>
<script setup>
import { ref } from 'vue';
const title = ref('');
</script>
Das ist ALLES! 🤯
Nova: „Wait… WAS?! Das wars? Kein addEventListener? Kein .value?“
Kat: „Genau! v-model macht Two-Way Data Binding:“
- User tippt im Input → Variable wird automatisch geupdatet
- Du änderst Variable im Code → Input wird automatisch geupdatet
Nova: „Das ist… Magic!“
Kat: „Willkommen zur Vue.js Magic! 🪄 Aber lass mich dir zeigen, was wirklich passiert…“
🔍 v-model unter der Haube (für die Neugierigen)
Kat zeigt Nova den „Trick“:
Was v-model macht:
<!-- Das hier: --> <input v-model="title"> <!-- Ist eigentlich: --> <input :value="title" @input="title = $event.target.value" >
v-model ist Shortcut für:
:value="..."– Bindet Variable an Input (one-way)@input="..."– Updated Variable bei Input-Event (other way)
= Two-Way Binding! ✨
Nova: „Oh! Das macht Sinn! Es ist eigentlich zwei Dinge gleichzeitig!“
Kat: „Exactly! Aber du musst nicht immer den langen Weg schreiben. v-model reicht!“
🚀 Los geht’s: Erstes Task-Form bauen!
Kat: „Okay Nova, lass uns dein erstes Form bauen!“
Schritt 1: Neue Component erstellen
# Erstelle neue Datei src/components/TaskForm.vue
Schritt 2: Basic Form Structure
<!-- src/components/TaskForm.vue -->
<template>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">
➕ Neue Task erstellen
</h2>
<form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Title Input -->
<div>
<label
for="title"
class="block text-sm font-medium text-gray-700 mb-2"
>
Titel *
</label>
<input
id="title"
v-model="newTask.title"
type="text"
required
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Was möchtest du erledigen?"
>
</div>
<!-- Description Textarea -->
<div>
<label
for="description"
class="block text-sm font-medium text-gray-700 mb-2"
>
Beschreibung (optional)
</label>
<textarea
id="description"
v-model="newTask.description"
rows="3"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Details zu deiner Task..."
/>
</div>
<!-- Submit Button -->
<button
type="submit"
class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium px-6 py-3 rounded-lg transition-colors duration-200"
>
Task hinzufügen
</button>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
const newTask = ref({
title: '',
description: ''
});
function handleSubmit() {
console.log('Form submitted!', newTask.value);
// TODO: POST zu API
}
</script>
Nova testet: „Ich tippe im Input… und die Variable updated sich automatisch! Das ist SO cool!“ 🤩
Kat: „Told you! v-model ist Magic! Aber warte – wir haben ein Problem…“
⚠️ Problem: Seite lädt neu bei Submit!
Nova klickt auf „Task hinzufügen“ und… 💥
Nova: „HÄH?! Die Seite hat neu geladen! Alles ist weg!“ 😱
Kat: „Willkommen zum klassischen Form-Problem! Das ist das Default-Browser-Verhalten.“
Was passiert beim Submit?
Default Browser-Behavior:
<form> <!-- Wenn Submit-Button geklickt wird --> → Browser sendet Form-Daten an Server → Seite lädt neu (Full Page Reload) → Alle JavaScript-States sind weg </form>
Das wollen wir NICHT! Wir wollen:
- Daten per JavaScript/Fetch senden
- KEINE Page Reload
- Vue.js State bleibt erhalten
Die Lösung: @submit.prevent
<form @submit.prevent="handleSubmit"> <!-- ^ Das hier ist wichtig! --> </form>
Was macht .prevent?
<form @submit.prevent="handleSubmit">
<!-- Ist Shortcut für: -->
</form>
<form @submit="handleSubmit($event)">
<script>
function handleSubmit(event) {
event.preventDefault(); // ← Das macht .prevent!
// ... rest
}
</script>
</form>
.prevent = ruft event.preventDefault() auf!
Nova: „Oh! Das verhindert das Default-Verhalten!“
Kat: „Genau! Das ist ein Event Modifier in Vue. Super praktisch!“
🎯 Andere nützliche Event Modifiers (Quick Overview)
Kat zeigt Nova mehr Modifiers:
<!-- .prevent - Verhindert Default-Action --> <form @submit.prevent="handleSubmit"> <!-- .stop - Stoppt Event Propagation (wie stopPropagation()) --> <button @click.stop="handleClick"> <!-- .once - Event wird nur EINMAL gefeuert --> <button @click.once="handleOnce"> <!-- .enter - Nur bei Enter-Taste --> <input @keyup.enter="handleEnter"> <!-- .escape - Nur bei Escape-Taste --> <input @keyup.escape="handleEscape"> <!-- Kombinieren möglich! --> <form @submit.prevent.stop="handleSubmit">
Nova: „Wow, das sind wie… Hilfsfunktionen für Events!“
Kat: „Exactly! Vue gibt dir viele Shortcuts für häufige Patterns.“
📤 POST zur API senden (Der wichtige Teil!)
Kat: „Okay, jetzt kommt der spannende Teil – Daten zur API senden!“
Aktualisierte TaskForm.vue (mit POST)
<script setup>
import { ref } from 'vue';
const API_BASE = 'http://localhost:8080/api';
const AUTH = btoa('nova:learning123');
const newTask = ref({
title: '',
description: ''
});
const loading = ref(false);
const error = ref(null);
const successMessage = ref('');
async function handleSubmit() {
// Validation
if (!newTask.value.title.trim()) {
error.value = 'Titel darf nicht leer sein!';
return;
}
loading.value = true;
error.value = null;
successMessage.value = '';
try {
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: {
'Authorization': `Basic ${AUTH}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(newTask.value)
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const createdTask = await response.json();
// Success!
successMessage.value = '✅ Task erfolgreich erstellt!';
// Reset Form
newTask.value = {
title: '',
description: ''
};
// TODO: Parent Component benachrichtigen
} catch (e) {
error.value = `Fehler beim Erstellen: ${e.message}`;
} finally {
loading.value = false;
}
}
</script>
Template mit Loading & Error States
<template>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">
➕ Neue Task erstellen
</h2>
<!-- Success Message -->
<div
v-if="successMessage"
class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4"
>
<p class="text-green-800">{{ successMessage }}</p>
</div>
<!-- Error Message -->
<div
v-if="error"
class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4"
>
<p class="text-red-800">{{ error }}</p>
</div>
<form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Title Input -->
<div>
<label
for="title"
class="block text-sm font-medium text-gray-700 mb-2"
>
Titel *
</label>
<input
id="title"
v-model="newTask.title"
type="text"
required
:disabled="loading"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
placeholder="Was möchtest du erledigen?"
>
</div>
<!-- Description Textarea -->
<div>
<label
for="description"
class="block text-sm font-medium text-gray-700 mb-2"
>
Beschreibung (optional)
</label>
<textarea
id="description"
v-model="newTask.description"
rows="3"
:disabled="loading"
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
placeholder="Details zu deiner Task..."
/>
</div>
<!-- Submit Button -->
<button
type="submit"
:disabled="loading"
class="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-6 py-3 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2"
>
<span v-if="loading">⏳ Wird erstellt...</span>
<span v-else>➕ Task hinzufügen</span>
</button>
</form>
</div>
</template>
Nova: „Wow! Das sieht professionell aus! Loading State, Error Messages… alles da!“
Kat: „Genau! Das ist Production-Ready Error Handling. User müssen IMMER wissen was passiert!“
🔄 Problem: Liste updated nicht automatisch!
Nova testet:
Nova: „Ich hab eine Task erstellt… aber sie erscheint nicht in der Liste! 😕“
Kat: „Ahh, das klassische Problem! Deine TaskList Component weiß nicht, dass eine neue Task erstellt wurde!“
Nova: „Aber… wieso? Sie ist doch in der Datenbank!“
Kat: „Ja, aber deine TaskList hat die Tasks beim onMounted() geladen. Sie checkt nicht automatisch ob es neue gibt!“
Das Problem visualisiert:
TaskForm erstellt Task
↓
POST zur API
↓
API speichert Task
✅ Success!
ABER:
TaskList weiß nichts davon!
Die hat ihre Tasks schon geladen (beim Mount)
Und wartet nicht auf Updates!
Lösungsansätze (Kat erklärt 3 Optionen):
Option 1: Page Reload (Quick & Dirty)
// In TaskForm nach erfolgreicher POST window.location.reload();
❌ Schlecht! Full page reload = alle States weg, schlechte UX
Option 2: TaskList.fetchTasks() nochmal aufrufen
// In TaskForm nach erfolgreicher POST taskList.fetchTasks();
❌ Problem: TaskForm kennt TaskList nicht! Components sind isoliert!
Option 3: Emits! (DIE richtige Lösung)
// TaskForm emitted Event "task-created" // Parent (App.vue) hört auf Event // Parent sagt TaskList: "Bitte neu laden!"
✅ Perfekt! Saubere Component-Communication!
Nova: „Wait… Emits? Was ist das?!“
Kat: „Geduld! Das ist Component-Communication. Kommt in Teil 6. Für jetzt zeige ich dir einen Quick Fix…“
🔧 Quick Fix: TaskList neu laden (Simple Version)
Kat zeigt Nova eine einfache Lösung für jetzt:
App.vue (Parent Component)
<!-- src/App.vue -->
<template>
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<header class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900">
🎯 Task Manager
</h1>
</div>
</header>
<main class="py-6 sm:py-8 lg:py-10">
<div class="max-w-4xl mx-auto px-4">
<!-- Task Form -->
<TaskForm @task-created="handleTaskCreated" />
<!-- Task List -->
<TaskList ref="taskListRef" />
</div>
</main>
</div>
</template>
<script setup>
import { ref } from 'vue';
import TaskForm from './components/TaskForm.vue';
import TaskList from './components/TaskList.vue';
const taskListRef = ref(null);
function handleTaskCreated() {
// TaskList neu laden
if (taskListRef.value) {
taskListRef.value.fetchTasks();
}
}
</script>
TaskForm.vue (emitted Event)
<script setup>
import { ref } from 'vue';
const emit = defineEmits(['task-created']);
// ... rest bleibt gleich
async function handleSubmit() {
// ... validation & POST wie vorher
try {
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: {
'Authorization': `Basic ${AUTH}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(newTask.value)
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
// Success!
successMessage.value = '✅ Task erfolgreich erstellt!';
// Reset Form
newTask.value = {
title: '',
description: ''
};
// ✨ NEU: Emit Event!
emit('task-created');
} catch (e) {
error.value = `Fehler beim Erstellen: ${e.message}`;
} finally {
loading.value = false;
}
}
</script>
TaskList.vue (expose fetchTasks)
<script setup>
import { ref, onMounted } from 'vue';
// ... existing code
// ✨ NEU: Expose fetchTasks für Parent
defineExpose({
fetchTasks
});
</script>
Nova: „ES FUNKTIONIERT! Die Liste updated sich! 🎉“
Kat: „Slay! Aber versteh: Das ist die ‚Quick Fix‘ Version. In Teil 6 zeige ich dir die professionelle Component-Communication mit Props & Emits!“
🛡️ Input-Validation (Wichtig!)
Kat wird ernst:
„Nova, ein Form ohne Validation ist wie ein Auto ohne Bremsen. Gefährlich!“
Browser-Validation (Level 1)
<input v-model="title" type="text" required ← Browser checkt: Nicht leer minlength="3" ← Mindestens 3 Zeichen maxlength="100" ← Maximal 100 Zeichen > <input v-model="email" type="email" ← Browser checkt: Valide Email required > <input v-model="age" type="number" ← Browser checkt: Nur Zahlen min="18" ← Mindestens 18 max="99" ← Maximal 99 >
Vorteile:
- ✅ Funktioniert ohne JavaScript
- ✅ Schnelle Feedback
- ✅ Built-in Browser-UI
Nachteile:
- ❌ Nicht customizable (UI)
- ❌ Keine komplexen Rules
- ❌ User kann HTML ändern (Developer-Tools!)
Custom Validation (Level 2)
<script setup>
import { ref, computed } from 'vue';
const title = ref('');
// Validation Rules
const titleError = computed(() => {
if (!title.value.trim()) {
return 'Titel darf nicht leer sein!';
}
if (title.value.length < 3) {
return 'Titel muss mindestens 3 Zeichen haben!';
}
if (title.value.length > 100) {
return 'Titel darf maximal 100 Zeichen haben!';
}
return null; // Keine Errors
});
const isValid = computed(() => {
return !titleError.value;
});
</script>
<template>
<div>
<input
v-model="title"
type="text"
:class="{ 'border-red-500': titleError }"
>
<!-- Error Message -->
<p v-if="titleError" class="text-red-600 text-sm mt-1">
{{ titleError }}
</p>
<!-- Submit nur wenn valid -->
<button
type="submit"
:disabled="!isValid"
>
Submit
</button>
</div>
</template>
Nova: „Wow, das ist viel komplexer!“
Kat: „Ja, aber auch viel flexibler! Du kontrollierst alles: UI, Messages, Timing.“
📋 Komplettes Beispiel: TaskForm mit Validation
Hier ist die vollständige, production-ready TaskForm:
<!-- src/components/TaskForm.vue -->
<template>
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 class="text-2xl font-bold text-gray-800 mb-4">
➕ Neue Task erstellen
</h2>
<!-- Success Message -->
<div
v-if="successMessage"
class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4 flex items-center justify-between"
>
<p class="text-green-800">{{ successMessage }}</p>
<button
@click="successMessage = ''"
class="text-green-600 hover:text-green-800"
>
✕
</button>
</div>
<!-- Error Message -->
<div
v-if="error"
class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4 flex items-center justify-between"
>
<p class="text-red-800">{{ error }}</p>
<button
@click="error = ''"
class="text-red-600 hover:text-red-800"
>
✕
</button>
</div>
<form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Title Input -->
<div>
<label
for="title"
class="block text-sm font-medium text-gray-700 mb-2"
>
Titel *
</label>
<input
id="title"
v-model="newTask.title"
type="text"
:disabled="loading"
:class="[
'w-full border rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors',
titleError ? 'border-red-500' : 'border-gray-300',
loading && 'bg-gray-100 cursor-not-allowed'
]"
placeholder="z.B. Vue.js lernen"
@blur="titleTouched = true"
>
<p
v-if="titleError && titleTouched"
class="text-red-600 text-sm mt-1"
>
{{ titleError }}
</p>
<p class="text-gray-500 text-xs mt-1">
{{ newTask.title.length }}/100 Zeichen
</p>
</div>
<!-- Description Textarea -->
<div>
<label
for="description"
class="block text-sm font-medium text-gray-700 mb-2"
>
Beschreibung (optional)
</label>
<textarea
id="description"
v-model="newTask.description"
rows="3"
:disabled="loading"
:class="[
'w-full border rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors',
descriptionError ? 'border-red-500' : 'border-gray-300',
loading && 'bg-gray-100 cursor-not-allowed'
]"
placeholder="Weitere Details zu deiner Task..."
@blur="descriptionTouched = true"
/>
<p
v-if="descriptionError && descriptionTouched"
class="text-red-600 text-sm mt-1"
>
{{ descriptionError }}
</p>
<p class="text-gray-500 text-xs mt-1">
{{ newTask.description.length }}/500 Zeichen
</p>
</div>
<!-- Submit Button -->
<button
type="submit"
:disabled="!isFormValid || loading"
class="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-6 py-3 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2"
>
<span v-if="loading">
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Wird erstellt...
</span>
<span v-else>➕ Task hinzufügen</span>
</button>
</form>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const API_BASE = 'http://localhost:8080/api';
const AUTH = btoa('nova:learning123');
const emit = defineEmits(['task-created']);
// Form Data
const newTask = ref({
title: '',
description: ''
});
// UI State
const loading = ref(false);
const error = ref(null);
const successMessage = ref('');
const titleTouched = ref(false);
const descriptionTouched = ref(false);
// Validation
const titleError = computed(() => {
const title = newTask.value.title.trim();
if (!title) {
return 'Titel ist erforderlich!';
}
if (title.length < 3) {
return 'Titel muss mindestens 3 Zeichen haben!';
}
if (title.length > 100) {
return 'Titel darf maximal 100 Zeichen haben!';
}
return null;
});
const descriptionError = computed(() => {
const desc = newTask.value.description;
if (desc.length > 500) {
return 'Beschreibung darf maximal 500 Zeichen haben!';
}
return null;
});
const isFormValid = computed(() => {
return !titleError.value && !descriptionError.value;
});
// Form Handler
async function handleSubmit() {
// Mark all fields as touched
titleTouched.value = true;
descriptionTouched.value = true;
// Check validation
if (!isFormValid.value) {
error.value = 'Bitte fülle alle Pflichtfelder korrekt aus!';
return;
}
loading.value = true;
error.value = null;
successMessage.value = '';
try {
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: {
'Authorization': `Basic ${AUTH}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: newTask.value.title.trim(),
description: newTask.value.description.trim() || null,
completed: false
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP Error: ${response.status}`);
}
const createdTask = await response.json();
// Success!
successMessage.value = `✅ Task "${createdTask.title}" erfolgreich erstellt!`;
// Reset Form
newTask.value = {
title: '',
description: ''
};
titleTouched.value = false;
descriptionTouched.value = false;
// Emit Event to Parent
emit('task-created', createdTask);
// Auto-hide success message after 5 seconds
setTimeout(() => {
successMessage.value = '';
}, 5000);
} catch (e) {
error.value = `Fehler beim Erstellen: ${e.message}`;
} finally {
loading.value = false;
}
}
</script>
Nova: „WOW! Das ist… richtig professionell!“ 🤩
Kat: „Das ist Production-Ready Code! Das kannst du so in echten Projekten verwenden!“
💡 Best Practices für Formulare (Kat’s Tipps)
Kat teilt ihre Erfahrung:
1. Immer .prevent bei Forms
<form @submit.prevent="handleSubmit"> <!-- ALWAYS! --> </form>
2. Loading States zeigen
<button :disabled="loading"> <span v-if="loading">⏳ Loading...</span> <span v-else>Submit</span> </button>
3. Errors deutlich anzeigen
<!-- ✅ Gut: Sichtbar, klar -->
<p class="text-red-600">{{ error }}</p>
<!-- ❌ Schlecht: console.log() -->
console.log('Error:', error); // User sieht das nicht!
4. Inputs disablen während Loading
<input :disabled="loading">
Verhindert dass User während Submit weiter tippt!
5. Success-Feedback geben
<div v-if="success" class="bg-green-50"> ✅ Erfolgreich gespeichert! </div>
User muss wissen: „Hat es geklappt?“
6. Form nach Success resetten
function handleSubmit() {
// ... POST
if (success) {
newTask.value = { title: '', description: '' };
}
}
7. Validation NUR nach Blur/Submit zeigen
<input @blur="titleTouched = true">
<p v-if="titleError && titleTouched">{{ titleError }}</p>
Nicht sofort beim Tippen nerven!
8. Required Fields markieren
<label> Titel * ← Stern zeigt: Pflichtfeld </label>
🎯 Java-Vergleich: Vue Forms vs. Java Servlets
Kat erklärt für die Backend-Devs:
Java Servlet:
@WebServlet("/tasks")
public class TaskServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
// Parameter lesen
String title = request.getParameter("title");
String description = request.getParameter("description");
// Validieren
if (title == null || title.trim().isEmpty()) {
response.sendError(400, "Title required");
return;
}
// Speichern
Task task = new Task(title, description);
taskDAO.save(task);
// Redirect
response.sendRedirect("/tasks");
}
}
Vue.js Form:
<script setup>
async function handleSubmit() {
// Validieren (Client-Side)
if (!title.value.trim()) {
error.value = 'Title required';
return;
}
// POST zu Backend
const response = await fetch('/api/tasks', {
method: 'POST',
body: JSON.stringify({ title, description })
});
// UI updaten (kein Page Reload!)
emit('task-created');
}
</script>
Unterschiede:
- Servlet: Server-Side Validation, Page Redirect
- Vue: Client-Side Validation, No Page Reload, Bessere UX
Nova: „Ah! Vue macht das alles im Browser, ohne Server-Roundtrip!“
Kat: „Exactly! Schneller, smoother, bessere UX! Aber wir brauchen natürlich AUCH Server-Side Validation – never trust the client!“
✅ Deine Erfolgs-Checkliste für heute
Nach diesem Teil kannst du:
- [ ] v-model verstehen und nutzen (Two-Way Binding!)
- [ ] Formulare in Vue.js erstellen
- [ ] @submit.prevent verwenden (keine Page Reloads!)
- [ ] POST-Requests zur API senden
- [ ] Loading States zeigen
- [ ] Error Messages anzeigen
- [ ] Input-Validation implementieren (Browser + Custom)
- [ ] Forms nach Success resetten
- [ ] Events emiten (Basics)
- [ ] Production-Ready Forms bauen
Wenn alle ✅ sind: Du kannst jetzt interaktive Vue.js Apps bauen! 🎉
🚀 Nächste Woche: Reaktivität verstehen!
Kat’s Vorschau:
„Nächste Woche wird’s spannend! Du lernst wie Vue ‚reagiert‘ auf Änderungen.
📦 Teil 4: Listen-Manipulation & Reaktivität
Was du lernen wirst:
- Wie Vue Änderungen trackt (Reaktivität unter der Haube!)
- ref() vs reactive() – wann was?
- Arrays manipulieren (push, splice, filter)
- Computed Properties (berechnete Werte!)
- Watchers (auf Änderungen reagieren)
Nova’s Struggle: ‚Warum updated sich meine Liste nicht?!‘ Kat’s Aha-Moment: ‚Reaktivität ist wie… Observable in Java!‘
Real talk: Reaktivität zu verstehen ist der Schlüssel zu Vue.js. Danach wird ALLES klarer!
Bis nächste Woche! 💚“
Offizielle Vue.js Ressourcen
RessourceLinkBeschreibungVue.js
Docshttps://vuejs.org/guide/introduction.htmlOffizielle DokumentationVue.js Tutorialhttps://vuejs.org/tutorial/Interaktives TutorialVue.js Playgroundhttps://play.vuejs.org/Online-Editor zum ExperimentierenVue.js API Referencehttps://vuejs.org/api/Komplette API-Referenz
❓ FAQ – Formulare & User Input Edition
Frage 1: Warum v-model statt normale Event Listener?
Antwort: v-model ist viel kürzer und sauberer! Statt :value + @input schreibst du einfach v-model. Das ist weniger Code, weniger Fehler, mehr Lesbarkeit. Plus: v-model hat Features wie .lazy, .number, .trim Modifiers!
Frage 2: Kann ich v-model mit Checkboxes und Radio Buttons nutzen?
Antwort: Absolut! v-model funktioniert mit allen Input-Typen:
<!-- Checkbox --> <input type="checkbox" v-model="isCompleted"> <!-- Radio --> <input type="radio" v-model="priority" value="high"> <input type="radio" v-model="priority" value="low"> <!-- Select --> <select v-model="category"> <option value="work">Work</option> <option value="personal">Personal</option> </select>
v-model passt sich automatisch an den Input-Typ an!
Frage 3: Was ist der Unterschied zwischen @submit und @submit.prevent?
Antwort:
@submit– Feuert Event, aber Browser macht Default-Action (Page Reload!)@submit.prevent– Feuert Event UND verhindert Default-Action (kein Reload!)
Für SPAs (Single Page Apps) brauchst du IMMER .prevent!
Frage 4: Soll ich Browser-Validation oder Custom-Validation nutzen?
Antwort: BEIDE! Browser-Validation für Basics (required, type="email") – ist schnell und funktioniert ohne JS. Custom-Validation für komplexe Rules und bessere UX. Regel: Browser-Validation als Fallback, Custom-Validation für Production!
Frage 5: Wie verhindere ich dass User mehrmals Submit klicken?
Antwort: Mit loading State! Setze :disabled="loading" auf dem Button. Solange der Request läuft, ist Button disabled. User kann nicht doppelt submitten!
Frage 6: Warum reset ich das Form nach Success?
Antwort: UX! User erwartet nach erfolgreichem Submit ein „sauberes“ Form für die nächste Eingabe. Sonst sieht er alte Daten und denkt „Hat das funktioniert?“. Clear Form = deutliches Signal „Success, bereit für nächste Task!“
Frage 7: Was macht ihr bei Form-Problemen im echten Leben?
Antwort: Real talk: Forms sind wie Beziehungen – Kommunikation ist key! 😅 User müssen wissen: „Was ist falsch? Was muss ich tun?“ Klare Error-Messages, deutliche Validation, gutes Feedback. Wenn Forms schlecht designed sind, frustrieren User… und das kennen wir auch außerhalb von Code, oder? Manchmal wünscht man sich bessere „Validation“ im echten Leben. Aber das gehört eher zu den… feels. Wer mehr über die emotionalen Seiten von Kommunikation (und Code) lesen will, findet vielleicht was Interessantes in unseren „private logs“. Just saying. 💔
Frage 8: Muss ich IMMER Validation auf Client UND Server machen?
Antwort: JA! Never trust the client! User können Developer-Tools öffnen, HTML ändern, Browser-Validation disablen. Client-Validation ist für UX. Server-Validation ist für Security! Beide sind wichtig!
Frage 9: Was sind die v-model Modifiers?
Antwort: Super praktische Features:
.lazy– Updated nur bei blur, nicht bei jedem keystroke.number– Konvertiert String zu Number automatisch.trim– Entfernt Whitespace automatisch
<input v-model.trim="title"> <!-- Auto-trim! --> <input v-model.number="age"> <!-- Auto-convert to number! --> <input v-model.lazy="search"> <!-- Update bei blur, nicht keystroke -->
Frage 10: Kann ich mehrere Forms auf einer Seite haben?
Antwort: Klar! Jede Form ist eine eigene Component. Du kannst TaskForm, CommentForm, SearchForm – alles gleichzeitig haben. Wichtig: Jede Form braucht ihren eigenen State (ref()), eigene Validation, eigene Submit-Handler. Vue isoliert Components perfekt!
📖 Vue.js für Anfänger – Alle Teile im Überblick
✅ Bereits veröffentlicht:
- Teil 1 (14.10.2025): Setup & Erste API-Anbindung
- Teil 2 (21.10.2025): Styling & Responsive Design
- Teil 3 (28.10.2025): Formulare & User Input – Du bist hier! ✨
🔜 Kommende Teile:
- Teil 4 (04.11.2025): Listen-Manipulation & Reaktivität
- Teil 5 (11.11.2025): CRUD-Operations Complete (Edit & Delete)
- Teil 6 (18.11.2025): Component Communication (Props & Emits!)
- Teil 7 (25.11.2025): State Management (Composables & Pinia)
- Teil 8 (02.12.2025): Routing & Multi-Page Apps
- Teil 9 (09.12.2025): API Integration & Error Handling
- Teil 10 (16.12.2025): Testing mit Vitest
- Teil 11 (23.12.2025): Performance & Production-Ready
- Teil 12 (30.12.2025): Nova’s eigenes Projekt „Book Buddy“ 🎓
Alle Teile der Serie findest du hier: Link zur Serie-Übersicht
🔧 Tools & Extensions für Forms
Für diese Session brauchst du:
| Tool | Zweck | Link |
|---|---|---|
| Vue DevTools | Sieh live wie v-model Daten bindet! | Chrome/Firefox Extension |
| Volar | VS Code Extension – Auto-Complete für Vue | VS Code Marketplace |
| Thunder Client | API-Testing direkt in VS Code | VS Code Extension |
Kat’s Tipp: „Vue DevTools sind GAME-CHANGER! Du siehst live welche Werte sich ändern wenn du im Form tippst. Perfekt zum Debuggen von v-model!“
Workflow-Integration:
# Form-Debugging Workflow 1. Vue DevTools öffnen (F12 → Vue Tab) 2. Component auswählen 3. Im "State" Tab die ref()-Werte beobachten 4. Im Form tippen → Werte ändern sich live!
💬 Real Talk: Formular-Frustration
Java Fleet Küche, 12:45 Uhr. Kat macht sich einen Tee, Nova scrollt frustriert durch Stack Overflow. Kofi kommt mit seinem Sandwich vorbei.
Kofi: „Nova, du siehst aus als hätte dein Code dich persönlich beleidigt.“
Nova: [seufzt] „Forms! Ich dachte, das wäre easy – <input> und fertig. Aber da ist SO viel mehr!“
Kat: [setzt sich dazu] „Welcome to Frontend! Forms sind die Schnittstelle zwischen User und Daten. Da kann alles schiefgehen.“
Kofi: „Was genau nervt dich?“
Nova: „Erstens: Die Seite hat neu geladen als ich Submit geklickt hab. Zweitens: Meine Validation zeigt Errors bevor der User überhaupt was getippt hat. Drittens: Der Loading-State… ich hatte keinen!“
Kat: „Classic! Die drei Horsemen of Form Apocalypse.“
Kofi: [lacht] „Bei mir war’s damals die doppelte Submission. User klickt zweimal, zwei Einträge in der Datenbank.“
Nova: „OMG, das hatte ich auch! Wie fixt man das?“
Kat: „Button disablen während loading. Klingt obvious, vergessen aber alle beim ersten Mal.“
Kofi: „Bernd hat mal gesagt: ‚Forms sind wie Verträge. Der User verspricht valide Daten, wir versprechen Feedback.‘ Hat er irgendwo in einem Commit-Comment gelassen.“
Nova: „Bernd und seine Commit-Comments… aber er hat recht. Ich hab kein gutes Feedback gegeben.“
Kat: „Das ist der Punkt! Error Messages, Success Messages, Loading States – das ist nicht ’nice to have‘, das ist UX 101.“
Nova: [nickt langsam] „Okay, ich versteh’s. Forms sind… komplexer als sie aussehen.“
Kat: „Aber wenn du sie einmal richtig baust, hast du ein Pattern das du überall nutzen kannst. Validation, Loading, Error Handling – das wiederholt sich in jedem Projekt.“
Kofi: „Real talk: Mein erstes Production-Form hatte keine Validation. Die User haben HTML-Tags in die Kommentare geschrieben. Fun times.“ 😅
Nova: „Okay, ich fühl mich besser. Danke für die Horror-Stories!“
[Kurze Pause. Kat schaut nachdenklich in ihre Tasse.]
Kat: „Manchmal wünschte ich, echte Probleme hätten auch so klare Validation-Rules, weißt du? ‚Error: Diese Entscheidung ist invalid. Bitte korrigieren.‘ Das wär manchmal hilfreich…“
Nova: „Das klingt… nicht nur über Code.“
Kat: [lächelt kurz] „Vielleicht. Aber hey – Forms können wir fixen. Den Rest… arbeiten wir dran.“
Kofi: „Deep. Aber jetzt erstmal: Dein Submit-Button – hat der schon :disabled während Loading?“
Nova: „…nein. Ich geh das fixen!“ [schnappt sich Laptop]
🎁 Bonus: Nützliche v-model Tips & Tricks
Custom v-model Modifiers
<!-- .lazy - Update bei blur statt input --> <input v-model.lazy="search"> <!-- .number - Auto-Convert zu Number --> <input v-model.number="age" type="number"> <!-- .trim - Auto-Trim Whitespace --> <input v-model.trim="username"> <!-- Kombinieren! --> <input v-model.trim.lazy="email">
v-model mit Checkboxes (Arrays!)
<script setup>
const selectedTags = ref([]);
</script>
<template>
<!-- Multiple Checkboxes, gleicher v-model! -->
<input type="checkbox" v-model="selectedTags" value="urgent">
<input type="checkbox" v-model="selectedTags" value="work">
<input type="checkbox" v-model="selectedTags" value="personal">
<!-- selectedTags wird Array: ['urgent', 'work'] -->
<p>Selected: {{ selectedTags }}</p>
</template>
v-model mit Select (Single & Multiple)
<script setup>
const category = ref('');
const tags = ref([]);
</script>
<template>
<!-- Single Select -->
<select v-model="category">
<option value="">Bitte wählen...</option>
<option value="work">Work</option>
<option value="personal">Personal</option>
</select>
<!-- Multiple Select -->
<select v-model="tags" multiple>
<option value="urgent">Urgent</option>
<option value="important">Important</option>
<option value="low">Low Priority</option>
</select>
</template>
🎨 Challenge für die Community!
Kat’s Herausforderung:
„Du hast jetzt die Basics von Forms! Mach deine Task-App einzigartig mit eigenen Features:
Level 1 – Basics erweitern:
- 🎯 Priorität-Auswahl (High, Medium, Low)
- 📅 Deadline-Picker (Date Input)
- 🏷️ Tags/Kategorien hinzufügen
Level 2 – Advanced Features:
- ⭐ Rating-System (1-5 Stars)
- 📎 File-Upload für Attachments
- ⏰ Reminder-Zeit einstellen
Level 3 – Expert Mode:
- 🎨 Custom Color-Picker für Tasks
- 🔁 Recurring Tasks (täglich, wöchentlich)
- 👥 Mehrere User (Dropdown)
Teile dein Ergebnis:
- Screenshot posten mit #VueFormsChallenge
- GitHub-Repo verlinken
- Erkläre WARUM du diese Features gewählt hast
Die kreativsten Lösungen featuren wir im nächsten Teil! 🎉“
💬 Das war Teil 3 der Vue.js für Anfänger Serie!
Nova & Kat: „Aus Anzeigen wurde Interaktivität! 🎨➡️✨“
Du hast heute einen RIESIGEN Schritt gemacht! Deine App ist jetzt nicht mehr read-only – sie ist interaktiv!
Was du geschafft hast:
✅ v-model verstanden (Two-Way Data Binding!) ✅ Erstes interaktives Form gebaut ✅ POST-Requests zur API gesendet ✅ Validation implementiert ✅ Loading States & Error Handling ✅ Production-Ready Code geschrieben
Real talk: Das ist HUGE! Viele Vue-Anfänger strugglen mit Forms, weil sie komplexer sind als sie aussehen. Du hast heute nicht nur „ein Form gebaut“ – du hast UX-Patterns, Error Handling, State Management, und API-Communication gelernt. Das sind Skills die du in JEDEM Frontend-Projekt brauchst!
Hast du es nachgebaut? Teile dein Form!
Fragen? Schreib uns:
- Nova: nova.trent@java-developer.online
- Kat: katharina.schmidt@java-developer.online
Nächster Teil: In 1 Woche! Reaktivität verstehen – das Herzstück von Vue.js! 🚀
Keep coding, keep creating! 🎨
Nova & Kat – Von read-only zu interactive, gemeinsam! 💚
Tags: #VueJS #Formulare #UserInput #TwoWayBinding #Frontend #WebDevelopment #vmodel #Validation #API #JavaScript
© 2025 Java Fleet Systems Consulting | java-developer.online

