Files
rspade_system/docs/skills/forms/SKILL.md
root 21a7149486 Switch Ajax transport to JSON with proper Content-Type
Migrate $name from Form_Field to input components
Refactor form inputs: $name moves from Form_Field to input components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-29 09:43:23 +00:00

285 lines
6.8 KiB
Markdown
Executable File

---
name: forms
description: Building RSX forms with Rsx_Form, Form_Field, input components, data binding, validation, and the vals() pattern. Use when creating forms, handling form submissions, implementing form validation, working with Form_Field or Form_Input components, or implementing polymorphic form fields.
---
# RSX Form Components
## Core Form Structure
Forms use `<Rsx_Form>` with automatic data binding:
```jqhtml
<Rsx_Form $data="<%= JSON.stringify(this.data.form_data) %>"
$controller="Controller" $method="save">
<Form_Field $label="Email" $required=true>
<Text_Input $name="email" $type="email" />
</Form_Field>
<Form_Hidden_Field $name="id" />
</Rsx_Form>
```
## Field Components
| Component | Purpose |
|-----------|---------|
| `Form_Field` | Standard formatted field with label, errors, help text |
| `Form_Hidden_Field` | Single-tag hidden input (extends Form_Field_Abstract) |
| `Form_Field_Abstract` | Base class for custom formatting (advanced) |
## Input Components
| Component | Usage |
|-----------|-------|
| `Text_Input` | Text, email, url, tel, number, textarea |
| `Select_Input` | Dropdown with options array |
| `Checkbox_Input` | Checkbox with optional label |
| `Radio_Input` | Radio button group |
| `Wysiwyg_Input` | Rich text editor (Quill) |
### Text_Input Attributes
```jqhtml
<Text_Input $type="email" $placeholder="user@example.com" />
<Text_Input $type="textarea" $rows="5" />
<Text_Input $type="number" $min="0" $max="100" />
<Text_Input $prefix="@" $placeholder="username" />
<Text_Input $maxlength="100" />
```
### Select_Input Formats
```jqhtml
<%-- Simple array --%>
<Select_Input $options="<%= JSON.stringify(['Option 1', 'Option 2']) %>" />
<%-- Value/label objects --%>
<Select_Input $options="<%= JSON.stringify([
{value: 'opt1', label: 'Option 1'},
{value: 'opt2', label: 'Option 2'}
]) %>" />
<%-- From model enum --%>
<Select_Input $options="<%= JSON.stringify(Project_Model.status_id__enum_select()) %>" />
```
---
## Disabled Fields
Use `$disabled=true` on input components. Unlike standard HTML, disabled fields still return values via `vals()` (useful for read-only data that should be submitted).
```jqhtml
<Text_Input $type="email" $disabled=true />
<Select_Input $options="..." $disabled=true />
```
---
## Multi-Column Layouts
Use Bootstrap grid for multi-column field layouts:
```jqhtml
<div class="row">
<div class="col-md-6">
<Form_Field $label="First Name">
<Text_Input $name="first_name" />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $label="Last Name">
<Text_Input $name="last_name" />
</Form_Field>
</div>
</div>
```
---
## The vals() Dual-Mode Pattern
Form components implement `vals()` for get/set:
```javascript
class My_Form extends Component {
vals(values) {
if (values) {
// Setter - populate form
this.$sid('name').val(values.name || '');
return null;
} else {
// Getter - extract values
return {name: this.$sid('name').val()};
}
}
}
```
---
## Form Validation
Apply server-side validation errors:
```javascript
const response = await Controller.save(form.vals());
if (response.errors) {
Form_Utils.apply_form_errors(form.$, response.errors);
}
```
Errors match by `name` attribute on form fields.
---
## Action/Controller Pattern
Forms follow load/save mirroring traditional Laravel:
**Action (loads data):**
```javascript
on_create() {
this.data.form_data = { title: '', status_id: Model.STATUS_ACTIVE };
this.data.is_edit = !!this.args.id;
}
async on_load() {
if (!this.data.is_edit) return;
const record = await My_Model.fetch(this.args.id);
this.data.form_data = { id: record.id, title: record.title };
}
```
**Controller (saves data):**
```php
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
if (empty($params['title'])) {
return response_form_error('Validation failed', ['title' => 'Required']);
}
$record = $params['id'] ? My_Model::find($params['id']) : new My_Model();
$record->title = $params['title'];
$record->save();
return ['redirect' => Rsx::Route('View_Action', $record->id)];
}
```
**Key principles:**
- `form_data` must be serializable (plain objects, no models)
- Keep load/save in same controller for field alignment
- `on_load()` loads data, `on_ready()` is UI-only
---
## Repeater Fields
For arrays of values (relationships, multiple items):
**Simple repeaters (array of IDs):**
```javascript
// form_data
this.data.form_data = {
client_ids: [1, 5, 12],
};
// Controller receives
$params['client_ids'] // [1, 5, 12]
// Sync
$project->clients()->sync($params['client_ids'] ?? []);
```
**Complex repeaters (array of objects):**
```javascript
// form_data
this.data.form_data = {
team_members: [
{user_id: 1, role_id: 2},
{user_id: 5, role_id: 1},
],
};
// Controller receives
$params['team_members'] // [{user_id: 1, role_id: 2}, ...]
// Sync with pivot data
$project->team()->detach();
foreach ($params['team_members'] ?? [] as $member) {
$project->team()->attach($member['user_id'], [
'role_id' => $member['role_id'],
]);
}
```
---
## Test Data (Debug Mode)
Widgets can implement `seed()` for debug mode test data. Rsx_Form displays "Fill Test Data" button when `window.rsxapp.debug` is true.
```jqhtml
<Text_Input $seeder="company_name" />
<Text_Input $seeder="email" />
<Text_Input $seeder="phone" />
```
---
## Creating Custom Input Components
Extend `Form_Input_Abstract`:
```javascript
class My_Custom_Input extends Form_Input_Abstract {
on_create() {
// NO on_load() - never use this.data
}
on_ready() {
// Render elements EMPTY - form calls val(value) to populate AFTER render
}
// Required: get/set value
val(value) {
if (value !== undefined) {
// Set value
this.$sid('input').val(value);
} else {
// Get value
return this.$sid('input').val();
}
}
}
```
Reference implementations: `Select_Input`, `Text_Input`, `Checkbox_Input`
---
## Polymorphic Form Fields
For fields that can reference multiple model types:
```php
use App\RSpade\Core\Polymorphic_Field_Helper;
$eventable = Polymorphic_Field_Helper::parse($params['eventable'], [
Contact_Model::class,
Project_Model::class,
]);
if ($error = $eventable->validate('Please select an entity')) {
$errors['eventable'] = $error;
}
$model->eventable_type = $eventable->model;
$model->eventable_id = $eventable->id;
```
Client submits: `{"model":"Contact_Model","id":123}`. Always use `Model::class` for the whitelist.
## More Information
Details: `php artisan rsx:man form_conventions`, `php artisan rsx:man forms_and_widgets`