Skip to content

Latest commit

 

History

History
144 lines (114 loc) · 5.96 KB

File metadata and controls

144 lines (114 loc) · 5.96 KB

Forms And Progressive Enhancement

GOWDK forms start as normal HTML forms. JavaScript can enhance a form into a fragment request, but Go handlers still own action behavior.

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. The submitted form data, handler response, redirected page, or returned fragment is the source of truth. 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.

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, return a fragment response for the target. 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.
  • Return a fragment response for the changed 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.

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.