Skip to content

Contact Form

Kontaktformular mit Validierung und Formular-Submission.

Component HTML

html
<div class="max-w-2xl mx-auto">
  <h2 class="text-3xl font-bold text-gray-900 mb-8 text-center">{{title}}</h2>

  <div
    data-island="contact-form"
    data-island-props='{
      "submitUrl": "{{submitUrl}}",
      "successMessage": "{{successMessage}}"
    }'
  >
    <!-- Form Island rendert hier -->
  </div>
</div>

Island Code

javascript
// islands/contact-form.js
export default class ContactFormIsland {
  constructor(element, props = {}) {
    this.element = element;
    this.props = props;
    this.errors = {};
    this.isSubmitting = false;
    this.init();
  }

  init() {
    this.render();
    this.attachEvents();
  }

  render() {
    const { successMessage } = this.props;

    this.element.innerHTML = `
      <form class="bg-white p-8 rounded-lg shadow-lg border border-gray-200">
        <div class="success-message hidden mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded">
          ${successMessage}
        </div>

        <div class="mb-6">
          <label for="name" class="block text-sm font-semibold text-gray-700 mb-2">
            Name *
          </label>
          <input
            type="text"
            id="name"
            name="name"
            class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            required
          />
          <span class="error-name text-red-600 text-sm hidden"></span>
        </div>

        <div class="mb-6">
          <label for="email" class="block text-sm font-semibold text-gray-700 mb-2">
            E-Mail *
          </label>
          <input
            type="email"
            id="email"
            name="email"
            class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            required
          />
          <span class="error-email text-red-600 text-sm hidden"></span>
        </div>

        <div class="mb-6">
          <label for="message" class="block text-sm font-semibold text-gray-700 mb-2">
            Nachricht *
          </label>
          <textarea
            id="message"
            name="message"
            rows="5"
            class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
            required
          ></textarea>
          <span class="error-message text-red-600 text-sm hidden"></span>
        </div>

        <button
          type="submit"
          class="w-full px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
        >
          <span class="submit-text">Absenden</span>
          <span class="loading-text hidden">Wird gesendet...</span>
        </button>
      </form>
    `;
  }

  attachEvents() {
    const form = this.element.querySelector('form');
    form.addEventListener('submit', (e) => this.handleSubmit(e));

    // Real-time validation
    ['name', 'email', 'message'].forEach(field => {
      const input = this.element.querySelector(`#${field}`);
      input.addEventListener('blur', () => this.validateField(field));
    });
  }

  validateField(field) {
    const input = this.element.querySelector(`#${field}`);
    const errorSpan = this.element.querySelector(`.error-${field}`);
    const value = input.value.trim();

    let error = '';

    if (!value) {
      error = 'Dieses Feld ist erforderlich';
    } else if (field === 'email' && !this.isValidEmail(value)) {
      error = 'Bitte geben Sie eine gültige E-Mail-Adresse ein';
    } else if (field === 'message' && value.length < 10) {
      error = 'Nachricht muss mindestens 10 Zeichen lang sein';
    }

    if (error) {
      this.errors[field] = error;
      errorSpan.textContent = error;
      errorSpan.classList.remove('hidden');
      input.classList.add('border-red-500');
    } else {
      delete this.errors[field];
      errorSpan.classList.add('hidden');
      input.classList.remove('border-red-500');
    }

    return !error;
  }

  isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  async handleSubmit(e) {
    e.preventDefault();

    // Validate all fields
    const isValid = ['name', 'email', 'message'].every(field =>
      this.validateField(field)
    );

    if (!isValid || this.isSubmitting) return;

    this.isSubmitting = true;
    this.updateSubmitButton(true);

    try {
      const formData = new FormData(e.target);
      const data = Object.fromEntries(formData);

      const response = await fetch(this.props.submitUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });

      if (response.ok) {
        this.showSuccess();
        e.target.reset();
      } else {
        throw new Error('Submission failed');
      }
    } catch (error) {
      alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.');
    } finally {
      this.isSubmitting = false;
      this.updateSubmitButton(false);
    }
  }

  updateSubmitButton(loading) {
    const submitBtn = this.element.querySelector('button[type="submit"]');
    const submitText = this.element.querySelector('.submit-text');
    const loadingText = this.element.querySelector('.loading-text');

    submitBtn.disabled = loading;
    submitText.classList.toggle('hidden', loading);
    loadingText.classList.toggle('hidden', !loading);
  }

  showSuccess() {
    const successMsg = this.element.querySelector('.success-message');
    successMsg.classList.remove('hidden');
    setTimeout(() => {
      successMsg.classList.add('hidden');
    }, 5000);
  }

  destroy() {
    // Cleanup
  }
}

Was du lernst

  • Form Handling: Formulardaten verwalten und absenden
  • Validation: Real-time Validierung mit Error Messages
  • Async Operations: API-Calls mit fetch
  • Loading States: UI-Feedback während async Operations
  • Success Handling: Success Messages anzeigen

Webmate Studio Component Development Dokumentation