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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
116
app/RSpade/upstream_changes/spa_action_method_12_29.txt
Executable file
116
app/RSpade/upstream_changes/spa_action_method_12_29.txt
Executable 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user