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:
root
2025-12-23 22:38:48 +00:00
parent 678ff17ad6
commit 69af4e87d4
5 changed files with 73 additions and 14 deletions

View File

@@ -107,7 +107,8 @@ class Form_Utils {
// Resolve the promise once all animations are complete
Promise.all(animations).then(() => {
// 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) {
const container_top = $error_container.offset().top;
@@ -251,9 +252,10 @@ class Form_Utils {
}
// Convert Laravel validator format {field: [msg1, msg2]} to {field: msg1}
// Skip reserved keys (prefixed with underscore) - these are metadata, not field errors
const normalized = {};
for (const field in errors) {
if (errors.hasOwnProperty(field)) {
if (errors.hasOwnProperty(field) && !field.startsWith('_')) {
const value = errors[field];
if (Array.isArray(value) && value.length > 0) {
normalized[field] = value[0];
@@ -345,7 +347,8 @@ class Form_Utils {
*/
static _apply_combined_error($parent, summary_msg, unmatched_errors) {
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;
// Create alert with summary message and bulleted list of unmatched errors
@@ -386,7 +389,8 @@ class Form_Utils {
const animations = [];
// 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;
if (typeof messages === 'string') {

View File

@@ -28,16 +28,16 @@ class Error_Response extends Rsx_Response_Abstract
if ($metadata === null) {
$this->metadata = [];
} elseif (is_string($metadata)) {
$this->metadata = ['message' => $metadata];
$this->metadata = ['_message' => $metadata];
} elseif (is_array($metadata)) {
$this->metadata = $metadata;
} else {
$this->metadata = ['message' => (string)$metadata];
$this->metadata = ['_message' => (string)$metadata];
}
// Set reason from message or use default
if (isset($this->metadata['message'])) {
$this->reason = $this->metadata['message'];
if (isset($this->metadata['_message'])) {
$this->reason = $this->metadata['_message'];
} else {
$this->reason = Ajax::get_default_message($error_code);
}

View File

@@ -1089,6 +1089,57 @@ function response_not_found(?string $message = null)
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
*

View File

@@ -97,17 +97,21 @@ class Frontend_Clients_Edit {
### Change
Adopted unified `response_error()` function with error code constants. 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.
Adopted unified `response_error()` function with error code constants, plus convenience helpers. Same constant names on server and client for zero mental translation.
### Pattern
```php
// Server - single function with constants
// Server - base function with constants
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid']);
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

View File

@@ -912,7 +912,7 @@ async on_load() {
// Controller: save() receives all form values, validates, persists
#[Ajax_Endpoint]
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->title = $params['title'];
$record->save();