Add form value persistence across cache revalidation re-renders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.8 KiB
Executable File
name, description
| name | description |
|---|---|
| forms | 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:
<Rsx_Form $data="<%= JSON.stringify(this.data.form_data) %>"
$controller="Controller" $method="save">
<Form_Field $name="email" $label="Email" $required=true>
<Text_Input $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
<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
<%-- 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).
<Text_Input $type="email" $disabled=true />
<Select_Input $options="..." $disabled=true />
Multi-Column Layouts
Use Bootstrap grid for multi-column field layouts:
<div class="row">
<div class="col-md-6">
<Form_Field $name="first_name" $label="First Name">
<Text_Input />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $name="last_name" $label="Last Name">
<Text_Input />
</Form_Field>
</div>
</div>
The vals() Dual-Mode Pattern
Form components implement vals() for get/set:
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:
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):
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):
#[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_datamust 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):
// 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):
// 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.
<Text_Input $seeder="company_name" />
<Text_Input $seeder="email" />
<Text_Input $seeder="phone" />
Creating Custom Input Components
Extend Form_Input_Abstract:
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:
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