Standardize settings file naming and relocate documentation files Fix code quality violations from rsx:check Reorganize user_management directory into logical subdirectories Move Quill Bundle to core and align with Tom Select pattern Simplify Site Settings page to focus on core site information Complete Phase 5: Multi-tenant authentication with login flow and site selection Add route query parameter rule and synchronize filename validation logic Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs Implement filename convention rule and resolve VS Code auto-rename conflict Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns Implement RPC server architecture for JavaScript parsing WIP: Add RPC server infrastructure for JS parsing (partial implementation) Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation Add JQHTML-CLASS-01 rule and fix redundant class names Improve code quality rules and resolve violations Remove legacy fatal error format in favor of unified 'fatal' error type Filter internal keys from window.rsxapp output Update button styling and comprehensive form/modal documentation Add conditional fly-in animation for modals Fix non-deterministic bundle compilation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
621 lines
20 KiB
Plaintext
Executable File
621 lines
20 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.
|
|
|
|
Architecture:
|
|
Form_Field extends Form_Field_Abstract, which provides all core
|
|
functionality (widget discovery, data-name attributes, error handling).
|
|
Form_Field adds visual formatting (labels, spacing, error display).
|
|
|
|
- Form_Field_Abstract: Base class with field functionality, no formatting
|
|
- Form_Field: Concrete implementation with Bootstrap formatting
|
|
- Form_Hidden_Field: Specialized single-tag hidden input
|
|
|
|
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>
|
|
|
|
Form_Field_Abstract:
|
|
Use Form_Field_Abstract directly when you need field functionality
|
|
without visual formatting (e.g., for hidden fields or custom layouts).
|
|
|
|
Example - Unformatted Field:
|
|
|
|
<Form_Field_Abstract $name="custom_field">
|
|
<Custom_Widget />
|
|
</Form_Field_Abstract>
|
|
|
|
Form_Hidden_Field:
|
|
Specialized single-tag component for hidden inputs. Unlike other
|
|
Form_Field components, this IS both the field wrapper AND the hidden
|
|
input itself (uses tag="input" type="hidden").
|
|
|
|
Example - Hidden Field:
|
|
|
|
<Form_Hidden_Field $name="id" />
|
|
|
|
Form_Hidden_Field extends Form_Field_Abstract and implements val()
|
|
to get/set the hidden input value. No child widget needed - the
|
|
component itself is the input.
|
|
|
|
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.
|
|
|
|
IMPORTANT: Unlike standard HTML disabled elements (which don't submit
|
|
their values in traditional HTML forms), RSX disabled fields DO return
|
|
values when vals() is called and ARE included in Ajax form submissions.
|
|
This makes disabled fields useful for displaying read-only data that
|
|
should still be included in form submissions.
|
|
|
|
Behavior:
|
|
- Widget displays grayed-out appearance
|
|
- User cannot interact with widget
|
|
- val() getter still returns current value
|
|
- Value included in form submission via Ajax (unlike HTML forms)
|
|
- HTML disabled attribute prevents traditional 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))
|
|
<Form_Hidden_Field $name="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=" ">
|
|
<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)
|