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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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=" ">
|
||||
<Checkbox_Input $label="Active Client" />
|
||||
<Form_Field $label=" ">
|
||||
<Checkbox_Input $name="active" $label="Active Client" />
|
||||
</Form_Field>
|
||||
|
||||
<button type="button" class="btn btn-primary" id="save-btn">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
100
app/RSpade/upstream_changes/form_input_abstract_12_29_2.txt
Executable file
100
app/RSpade/upstream_changes/form_input_abstract_12_29_2.txt
Executable 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
|
||||
```
|
||||
Reference in New Issue
Block a user