# JQHTML SPA Router System - LLM Reference ## Core Architecture - Static router class `Jqhtml_Router` manages all navigation - Routes are ES6 classes extending `Jqhtml_Route` - Layouts are ES6 classes extending `Jqhtml_Layout` - SPA container is ES6 class extending `Jqhtml_SPA` - Hash routing (`#/path`) automatically enabled for `file://` protocol - Standard routing for `http://` and `https://` protocols ## Route Definition ### Basic Route Class ```javascript class HomeRoute extends Jqhtml_Route { static routes = ['/']; // URL patterns this route handles static layout = 'MainLayout'; // Layout to use (optional) static meta = { name: 'home', requiresAuth: false }; // Metadata async on_render() { // Route component render logic } async pre_dispatch() { // Return false to cancel navigation // Return string URL to redirect return true; } async post_dispatch() { // Called after route fully loaded } } ``` ### Route Patterns ```javascript static routes = [ '/', // Exact match '/users', // Static path '/users/:id', // Parameter capture '/posts/:id/comments', // Multiple segments '/admin/:section/:id' // Multiple parameters ]; ``` ### Parameter Access ```javascript class UserRoute extends Jqhtml_Route { static routes = ['/users/:id']; async on_load() { // URL: /users/123?tab=profile const userId = this.args.id; // '123' from URL pattern const tab = this.args.tab; // 'profile' from query string const hash = this.args.hash; // Fragment after # } } ``` ## Layout System ### Layout Definition ```javascript class MainLayout extends Jqhtml_Layout { async on_render() { this.$.html(`
Navigation
`); } async on_route_change(old_route, new_route) { // Update active navigation state this.$find('.nav-link').removeClass('active'); this.$find(`[href="${new_route.path}"]`).addClass('active'); } should_rerender() { return false; // Layouts persist across route changes } async pre_dispatch(route_info) { // Can intercept routing at layout level if (route_info.meta.requiresAuth && !this.data.user) { return '/login'; // Redirect } return true; } } ``` ### Layout Persistence - Layouts never re-render during route changes within same layout - Layout instance persists until different layout needed - `$sid="content"` element required for route injection point ## SPA Application Container ### Basic SPA Setup ```javascript class App extends Jqhtml_SPA { async on_create() { // Register all routes HomeRoute.init(); UserRoute.init(); AdminRoute.init(); // Register layouts Jqhtml_Router.register_layout('MainLayout', MainLayout); Jqhtml_Router.register_layout('AdminLayout', AdminLayout); // Set default layout Jqhtml_Router.set_default_layout('MainLayout'); // Initialize router await this.init_router({ default_layout: 'MainLayout' }); } async pre_dispatch(route_info) { // Global route guard // Runs before layout.pre_dispatch() return true; } async post_dispatch(route_info) { // Global post-navigation logic // Runs after layout.post_dispatch() } } // Mount application $('#app').append(new App().$); ``` ## Navigation Methods ### Programmatic Navigation ```javascript // Navigate to URL Jqhtml_Router.dispatch('/users/123'); // Navigate with replace (no history entry) Jqhtml_Router.replace('/login'); // Navigate with parameters const url = Jqhtml_Router.url('user-profile', { id: 123, tab: 'settings' }); Jqhtml_Router.dispatch(url); // From within route component this.dispatch({ id: 456 }); // Merges with current args ``` ### Link Interception - All `` tags automatically intercepted for same-origin navigation - External links and `target="_blank"` not intercepted - Ctrl/Cmd+Click opens in new tab (not intercepted) ### URL Building ```javascript // Build URL from route name Jqhtml_Router.url('user-profile', { id: 123 }); // '/users/123' // Build URL from pattern Jqhtml_Router.build_url('/users/:id', { id: 123, tab: 'info' }); // '/users/123?tab=info' // From route class UserRoute.url({ id: 123 }); // '/users/123' ``` ## Dispatch Lifecycle ### Execution Order 1. **URL Matching** - Parse URL, extract parameters 2. **App.pre_dispatch()** - Global guard, can cancel/redirect 3. **Layout Resolution** - Create/reuse layout based on route 4. **Layout.pre_dispatch()** - Layout-level guard 5. **Route Instantiation** - Create route component with args 6. **Route.pre_dispatch()** - Route-level guard 7. **Layout._render_route()** - Inject route into layout 8. **Route Lifecycle** - Standard component lifecycle 9. **Layout.on_route_change()** - Notify layout of change 10. **Route.post_dispatch()** - Route complete 11. **Layout.post_dispatch()** - Layout handling complete 12. **App.post_dispatch()** - Global post-navigation ### Guard Return Values ```javascript async pre_dispatch(route_info) { return true; // Continue navigation return false; // Cancel navigation return '/login'; // Redirect to different route } ``` ## Router State ### Access Current State ```javascript // Static access Jqhtml_Router.state = { route: 'UserRoute', // Current route component name layout: 'MainLayout', // Current layout name url: '/users/123', // Current URL path args: { id: '123' }, // Current parameters hash: 'section' // Current hash fragment }; // Get current route info const current = Jqhtml_Router.current_route_info; // { url, path, args, hash, meta, component_name, component_class, layout } // From SPA class Jqhtml_SPA.state; // Same as Jqhtml_Router.state Jqhtml_SPA.current_route; // Current route info ``` ## Hash Routing (file:// Protocol) ### Automatic Detection ```javascript // Router automatically uses hash routing for file:// URLs // file:///app/index.html#/users/123 // Translates to route: /users/123 // Hash routing format window.location.href = 'file:///path/index.html#/users/123'; // Router sees: /users/123 // Regular routing format (http/https) window.location.href = 'https://example.com/users/123'; // Router sees: /users/123 ``` ### Manual Hash Control ```javascript if (Jqhtml_Router.use_hash_routing) { // In hash mode window.location.hash = '#/users/123'; } else { // In regular mode window.history.pushState({}, '', '/users/123'); } ``` ## Route Metadata & Named Routes ### Named Route Pattern ```javascript class UserProfileRoute extends Jqhtml_Route { static routes = ['/users/:id/profile']; static meta = { name: 'user-profile', // Reference name requiresAuth: true, title: 'User Profile' }; } // Navigate by name Jqhtml_Router.url('user-profile', { id: 123 }); UserProfileRoute.dispatch({ id: 123 }); ``` ## Advanced Patterns ### Nested Route Components ```javascript class DashboardRoute extends Jqhtml_Route { async on_render() { this.$.html(`
`); } } ``` ### Route-Level Data Loading ```javascript class UserRoute extends Jqhtml_Route { async on_load() { // Runs during component load phase // Data fetching happens in parallel with siblings const userId = this.args.id; this.data.user = await fetch(`/api/users/${userId}`).then(r => r.json()); } } ``` ### Authentication Flow ```javascript class AuthLayout extends Jqhtml_Layout { async pre_dispatch(route_info) { const token = localStorage.getItem('auth_token'); if (!token && route_info.meta.requiresAuth) { // Store intended destination sessionStorage.setItem('redirect_after_login', route_info.url); return '/login'; // Redirect to login } return true; } } class LoginRoute extends Jqhtml_Route { async handleLogin() { const token = await authenticate(this.data.credentials); localStorage.setItem('auth_token', token); // Redirect to intended destination or home const redirect = sessionStorage.getItem('redirect_after_login') || '/'; sessionStorage.removeItem('redirect_after_login'); Jqhtml_Router.dispatch(redirect); } } ``` ### Query String Handling ```javascript // URL: /search?q=jqhtml&category=docs&page=2 class SearchRoute extends Jqhtml_Route { static routes = ['/search']; async on_load() { const query = this.args.q; // 'jqhtml' const category = this.args.category; // 'docs' const page = parseInt(this.args.page) || 1; // 2 } nextPage() { // Navigate with updated query params this.dispatch({ page: (parseInt(this.args.page) || 1) + 1 }); } } ``` ## Critical Invariants 1. Layouts must have element with `$sid="content"` for route injection 2. Route dispatch is asynchronous and can be cancelled at multiple points 3. Layout instances persist across same-layout route changes 4. Routes are destroyed and recreated on each navigation 5. Hash routing auto-enabled for file:// protocol, cannot be disabled 6. All same-origin link clicks are intercepted unless explicitly prevented 7. Router state is global singleton, only one router instance exists 8. Route parameters are always strings, cast as needed 9. Query parameters merge with route parameters in args object 10. Guard functions execute in strict order: app → layout → route