Switch Ajax transport to JSON with proper Content-Type

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>
This commit is contained in:
root
2025-12-29 09:43:23 +00:00
parent 88aa57d591
commit 21a7149486
11 changed files with 197 additions and 96 deletions

View File

@@ -46,20 +46,18 @@ class Ajax_Batch_Controller extends Rsx_Controller_Abstract
\App\RSpade\Core\Debug\Debugger::disable_console_html_output();
// Get batch calls from request
$batch_calls_json = $request->input('batch_calls');
// With JSON Content-Type, Laravel auto-decodes the body
$batch_calls = $request->input('batch_calls');
if (empty($batch_calls_json)) {
if (empty($batch_calls)) {
return response()->json([
'error' => 'Missing batch_calls parameter'
], 400);
}
// Parse batch calls
$batch_calls = json_decode($batch_calls_json, true);
if (!is_array($batch_calls)) {
return response()->json([
'error' => 'Invalid batch_calls format - must be JSON array'
'error' => 'Invalid batch_calls format - must be array'
], 400);
}

View File

@@ -195,7 +195,8 @@ class Ajax {
$.ajax({
url: url,
method: 'POST',
data: params,
contentType: 'application/json',
data: JSON.stringify(params),
dataType: 'json',
__local_integration: true, // Bypass $.ajax override
success: (response) => {
@@ -364,7 +365,8 @@ class Ajax {
const response = await $.ajax({
url: '/_ajax/_batch',
method: 'POST',
data: { batch_calls: JSON.stringify(calls_to_send) },
contentType: 'application/json',
data: JSON.stringify({ batch_calls: calls_to_send }),
dataType: 'json',
__local_integration: true, // Bypass $.ajax override
});

View File

@@ -455,12 +455,12 @@ EDIT PAGE (ADD/EDIT COMBINED)
<Form_Hidden_Field $name="id" />
<% } %>
<Form_Field $name="name" $label="Name" $required=true>
<Text_Input />
<Form_Field $label="Name" $required=true>
<Text_Input $name="name" />
</Form_Field>
<Form_Field $name="status" $label="Status">
<Select_Input $options="<%= JSON.stringify(this.data.status_options) %>" />
<Form_Field $label="Status">
<Select_Input $name="status" $options="<%= JSON.stringify(this.data.status_options) %>" />
</Form_Field>
<button type="submit" class="btn btn-primary">Save</button>
@@ -479,11 +479,11 @@ RSX_FORM
$method - Ajax endpoint method name
Form Fields
<Form_Field $name="fieldname" $label="Label" $required=true>
<Text_Input />
<Form_Field $label="Label" $required=true>
<Text_Input $name="fieldname" />
</Form_Field>
The $name must match:
The $name on the input component must match:
- The key in $data JSON
- The key in server-side $params
- The key in validation $errors array

View File

@@ -5,8 +5,8 @@ NAME
SYNOPSIS
Client-side (template):
<Form_Field $name="schedule" $label="Date & Time">
<Schedule_Input />
<Form_Field $label="Date & Time">
<Schedule_Input $name="schedule" />
</Form_Field>
Server-side:
@@ -42,8 +42,8 @@ DESCRIPTION
Schedule_Input combines all scheduling fields into one component:
<Form_Field $name="schedule">
<Schedule_Input />
<Form_Field $label="Schedule">
<Schedule_Input $name="schedule" />
</Form_Field>
Submits as JSON:
@@ -90,8 +90,8 @@ SCHEDULE_INPUT COMPONENT
Template Usage
Basic usage:
<Form_Field $name="schedule" $label="Date & Time" $required=true>
<Schedule_Input />
<Form_Field $label="Date & Time" $required=true>
<Schedule_Input $name="schedule" />
</Form_Field>
Without timezone picker:
@@ -334,12 +334,12 @@ COMPLETE EXAMPLE
Template
<Rsx_Form $data=this.data.form_data $controller="Events_Controller" $method="save">
<Form_Field $name="title" $label="Title" $required=true>
<Text_Input />
<Form_Field $label="Title" $required=true>
<Text_Input $name="title" />
</Form_Field>
<Form_Field $name="schedule" $label="Date & Time" $required=true>
<Schedule_Input $sid="schedule_input" />
<Form_Field $label="Date & Time" $required=true>
<Schedule_Input $name="schedule" $sid="schedule_input" />
</Form_Field>
<button type="submit" class="btn btn-primary">Save</button>

View File

@@ -116,16 +116,16 @@ TEMPLATE PATTERN
<Form_Hidden_Field $name="id" />
<% } %>
<Form_Field $name="title" $label="Title" $required=true>
<Text_Input />
<Form_Field $label="Title" $required=true>
<Text_Input $name="title" />
</Form_Field>
<Form_Field $name="status_id" $label="Status">
<Select_Input $options=this.data.status_options />
<Form_Field $label="Status">
<Select_Input $name="status_id" $options=this.data.status_options />
</Form_Field>
<Form_Field $name="team_members" $label="Team">
<Form_Repeater
<Form_Field $label="Team">
<Form_Repeater $name="team_members"
$edit_input="Team_Member_Edit"
$display_input="Team_Member_Display"
/>
@@ -350,7 +350,8 @@ COMMON MISTAKES
this.data.form_data = { name: 'Test' };
// Template uses 'title' - VALUE WILL BE EMPTY
<Form_Field $name="title">
<Form_Field $label="Title">
<Text_Input $name="title" />
SEE ALSO
forms_and_widgets(3), jqhtml(3), ajax(3)

View File

@@ -8,12 +8,12 @@ SYNOPSIS
<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 $label="Email Address" $required=true>
<Text_Input $name="email" $type="email" $placeholder="user@example.com" />
</Form_Field>
<Form_Field $name="bio" $label="Biography">
<Text_Input $type="textarea" $rows=5 />
<Form_Field $label="Biography">
<Text_Input $name="bio" $type="textarea" $rows=5 />
</Form_Field>
<button type="button" id="save-btn">Save</button>
@@ -69,12 +69,12 @@ RSX_FORM COMPONENT
Example - Basic Form:
<Rsx_Form $action="{{ Rsx::Route('Users_Controller', 'save') }}">
<Form_Field $name="first_name" $label="First Name">
<Text_Input />
<Form_Field $label="First Name">
<Text_Input $name="first_name" />
</Form_Field>
<Form_Field $name="last_name" $label="Last Name">
<Text_Input />
<Form_Field $label="Last Name">
<Text_Input $name="last_name" />
</Form_Field>
<button type="button" id="save-btn">Save</button>
@@ -119,39 +119,38 @@ FORM_FIELD WRAPPER
Responsibilities:
- Display label with optional required indicator
- Set data-name attribute on child widget
- Read data-name from child widget (set by Form_Input_Abstract)
- 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 $label="Email Address">
<Text_Input $name="email" $type="email" />
</Form_Field>
Example - Required Field with Help Text:
<Form_Field $name="password"
$label="Password"
<Form_Field $label="Password"
$required=true
$help="Must be at least 8 characters">
<Text_Input $type="password" />
<Text_Input $name="password" $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 $label="<i class='bi bi-twitter'></i> Twitter">
<Text_Input $name="twitter" $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).
without visual formatting (e.g., for custom layouts).
Example - Unformatted Field:
<Form_Field_Abstract $name="custom_field">
<Custom_Widget />
<Form_Field_Abstract>
<Custom_Widget $name="custom_field" />
</Form_Field_Abstract>
Form_Hidden_Field:
@@ -388,14 +387,14 @@ DISABLED STATE
Example - Disable Individual Fields:
<Form_Field $name="email" $label="Email (Cannot Edit)">
<Text_Input $type="email" $disabled=true />
<Form_Field $label="Email (Cannot Edit)">
<Text_Input $name="email" $type="email" $disabled=true />
</Form_Field>
Example - Conditional Disable:
<Form_Field $name="status" $label="Status">
<Select_Input $options="{{ json_encode($statuses) }}"
<Form_Field $label="Status">
<Select_Input $name="status" $options="{{ json_encode($statuses) }}"
$disabled="{{ !$can_edit_status }}" />
</Form_Field>
@@ -465,31 +464,31 @@ MULTI-COLUMN LAYOUTS
<div class="row">
<div class="col-md-6">
<Form_Field $name="first_name" $label="First Name">
<Text_Input />
<Form_Field $label="First Name">
<Text_Input $name="first_name" />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $name="last_name" $label="Last Name">
<Text_Input />
<Form_Field $label="Last Name">
<Text_Input $name="last_name" />
</Form_Field>
</div>
</div>
<div class="row">
<div class="col-md-4">
<Form_Field $name="city" $label="City">
<Text_Input />
<Form_Field $label="City">
<Text_Input $name="city" />
</Form_Field>
</div>
<div class="col-md-2">
<Form_Field $name="state" $label="State">
<Text_Input $maxlength=2 />
<Form_Field $label="State">
<Text_Input $name="state" $maxlength=2 />
</Form_Field>
</div>
<div class="col-md-6">
<Form_Field $name="zip" $label="ZIP Code">
<Text_Input />
<Form_Field $label="ZIP Code">
<Text_Input $name="zip" />
</Form_Field>
</div>
</div>
@@ -547,8 +546,8 @@ CREATING CUSTOM WIDGETS
Usage:
<Form_Field $name="satisfaction" $label="Rate Your Experience">
<Rating_Input />
<Form_Field $label="Rate Your Experience">
<Rating_Input $name="satisfaction" />
</Form_Field>
EXAMPLES
@@ -573,34 +572,34 @@ EXAMPLES
<Form_Hidden_Field $name="id" />
@endif
<Form_Field $name="name" $label="Company Name" $required=true>
<Text_Input $seeder="company_name" />
<Form_Field $label="Company Name" $required=true>
<Text_Input $name="name" $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 $label="Email">
<Text_Input $name="email" $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 $label="Phone">
<Text_Input $name="phone" $type="tel" $seeder="phone" />
</Form_Field>
</div>
</div>
<Form_Field $name="industry" $label="Industry">
<Select_Input $options="{{ json_encode($industries) }}"
<Form_Field $label="Industry">
<Select_Input $name="industry" $options="{{ json_encode($industries) }}"
$placeholder="Select Industry..." />
</Form_Field>
<Form_Field $name="notes" $label="Notes">
<Text_Input $type="textarea" $rows=5 />
<Form_Field $label="Notes">
<Text_Input $name="notes" $type="textarea" $rows=5 />
</Form_Field>
<Form_Field $name="active" $label="&nbsp;">
<Checkbox_Input $label="Active Client" />
<Form_Field $label="&nbsp;">
<Checkbox_Input $name="active" $label="Active Client" />
</Form_Field>
<button type="button" class="btn btn-primary" id="save-btn">

View File

@@ -570,16 +570,16 @@ Full implementation showing modal class, form component, and page integration:
2. Form Component (add_user_modal_form.jqhtml):
<Define:Add_User_Modal_Form tag="div">
<Form_Field $name="email" $label="Email" $required=true>
<Text_Input $type="email" />
<Form_Field $label="Email" $required=true>
<Text_Input $name="email" $type="email" />
</Form_Field>
<Form_Field $name="first_name" $label="First Name">
<Text_Input />
<Form_Field $label="First Name">
<Text_Input $name="first_name" />
</Form_Field>
<Form_Field $name="last_name" $label="Last Name">
<Text_Input />
<Form_Field $label="Last Name">
<Text_Input $name="last_name" />
</Form_Field>
</Define:Add_User_Modal_Form>

View File

@@ -0,0 +1,100 @@
# Form Input $name Migration
**Date:** 2025-12-29
**Affects:** All forms, all custom input components
## Breaking Change
The `$name` attribute now belongs on the **input component**, not on `Form_Field`.
### Before (Old Pattern)
```html
<Form_Field $name="email" $label="Email Address" $required=true>
<Text_Input $placeholder="user@example.com" />
</Form_Field>
```
### After (New Pattern)
```html
<Form_Field $label="Email Address" $required=true>
<Text_Input $name="email" $placeholder="user@example.com" />
</Form_Field>
```
## Core Principle: Components Are Black Boxes
The input component IS the input. The form system interacts with components through:
- `data-name` attribute on the component's root element (set automatically by Form_Input_Abstract.on_create())
- `val()` method for getting/setting values
Internal elements are implementation details. The form system never looks inside components.
## Migration Checklist
### 1. Move $name from Form_Field to Input
Find and update all forms:
```bash
grep -r '<Form_Field[^>]*\$name=' rsx/app --include="*.jqhtml" --include="*.blade.php"
```
### 2. Ensure extends="Form_Input_Abstract" on all input templates
```html
<Define:My_Custom_Input extends="Form_Input_Abstract">
...
</Define:My_Custom_Input>
```
Without this, `data-name` won't be set on the component root.
### 3. Call super.on_create() in subclasses
All input components overriding `on_create()` MUST call `super.on_create()`:
```javascript
class My_Input extends Form_Input_Abstract {
on_create() {
super.on_create(); // REQUIRED - sets data-name
// ... rest of initialization
}
}
```
### 4. Remove name from internal elements
Input components must NOT put `name` on internal elements:
```html
<!-- WRONG -->
<input type="hidden" name="<%= this.args.name %>" />
<!-- CORRECT - no name attribute, data-name is on component root -->
<input type="hidden" $sid="hidden_input" />
```
### 5. Remove deprecated methods
Remove any implementations of:
- `_transform_value(value)` - no longer in base class
- `seed()` - removed from Form_Input_Abstract contract
## How It Works Now
1. Input component receives `$name="email"` as argument
2. `Form_Input_Abstract.on_create()` sets `data-name="email"` on component root
3. `Form_Field` finds child `.Form_Input_Abstract` and reads its `data-name`
4. `Rsx_Form.vals()` finds all `[data-name]` elements and calls `val()` on each
## Validation
After migration, verify:
```bash
# No $name on Form_Field
grep -r '<Form_Field[^>]*\$name=' rsx/app --include="*.jqhtml"
# Should return nothing
# No name on internal elements
grep -rn 'name="<%' rsx/theme/components/inputs --include="*.jqhtml"
# Should return nothing
```