Skip to content

Show each error on its field ​

Task: refuse a bad form and say, next to each field, what is wrong with it.

yaml
# Recipe: the rules live on the action; the form shows each refusal on its field.
paths:
  components: ./components
  migrations: ./migrations

datasources:
  db:
    driver: sqlite
    database: ./data/guests.db
sql
CREATE TABLE guests (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    age INTEGER NOT NULL
);

The rules are written once, on the action's q:params. The ui:form reads them and puts them on its fields (required, minlength, type="number", min...), so the browser checks first. The server checks again, every field at once: when one fails, the action does not run, the page comes back and each field shows its own message.

xml
<q:component name="Guests">
  <!-- The rules are written once, on the action. The form reads them (UI-9)
       and the server checks them again (ACT-2), every field at once. -->
  <q:action name="register" method="POST">
    <q:param name="name" required="true" minlength="2" maxlength="60" />
    <q:param name="email" type="email" required="true" />
    <q:param name="age" type="integer" required="true" min="18" max="120" />
    <q:query name="added" datasource="db">
      INSERT INTO guests (name, email, age) VALUES (:name, :email, :age)
      <q:param name="name" value="{name}" type="string" />
      <q:param name="email" value="{email}" type="string" />
      <q:param name="age" value="{age}" type="integer" />
    </q:query>
    <q:redirect url="/" flash="{name} is on the list." />
  </q:action>

  <ui:window title="Guest list">
    <q:if condition="flash">
      <ui:alert variant="{flashType == 'error' and 'danger' or 'success'}">{flash}</ui:alert>
    </q:if>
    <ui:form on-submit="register">
      <ui:formitem label="Name"><ui:input bind="name" /></ui:formitem>
      <ui:formitem label="E-mail"><ui:input bind="email" /></ui:formitem>
      <ui:formitem label="Age"><ui:input bind="age" /></ui:formitem>
      <ui:button variant="primary">Add guest</ui:button>
    </ui:form>
  </ui:window>
</q:component>

error="field" in a test checks that the field was refused, and message= the text shown next to it:

xml
<q:test name="a valid guest is added" page="/">
  <test:submit action="register" name="Ana" email="ana@example.com" age="30" />
  <test:expect redirect="/" flash="Ana is on the list." />
  <test:expect table="guests" count="1" where="name = 'Ana' AND age = 30" />
</q:test>

<q:test name="every broken field gets its own message" page="/">
  <test:submit action="register" name="A" email="not an address" age="12" />
  <test:expect redirect="/" />
  <test:expect error="name" message="Must be at least 2 characters" />
  <test:expect error="email" message="Must be a valid email" />
  <test:expect error="age" message="Must be at least 18 (got 12)" />
  <test:expect text="Must be at least 2 characters" />
  <test:expect table="guests" count="0" />
</q:test>

<q:test name="a missing field is refused as Required" page="/">
  <test:submit action="register" name="Ana" email="ana@example.com" />
  <test:expect error="age" message="Required" />
  <test:expect table="guests" count="0" />
</q:test>
text
tests/guests.test.q
  PASS  a valid guest is added
  PASS  every broken field gets its own message
  PASS  a missing field is refused as Required
3 passed, 0 failed

The rules and their messages: ACT-2 and UI-9.

MIT Licensed · Built with VitePress