feat(18.1-01): task 1 taglist field in array mode end to end
- cabana: core type taglist (mode string|array, separator, customTags, options as a list of tags), column type gate, save-time normalisation (trim, drop empties, dedupe) and taglist.invalid 422, record hydration - FormField gains separator and customTags (omitempty); OpenAPI and schema.d.ts regenerated - admin: TaglistField chip input with suggestions, registry entry, vitest - acme.catalog fixture with TestTaglistFieldCompile and TestTaglistFieldSmoke - docs: forms.md Taglist section, cabana README; rebuilt dist
This commit is contained in:
@@ -104,12 +104,13 @@ for _, f := range form.Fields {
|
||||
| `richeditor` | A WYSIWYG HTML editor for a host text column, sanitized on every save; see [Rich text](#rich-text). |
|
||||
| `repeater` | A list of rows, each with its own small form, stored as JSON in one column; see [Repeater](#repeater). |
|
||||
| `money` | An exact decimal amount with a currency suffix, never a float and never rounded; see [Money](#money). |
|
||||
| `taglist` | A list of tags edited as chips, stored as a JSON array or as one separator-joined string; see [Taglist](#taglist). |
|
||||
|
||||
The WinterCMS widgets that are not in this list (the code editor, the color picker, the media finder, the tag list and the others) are not provided. A field with one of those types stops the start-up, unless a plugin registers that alias as its own field type: plugins add field types with a custom element and Go code that decides what is stored; see [Plugin field types](field-types.md).
|
||||
The WinterCMS widgets that are not in this list (the code editor, the color picker, the media finder and the others) are not provided. A field with one of those types stops the start-up, unless a plugin registers that alias as its own field type: plugins add field types with a custom element and Go code that decides what is stored; see [Plugin field types](field-types.md).
|
||||
|
||||
### Field options
|
||||
|
||||
A field takes `label`, `comment`, `type`, `required`, `default`, `tab`, `span` (`left`, `right`, `full`, `auto`, `row`), `size` (`tiny`, `small`, `large`, `huge`, `giant`), `context`, `attributes` (scalar HTML attributes for the input), `options` and `emptyOption`, plus `nameFrom` and `relation` on relation fields, `preset` on text and mltext fields, `dependsOn` on dropdowns (see [Dependent dropdowns](#dependent-dropdowns)) and `currency`, `currencyFrom`, `scale` and `max` on money fields (see [Money](#money)). WinterCMS keys outside this set, such as `readOnly`, `disabled` or `trigger`, are refused.
|
||||
A field takes `label`, `comment`, `type`, `required`, `default`, `tab`, `span` (`left`, `right`, `full`, `auto`, `row`), `size` (`tiny`, `small`, `large`, `huge`, `giant`), `context`, `attributes` (scalar HTML attributes for the input), `options` and `emptyOption`, plus `nameFrom` and `relation` on relation fields, `preset` on text and mltext fields, `dependsOn` on dropdowns (see [Dependent dropdowns](#dependent-dropdowns)), `currency`, `currencyFrom`, `scale` and `max` on money fields (see [Money](#money)), and `mode`, `separator` and `customTags` on taglist fields (see [Taglist](#taglist)). WinterCMS keys outside this set, such as `readOnly`, `disabled` or `trigger`, are refused.
|
||||
|
||||
`context: update` shows a field only on the update form, and `context: create` only on the create form; a list of contexts is also accepted, and `context: preview` shows a field only on the [preview screen](#preview-screen). The context is enforced on the server too: a field that is hidden on a form is never written by that form's save, whatever the request body holds.
|
||||
|
||||
@@ -504,7 +505,34 @@ A save checks every submitted money value before the transaction and answers a 4
|
||||
|
||||
`null` is accepted and stored as NULL (or the column type's empty value); `required: true` refuses it through the usual rule. A value the administrator did not edit is sent back as it was loaded, so opening and saving a form with a stored `85.4193` writes `85.4193` again, never `85.42`. The same checks run on settings forms and relation forms.
|
||||
|
||||
In the admin SPA the field is a text input in decimal mode, right-aligned in tabular numerals, with the ISO code as a suffix (omitted while a `currencyFrom` sibling is still empty). A loaded value is shown with its trailing zeros trimmed down to two decimals and the locale's decimal separator; typing accepts a comma or a dot and ignores spaces, and emits the canonical dot string. The error line shows on blur for a malformed or too-precise text, and the text is still sent as typed so the server's answer is the gate. Read-only and preview show the locale-formatted amount with the code (`PLN 39.84`, `39,84 PLN`) or an em dash when empty.
|
||||
In the admin SPA the field is a text input in decimal mode, right-aligned in tabular numerals, with the ISO code as a suffix (omitted while a `currencyFrom` sibling is still empty). A loaded value is shown with its trailing zeros trimmed down to two decimals and the locale's decimal separator; typing accepts a comma or a dot and ignores spaces, and emits the canonical dot string. The error line shows on blur for a malformed or too-precise text, and the text is still sent as typed so the server's answer is the gate. Read-only and preview show the locale-formatted amount with the code (`PLN 39.84`, `39,84 PLN`) or an em dash when empty.
|
||||
|
||||
## Taglist
|
||||
|
||||
`type: taglist` edits a list of tags, as the WinterCMS tag list does. In `mode: array` the value travels as a JSON array of strings (`["alpha", "beta"]`) and is stored as that JSON array in one column: the column is a type whose pointer implements `sql.Scanner` and that implements `driver.Valuer`, such as `lagoon.Jsonable[[]string]` over a `text` column. Any other column type stops the start-up naming the field.
|
||||
|
||||
```yaml
|
||||
fields:
|
||||
features:
|
||||
label: acme.shop::lang.plans.features
|
||||
type: taglist
|
||||
mode: array
|
||||
options: [Support, Reports, Export]
|
||||
```
|
||||
|
||||
The field takes the generic keys plus:
|
||||
|
||||
| Key | Meaning |
|
||||
|-----|---------|
|
||||
| `mode` | `array` stores a JSON array of strings. |
|
||||
| `options` | A YAML list of tags offered as suggestions while typing. A mapping or a method name stops the start-up. |
|
||||
|
||||
`nameFrom`, `emptyOption`, `dependsOn`, `preset` and the money keys are refused on a taglist field, `separator` and `customTags` are refused on every other type, and a taglist inside a [repeater](#repeater) row stops the start-up.
|
||||
|
||||
A save normalises every submitted tag list before the transaction: each tag is trimmed, empty tags are dropped and a repeated tag is kept once, at its first position, so `[" alpha ", "beta", "alpha", ""]` stores `["alpha","beta"]`. `null` and an empty list store `[]`, never NULL. A value that is not a list of strings (a string, a number, a list holding a number) is a 422 on the field with `backend::lang.taglist.invalid`. A `required: true` taglist needs at least one tag. The record GET serves the stored list; a NULL or unreadable column is served as an empty list.
|
||||
|
||||
In the admin SPA the field shows its tags as chips, each with a remove button, followed by an input that adds the typed text as a tag on Enter or on typing a comma; empty text and a tag already in the list are ignored, and the options are offered as suggestions. Read-only shows the chips alone, or an em dash when empty.
|
||||
|
||||
## What a save may write
|
||||
|
||||
The form's writable fields are bound to model columns at boot. A save passes only those fields that are also in the model's `Fillable` list, drops unknown keys, case variants and nested objects, and fills the model with `lagoon.Fill` (see [Models](../database/models.md)). Declared `mltext`, `mltextarea` and `mlmarkdown` locale maps are the exception: they are lifted first, as [Markdown and multilingual fields](#markdown-and-multilingual-fields). A [repeater](#repeater)'s list of rows is the other exception: it is decoded row by row before the projection. The model's validation rules (a `Rules` method returning `lagoon.Validate` rule strings) and the form's `required` flags are checked in the save's transaction, and a failure is a 422 with messages per field. A value that does not fit its column is also a 422 on that field. A controller implementing `pact.FormRules` supplies its own rule set per operation, which replaces the model's rules for admin saves.
|
||||
|
||||
Reference in New Issue
Block a user