run-what 1.3.0

HTML-first web framework powered by Rust. No JavaScript frameworks, no build steps—just HTML.
<what>
title: "Step 8 — Forms & Validation"
active_step: 8
</what>

<h1 class="text-3xl font-bold mb-2" style="color: #111827; letter-spacing: -0.02em;">Step 8 — Forms &amp; Validation</h1>
<p class="text-gray-500 text-sm mb-8">Server-side and client-side validation declared in HTML. No JavaScript required.</p>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">How validation works</h2>
  <p class="text-gray-600 mb-4" style="line-height: 1.7;">
    Add <code>w-validate</code> to any <code>&lt;form&gt;</code>. Annotate fields with validation attributes. wwwhat validates server-side on submit and also injects client-side validation without any JavaScript from you.
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;form method="post" action="/w-action/contacts" w-validate&gt;
  &lt;input name="email" type="email" w-required&gt;
  &lt;input name="age" type="number" w-min="18" w-max="99"&gt;
  &lt;button type="submit"&gt;Save&lt;/button&gt;
  &lt;button type="reset"&gt;Cancel&lt;/button&gt;
&lt;/form&gt;

&lt;!-- CSRF token is auto-injected. No manual setup needed. --&gt;</code></pre>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Live example — contact form</h2>

  <if flash.success>
    <div class="alert alert-success mb-4">#flash.success#</div>
  </if>
  <if flash.error>
    <div class="alert alert-danger mb-4">#flash.error#</div>
  </if>

  <div class="card mb-6">
    <div class="card-body">
      <form method="post" action="/w-action/contacts" w-validate>
        <div class="form-group">
          <label class="form-label" for="contact-name">
            Full name <span style="color: #ef4444;">*</span>
          </label>
          <input
            type="text"
            id="contact-name"
            name="name"
            class="form-control <if errors.name>border-red-400</if>"
            placeholder="Jane Smith"
            value="#old.name#"
            w-required
            w-min="2"
            autocomplete="off"
            data-1p-ignore
          >
          <if errors.name>
            <div class="text-sm mt-1" style="color: #dc2626;">#errors.name#</div>
          </if>
        </div>

        <div class="form-group">
          <label class="form-label" for="contact-email">
            Email address <span style="color: #ef4444;">*</span>
          </label>
          <input
            type="email"
            id="contact-email"
            name="email"
            class="form-control <if errors.email>border-red-400</if>"
            placeholder="jane@example.com"
            value="#old.email#"
            w-required
            autocomplete="off"
            data-1p-ignore
          >
          <if errors.email>
            <div class="text-sm mt-1" style="color: #dc2626;">#errors.email#</div>
          </if>
        </div>

        <div class="form-group">
          <label class="form-label" for="contact-message">
            Message <span style="color: #ef4444;">*</span>
          </label>
          <textarea
            id="contact-message"
            name="message"
            class="form-control <if errors.message>border-red-400</if>"
            rows="4"
            placeholder="Your message..."
            w-required
            w-min="10"
            autocomplete="off"
            data-1p-ignore
          >#old.message#</textarea>
          <if errors.message>
            <div class="text-sm mt-1" style="color: #dc2626;">#errors.message#</div>
          </if>
          <div class="text-xs text-gray-400 mt-1">Minimum 10 characters</div>
        </div>

        <div class="flex gap-2">
          <button type="submit" class="btn btn-primary">Send message</button>
          <button type="reset" class="btn btn-outline">Cancel</button>
        </div>
      </form>
    </div>
  </div>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Validation attributes</h2>
  <div class="card mb-4">
    <div class="card-body" style="padding: 0;">
      <table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">
        <thead>
          <tr style="background: #f9fafb; border-bottom: 1px solid #e5e7eb;">
            <th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Attribute</th>
            <th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Effect</th>
          </tr>
        </thead>
        <tbody>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.625rem 1rem;"><code>w-required</code></td>
            <td style="padding: 0.625rem 1rem; color: #6b7280;">Field must not be empty</td>
          </tr>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.625rem 1rem;"><code>w-min="N"</code></td>
            <td style="padding: 0.625rem 1rem; color: #6b7280;">Min length (text) or min value (number)</td>
          </tr>
          <tr style="border-bottom: 1px solid #f3f4f6;">
            <td style="padding: 0.625rem 1rem;"><code>w-max="N"</code></td>
            <td style="padding: 0.625rem 1rem; color: #6b7280;">Max length (text) or max value (number)</td>
          </tr>
          <tr>
            <td style="padding: 0.625rem 1rem;"><code>type="email"</code></td>
            <td style="padding: 0.625rem 1rem; color: #6b7280;">Valid email format enforced</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Error display and value preservation</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    On validation failure, <code>#errors.field#</code> contains the error message. <code>#old.field#</code> contains the previously submitted value so the user doesn't have to retype everything.
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;input name="email" value="#old.email#" w-required&gt;
&lt;if errors.email&gt;
  &lt;div class="error"&gt;#errors.email#&lt;/div&gt;
&lt;/if&gt;</code></pre>
</section>

<section class="mb-8">
  <h2 class="text-lg font-semibold mb-3" style="color: #111827;">Flash messages</h2>
  <p class="text-gray-600 mb-3" style="line-height: 1.7;">
    After a form submit, flash messages are set by the engine and consumed once on the next render. Use them to show success or error notices.
  </p>
  <pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code>&lt;if flash.success&gt;
  &lt;div class="alert alert-success"&gt;#flash.success#&lt;/div&gt;
&lt;/if&gt;

&lt;if flash.error&gt;
  &lt;div class="alert alert-danger"&gt;#flash.error#&lt;/div&gt;
&lt;/if&gt;</code></pre>
</section>

<div class="card" style="border-color: #bbf7d0; background: #f0fdf4;">
  <div class="card-body">
    <div class="text-sm font-semibold mb-2" style="color: #14532d;">What you learned</div>
    <ul class="text-sm space-y-1" style="color: #166534; padding-left: 1.25rem; list-style: disc;">
      <li><code>w-validate</code> on a form enables validation (both server + client-side)</li>
      <li>Validation attrs: <code>w-required</code>, <code>w-min="N"</code>, <code>w-max="N"</code></li>
      <li><code>#errors.fieldname#</code> contains the error message for that field</li>
      <li><code>#old.fieldname#</code> repopulates the field after a failed submit</li>
      <li><code>#flash.success#</code> and <code>#flash.error#</code> for one-time messages</li>
      <li>CSRF tokens are auto-injected — every POST form is protected against forgery</li>
      <li>Always include a <code>&lt;button type="reset"&gt;</code> to let users cancel</li>
    </ul>
  </div>
</div>