Skip to content

Source code

Source code

src/showcase/pages/forms.js

This file is included in this documentation build. It is displayed as code and is not executed.

Download source

/**
 * Demo jobs, notification, preferences, and form workflows.
 *
 * @author Laurent Declercq l.declercq@agon-innovation.ch
 * @version 20260921
 */
import { controlDemo } from './controls.js';

/**
 * Create an accessible sample form with explicit validation and busy state.
 *
 * @param {object} services - Resource creation adapter.
 * @returns {object} Alpine provider.
 */
export function projectForm(services) {
  return {
    /** @type {object} Draft fields, never interpreted as HTML. */
    form: { name: '', email: '', notes: '', type: 'application', confirmed: false },

    /**
     * Whether field validation needs an announced summary.
     *
     * @returns {boolean} Validation state.
     */
    get hasErrors() {
      return Object.keys(this.errors).length > 0;
    },

    /** @type {Record<string,string>} Field validation messages. */
    errors: {},

    /** @type {boolean} Prevent duplicate submissions. */
    saving: false,

    /**
     * Validate and create a sample project in the resource list.
     *
     * @returns {Promise<void>}
     */
    async submit() {
      if (this.saving) {
        return;
      }

      this.errors = {};

      if (!this.form.name.trim()) {
        this.errors.name = this.$t('required');
      }

      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.form.email)) {
        this.errors.email = this.$t('invalidEmail');
      }

      if (!this.form.confirmed) {
        this.errors.confirmed = this.$t('required');
      }

      if (Object.keys(this.errors).length) {
        this.$nextTick(
          /**
           * Focus the first invalid project field after validation renders.
           *
           * @returns {void}
           */
          () => this.$el.querySelector('[aria-invalid="true"]')?.focus()
        );
        return;
      }

      this.saving = true;

      try {
        await services.save('resources', {
          name: this.form.name.trim(),
          notes: this.form.notes,
          type: this.form.type,
          status: 'active',
          region: 'Zurich'
        });
        this.$store.toasts.push(this.$t('projectCreated'));
        this.form = { name: '', email: '', notes: '', type: 'application', confirmed: false };
      } finally {
        this.saving = false;
      }
    }
  };
}

/**
 * Register the forms page provider after its route module is requested.
 *
 * @param {object} context - Application runtime and consumer-owned services.
 * @param {object} context.Alpine - Active Alpine runtime.
 * @param {object} context.services - Consumer-owned services.
 * @returns {void} Makes the page controller available before template mounting.
 */
export function register({ Alpine, services }) {
  Alpine.data('controlDemo', controlDemo);
  Alpine.data(
    'projectForm',
    /**
     * Construct a fresh forms controller for the mounted page.
     *
     * @returns {object} New page provider with its own lifecycle state.
     */
    () => projectForm(services)
  );
}