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:

  1. :value="..." – Bindet Variable an Input (one-way)
  2. @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:

ToolZweckLink
Vue DevToolsSieh live wie v-model Daten bindet!Chrome/Firefox Extension
VolarVS Code Extension – Auto-Complete für VueVS Code Marketplace
Thunder ClientAPI-Testing direkt in VS CodeVS 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

Autoren

  • Katharina Schmidt

    Katharina „Kat“ Schmidt – Die Design-Denkerin

    Senior Frontend- & UI/UX-Developerin | 29 Jahre | „Design ist Kommunikation – nicht Dekoration.“

    Kat ist das Gesicht hinter dem, was Nutzer sehen, fühlen und anklicken.
    In einem Team, das tief in Backend-Architekturen, Security und Data Flows denkt, sorgt sie dafür, dass der Mensch nicht vergessen wird.
    Ihre Arbeitsweise ist wie ihr Stil: präzise, ruhig, ästhetisch – und immer auf das Wesentliche reduziert.

    Sie kam 2022 zu Java Fleet, mit einem Hintergrund in Medieninformatik und einer klaren Mission: gute Technik sichtbar machen.
    Sie übersetzt komplexe Backend-Prozesse in klare Interfaces, macht aus endlosen Formularen strukturierte Nutzerreisen – und schafft es, dass ein Button „richtig“ wirkt, ohne dass jemand erklären kann, warum.

    💻 Die Tech-Seite

    Kat beherrscht den kompletten modernen Frontend-Stack:
    React, Vue.js, Tailwind CSS, SASS, Figma, Storybook – sie baut Systeme, keine Einzelkomponenten.
    Für sie ist Design kein Add-on, sondern ein integraler Teil der Architektur.
    Wenn sie an einer Oberfläche arbeitet, denkt sie wie ein Entwickler; wenn sie mit Entwicklern spricht, denkt sie wie eine Nutzerin.

    „Design ohne Verständnis für Code ist Deko. Code ohne Verständnis für Menschen ist Selbstgespräch.“

    Kat liebt es, Barrieren zu entfernen – technische, visuelle und emotionale.
    Accessibility (WCAG 2.1), Responsiveness, Semantik – das sind für sie keine Checklisten, sondern Selbstverständlichkeiten.

    🌿 Die menschliche Seite

    Kat ist der kreative Ruhepol im Team.
    Sie arbeitet konzentriert, lacht leise, und wenn sie eine Idee erklärt, spürt man sofort: Sie hat sie nicht nur verstanden – sie hat sie durchdacht.
    In ihrem Büro stehen eine Zimmerpflanze, ein Notizblock mit Skizzen, ein Grafiktablett und eine große Tasse Tee.
    Wenn sie nachdenkt, zeichnet sie. Wenn sie zuhört, beobachtet sie.

    Sie ist die empathischste Entwicklerin im Raum – und genau das macht sie so wichtig.
    Nova nennt sie „meine UI-Großschwester“, Cassian bewundert ihre gedankliche Klarheit, und Franz-Martin sagt:

    „Kat sieht das, was wir übersehen – und sie sagt’s, ohne laut zu werden.“

    🧠 Ihre Rolle im Team

    Kat ist die Schnittstelle zwischen Technik und Mensch – zwischen Backend und Benutzer, Code und Kommunikation.
    Sie organisiert Design-System-Workshops, dokumentiert Prinzipien, und sorgt dafür, dass jedes Projekt der Java Fleet ein Gesicht bekommt.
    Dabei denkt sie nicht in Farben oder Fonts, sondern in Abläufen und Emotionen: Wie fühlt sich ein Login an? Wann entsteht Vertrauen?
    Ihr Ziel: Interfaces, die sich richtig anfühlen – weil sie logisch sind.

    ⚡ Superkraft

    Empathie in Codeform.
    Kat übersetzt komplexe Technik in nutzerfreundliche Realität – elegant, barrierefrei und ehrlich.

    💬 Motto

    „Design ist, wenn du nicht erklären musst, wie es funktioniert.“

  • Ensign Nova Trent

    24 Jahre alt, frisch von der Universität als Junior Entwicklerin bei Java Fleet Systems Consulting. Nova ist brilliant in Algorithmen und Datenstrukturen, aber neu in der praktischen Java-Enterprise-Entwicklung. Sie brennt darauf, ihre ersten echten Projekte zu bauen und entdeckt dabei die Lücke zwischen Uni-Theorie und Entwickler-Realität. Sie liebt Star Treck das ist der Grund warum alle Sie Ensign Nova nennen und arbeitet daraufhin das sie Ihren ersten Knopf am Kragen bekommt.