diff --git a/app/RSpade/Core/SPA/Spa.js b/app/RSpade/Core/SPA/Spa.js index 9010995a6..87e1be9b4 100755 --- a/app/RSpade/Core/SPA/Spa.js +++ b/app/RSpade/Core/SPA/Spa.js @@ -577,6 +577,10 @@ class Spa { // (Browser refresh scroll is handled separately by Rsx._restore_scroll_on_refresh) Rsx.reset_pending_scroll(); + // Trigger spa_dispatch_start event - allows cleanup before navigation + // Use case: close modals, cancel pending operations, etc. + Rsx.trigger('spa_dispatch_start', { url }); + try { const opts = { history: options.history || 'auto', @@ -943,10 +947,13 @@ class Spa { // This is the action - set reference but don't wait Spa._action = component; - // After action is fully ready (on_load + on_ready complete), retry scroll restoration - // This handles cases where on_load fetches data that increases page height + // After action is fully ready (on_load + on_ready complete) component.ready().then(() => { + // Retry scroll restoration - handles cases where on_load fetches data that increases page height Rsx.try_restore_scroll(); + + // Trigger spa_dispatch_ready event - action is fully loaded and rendered + Rsx.trigger('spa_dispatch_ready', { url, action: component }); }); } else { // This is a layout diff --git a/app/RSpade/man/modals.txt b/app/RSpade/man/modals.txt index c02a11a06..c34ea7952 100755 --- a/app/RSpade/man/modals.txt +++ b/app/RSpade/man/modals.txt @@ -692,6 +692,29 @@ Returns: void (does not wait for close) Note: Call Modal.close() to dismiss the modal programmatically. +================================================================================ +SPA INTEGRATION +================================================================================ + +The Modal system automatically integrates with SPA navigation: + + Auto-Close on Navigation + When SPA navigation begins (spa_dispatch_start event), any open modal + is automatically closed. This prevents stale modals from persisting + across page transitions. + + This behavior is automatic - no configuration required. + + Modal Flow and Navigation + If you need to navigate after a modal action, use Spa.dispatch(): + + const result = await Modal.form({...}); + if (result) { + Spa.dispatch(Rsx.Route('Target_Action')); + } + + The modal closes first, then navigation proceeds. + ================================================================================ MODAL STATE MANAGEMENT ================================================================================ diff --git a/app/RSpade/man/spa.txt b/app/RSpade/man/spa.txt index aaca0d5e9..fdbd47a80 100755 --- a/app/RSpade/man/spa.txt +++ b/app/RSpade/man/spa.txt @@ -580,6 +580,60 @@ NAVIGATION generated by Rsx.Route(), so it never appears in the browser address bar or generated links. +SPA EVENTS + The SPA system fires global events that can be used to hook into the + navigation lifecycle. Register handlers using Rsx.on(): + + spa_dispatch_start + Fired at the beginning of navigation, before the old action is destroyed. + Use for cleanup before page transition. + + Rsx.on('spa_dispatch_start', (data) => { + console.log('Navigating to:', data.url); + // Close modals, cancel pending operations, etc. + }); + + Data payload: + url: Target URL being navigated to + + Common uses: + - Close open modals (Modal system does this automatically) + - Cancel pending Ajax requests + - Save unsaved form state + - Stop video/audio playback + + spa_dispatch_ready + Fired after the new action has fully loaded (on_ready complete). + Use for post-navigation setup that needs the action to be ready. + + Rsx.on('spa_dispatch_ready', (data) => { + console.log('Action ready:', data.action.constructor.name); + // Perform post-navigation actions + }); + + Data payload: + url: Current URL + action: The action component instance (fully loaded) + + Common uses: + - Analytics page view tracking + - Focus management after navigation + - Lazy-load additional resources + + Example: Custom navigation tracking + + class Analytics_Tracker { + static on_app_modules_init() { + Rsx.on('spa_dispatch_start', () => { + Analytics.track_page_exit(); + }); + + Rsx.on('spa_dispatch_ready', (data) => { + Analytics.track_page_view(data.url); + }); + } + } + SESSION VALIDATION After each SPA navigation (except initial load and back/forward), the client validates its state against the server by calling Rsx.validate_session().