} 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});
*
* @example
* // Preload with children (warms cache for entire page tree)
* const action = await Spa.load_detached_action('/contacts/123', {}, {load_children: true});
* action.stop();
*/
static async load_detached_action(url, extra_args = {}, options = {}) {
// 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;
// _load_only: action on_load() only, no children
// _load_render_only: renders full tree, all children run on_load() too
const lifecycle_flag = options.load_children ? '_load_render_only' : '_load_only';
const args = {
...route_match.args,
...extra_args,
[lifecycle_flag]: true
};
console_debug('Spa', `load_detached_action: Loading ${action_name} with args:`, args);
// Create a detached container (not in DOM)
const $detached = $('');
// Instantiate the action on the detached element
$detached.component(action_name, args);
const action = $detached.component();
// Wait for on_load to complete (data fetching)
await action.ready();
console_debug('Spa', `load_detached_action: ${action_name} ready`);
return action;
}
}