Add Spa.load_detached_action, decorator identifier rule, npm updates
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -941,4 +941,75 @@ class Spa {
|
||||
static spa_unknown_route_fatal(path) {
|
||||
console.error(`Unknown route for path ${path} - this shouldn't happen`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an action in detached mode without affecting the live SPA state
|
||||
*
|
||||
* This method resolves a URL to an action, instantiates it on a detached DOM element
|
||||
* (not in the actual document), runs its full lifecycle including on_load(), and
|
||||
* returns the fully-initialized component instance.
|
||||
*
|
||||
* Use cases:
|
||||
* - Getting action metadata (title, breadcrumbs) for navigation UI
|
||||
* - Pre-fetching action data before navigation
|
||||
* - Inspecting action state without displaying it
|
||||
*
|
||||
* IMPORTANT: The caller is responsible for calling action.stop() when done
|
||||
* to prevent memory leaks. The detached action holds references and may have
|
||||
* event listeners that need cleanup.
|
||||
*
|
||||
* @param {string} url - The URL to resolve and load
|
||||
* @param {object} extra_args - Optional extra parameters to pass to the action component.
|
||||
* These are merged with URL-extracted args (extra_args take precedence).
|
||||
* Pass {use_cached_data: true} to have the action load with cached data
|
||||
* without revalidation if cached data is available.
|
||||
* @returns {Promise<Spa_Action|null>} The fully-loaded action instance, or null if route not found
|
||||
*
|
||||
* @example
|
||||
* // Basic usage
|
||||
* const action = await Spa.load_detached_action('/contacts/123');
|
||||
* if (action) {
|
||||
* const title = action.get_title?.() ?? action.constructor.name;
|
||||
* console.log('Page title:', title);
|
||||
* action.stop(); // Clean up when done
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* // With cached data (faster, no network request if cached)
|
||||
* const action = await Spa.load_detached_action('/contacts/123', {use_cached_data: true});
|
||||
*/
|
||||
static async load_detached_action(url, extra_args = {}) {
|
||||
// Parse URL and match to route
|
||||
const parsed = Spa.parse_url(url);
|
||||
const url_without_hash = parsed.path + parsed.search;
|
||||
const route_match = Spa.match_url_to_route(url_without_hash);
|
||||
|
||||
if (!route_match) {
|
||||
console_debug('Spa', 'load_detached_action: No route match for ' + url);
|
||||
return null;
|
||||
}
|
||||
|
||||
const action_class = route_match.action_class;
|
||||
const action_name = action_class.name;
|
||||
|
||||
// Merge URL args with extra_args (extra_args take precedence)
|
||||
const args = { ...route_match.args, ...extra_args };
|
||||
|
||||
console_debug('Spa', `load_detached_action: Loading ${action_name} with args:`, args);
|
||||
|
||||
// Create a detached container (not in DOM)
|
||||
const $detached = $('<div>');
|
||||
|
||||
// Instantiate the action on the detached element
|
||||
// This triggers the full component lifecycle: on_create -> render -> on_render -> on_load -> on_ready
|
||||
$detached.component(action_name, args);
|
||||
const action = $detached.component();
|
||||
|
||||
// Wait for the action to be fully ready (including on_load completion)
|
||||
await action.ready();
|
||||
|
||||
console_debug('Spa', `load_detached_action: ${action_name} ready`);
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user