Add loader title hint for SPA navigation feedback
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -491,9 +491,11 @@ class Rsx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect any extra parameters for query string
|
// Collect any extra parameters for query string
|
||||||
|
// Filter out internal parameters that should not appear in URLs
|
||||||
|
const internal_params = ['_loader_title_hint'];
|
||||||
const query_params = {};
|
const query_params = {};
|
||||||
for (const key in params) {
|
for (const key in params) {
|
||||||
if (!used_params[key]) {
|
if (!used_params[key] && !internal_params.includes(key)) {
|
||||||
query_params[key] = params[key];
|
query_params[key] = params[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -465,7 +465,19 @@ class Spa {
|
|||||||
|
|
||||||
console_debug('Spa', 'Intercepting link click: ' + href);
|
console_debug('Spa', 'Intercepting link click: ' + href);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
Spa.dispatch(href, { history: 'auto' });
|
|
||||||
|
// Check for loader title hint - provides immediate title feedback while page loads
|
||||||
|
const loader_title_hint = link.getAttribute('data-loader-title-hint');
|
||||||
|
const dispatch_options = { history: 'auto' };
|
||||||
|
|
||||||
|
if (loader_title_hint) {
|
||||||
|
// Set document title immediately for instant feedback
|
||||||
|
document.title = loader_title_hint;
|
||||||
|
dispatch_options.loader_title_hint = loader_title_hint;
|
||||||
|
console_debug('Spa', 'Loader title hint: ' + loader_title_hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
Spa.dispatch(href, dispatch_options);
|
||||||
} else {
|
} else {
|
||||||
console_debug('Spa', 'No SPA route match, letting server handle: ' + href);
|
console_debug('Spa', 'No SPA route match, letting server handle: ' + href);
|
||||||
}
|
}
|
||||||
@@ -514,6 +526,7 @@ class Spa {
|
|||||||
history: options.history || 'auto',
|
history: options.history || 'auto',
|
||||||
scroll: 'scroll' in options ? options.scroll : undefined,
|
scroll: 'scroll' in options ? options.scroll : undefined,
|
||||||
triggers: options.triggers !== false,
|
triggers: options.triggers !== false,
|
||||||
|
loader_title_hint: options.loader_title_hint || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
console_debug('Spa', 'Dispatching to: ' + url + ' (history: ' + opts.history + ')');
|
console_debug('Spa', 'Dispatching to: ' + url + ' (history: ' + opts.history + ')');
|
||||||
@@ -660,18 +673,25 @@ class Spa {
|
|||||||
const action_class = route_match.action_class;
|
const action_class = route_match.action_class;
|
||||||
const action_name = action_class.name;
|
const action_name = action_class.name;
|
||||||
|
|
||||||
|
// Merge loader title hint into action args if provided
|
||||||
|
// This allows the action to show a placeholder title while loading
|
||||||
|
let action_args = route_match.args;
|
||||||
|
if (opts.loader_title_hint) {
|
||||||
|
action_args = { ...route_match.args, _loader_title_hint: opts.loader_title_hint };
|
||||||
|
}
|
||||||
|
|
||||||
// Log successful SPA navigation
|
// Log successful SPA navigation
|
||||||
console.warn('[Spa.dispatch] Executing SPA navigation', {
|
console.warn('[Spa.dispatch] Executing SPA navigation', {
|
||||||
url: url,
|
url: url,
|
||||||
path: parsed.path,
|
path: parsed.path,
|
||||||
params: route_match.args,
|
params: action_args,
|
||||||
action: action_name,
|
action: action_name,
|
||||||
layouts: target_layouts,
|
layouts: target_layouts,
|
||||||
history_mode: opts.history
|
history_mode: opts.history
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolve layout chain - find divergence point and reuse matching layouts
|
// Resolve layout chain - find divergence point and reuse matching layouts
|
||||||
await Spa._resolve_layout_chain(target_layouts, action_name, route_match.args, url);
|
await Spa._resolve_layout_chain(target_layouts, action_name, action_args, url);
|
||||||
|
|
||||||
// Scroll Restoration #1: Immediate (after action starts)
|
// Scroll Restoration #1: Immediate (after action starts)
|
||||||
// This occurs synchronously after the action component is created
|
// This occurs synchronously after the action component is created
|
||||||
|
|||||||
@@ -523,6 +523,37 @@ NAVIGATION
|
|||||||
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:
|
||||||
|
When navigating from a list to a detail page, you can provide a hint
|
||||||
|
for the page title to display while the action loads its data. This
|
||||||
|
provides immediate visual feedback instead of showing a blank or
|
||||||
|
generic title during the loading state.
|
||||||
|
|
||||||
|
Add data-loader-title-hint attribute to links:
|
||||||
|
|
||||||
|
<a href="<%= Rsx.Route('Contacts_View_Action', {id: contact.id}) %>"
|
||||||
|
data-loader-title-hint="<%= contact.name %>">
|
||||||
|
<%= contact.name %>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
When the link is clicked:
|
||||||
|
1. document.title is immediately set to the hint value
|
||||||
|
2. The hint is passed to the action as this.args._loader_title_hint
|
||||||
|
3. Action can use the hint while loading, then replace with real title
|
||||||
|
|
||||||
|
Using the Hint in Actions:
|
||||||
|
async page_title() {
|
||||||
|
// Show hint while loading, real title when data is ready
|
||||||
|
if (this.is_loading() && this.args._loader_title_hint) {
|
||||||
|
return this.args._loader_title_hint;
|
||||||
|
}
|
||||||
|
return `Contact: ${this.data.contact.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
The _loader_title_hint parameter is automatically filtered from URLs
|
||||||
|
generated by Rsx.Route(), so it never appears in the browser address
|
||||||
|
bar or generated links.
|
||||||
|
|
||||||
SESSION VALIDATION
|
SESSION VALIDATION
|
||||||
After each SPA navigation (except initial load and back/forward), the client
|
After each SPA navigation (except initial load and back/forward), the client
|
||||||
validates its state against the server by calling Rsx.validate_session().
|
validates its state against the server by calling Rsx.validate_session().
|
||||||
|
|||||||
Reference in New Issue
Block a user