# SAPUI5 Routing & Navigation **Source**: Official SAP SAPUI5 Documentation **Documentation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) **Last Updated**: 2025-11-21 --- ## Overview SAPUI5 implements hash-based routing for single-page applications, enabling deep linking, browser history support, and seamless navigation without page reloads. **Key Benefits**: - Browser back/forward button support - Bookmarkable URLs (deep linking) - State preservation through URL parameters - SEO-friendly with hash-based navigation - Mobile back button handling --- ## Core Concepts ### Hash-Based Navigation SAPUI5 uses URL hash to manage application state: ``` [https://myapp.com/index.html#/products/123?tab=details](https://myapp.com/index.html#/products/123?tab=details) ^^^^^^^^^^^^^^^^^^^^^^^^^ Hash ``` **Components**: - **Pattern**: `/products/{productId}` - **Parameters**: `{productId}` = `123` - **Query**: `?tab=details` ### Routes Routes match hash patterns and trigger handlers: ```javascript { "pattern": "products/{productId}", "name": "productDetail", "target": "productDetail" } ``` **When matched**: Loads target view and passes parameters. ### Targets Targets define what to display and where: ```javascript { "productDetail": { "viewName": "ProductDetail", "viewLevel": 2, "viewId": "productDetail", "controlId": "app", "controlAggregation": "pages" } } ``` --- ## Configuration ### manifest.json Routing Configuration **Complete Example**: ```json { "sap.ui5": { "routing": { "config": { "routerClass": "sap.m.routing.Router", "type": "View", "viewType": "XML", "path": "my.app.view", "controlId": "app", "controlAggregation": "pages", "transition": "slide", "bypassed": { "target": "notFound" }, "async": true }, "routes": [ { "pattern": "", "name": "home", "target": "home" }, { "pattern": "products", "name": "productList", "target": "productList" }, { "pattern": "products/{productId}", "name": "productDetail", "target": ["productList", "productDetail"] }, { "pattern": "products/{productId}/items/{itemId}", "name": "itemDetail", "target": ["productList", "productDetail", "itemDetail"] } ], "targets": { "home": { "viewName": "Home", "viewId": "home", "viewLevel": 1 }, "productList": { "viewName": "ProductList", "viewId": "productList", "viewLevel": 1 }, "productDetail": { "viewName": "ProductDetail", "viewId": "productDetail", "viewLevel": 2 }, "itemDetail": { "viewName": "ItemDetail", "viewId": "itemDetail", "viewLevel": 3 }, "notFound": { "viewName": "NotFound", "viewId": "notFound" } } } } } ``` ### Configuration Options **Router Config**: - `routerClass`: Router implementation (sap.m.routing.Router, sap.f.routing.Router) - `type`: View type (View, Component) - `viewType`: View format (XML, JSON, JS, HTML) - `path`: View namespace prefix - `controlId`: Container control ID - `controlAggregation`: Aggregation to add views to - `transition`: Animation (slide, show, flip, fade) - `async`: Asynchronous view loading (always use true) - `bypassed`: Target for unmatched routes **Route Properties**: - `pattern`: URL pattern to match - `name`: Route name (for navigation) - `target`: Single target or array of targets - `subroutes`: Nested routes (deprecated, use multiple targets) - `greedy`: Match even if longer patterns exist **Target Properties**: - `viewName`: View name (without path) - `viewId`: View instance ID - `viewLevel`: Hierarchy level (for transitions) - `controlId`: Parent control ID - `controlAggregation`: Parent aggregation - `clearControlAggregation`: Clear before adding --- ## Initializing Router ### In Component.js ```javascript sap.ui.define([ "sap/ui/core/UIComponent" ], function(UIComponent) { "use strict"; return UIComponent.extend("my.app.Component", { metadata: { manifest: "json" }, init: function() { // Call parent init UIComponent.prototype.init.apply(this, arguments); // Initialize router this.getRouter().initialize(); } }); }); ``` **Important**: Always call `getRouter().initialize()` in component's init method. --- ## Navigation ### Programmatic Navigation **Navigate to Route**: ```javascript // Simple navigation this.getOwnerComponent().getRouter().navTo("productList"); // With parameters this.getOwnerComponent().getRouter().navTo("productDetail", { productId: "123" }); // With query parameters this.getOwnerComponent().getRouter().navTo("productDetail", { productId: "123" }, { query: { tab: "details", mode: "edit" } }); // Replace history (no back button) this.getOwnerComponent().getRouter().navTo("home", {}, {}, true); ``` **Navigate Back**: ```javascript // Browser back window.history.go(-1); // Or use NavContainer var oNavContainer = this.byId("app"); oNavContainer.back(); ``` ### Link in XML View **sap.m.Link**: ```xml ``` **Controller**: ```javascript formatProductLink: function(sProductId) { var oRouter = this.getOwnerComponent().getRouter(); return oRouter.getURL("productDetail", { productId: sProductId }); } ``` --- ## Route Matching ### Pattern Matched Event **Attach in Controller**: ```javascript sap.ui.define([ "sap/ui/core/mvc/Controller" ], function(Controller) { "use strict"; return Controller.extend("my.app.controller.ProductDetail", { onInit: function() { var oRouter = this.getOwnerComponent().getRouter(); oRouter.getRoute("productDetail").attachPatternMatched(this._onPatternMatched, this); }, _onPatternMatched: function(oEvent) { var mArguments = oEvent.getParameter("arguments"); var sProductId = mArguments.productId; // Load product data this._loadProduct(sProductId); // Handle query parameters var mQuery = oEvent.getParameter("arguments")["?query"]; if (mQuery && mQuery.tab) { this._selectTab(mQuery.tab); } }, _loadProduct: function(sProductId) { var sPath = "/Products('" + sProductId + "')"; this.getView().bindElement({ path: sPath, events: { dataRequested: function() { this.getView().setBusy(true); }.bind(this), dataReceived: function() { this.getView().setBusy(false); }.bind(this) } }); }, _selectTab: function(sTabKey) { this.byId("iconTabBar").setSelectedKey(sTabKey); } }); }); ``` ### Route Matched Event Match any route: ```javascript oRouter.attachRouteMatched(function(oEvent) { var sRouteName = oEvent.getParameter("name"); console.log("Route matched: " + sRouteName); }); ``` ### Bypassed Event No route matched: ```javascript oRouter.attachBypassed(function(oEvent) { var sHash = oEvent.getParameter("hash"); console.log("No route matched: " + sHash); }); ``` --- ## Route Parameters ### Mandatory Parameters ```javascript // Route pattern "products/{productId}" // Navigation this.getRouter().navTo("productDetail", { productId: "123" // Required }); // Access in controller var sProductId = mArguments.productId; ``` ### Optional Parameters ```javascript // Route pattern with :optional: prefix "products/:productId:" // Navigation (parameter optional) this.getRouter().navTo("productList", { // productId can be omitted }); // Or with parameter this.getRouter().navTo("productList", { productId: "123" }); ``` ### Query Parameters ```javascript // Not in pattern, passed via options this.getRouter().navTo("productDetail", { productId: "123" }, { query: { tab: "specifications", mode: "edit", highlight: "true" } }); // Access in controller var mQuery = oEvent.getParameter("arguments")["?query"]; var sTab = mQuery.tab; // "specifications" var sMode = mQuery.mode; // "edit" var bHighlight = mQuery.highlight === "true"; ``` ### Rest Parameters Capture remaining path: ```javascript // Pattern with rest parameter "files/{path*}" // Matches "/files/documents/reports/2025.pdf" // Access var sPath = mArguments.path; // "documents/reports/2025.pdf" ``` --- ## Master-Detail Pattern ### Flexible Column Layout **manifest.json**: ```json { "sap.ui5": { "routing": { "config": { "routerClass": "sap.f.routing.Router", "flexibleColumnLayout": { "defaultTwoColumnLayoutType": "TwoColumnsMidExpanded", "defaultThreeColumnLayoutType": "ThreeColumnsMidExpanded" } }, "routes": [ { "pattern": "", "name": "master", "target": ["master"] }, { "pattern": "products/{productId}", "name": "detail", "target": ["master", "detail"] }, { "pattern": "products/{productId}/items/{itemId}", "name": "detailDetail", "target": ["master", "detail", "detailDetail"] } ], "targets": { "master": { "viewName": "Master", "viewLevel": 1, "controlAggregation": "beginColumnPages" }, "detail": { "viewName": "Detail", "viewLevel": 2, "controlAggregation": "midColumnPages" }, "detailDetail": { "viewName": "DetailDetail", "viewLevel": 3, "controlAggregation": "endColumnPages" } } } } } ``` **Layout Management**: ```javascript // Get FCL var oFCL = this.byId("flexibleColumnLayout"); // Change layout oFCL.setLayout("TwoColumnsMidExpanded"); // Available layouts // - OneColumn // - TwoColumnsBeginExpanded // - TwoColumnsMidExpanded // - ThreeColumnsMidExpanded // - ThreeColumnsEndExpanded // - ThreeColumnsMidExpandedEndHidden // - ThreeColumnsBeginExpandedEndHidden ``` --- ## Advanced Patterns ### Nested Routing Use multiple targets instead of subroutes: ```javascript // Bad (deprecated subroutes) { "pattern": "products", "name": "products", "subroutes": [...] } // Good (multiple targets) { "pattern": "products/{productId}", "name": "productDetail", "target": ["productList", "productDetail"] } ``` ### Greedy Routes Match even when longer patterns exist: ```javascript { "pattern": "products/{productId}", "name": "productDetail", "greedy": true } ``` ### Title Propagation Set page title based on route: ```javascript // In controller _onPatternMatched: function(oEvent) { var sProductId = oEvent.getParameter("arguments").productId; this.getView().bindElement({ path: "/Products('" + sProductId + "')", events: { dataReceived: function(oData) { var sTitle = oData.getParameter("data").Name; // Set page title this.getOwnerComponent().getRouter().getTitleTarget("productDetail").setTitle(sTitle); }.bind(this) } }); } ``` --- ## Error Handling ### Not Found Page **Configure in manifest.json**: ```json { "routing": { "config": { "bypassed": { "target": "notFound" } }, "targets": { "notFound": { "viewName": "NotFound", "viewId": "notFound" } } } } ``` **NotFound.view.xml**: ```xml ``` ### Object Not Found Handle when object doesn't exist: ```javascript _loadProduct: function(sProductId) { this.getView().bindElement({ path: "/Products('" + sProductId + "')", events: { dataRequested: function() { this.getView().setBusy(true); }.bind(this), dataReceived: function(oData) { this.getView().setBusy(false); // Check if data exists if (!oData.getParameter("data")) { this._showObjectNotFound(); } }.bind(this) } }); }, _showObjectNotFound: function() { this.getOwnerComponent().getRouter().getTargets().display("objectNotFound"); } ``` --- ## Best Practices ### 1. Always Use Async ```json { "routing": { "config": { "async": true } } } ``` ### 2. Initialize in Component ```javascript init: function() { UIComponent.prototype.init.apply(this, arguments); this.getRouter().initialize(); } ``` ### 3. Use Pattern Matched Don't use onBeforeRendering/onAfterRendering for navigation logic: ```javascript // Good onInit: function() { this.getRouter().getRoute("detail").attachPatternMatched(this._onPatternMatched, this); } // Bad onBeforeRendering: function() { // Don't load data here } ``` ### 4. Clean Up Event Handlers ```javascript onExit: function() { this.getRouter().getRoute("detail").detachPatternMatched(this._onPatternMatched, this); } ``` ### 5. Use getURL for Links ```javascript // Generate URL var sURL = this.getRouter().getURL("productDetail", { productId: "123" }); // Use in href oLink.setHref(sURL); ``` --- ## Troubleshooting ### Issue: Router not initializing **Check**: 1. Called `getRouter().initialize()` in Component? 2. manifest.json routing config correct? 3. Console errors? ### Issue: Route not matching **Check**: 1. Pattern syntax correct? 2. Parameters match? 3. Route name used in navTo? **Debug**: ```javascript // Log all routes var aRoutes = this.getRouter().getRoutes(); aRoutes.forEach(function(oRoute) { console.log(oRoute.getPattern()); }); ``` ### Issue: View not displaying **Check**: 1. Target viewName correct? 2. controlId exists? 3. controlAggregation correct? **Debug**: ```javascript // Attach route matched this.getRouter().attachRouteMatched(function(oEvent) { console.log("Route matched:", oEvent.getParameter("name")); console.log("Arguments:", oEvent.getParameter("arguments")); }); ``` --- ## Official Documentation - **Routing and Navigation**: [https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials](https://github.com/SAP-docs/sapui5/tree/main/docs/04_Essentials) (search: routing) - **API Reference - Router**: [https://sapui5.hana.ondemand.com/#/api/sap.ui.core.routing.Router](https://sapui5.hana.ondemand.com/#/api/sap.ui.core.routing.Router) - **API Reference - Route**: [https://sapui5.hana.ondemand.com/#/api/sap.ui.core.routing.Route](https://sapui5.hana.ondemand.com/#/api/sap.ui.core.routing.Route) - **Flexible Column Layout**: [https://sapui5.hana.ondemand.com/#/api/sap.f.FlexibleColumnLayout](https://sapui5.hana.ondemand.com/#/api/sap.f.FlexibleColumnLayout) --- **Note**: This document covers routing and navigation in SAPUI5. For Fiori Elements routing, see the fiori-elements.md reference file.