Convert Spa.action property to Spa.action() method

🤖 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 05:49:17 +00:00
parent 1b46c5270c
commit bded711d1c
5 changed files with 161 additions and 15 deletions

View File

@@ -19,8 +19,8 @@ class Spa {
// Current layout instance
static layout = null;
// Current action instance
static action = null;
// Current action instance (use Spa.action() to access)
static _action = null;
// Current route instance
static route = null;
@@ -49,6 +49,29 @@ class Spa {
// Grace period in milliseconds for suppressing errors after navigation
static NAVIGATION_GRACE_PERIOD_MS = 10000;
/**
* Get the current action component instance
*
* Returns the cached action if available, otherwise finds it from the DOM.
* This method should be used instead of direct property access to ensure
* the action is always found even if the cache was cleared.
*
* @returns {Spa_Action|null} The current action instance, or null if none
*/
static action() {
if (Spa._action) {
return Spa._action;
}
const $spa_action = $('.Spa_Action').first();
if (!$spa_action.exists()) {
return null;
}
Spa._action = $spa_action.component();
return Spa._action;
}
/**
* Check if we're within the navigation grace period
*
@@ -543,6 +566,9 @@ class Spa {
Spa.is_dispatching = true;
// Clear cached action - will be set when new action is created
Spa._action = null;
// Record navigation timestamp for error suppression grace period
// Errors from previous page's pending requests should be ignored for 10 seconds
Spa._navigation_timestamp = Date.now();
@@ -911,7 +937,7 @@ class Spa {
if (is_last) {
// This is the action - set reference but don't wait
Spa.action = component;
Spa._action = component;
} else {
// This is a layout
// Wait for render to complete (not full ready - we don't need child data to load)
@@ -932,7 +958,7 @@ class Spa {
for (const layout of layouts_for_on_action) {
// Set action reference before calling on_action so layouts can access it
layout.action = Spa.action;
layout.action = Spa.action();
if (layout.on_action) {
layout.on_action(url, action_name, args);
}

View File

@@ -419,7 +419,7 @@ SUBLAYOUTS
References:
Spa.layout Always the top-level layout
Spa.action Always the bottom-level action (not a layout)
Spa.action() Always the bottom-level action (not a layout)
To access intermediate layouts, use DOM traversal or layout hooks.
@@ -516,11 +516,11 @@ NAVIGATION
Accessing Active Components:
Spa.layout Reference to current layout component instance
Spa.action Reference to current action component instance
Spa.action() Reference to current action component instance
Example:
// From anywhere in SPA context
Spa.action.reload(); // Reload current action
Spa.action().reload(); // Reload current action
Spa.layout.update_nav(); // Call layout method
Loader Title Hint:
@@ -821,7 +821,7 @@ COMMON PATTERNS
Reloading Current Action:
// Refresh current action data
Spa.action.reload();
Spa.action().reload();
Form Submission with Redirect:
async on_submit() {
@@ -880,7 +880,7 @@ DETACHED ACTION LOADING
});
What It Does NOT Affect:
- Spa.action (current live action remains unchanged)
- Spa.action() (current live action remains unchanged)
- Spa.layout (current live layout remains unchanged)
- Spa.route / Spa.params (current route state unchanged)
- Browser history

View File

@@ -33,9 +33,13 @@ CHANGES REQUIRED
// the cache revalidates and the form re-renders with fresh data.
// This system caches user changes and re-applies them after re-render.
// Get parent component (closest .Component that isn't this one)
const $parent = this.$.closest('.Component').not(this.$);
const parent_component = $parent.exists() ? $parent.component() : null;
// Determine cache storage location and key
const cache_location = this.parent() || this;
const cache_key = this.parent() ? `__formvals_${this._cid}` : '__this_formvals';
const cache_location = parent_component || this;
const cache_key = parent_component ? `__formvals_${this._cid}` : '__this_formvals';
// Initialize cache if it doesn't exist
if (!cache_location[cache_key]) {
@@ -118,10 +122,10 @@ VERIFICATION
5. In browser console, you can inspect the cache:
// If form has a parent:
Spa.action.__formvals_form
Spa.action().__formvals_form
// Or check what's stored:
console.log(Object.keys(Spa.action).filter(k => k.startsWith('__formvals')));
console.log(Object.keys(Spa.action()).filter(k => k.startsWith('__formvals')));
REFERENCE

View File

@@ -0,0 +1,116 @@
SPA ACTION METHOD - MIGRATION GUIDE
Date: 2025-12-29
SUMMARY
The Spa.action property has been converted to a method: Spa.action().
This change ensures the current action is always accessible, even when
the cached reference is cleared during navigation. The method will find
the action from the DOM if the cache is empty.
All existing code that accesses Spa.action must be updated to Spa.action().
AFFECTED FILES
Any JavaScript file that accesses Spa.action:
- SPA action classes
- Layout classes
- Utility code that interacts with the current action
CHANGES REQUIRED
1. Update All Spa.action References to Spa.action()
BEFORE:
Spa.action.reload();
const title = Spa.action.get_title();
if (Spa.action) { ... }
AFTER:
Spa.action().reload();
const title = Spa.action().get_title();
if (Spa.action()) { ... }
2. Search and Replace Pattern
Find all occurrences in your codebase:
grep -r "Spa\.action[^(]" --include="*.js" rsx/
Replace Spa.action with Spa.action() (note: don't match Spa.action() itself)
3. Common Usage Patterns
Reloading the current action:
BEFORE: Spa.action.reload();
AFTER: Spa.action().reload();
Accessing action data:
BEFORE: const data = Spa.action.data;
AFTER: const data = Spa.action().data;
Checking if action exists:
BEFORE: if (Spa.action) { ... }
AFTER: if (Spa.action()) { ... }
Getting action args:
BEFORE: const id = Spa.action.args.id;
AFTER: const id = Spa.action().args.id;
HOW IT WORKS
The Spa.action() method:
1. Returns the cached _action if available
2. If cache is empty, finds $('.Spa_Action').first()
3. Gets the component from that element
4. Caches and returns it
5. Returns null if no action exists
The cache is cleared at the start of each Spa.dispatch() call, ensuring
the old action reference doesn't persist during navigation.
Code Example:
static action() {
if (Spa._action) {
return Spa._action;
}
const $spa_action = $('.Spa_Action').first();
if (!$spa_action.exists()) {
return null;
}
Spa._action = $spa_action.component();
return Spa._action;
}
WHY THIS CHANGE
The property-based approach (Spa.action) had timing issues:
- During navigation, Spa.action could be null or stale
- Forms using stale-while-revalidate caching need reliable action access
- The method approach ensures the action is always found if it exists
The method-based approach:
- Always returns the current action if one exists in the DOM
- Falls back to DOM lookup when cache is cleared
- Provides consistent behavior during navigation transitions
CONFIGURATION
No configuration required. The change is automatic.
VERIFICATION
1. Search your codebase for Spa.action usage:
grep -r "Spa\.action[^(]" --include="*.js" rsx/
2. Update all occurrences to Spa.action()
3. Test SPA navigation works correctly
4. Test that Spa.action().reload() works from console
REFERENCE
php artisan rsx:man spa
system/app/RSpade/Core/SPA/Spa.js

View File

@@ -332,8 +332,8 @@ Rsx::Route('Contacts_View_Action', 123) // /contacts/123
```javascript
Spa.dispatch('/contacts/123'); // Programmatic navigation
Spa.layout // Current layout instance
Spa.action // Current action instance
Spa.layout // Current layout instance
Spa.action() // Current action instance
```
### URL Parameters