Skip to content

Latest commit

 

History

History
158 lines (126 loc) · 6.88 KB

forms.md

File metadata and controls

158 lines (126 loc) · 6.88 KB

Forms And Progressive Enhancement

GOWDK forms start as normal HTML forms. JavaScript can enhance a form into a fragment request. GOWDK source owns page and fragment markup; Go handlers own action data, domain behavior, and response decisions.

Baseline Form Behavior

g:post={Submit} lowers to a standard POST form:

<form g:post={Submit}>
  <input name="email" required />
  <button>Subscribe</button>
</form>

The generated page remains usable without JavaScript. In generated apps, the POST route decodes the declared request shape, validates supported literal request-shape constraints, runs guards and CSRF when configured, calls the same-package Go action handler, and writes the returned runtime/response.Response.

There is no generated page-level form state object today. Submitted form data, the handler's response decision, redirected pages, and source-declared fragments remain authoritative. Component state and g:bind can improve client interaction, but they do not replace server validation or action results.

Action Results

Full-page POST handlers return runtime/response.Response:

  • response.RedirectTo("/next") for POST/redirect/get.
  • response.HTMLBody(status, body) for an explicit HTML response.
  • response.JSONBody(status, body) or response.JSONValue(status, value) for JSON.
  • partial.Fragment(target, body) or partial.Swap(target, swap, body) for fragment responses.
  • response.ValidationJSON(result) or response.ValidationFragment(target, result) for validation responses.
  • response.ReloadPage() for enhanced forms that should reload the current page after the action completes.

response.HTMLBody, partial.Fragment, and other body-bearing response helpers remain low-level compatibility APIs. New page and fragment markup belongs in .gwdk; handlers should prefer status, header, redirect, JSON, reload, target, and swap decisions. Generated typed binding from action data into declared fragment markup remains planned.

Generated request-shape validation is intentionally narrow. It covers direct literal form fields and literal constraints such as required, minlength, maxlength, and pattern. Domain validation belongs in the Go handler.

Enhanced Fragment Requests

A form with g:target opts into partial enhancement:

<form g:post={Refresh} g:target="#patients" g:swap="innerHTML">
  <input name="query" />
  <button>Refresh</button>
</form>
<section id="patients"></section>

The compiler lowers this to normal form attributes plus data-gowdk-* metadata and emits assets/gowdk/gowdk.js when the page needs it. The runtime submits the form with:

  • X-GOWDK-Partial: 1
  • X-GOWDK-Target: <target>
  • X-GOWDK-Swap: <swap>

Successful enhanced responses swap innerHTML or outerHTML into the target. Before the partial POST, the runtime runs the browser's native constraint validation (checkValidity / reportValidity) when available. Invalid enhanced forms are not posted, gowdk:validation-blocked is dispatched on the form, and the server remains authoritative for every request that reaches the action handler. The runtime dispatches gowdk:before-request, gowdk:after-swap, and gowdk:request-error, toggles aria-busy, preserves focus where possible, and remounts generated islands around replaced DOM. Failed enhanced requests dispatch gowdk:request-error with detail.status, detail.body, and detail.response when an HTTP response exists.

Enhanced redirects are not a stable contract today. For enhanced requests, the handler chooses the target/swap outcome while source owns the fragment markup. The current action compatibility path can still return an explicit fragment body. Use normal full-page POST redirects for the no-JavaScript path.

There is no nearest error-boundary lookup for enhanced actions today. Failed enhanced requests dispatch gowdk:request-error; generated validation fragments can target a declared error container such as #errors. Generated validation fragments are escaped live regions with role="alert" and aria-live="polite".

Invalidation

GOWDK does not automatically invalidate page data after actions. Action handlers choose the lifecycle outcome explicitly.

Use one of these explicit outcomes:

  • Redirect after full-page POST so the browser loads fresh page output.
  • Choose a partial target/swap response for the changed source-owned region.
  • Return response.ReloadPage() so enhanced forms reload the current page and rerun request-time server {} data.
  • Return JSON to a user-owned client integration.
  • Call an app-owned API or reload policy outside generated core.

Generated JavaScript must not own routing, auth, business rules, database access, server validation, action behavior, global app state, or page loading policy.

Field Inference

The generated first slice infers direct input, textarea, select, and named submit controls with literal name attributes. It does not infer fields hidden inside component calls.

Author-written form controls keep their literal browser validation attributes such as required, type, inputmode, min, and max. When a g:post action has bound Go input metadata, the renderer can add missing numeric browser attributes to direct literal <input name="..."> controls: type="number", inputmode="numeric", min="0" for unsigned integers, and exact min/max bounds for sized integer types. It does not synthesize required because requiredness is enforced from literal form constraints, not from Go field type metadata.

Multipart action uploads use normal HTML forms with explicit generated policy:

<form g:post={UploadAvatar} enctype="multipart/form-data">
  <input name="avatar" type="file" required
    g:max-file-size="1048576"
    g:max-files="1"
    accept="image/png,image/jpeg" />
  <button>Upload</button>
</form>

Each direct file control must declare literal g:max-file-size, g:max-files, and MIME accept entries. Exact MIME types and type/* wildcards are allowed; extension-only accept entries such as .png are rejected because generated server policy checks submitted content types. The generated action adapter parses multipart requests under Build.BodyLimits.ActionBytes, enforces the per-file policy, preserves CSRF behavior, and decodes uploads into form.File or []form.File fields on typed action input structs. Low-level handlers can accept form.Data.

Multipart classification parses Content-Type as a structured media type. Valid multipart/form-data; boundary=... requests use multipart parsing; prefix-only values such as multipart/form-dataevil do not. Missing or malformed multipart boundaries return HTTP 400. Typed request-limit failures return HTTP 413, while other malformed form data returns HTTP 400.

Storage, content scanning, persistence, cleanup beyond parser temporary files, authorization, and domain validation remain user-owned Go behavior. Stream file content with file.Open() during the request.