--- 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 `` with automatic data binding: ```jqhtml ``` ## 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 ``` ### Select_Input Formats ```jqhtml <%-- Simple array --%> <%-- Value/label objects --%> <%-- From model enum --%> ``` --- ## 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 ``` --- ## Multi-Column Layouts Use Bootstrap grid for multi-column field layouts: ```jqhtml
``` --- ## 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 ``` --- ## 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`