Files
rspade_system/docs/skills/ajax-error-handling/SKILL.md
root 1b46c5270c Add skills documentation and misc updates
Add form value persistence across cache revalidation re-renders

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-29 04:38:06 +00:00

5.9 KiB
Executable File

name, description
name description
ajax-error-handling Handling Ajax errors in RSX including response formats, error codes, client-side error display, and Form_Utils. Use when implementing error handling for Ajax calls, working with response_error(), form validation errors, or debugging Ajax failures.

RSX Ajax Error Handling

Response Architecture

RSX returns HTTP 200 for ALL Ajax responses (success and errors). Success/failure is encoded in the response body via _success field.

// Success response
{
    "_success": true,
    "_ajax_return_value": { /* your data */ }
}

// Error response
{
    "_success": false,
    "error_code": "validation|not_found|unauthorized|auth_required|fatal",
    "reason": "User-friendly message",
    "metadata": { /* field errors for validation */ }
}

Rationale: Batch requests need uniform status codes. Always get parseable response body. Non-200 only means "couldn't reach PHP at all".


Error Codes

Constant Purpose
Ajax::ERROR_VALIDATION Field validation failures
Ajax::ERROR_NOT_FOUND Resource not found
Ajax::ERROR_UNAUTHORIZED User lacks permission
Ajax::ERROR_AUTH_REQUIRED User not logged in
Ajax::ERROR_FATAL Uncaught PHP exceptions

Constants available in both PHP (Ajax::ERROR_*) and JavaScript (Ajax.ERROR_*).


Server-Side: Returning Errors

Use response_error() helper - never return _success manually:

#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
    // Validation error with field-specific messages
    if (empty($params['email'])) {
        return response_error(Ajax::ERROR_VALIDATION, [
            'email' => 'Email is required'
        ]);
    }

    // Not found error
    $user = User_Model::find($params['id']);
    if (!$user) {
        return response_error(Ajax::ERROR_NOT_FOUND, 'User not found');
    }

    // Success - just return data (framework wraps it)
    $user->name = $params['name'];
    $user->save();
    return ['id' => $user->id];
}

Let Exceptions Bubble

Don't wrap database/framework operations in try/catch. Let exceptions bubble to the global handler:

// WRONG - Don't catch framework exceptions
try {
    $user->save();
} catch (Exception $e) {
    return ['error' => $e->getMessage()];
}

// CORRECT - Let it throw
$user->save();  // Framework catches and formats

When try/catch IS appropriate: File uploads, external API calls, user input parsing (expected failures).


Client-Side: Handling Errors

Ajax.js automatically unwraps responses:

  • _success: true → Promise resolves with _ajax_return_value
  • _success: false → Promise rejects with Error object
try {
    const user = await User_Controller.get_user(123);
    console.log(user.name);  // Already unwrapped
} catch (error) {
    console.log(error.code);     // 'validation', 'not_found', etc.
    console.log(error.message);  // User-displayable message
    console.log(error.metadata); // Field errors for validation
}

Automatic Error Display

Uncaught Ajax errors automatically display via Modal.error():

// No try/catch - error shows in modal automatically
const user = await User_Controller.get_user(123);

Form Error Handling

const result = await Modal.form({
    title: 'Add User',
    component: 'User_Form',
    on_submit: async (form) => {
        try {
            const result = await Controller.save(form.vals());
            return result; // Success - close modal
        } catch (error) {
            await form.render_error(error);
            return false; // Keep modal open
        }
    }
});

form.render_error() handles all error types:

  • validation: Shows inline field errors + alert for unmatched errors
  • fatal/network/auth: Shows error in form's error container

With Form_Utils (Non-Rsx_Form)

try {
    const result = await Controller.save(form_data);
} catch (error) {
    if (error.code === Ajax.ERROR_VALIDATION) {
        Form_Utils.apply_form_errors($form, error.metadata);
    } else {
        Rsx.render_error(error, '#error_container');
    }
}

Form_Utils.apply_form_errors():

  • Matches errors to fields by name attribute
  • Adds .is-invalid class and inline error text
  • Shows alert ONLY for errors that couldn't match to fields

Error Display Methods

Modal.error() - Critical Errors

await Modal.error(error, 'Operation Failed');

Red danger modal, can stack over other modals.

Rsx.render_error() - Container Display

Rsx.render_error(error, '#error_container');
Rsx.render_error(error, this.$sid('error'));

Displays error in any container element.


Developer vs Production Mode

Developer Mode (IS_DEVELOPER=true):

  • Full exception message
  • File path and line number
  • SQL queries with parameters
  • Stack trace (up to 10 frames)

Production Mode:

  • Generic message: "An unexpected error occurred"
  • No technical details exposed
  • Errors logged server-side

Common Patterns

Simple Validation

#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
    $errors = [];

    if (empty($params['email'])) {
        $errors['email'] = 'Email is required';
    }
    if (empty($params['name'])) {
        $errors['name'] = 'Name is required';
    }

    if ($errors) {
        return response_error(Ajax::ERROR_VALIDATION, $errors);
    }

    // ... save logic
}

Check Specific Error Type

try {
    const data = await Controller.get_data(id);
} catch (error) {
    if (error.code === Ajax.ERROR_NOT_FOUND) {
        show_not_found_message();
    } else if (error.code === Ajax.ERROR_UNAUTHORIZED) {
        redirect_to_login();
    } else {
        // Let default handler show modal
        throw error;
    }
}

More Information

Details: php artisan rsx:man ajax_error_handling