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>
285 lines
6.8 KiB
Markdown
Executable File
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`
|