Files
rspade_system/app/RSpade/man/forms_and_widgets.txt
root f6ac36c632 Enhance refactor commands with controller-aware Route() updates and fix code quality violations
Add semantic token highlighting for 'that' variable and comment file references in VS Code extension
Add Phone_Text_Input and Currency_Input components with formatting utilities
Implement client widgets, form standardization, and soft delete functionality
Add modal scroll lock and update documentation
Implement comprehensive modal system with form integration and validation
Fix modal component instantiation using jQuery plugin API
Implement modal system with responsive sizing, queuing, and validation support
Implement form submission with validation, error handling, and loading states
Implement country/state selectors with dynamic data loading and Bootstrap styling
Revert Rsx::Route() highlighting in Blade/PHP files
Target specific PHP scopes for Rsx::Route() highlighting in Blade
Expand injection selector for Rsx::Route() highlighting
Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls
Update jqhtml packages to v2.2.165
Add bundle path validation for common mistakes (development mode only)
Create Ajax_Select_Input widget and Rsx_Reference_Data controller
Create Country_Select_Input widget with default country support
Initialize Tom Select on Select_Input widgets
Add Tom Select bundle for enhanced select dropdowns
Implement ISO 3166 geographic data system for country/region selection
Implement widget-based form system with disabled state support

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 06:21:56 +00:00

583 lines
19 KiB
Plaintext
Executable File

FORMS_AND_WIDGETS(3) RSX Framework Manual FORMS_AND_WIDGETS(3)
NAME
Forms and Widgets - RSX form system with reusable widget components
SYNOPSIS
// Blade form markup
<Rsx_Form $data="{{ json_encode($form_data) }}"
$action="{{ Rsx::Route('Controller', 'save') }}">
<Form_Field $name="email" $label="Email Address" $required=true>
<Text_Input $type="email" $placeholder="user@example.com" />
</Form_Field>
<Form_Field $name="bio" $label="Biography">
<Text_Input $type="textarea" $rows=5 />
</Form_Field>
<button type="button" id="save-btn">Save</button>
</Rsx_Form>
// JavaScript - wire save button to form
$('#save-btn').on('click', function() {
const $form = $('.Rsx_Form').first();
$form.component().submit();
});
DESCRIPTION
The RSX form system provides a clean separation between form structure
(Rsx_Form), field layout (Form_Field), and input widgets (Text_Input,
Select_Input, etc). This architecture enables:
- Reusable widgets across all forms
- Consistent validation error display
- Automatic value collection and population
- Test data generation via seeders
- Read-only/disabled states
- Custom field layouts without modifying widget code
Key Components:
- Rsx_Form: Container managing form submission and validation
- Form_Field: Layout wrapper providing labels, help text, error display
- Widgets: Reusable input components (Text_Input, Select_Input, etc)
RSX_FORM COMPONENT
The Rsx_Form component manages form data flow, submission, and validation.
Required Attributes:
$action - Controller method reference for form submission
Example: Frontend_Clients_Controller.save
Optional Attributes:
$data - JSON-encoded object with initial form values
Used for edit mode to populate fields
Example: $data="{{ json_encode($client_data) }}"
Methods:
vals() - Get all form values as object
vals(values) - Set all form values from object
submit() - Submit form to $action endpoint
seed() - Fill all fields with test data (debug mode only)
Form Discovery:
Rsx_Form automatically discovers all widgets using shallowFind('.Widget')
and collects values based on their data-name attributes. No registration
or manual wiring required.
Example - Basic Form:
<Rsx_Form $action="{{ Rsx::Route('Users_Controller', 'save') }}">
<Form_Field $name="first_name" $label="First Name">
<Text_Input />
</Form_Field>
<Form_Field $name="last_name" $label="Last Name">
<Text_Input />
</Form_Field>
<button type="button" id="save-btn">Save</button>
</Rsx_Form>
Example - Edit Mode with Initial Data:
@php
$form_data = [
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
];
@endphp
<Rsx_Form $data="{{ json_encode($form_data) }}"
$action="{{ Rsx::Route('Users_Controller', 'save') }}">
<!-- Fields automatically populated from $data -->
</Rsx_Form>
FORM_FIELD WRAPPER
Form_Field provides consistent layout for labels, help text, and error
display. It wraps a single widget and connects it to the form.
Required Attributes:
$name - Field name for form serialization and error display
Optional Attributes:
$label - Label text displayed above field
$required - Boolean, adds red asterisk to label
$help - Help text displayed below field
Responsibilities:
- Display label with optional required indicator
- Set data-name attribute on child widget
- Display validation errors returned from server
- Provide consistent spacing and styling
Example - Basic Field:
<Form_Field $name="email" $label="Email Address">
<Text_Input $type="email" />
</Form_Field>
Example - Required Field with Help Text:
<Form_Field $name="password"
$label="Password"
$required=true
$help="Must be at least 8 characters">
<Text_Input $type="password" />
</Form_Field>
Example - Field with HTML in Label:
<Form_Field $name="twitter" $label="<i class='bi bi-twitter'></i> Twitter">
<Text_Input $prefix="@" />
</Form_Field>
Custom Layout:
Form_Field can be extended or replaced with custom jqhtml to change
field layout. The only requirement is that the child widget must
have the data-name attribute set to the field name.
Example - Horizontal Layout:
<Define:Form_Field_Horizontal extends="Form_Field">
<div class="row mb-3">
<label class="col-md-3 col-form-label">
<%!= this.args.label %>
</label>
<div class="col-md-9">
<%= content() %>
<% if (this.has_error()) { %>
<div class="invalid-feedback d-block">
<%= this.get_error() %>
</div>
<% } %>
</div>
</div>
</Define:Form_Field_Horizontal>
WIDGET INTERFACE
All form widgets must implement the standard widget interface:
Required:
- CSS class "Widget" on root element
- val() method for getting current value
- val(value) method for setting value
Optional:
- seed() method for generating test data
- Support for $disabled attribute
Widget Responsibilities:
1. Value Management
Widgets must implement getter/setter via val() method:
val() {
// Getter - return current value
if (arguments.length === 0) {
return this.$id('input').val();
}
// Setter - update value
else {
this.data.value = value || '';
this.$id('input').val(this.data.value);
}
}
2. Disabled State
Widgets should respect $disabled attribute:
- Render with disabled HTML attribute
- Display grayed-out appearance
- Still return value via val() getter
- Do not submit in HTML form (handled by browser)
3. Test Data (Optional)
Widgets may implement seed() for debug mode:
async seed() {
if (this.args.seeder) {
// Generate test data
this.val('Test Value');
}
}
BUILT-IN WIDGETS
Text_Input
Basic text input supporting multiple types and textarea.
Attributes:
$type - Input type (text, email, url, tel, number, textarea)
$rows - Number of rows for textarea (default: 3)
$placeholder - Placeholder text
$prefix - Text to prepend (creates input-group)
$suffix - Text to append (creates input-group)
$min - Minimum value for number inputs
$max - Maximum value for number inputs
$maxlength - Maximum length for text inputs
$disabled - Disable input (grayed out, still returns value)
$seeder - Seeder function name for test data
Examples:
<Text_Input $type="email" $placeholder="user@example.com" />
<Text_Input $type="textarea" $rows=5 $placeholder="Enter bio..." />
<Text_Input $type="number" $min=0 $max=100 />
<Text_Input $prefix="@" $placeholder="username" />
<Text_Input $type="url" $disabled=true />
Select_Input
Dropdown select with options.
Attributes:
$options - Array of options (see below)
$placeholder - Placeholder option text
$disabled - Disable select (grayed out, still returns value)
$seeder - Seeder function name for test data
Options Format:
Simple array: ['Option 1', 'Option 2', 'Option 3']
Object array: [
{value: 'opt1', label: 'Option 1'},
{value: 'opt2', label: 'Option 2'}
]
From Blade: $options="{{ json_encode($options_array) }}"
Examples:
@php
$industries = ['Technology', 'Finance', 'Healthcare'];
@endphp
<Select_Input $options="{{ json_encode($industries) }}"
$placeholder="Select Industry..." />
@php
$sizes = [
['value' => 'sm', 'label' => 'Small (1-10)'],
['value' => 'md', 'label' => 'Medium (11-50)'],
['value' => 'lg', 'label' => 'Large (50+)'],
];
@endphp
<Select_Input $options="{{ json_encode($sizes) }}" />
Checkbox_Input
Checkbox with optional label.
Attributes:
$label - Label text displayed next to checkbox
$checked_value - Value when checked (default: "1")
$unchecked_value - Value when unchecked (default: "0")
$disabled - Disable checkbox (grayed out, still returns value)
Examples:
<Checkbox_Input $label="Subscribe to newsletter" />
<Checkbox_Input $label="I agree to terms"
$checked_value="yes"
$unchecked_value="no" />
Wysiwyg_Input
Rich text editor using Quill.
Attributes:
$placeholder - Placeholder text
$disabled - Disable editor (not yet implemented)
$seeder - Seeder function name for test data
Example:
<Wysiwyg_Input $placeholder="Enter description..." />
SEEDING TEST DATA
The seed system generates realistic test data for forms during development.
Enabled only when window.rsxapp.debug is true.
Seed Button:
Rsx_Form automatically displays a "Fill Test Data" button in debug mode.
Clicking this button calls seed() on all widgets.
Widget Seeding:
Widgets implement seed() to generate appropriate test data:
async seed() {
if (this.args.seeder) {
// TODO: Implement Rsx_Random_Values endpoint
let value = 'Test ' + (this.args.seeder || 'Value');
this.val(value);
}
}
Seeder Names:
Specify seeder via $seeder attribute:
<Text_Input $seeder="company_name" />
<Text_Input $seeder="email" />
<Text_Input $seeder="phone" />
Future Implementation:
Planned Rsx_Random_Values endpoint will provide:
- company_name() - Random company names
- email() - Random email addresses
- phone() - Random phone numbers
- first_name() - Random first names
- last_name() - Random last names
- address() - Random street addresses
- city() - Random city names
DISABLED STATE
Disabled widgets display as read-only but still participate in form
value collection.
Behavior:
- Widget displays grayed-out appearance
- User cannot interact with widget
- val() getter still returns current value
- Value included in form submission via Ajax
- HTML disabled attribute prevents browser form submission
Example - Disable Individual Fields:
<Form_Field $name="email" $label="Email (Cannot Edit)">
<Text_Input $type="email" $disabled=true />
</Form_Field>
Example - Conditional Disable:
<Form_Field $name="status" $label="Status">
<Select_Input $options="{{ json_encode($statuses) }}"
$disabled="{{ !$can_edit_status }}" />
</Form_Field>
Use Cases:
- Display data that cannot be edited
- Show calculated or system-managed values
- Enforce permissions (some users see but cannot edit)
- Multi-step forms (disable completed steps)
FORM SUBMISSION
Form submission uses Ajax to send data to controller methods.
JavaScript Submission:
$('#save-btn').on('click', function() {
const $form = $('.Rsx_Form').first();
const form_component = $form.component();
form_component.submit();
});
What Happens:
1. Form calls vals() to collect all widget values
2. Sends values to this.args.action via Ajax.call()
3. Handles response:
- Success: Redirect if response.redirect provided
- Validation errors: Display errors on fields
- General errors: Log to console
Controller Method:
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
// Validation
$validated = $request->validate([
'email' => 'required|email',
'name' => 'required|string|max:255',
]);
// Save data
$user = User::create($validated);
// Return response
return [
'success' => true,
'redirect' => Rsx::Route('Users_Controller', 'view', $user->id),
];
}
Validation Errors:
When validation fails, return errors in format:
return [
'success' => false,
'errors' => [
'email' => 'The email field is required.',
'name' => 'The name field is required.',
],
];
Form automatically displays errors below each field.
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>
<div class="row">
<div class="col-md-4">
<Form_Field $name="city" $label="City">
<Text_Input />
</Form_Field>
</div>
<div class="col-md-2">
<Form_Field $name="state" $label="State">
<Text_Input $maxlength=2 />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $name="zip" $label="ZIP Code">
<Text_Input />
</Form_Field>
</div>
</div>
CREATING CUSTOM WIDGETS
Create custom widgets by implementing the widget interface.
Example - Rating Widget:
File: rating_input.jqhtml
<Define:Rating_Input class="Widget">
<div class="rating">
<% for (let i = 1; i <= 5; i++) { %>
<i $id="star_<%= i %>"
class="bi bi-star<%= this.data.value >= i ? '-fill' : '' %>"
data-rating="<%= i %>"></i>
<% } %>
</div>
</Define:Rating_Input>
File: rating_input.js
class Rating_Input extends Form_Input_Abstract {
on_create() {
this.data.value = 0;
}
on_ready() {
const that = this;
this.$.find('[data-rating]').on('click', function() {
that.val($(this).data('rating'));
});
}
val(value) {
if (arguments.length === 0) {
return this.data.value;
} else {
this.data.value = value || 0;
// Update star display
this.$.find('[data-rating]').each(function() {
const rating = $(this).data('rating');
$(this).toggleClass('bi-star-fill', rating <= value);
$(this).toggleClass('bi-star', rating > value);
});
}
}
async seed() {
this.val(Math.floor(Math.random() * 5) + 1);
}
}
Usage:
<Form_Field $name="satisfaction" $label="Rate Your Experience">
<Rating_Input />
</Form_Field>
EXAMPLES
Complete Form Example:
@php
$form_data = isset($client) ? [
'name' => $client->name,
'email' => $client->email,
'industry' => $client->industry,
'active' => $client->active,
] : [];
$industries = ['Technology', 'Finance', 'Healthcare'];
@endphp
<Rsx_Form $data="{{ json_encode($form_data) }}"
$action="{{ Rsx::Route('Clients_Controller', 'save') }}">
@if (isset($client))
<input type="hidden" name="id" value="{{ $client->id }}">
@endif
<Form_Field $name="name" $label="Company Name" $required=true>
<Text_Input $seeder="company_name" />
</Form_Field>
<div class="row">
<div class="col-md-6">
<Form_Field $name="email" $label="Email">
<Text_Input $type="email" $seeder="email" />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $name="phone" $label="Phone">
<Text_Input $type="tel" $seeder="phone" />
</Form_Field>
</div>
</div>
<Form_Field $name="industry" $label="Industry">
<Select_Input $options="{{ json_encode($industries) }}"
$placeholder="Select Industry..." />
</Form_Field>
<Form_Field $name="notes" $label="Notes">
<Text_Input $type="textarea" $rows=5 />
</Form_Field>
<Form_Field $name="active" $label="&nbsp;">
<Checkbox_Input $label="Active Client" />
</Form_Field>
<button type="button" class="btn btn-primary" id="save-btn">
Save Client
</button>
</Rsx_Form>
<script>
$('#save-btn').on('click', function() {
$('.Rsx_Form').first().component().submit();
});
</script>
SEE ALSO
jqhtml(3), ajax(3), validation(3)
RSX Framework October 2025 FORMS_AND_WIDGETS(3)