Fix Form_Utils to use component.$sid() instead of data-sid selector
Add response helper functions and use _message as reserved metadata key 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,8 @@ class Form_Utils {
|
|||||||
// Resolve the promise once all animations are complete
|
// Resolve the promise once all animations are complete
|
||||||
Promise.all(animations).then(() => {
|
Promise.all(animations).then(() => {
|
||||||
// Scroll to error container if it exists
|
// Scroll to error container if it exists
|
||||||
const $error_container = $parent.find('[data-sid="error_container"]').first();
|
const component = $parent.component();
|
||||||
|
const $error_container = component ? component.$sid('error_container') : $();
|
||||||
if ($error_container.length > 0) {
|
if ($error_container.length > 0) {
|
||||||
const container_top = $error_container.offset().top;
|
const container_top = $error_container.offset().top;
|
||||||
|
|
||||||
@@ -251,9 +252,10 @@ class Form_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert Laravel validator format {field: [msg1, msg2]} to {field: msg1}
|
// Convert Laravel validator format {field: [msg1, msg2]} to {field: msg1}
|
||||||
|
// Skip reserved keys (prefixed with underscore) - these are metadata, not field errors
|
||||||
const normalized = {};
|
const normalized = {};
|
||||||
for (const field in errors) {
|
for (const field in errors) {
|
||||||
if (errors.hasOwnProperty(field)) {
|
if (errors.hasOwnProperty(field) && !field.startsWith('_')) {
|
||||||
const value = errors[field];
|
const value = errors[field];
|
||||||
if (Array.isArray(value) && value.length > 0) {
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
normalized[field] = value[0];
|
normalized[field] = value[0];
|
||||||
@@ -345,7 +347,8 @@ class Form_Utils {
|
|||||||
*/
|
*/
|
||||||
static _apply_combined_error($parent, summary_msg, unmatched_errors) {
|
static _apply_combined_error($parent, summary_msg, unmatched_errors) {
|
||||||
const animations = [];
|
const animations = [];
|
||||||
const $error_container = $parent.find('[data-sid="error_container"]').first();
|
const component = $parent.component();
|
||||||
|
const $error_container = component ? component.$sid('error_container') : $();
|
||||||
const $target = $error_container.length > 0 ? $error_container : $parent;
|
const $target = $error_container.length > 0 ? $error_container : $parent;
|
||||||
|
|
||||||
// Create alert with summary message and bulleted list of unmatched errors
|
// Create alert with summary message and bulleted list of unmatched errors
|
||||||
@@ -386,7 +389,8 @@ class Form_Utils {
|
|||||||
const animations = [];
|
const animations = [];
|
||||||
|
|
||||||
// Look for a specific error container div (e.g., in Rsx_Form component)
|
// Look for a specific error container div (e.g., in Rsx_Form component)
|
||||||
const $error_container = $parent.find('[data-sid="error_container"]').first();
|
const component = $parent.component();
|
||||||
|
const $error_container = component ? component.$sid('error_container') : $();
|
||||||
const $target = $error_container.length > 0 ? $error_container : $parent;
|
const $target = $error_container.length > 0 ? $error_container : $parent;
|
||||||
|
|
||||||
if (typeof messages === 'string') {
|
if (typeof messages === 'string') {
|
||||||
|
|||||||
@@ -28,16 +28,16 @@ class Error_Response extends Rsx_Response_Abstract
|
|||||||
if ($metadata === null) {
|
if ($metadata === null) {
|
||||||
$this->metadata = [];
|
$this->metadata = [];
|
||||||
} elseif (is_string($metadata)) {
|
} elseif (is_string($metadata)) {
|
||||||
$this->metadata = ['message' => $metadata];
|
$this->metadata = ['_message' => $metadata];
|
||||||
} elseif (is_array($metadata)) {
|
} elseif (is_array($metadata)) {
|
||||||
$this->metadata = $metadata;
|
$this->metadata = $metadata;
|
||||||
} else {
|
} else {
|
||||||
$this->metadata = ['message' => (string)$metadata];
|
$this->metadata = ['_message' => (string)$metadata];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set reason from message or use default
|
// Set reason from message or use default
|
||||||
if (isset($this->metadata['message'])) {
|
if (isset($this->metadata['_message'])) {
|
||||||
$this->reason = $this->metadata['message'];
|
$this->reason = $this->metadata['_message'];
|
||||||
} else {
|
} else {
|
||||||
$this->reason = Ajax::get_default_message($error_code);
|
$this->reason = Ajax::get_default_message($error_code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1089,6 +1089,57 @@ function response_not_found(?string $message = null)
|
|||||||
return response_error(\App\RSpade\Core\Ajax\Ajax::ERROR_NOT_FOUND, $message);
|
return response_error(\App\RSpade\Core\Ajax\Ajax::ERROR_NOT_FOUND, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a form validation error response
|
||||||
|
*
|
||||||
|
* Use this for validation errors with field-specific messages.
|
||||||
|
* The client-side form handling will apply errors to matching fields.
|
||||||
|
*
|
||||||
|
* @param string $message Summary message for the error
|
||||||
|
* @param array $field_errors Field-specific errors as ['field_name' => 'error message']
|
||||||
|
* @return \App\RSpade\Core\Response\Error_Response
|
||||||
|
*/
|
||||||
|
function response_form_error(string $message, array $field_errors = [])
|
||||||
|
{
|
||||||
|
return response_error(
|
||||||
|
\App\RSpade\Core\Ajax\Ajax::ERROR_VALIDATION,
|
||||||
|
array_merge(['_message' => $message], $field_errors)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an authentication required error response
|
||||||
|
*
|
||||||
|
* Use this when the user is not logged in and needs to authenticate.
|
||||||
|
* Distinct from response_unauthorized() which is for permission denied.
|
||||||
|
*
|
||||||
|
* @param string|null $message Custom error message (optional)
|
||||||
|
* @return \App\RSpade\Core\Response\Error_Response
|
||||||
|
*/
|
||||||
|
function response_auth_required(?string $message = null)
|
||||||
|
{
|
||||||
|
return response_error(\App\RSpade\Core\Ajax\Ajax::ERROR_AUTH_REQUIRED, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a fatal error response
|
||||||
|
*
|
||||||
|
* Use this for unrecoverable errors that prevent the operation from completing.
|
||||||
|
* These are typically logged and displayed prominently to the user.
|
||||||
|
*
|
||||||
|
* @param string|null $message Error message
|
||||||
|
* @param array $details Additional error details (e.g., file, line, backtrace)
|
||||||
|
* @return \App\RSpade\Core\Response\Error_Response
|
||||||
|
*/
|
||||||
|
function response_fatal_error(?string $message = null, array $details = [])
|
||||||
|
{
|
||||||
|
$metadata = $details;
|
||||||
|
if ($message !== null) {
|
||||||
|
$metadata['_message'] = $message;
|
||||||
|
}
|
||||||
|
return response_error(\App\RSpade\Core\Ajax\Ajax::ERROR_FATAL, $metadata ?: $message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current request is from a loopback IP address
|
* Check if the current request is from a loopback IP address
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -97,17 +97,21 @@ class Frontend_Clients_Edit {
|
|||||||
|
|
||||||
### Change
|
### Change
|
||||||
|
|
||||||
Adopted unified `response_error()` function with error code constants. Same constant names on server and client for zero mental translation.
|
Adopted unified `response_error()` function with error code constants, plus convenience helpers. Same constant names on server and client for zero mental translation.
|
||||||
|
|
||||||
**Note**: The fragmented helper functions (`response_form_error()`, `response_auth_required()`, etc.) shown in earlier documentation were a planned design that was superseded before implementation. Only `response_error()` exists.
|
|
||||||
|
|
||||||
### Pattern
|
### Pattern
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// Server - single function with constants
|
// Server - base function with constants
|
||||||
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid']);
|
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid']);
|
||||||
return response_error(Ajax::ERROR_NOT_FOUND, 'Project not found');
|
return response_error(Ajax::ERROR_NOT_FOUND, 'Project not found');
|
||||||
return response_error(Ajax::ERROR_UNAUTHORIZED); // Auto-message
|
|
||||||
|
// Convenience helpers (recommended for clarity)
|
||||||
|
return response_form_error('Validation failed', ['email' => 'Invalid']);
|
||||||
|
return response_not_found('Project not found');
|
||||||
|
return response_unauthorized();
|
||||||
|
return response_auth_required();
|
||||||
|
return response_fatal_error('Something went wrong');
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@@ -912,7 +912,7 @@ async on_load() {
|
|||||||
// Controller: save() receives all form values, validates, persists
|
// Controller: save() receives all form values, validates, persists
|
||||||
#[Ajax_Endpoint]
|
#[Ajax_Endpoint]
|
||||||
public static function save(Request $request, array $params = []) {
|
public static function save(Request $request, array $params = []) {
|
||||||
if (empty($params['title'])) return response_form_error('Error', ['title' => 'Required']);
|
if (empty($params['title'])) return response_form_error('Validation failed', ['title' => 'Required']);
|
||||||
$record = $params['id'] ? My_Model::find($params['id']) : new My_Model();
|
$record = $params['id'] ? My_Model::find($params['id']) : new My_Model();
|
||||||
$record->title = $params['title'];
|
$record->title = $params['title'];
|
||||||
$record->save();
|
$record->save();
|
||||||
|
|||||||
Reference in New Issue
Block a user