SPA(3) RSX Framework Manual SPA(3) NAME spa - Single Page Application routing for authenticated areas SYNOPSIS PHP Bootstrap Controller (one per SPA module): class Frontend_Spa_Controller extends Rsx_Controller_Abstract { #[SPA] #[Auth('Permission::authenticated()')] public static function index(Request $request, array $params = []) { return rsx_view(SPA); } } JavaScript Action (many per module): @route('/contacts') @layout('Frontend_Layout') @spa('Frontend_Spa_Controller::index') class Contacts_Index_Action extends Spa_Action { async on_load() { this.data.contacts = await Frontend_Contacts_Controller.datagrid_fetch(); } } DESCRIPTION The RSX SPA system provides client-side routing for authenticated application areas, enabling navigation without page reloads. Unlike traditional Laravel views where each navigation triggers a full page load, SPA modules bootstrap once and handle all subsequent navigation client-side through JavaScript actions. This approach is fundamentally different from traditional server-side routing: - Traditional: Every navigation loads new HTML from server - SPA: First request bootstraps application, subsequent navigation is client-side Key differences from Laravel: - Laravel: Full page reload per navigation, routes in routes/ files - RSX: Bootstrap once, routes defined in JavaScript with @route() decorator - Laravel: Separate frontend frameworks (Vue, React) require build tooling - RSX: Integrated JQHTML component system, no external build required - Laravel: API routes separate from view routes - RSX: Controllers provide Ajax endpoints, actions consume them Benefits: - No page reloads after initial bootstrap - Persistent layout across navigation - Automatic browser history integration - Same Rsx::Route() syntax for both traditional and SPA routes - No frontend build tooling required When to use: - Authenticated areas (dashboards, admin panels) - Applications with frequent navigation - Features requiring persistent state When to avoid: - Public pages requiring SEO - Simple static content - Forms without complex interactions SPA ARCHITECTURE SPA Module Structure: An SPA module consists of six component types: 1. PHP Bootstrap Controller (ONE per feature/bundle) - Single entry point marked with #[SPA] - Performs authentication check with failure/redirect - Renders SPA bootstrap layout - Referenced by all actions via @spa() decorator - One #[SPA] per feature/bundle (e.g., /app/frontend, /app/root, /app/login) - Bundles segregate code for security and performance: * Save bandwidth by loading only needed features * Reduce processing time by smaller bundle sizes * Protect confidential code (e.g., root admin from unauthorized users) - Typically one #[SPA] per feature at rsx/app/(feature)/(feature)_spa_controller::index 2. Feature Controllers (Ajax Endpoints Only) - Provide data via #[Ajax_Endpoint] methods - No #[SPA] or #[Route] methods - Called by actions to fetch/save data 3. JavaScript Actions (MANY per module) - Represent individual pages/routes - Define route patterns with @route() - Load data in on_load() lifecycle method - Access URL parameters via this.args 4. Action Templates (.jqhtml) - Render action content - Standard JQHTML component templates - Replace traditional Blade views 5. Layout Template (.jqhtml) - Persistent wrapper around actions - Must have element with $sid="content" - Persists across action navigation 6. Layout Class (.js) - Extends Spa_Layout - Optional on_action() hook for navigation tracking Execution Flow: First Request: 1. User navigates to SPA route (e.g., /contacts) 2. Dispatcher calls bootstrap controller 3. Bootstrap returns rsx_view(SPA) with window.rsxapp.is_spa = true 4. Client JavaScript discovers all actions via manifest 5. Router matches URL to action class 6. Creates layout on
7. Creates action inside layout $sid="content" area Subsequent Navigation: 1. User clicks link or calls Spa.dispatch() 2. Router matches URL to action class 3. Destroys old action, creates new action 4. Layout persists - no re-render 5. No server request, no page reload BOOTSTRAP CONTROLLER Creating SPA Entry Point: // /rsx/app/frontend/Frontend_Spa_Controller.php class Frontend_Spa_Controller extends Rsx_Controller_Abstract { #[SPA] #[Auth('Permission::authenticated()')] public static function index(Request $request, array $params = []) { return rsx_view(SPA); } } Key Points: - One bootstrap controller per feature/bundle - #[SPA] attribute marks as SPA entry point - #[Auth] performs server-side authentication with failure/redirect - Must return rsx_view(SPA) - special constant - All actions in the same bundle reference this via @spa() decorator Bundle Segregation: - Separate bundles for different features: /app/frontend, /app/root, /app/login - Each bundle has its own #[SPA] bootstrap - Benefits: * Bandwidth: Users only download code for authorized features * Performance: Smaller bundles = faster load times * Security: Root admin code never sent to unauthorized users - Example: Frontend users never receive root admin JavaScript/CSS Location: - Place at feature root: /rsx/app/{feature}/ - Not in subdirectories - One per feature/bundle - Naming: {Feature}_Spa_Controller::index Multiple SPA Bootstraps: Different features can have separate SPA bootstraps: - /app/frontend/Frontend_Spa_Controller::index (regular users) - /app/root/Root_Spa_Controller::index (root admins) - /app/login/Login_Spa_Controller::index (authentication flow) Each bootstrap controls access to its feature's actions via #[Auth]. FEATURE CONTROLLERS Ajax Endpoint Pattern: Feature controllers provide data endpoints only, no routes or SPA entry: // /rsx/app/frontend/contacts/frontend_contacts_controller.php class Frontend_Contacts_Controller extends Rsx_Controller_Abstract { #[Ajax_Endpoint] public static function datagrid_fetch(Request $request, array $params = []): array { return Contacts_DataGrid::fetch($params); } #[Ajax_Endpoint] public static function save(Request $request, array $params = []) { // Validation and save logic $contact->save(); Flash_Alert::success('Contact saved successfully'); return [ 'contact_id' => $contact->id, 'redirect' => Rsx::Route('Contacts_View_Action', $contact->id), ]; } } Guidelines: - All methods are #[Ajax_Endpoint] - No #[Route] methods in SPA feature controllers - No #[SPA] methods (only in bootstrap controller) - Return arrays/data, not views - Use Flash_Alert with redirects for success messages See Also: ajax_error_handling(3) for error patterns controller(3) for #[Ajax_Endpoint] details JAVASCRIPT ACTIONS Creating Actions: // /rsx/app/frontend/contacts/Contacts_Index_Action.js @route('/contacts') @layout('Frontend_Layout') @spa('Frontend_Spa_Controller::index') class Contacts_Index_Action extends Spa_Action { async on_load() { this.data.contacts = await Frontend_Contacts_Controller.datagrid_fetch(); } on_create() { // Component initialization } on_ready() { // DOM is ready, setup event handlers this.$sid('search').on('input', () => this.reload()); } } Decorator Syntax: @route(pattern) URL pattern with optional :param segments Example: '/contacts/:id', '/users/:id/posts/:post_id' Multiple routes per action: @route('/contacts/add') @route('/contacts/:id/edit') The best matching route is selected based on provided parameters. Enables single action to handle add/edit in one class. @layout(class_name) Layout class to render within Example: 'Frontend_Layout', 'Dashboard_Layout' @spa(controller::method) References bootstrap controller Example: 'Frontend_Spa_Controller::index' @title(page_title) Browser page title for this action (optional, clears if not present) Alternatively, set dynamically in on_ready() via document.title = "value" Example: @title('Contacts - RSX') URL Parameters: Parameters from route pattern and query string available in this.args: // URL: /contacts/123?tab=history @route('/contacts/:id') class Contacts_View_Action extends Spa_Action { on_create() { console.log(this.args.id); // "123" console.log(this.args.tab); // "history" } } Lifecycle Methods: on_create() Component construction, setup this.state on_load() Fetch data, populate this.data (read-only this.args) on_render() Template rendering on_ready() DOM ready, setup event handlers See Also: jqhtml(3) for complete lifecycle documentation ACTION TEMPLATES Creating Templates: Action templates are standard JQHTML components: