Skip to content

Form and Request Parameters

FormFor

formFor is a function that generates form components. It takes a record as an argument. RequestParameters is a class that parses the request parameters, and Accella provides it via Astro.locals.params.

These tools make it easy to create forms and retrieve request parameters. Below is an example of a sign-up form.

src/pages/signup.astro
---
import { formFor } from "accel-web";
import Layout from "../layouts/Layout.astro";
import { Account } from "../models";
import { z } from "astro/zod";
const account = Account.build({});
if (Astro.request.method === "POST") {
// Using zod, you can validate request parameters and extract them with type information
const accountParams = Astro.locals.params.require("account").parseWith(
z.object({
email: z.string(),
password: z.string(),
passwordConfirmation: z.string(),
})
);
// If strict type information is not required, you can write it as follows
/*
const accountParams = Astro.locals.params.require("account")
.permit("email", "password", "passwordConfirmation");
*/
if (account.update(accountParams)) {
return Astro.redirect("/");
}
}
// `formFor` is a helper function that creates a form object for the model
const { Form, Submit, Label, TextField, PasswordField } = formFor(account);
---
<Layout title="Sign Up">
<main class="container" style="max-width: 700px; min-width: 400px;">
<h1>Sign Up</h1>
<Form method="post" class="form">
{
account.errors.fullMessages.length > 0 && (
<div class="alert alert-danger">
{account.errors.fullMessages.map((message) => (
<div>{message}</div>
))}
</div>
)
}
<div class="mb-3">
<!--
The value of Label will be set according to the i18n settings configured in i18next.
`accelrecord.attributes.Account.email` is the key to be translated.
-->
<Label for="email" />
<!-- The value of TextField will be pre-filled with the value of account.email -->
<TextField attr="email" class="form-control" />
</div>
<div class="mb-3">
<Label for="password" />
<PasswordField attr="password" class="form-control" />
</div>
<div class="mb-3">
<Label for="passwordConfirmation" />
<PasswordField attr="passwordConfirmation" class="form-control" />
</div>
<div class="d-flex justify-content-between">
<Submit class="btn btn-secondary">Sign Up</Submit>
<a href="/" class="btn btn-link">Back</a>
</div>
</Form>
</main>
</Layout>

SearchFormFor

searchFormFor is a function that generates a search form component.

The following is an example of a search form using searchFormFor. You can implement a search form and pagination by combining it with the previously introduced paginate.

src/pages/todos/index.astro
---
import { paginate, searchFormFor } from "accel-web";
import { Todo } from "src/models";
import Layout from "../../layouts/Layout.astro";
const { params } = Astro.locals;
const page = Number(params.p) || 1;
// Model.search() returns a search object.
// params.q is a search query object from query parameters.
// Example:
// Path like "/todos?q.id_eq=1&q.title_cont=foo" is parsed as follows:
// { q: { id_eq: 1, title_cont: "foo" } }
const search = Todo.search(params.q);
// searchFormFor() returns form fields for search.
const { NumberField, TextField } = searchFormFor(search);
const { Nav, PageEntriesInfo, records } = paginate(search.result().order("id", "desc"), { page });
---
<Layout title="TODOs">
<main class="container" style="max-width: 700px; min-width: 550px;">
<h1 class="text-center">All TODOs</h1>
<div class="d-flex justify-content-end">
<a href="/todos/new" class="btn btn-link">Add a new todo</a>
</div>
<form method="get" class="row">
<div class="col mb-3">
<NumberField attr="id_eq" placeholder="Search by ID" class="form-control" />
</div>
<div class="col mb-3">
<TextField attr="title_cont" placeholder="Search by Title" class="form-control" />
</div>
<div class="col-md-auto mb-3">
<button type="submit" class="btn btn-dark">Search</button>
<a href="/todos" class="btn btn-secondary">Clear</a>
</div>
</form>
<ul>
{
records.map((todo) => (
<li>
<span>{todo.id}</span>
<span>{todo.title}</span>
{todo.estimate ? <span>{todo.estimate}</span> : null}
{todo.dueDate ? <span>{todo.dueDate.toISOString().slice(0, 10)}</span> : null}
<span>({todo.status})</span>
</li>
))
}
</ul>
<div class="d-flex justify-content-center">
<Nav />
</div>
<div class="d-flex justify-content-center">
<PageEntriesInfo />
</div>
</main>
</Layout>

SortLink is a component that generates a link for sorting. It takes a query and options as arguments.

---
import { SortLink } from "accel-web";
import { Account } from "src/models";
const search = Account.search(Astro.locals.params.q);
---
<table>
<thead>
<tr>
<th>
<!-- Basic Usage -->
<SortLink q={search} key="id" />
</th>
<th>
<!--
Customization Examples
- Specifying multiple sort attributes
- Changing the appearance of ascending/descending arrows (default is "▲" and "▼")
- Custom text
-->
<SortLink q={search} key={["email", "id desc"]} asc="" desc="">
Email Address
</SortLink>
</th>
</tr>
</thead>
<tbody>
{
search.result().map((account) => (
<tr>
<td>{account.id}</td>
<td>{account.email}</td>
</tr>
))
}
</tbody>
</table>