Files
gh-secondsky-sap-skills-ski…/references/routing-navigation.md
2025-11-30 08:55:36 +08:00

17 KiB

SAPUI5 Routing & Navigation

Source: Official SAP SAPUI5 Documentation Documentation: 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:

{
    "pattern": "products/{productId}",
    "name": "productDetail",
    "target": "productDetail"
}

When matched: Loads target view and passes parameters.

Targets

Targets define what to display and where:

{
    "productDetail": {
        "viewName": "ProductDetail",
        "viewLevel": 2,
        "viewId": "productDetail",
        "controlId": "app",
        "controlAggregation": "pages"
    }
}

Configuration

manifest.json Routing Configuration

Complete Example:

{
    "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

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:

// 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:

// Browser back
window.history.go(-1);

// Or use NavContainer
var oNavContainer = this.byId("app");
oNavContainer.back();

sap.m.Link:

<Link
    text="View Product"
    href="{
        parts: ['productId'],
        formatter: '.formatProductLink'
    }"/>

Controller:

formatProductLink: function(sProductId) {
    var oRouter = this.getOwnerComponent().getRouter();
    return oRouter.getURL("productDetail", {
        productId: sProductId
    });
}

Route Matching

Pattern Matched Event

Attach in Controller:

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:

oRouter.attachRouteMatched(function(oEvent) {
    var sRouteName = oEvent.getParameter("name");
    console.log("Route matched: " + sRouteName);
});

Bypassed Event

No route matched:

oRouter.attachBypassed(function(oEvent) {
    var sHash = oEvent.getParameter("hash");
    console.log("No route matched: " + sHash);
});

Route Parameters

Mandatory Parameters

// Route pattern
"products/{productId}"

// Navigation
this.getRouter().navTo("productDetail", {
    productId: "123"  // Required
});

// Access in controller
var sProductId = mArguments.productId;

Optional Parameters

// 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

// 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:

// 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:

{
    "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:

// 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:

// 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:

{
    "pattern": "products/{productId}",
    "name": "productDetail",
    "greedy": true
}

Title Propagation

Set page title based on route:

// 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:

{
    "routing": {
        "config": {
            "bypassed": {
                "target": "notFound"
            }
        },
        "targets": {
            "notFound": {
                "viewName": "NotFound",
                "viewId": "notFound"
            }
        }
    }
}

NotFound.view.xml:

<mvc:View
    xmlns="sap.m"
    xmlns:mvc="sap.ui.core.mvc">
    <MessagePage
        title="{i18n>notFoundTitle}"
        text="{i18n>notFoundText}"
        description="{i18n>notFoundDescription}"
        icon="sap-icon://documents"
        showNavButton="true"
        navButtonPress=".onNavBack"/>
</mvc:View>

Object Not Found

Handle when object doesn't exist:

_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

{
    "routing": {
        "config": {
            "async": true
        }
    }
}

2. Initialize in Component

init: function() {
    UIComponent.prototype.init.apply(this, arguments);
    this.getRouter().initialize();
}

3. Use Pattern Matched

Don't use onBeforeRendering/onAfterRendering for navigation logic:

// Good
onInit: function() {
    this.getRouter().getRoute("detail").attachPatternMatched(this._onPatternMatched, this);
}

// Bad
onBeforeRendering: function() {
    // Don't load data here
}

4. Clean Up Event Handlers

onExit: function() {
    this.getRouter().getRoute("detail").detachPatternMatched(this._onPatternMatched, this);
}
// 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:

// 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:

// Attach route matched
this.getRouter().attachRouteMatched(function(oEvent) {
    console.log("Route matched:", oEvent.getParameter("name"));
    console.log("Arguments:", oEvent.getParameter("arguments"));
});

Official Documentation


Note: This document covers routing and navigation in SAPUI5. For Fiori Elements routing, see the fiori-elements.md reference file.