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
|
// Current layout instance
|
||||||
static layout = null;
|
static layout = null;
|
||||||
|
|
||||||
// Current action instance
|
// Current action instance (use Spa.action() to access)
|
||||||
static action = null;
|
static _action = null;
|
||||||
|
|
||||||
// Current route instance
|
// Current route instance
|
||||||
static route = null;
|
static route = null;
|
||||||
@@ -49,6 +49,29 @@ class Spa {
|
|||||||
// Grace period in milliseconds for suppressing errors after navigation
|
// Grace period in milliseconds for suppressing errors after navigation
|
||||||
static NAVIGATION_GRACE_PERIOD_MS = 10000;
|
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
|
* Check if we're within the navigation grace period
|
||||||
*
|
*
|
||||||
@@ -543,6 +566,9 @@ class Spa {
|
|||||||
|
|
||||||
Spa.is_dispatching = true;
|
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
|
// Record navigation timestamp for error suppression grace period
|
||||||
// Errors from previous page's pending requests should be ignored for 10 seconds
|
// Errors from previous page's pending requests should be ignored for 10 seconds
|
||||||
Spa._navigation_timestamp = Date.now();
|
Spa._navigation_timestamp = Date.now();
|
||||||
@@ -911,7 +937,7 @@ class Spa {
|
|||||||
|
|
||||||
if (is_last) {
|
if (is_last) {
|
||||||
// This is the action - set reference but don't wait
|
// This is the action - set reference but don't wait
|
||||||
Spa.action = component;
|
Spa._action = component;
|
||||||
} else {
|
} else {
|
||||||
// This is a layout
|
// This is a layout
|
||||||
// Wait for render to complete (not full ready - we don't need child data to load)
|
// 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) {
|
for (const layout of layouts_for_on_action) {
|
||||||
// Set action reference before calling on_action so layouts can access it
|
// Set action reference before calling on_action so layouts can access it
|
||||||
layout.action = Spa.action;
|
layout.action = Spa.action();
|
||||||
if (layout.on_action) {
|
if (layout.on_action) {
|
||||||
layout.on_action(url, action_name, args);
|
layout.on_action(url, action_name, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ SUBLAYOUTS
|
|||||||
|
|
||||||
References:
|
References:
|
||||||
Spa.layout Always the top-level layout
|
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.
|
To access intermediate layouts, use DOM traversal or layout hooks.
|
||||||
|
|
||||||
@@ -516,11 +516,11 @@ NAVIGATION
|
|||||||
|
|
||||||
Accessing Active Components:
|
Accessing Active Components:
|
||||||
Spa.layout Reference to current layout component instance
|
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:
|
Example:
|
||||||
// From anywhere in SPA context
|
// From anywhere in SPA context
|
||||||
Spa.action.reload(); // Reload current action
|
Spa.action().reload(); // Reload current action
|
||||||
Spa.layout.update_nav(); // Call layout method
|
Spa.layout.update_nav(); // Call layout method
|
||||||
|
|
||||||
Loader Title Hint:
|
Loader Title Hint:
|
||||||
@@ -821,7 +821,7 @@ COMMON PATTERNS
|
|||||||
|
|
||||||
Reloading Current Action:
|
Reloading Current Action:
|
||||||
// Refresh current action data
|
// Refresh current action data
|
||||||
Spa.action.reload();
|
Spa.action().reload();
|
||||||
|
|
||||||
Form Submission with Redirect:
|
Form Submission with Redirect:
|
||||||
async on_submit() {
|
async on_submit() {
|
||||||
@@ -880,7 +880,7 @@ DETACHED ACTION LOADING
|
|||||||
});
|
});
|
||||||
|
|
||||||
What It Does NOT Affect:
|
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.layout (current live layout remains unchanged)
|
||||||
- Spa.route / Spa.params (current route state unchanged)
|
- Spa.route / Spa.params (current route state unchanged)
|
||||||
- Browser history
|
- Browser history
|
||||||
|
|||||||
@@ -33,9 +33,13 @@ CHANGES REQUIRED
|
|||||||
// the cache revalidates and the form re-renders with fresh data.
|
// the cache revalidates and the form re-renders with fresh data.
|
||||||
// This system caches user changes and re-applies them after re-render.
|
// 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
|
// Determine cache storage location and key
|
||||||
const cache_location = this.parent() || this;
|
const cache_location = parent_component || this;
|
||||||
const cache_key = this.parent() ? `__formvals_${this._cid}` : '__this_formvals';
|
const cache_key = parent_component ? `__formvals_${this._cid}` : '__this_formvals';
|
||||||
|
|
||||||
// Initialize cache if it doesn't exist
|
// Initialize cache if it doesn't exist
|
||||||
if (!cache_location[cache_key]) {
|
if (!cache_location[cache_key]) {
|
||||||
@@ -118,10 +122,10 @@ VERIFICATION
|
|||||||
|
|
||||||
5. In browser console, you can inspect the cache:
|
5. In browser console, you can inspect the cache:
|
||||||
// If form has a parent:
|
// If form has a parent:
|
||||||
Spa.action.__formvals_form
|
Spa.action().__formvals_form
|
||||||
|
|
||||||
// Or check what's stored:
|
// 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
|
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
|
```javascript
|
||||||
Spa.dispatch('/contacts/123'); // Programmatic navigation
|
Spa.dispatch('/contacts/123'); // Programmatic navigation
|
||||||
Spa.layout // Current layout instance
|
Spa.layout // Current layout instance
|
||||||
Spa.action // Current action instance
|
Spa.action() // Current action instance
|
||||||
```
|
```
|
||||||
|
|
||||||
### URL Parameters
|
### URL Parameters
|
||||||
|
|||||||
Reference in New Issue
Block a user