An OVP Card has two components, the header area and the content area. Every card supports navigation at the header area. Cards like List Card and Table Card also support navigation at the content area, or in other words – Navigation at item level. Header navigation is mostly used to navigate to the application the card is about. Navigation at item level is all about navigating to an app specific route or an external url, enriched with the item context.
Guess what? We will cover everything in this blog post – Let’s start đź’Ş
🛎️ Important
Sample code is only provided as xml annotation. Depending on the environment, you implement annotations directly in the fiori elements app via local annotations or in your cds view via cds annotations. This blog post only covers xml annotations.
Card Navigation: Intent-Based Navigation
The navigation information is taken from the UI.Identification
 and UI.DataFieldForIntentBasedNavigation
 vocabularie. As an intent only comprises of a semantic object and a action we just have to define these two propertys. To identify the annotation set a qualifier card01SemObj
.
<Annotation Term="UI.Identification" Qualifier="card01SemObj"> <Collection> <Record Type="UI.DataFieldForIntentBasedNavigation"> <PropertyValue Property="SemanticObject" String="SemanticObject"/> <PropertyValue Property="Action" String="display"/> </Record> </Collection> </Annotation>
To assign the annotation to the corresponding card, add theidentificationAnnotationPath
property to the existing card settings in the manifest.json.
"card01": { ... "settings": { ... "identificationAnnotationPath": "com.sap.vocabularies.UI.v1.Identification#card01SemObj" } }
That’s it. The card header now triggers an intent based navigation to the semantic object #SemanticObject-display. Card navigation at item level will behave the same with the difference that the context is passed in the url #SemanticObject-display?Parameter1=xyz&Parameter2=xyz.
đź’ˇ Nice to know
If a relevant property is missing in the url set the propertyaddODataSelect
of the card settings in the manifest.json tofalse
. This way the complete entity will be accessible in the url.
In the target app you can access the url parameters in the Component.js via:
 this.getComponentData().startupParameters
If you need to access the url parameter in a view controller you can use the following statement: this.getView().getOwnerComponent().getComponentData().startupParameters
✨ Recommendation
If you want to trigger an intent based navigation with an appSpecficRoute like #SemanticObject-display&/pattern/{Var1}/{Var2} use the Url Navigation – Simply read on!
Card Navigation: Url Navigation
The navigation information for Url Navigation is taken from the UI.Identification
 and UI.DataFieldWithUrl
 vocabularie. The vocabularie includes two mandatory fields. The property Url
, where we define the target url, aswell as the property Value
. The property brings no added value in this context (I’m not aware of any added value). Just define the property cause it’s mandatory. To identify the annotation set a qualifier card01UrlNav
.
<Annotation Term="UI.Identification" Qualifier="card01UrlNav"> <Collection> <Record Type="UI.DataFieldWithUrl"> <PropertyValue Property="Url" String="https://www.google.com"/> <PropertyValue Property="Value" Path="AppraisalId"/> </Record> </Collection> </Annotation>
As described previously we have to assign the annotation to the corresponding card settings via the identificationAnnotationPath
property.
"card01": { ... "settings": { ... "identificationAnnotationPath": "com.sap.vocabularies.UI.v1.Identification#card01UrlNav" } }
The card header now triggers an explace url navigation to https://www.google.com. Same goes for item level navigation. To include values of the context into the url we can use curly bracket placeholders https://www.google.de/search?q={Var1}. Map the placeholder to a context path via the LabeledElement
.
<Annotation Term="UI.Identification" Qualifier="card01URLNav"> <Collection> <Record Type="UI.DataFieldWithUrl"> <PropertyValue Property="Url"> <Apply Function="odata.fillUriTemplate"> <String>https://www.google.com/search?q={Var1}</String> <LabeledElement Name="Var1" Path="PropertyOfEntity1"/> </Apply> </PropertyValue> <PropertyValue Property="Value" Path="PropertyOfEntity1"/> </Record> </Collection> </Annotation>
Through these changes card navigation at item level works but at the same time it is leading to problems with header navigation. We will fix that in a moment.
Lets talk shortly about intent based navigation with an app specific route at item level – It’s pretty easy. We just have to use the UI.DataFieldWithUrl
 vocabularie and define the property Url
somewhat like this: #SemanticObject-display&/pattern/{Var1}/{Var2}. The overview page will identify that it is an intent based navigation and will navigate inplace.
<Annotation Term="UI.Identification" Qualifier="card01AppSpecificRoute"> <Collection> <Record Type="UI.DataFieldWithUrl"> <PropertyValue Property="Url"> <Apply Function="odata.fillUriTemplate"> <String>#SemanticObject-display&/Pattern/{Var1}/{Var2}</String> <LabeledElement Name="Var1" Path="PropertyOfEntity1"/> <LabeledElement Name="Var2" Path="PropertyOfEntity2"/> </Apply> </PropertyValue> <PropertyValue Property="Value" Path="PropertyOfEntity1"/> </Record> </Collection> </Annotation>
…That leads us to the same problem as before. Card navigation at item level works but navigation at header level is broken. To fix that we have to create a controller extension.
Controller Extension
You can create a controller extension for the overview page easily if you use the corresponding wizards/generators/guided development tools in your IDE. (VS Code and Fiori Tools, SAP WebIDE or SAP BAS)
The following code will be generated in different files:
manifest.json
"extends": { "extensions": { "sap.ui.controllerExtensions": { "sap.ovp.app.Main": { "controllerName": "<PROJECT_PATH>.ext.controller.OverViewPageExt" } } } }
ext/controller/OverViewPageExt.controller.js
sap.ui.define([ "sap/ui/model/Filter" ], function(Filter) { "use strict"; // controller for custom filter, navigation param, action(quick view and global filter), navigation target // controller class name can be like app.ovp.ext.CustomFilter where app.ovp can be replaced with your application namespace return { ////Store custom data in oCustomData // getCustomAppStateDataExtension: function (oCustomData) { // }, // //Appstate will return the same oCustomData // restoreCustomAppStateDataExtension: function (oCustomData) { // }, // //Returns Filter object to be used in filtering // getCustomFilters: function () { // }, // //Returns Custom Action function // onCustomActionPress: function (sCustomAction) { // }, // //Returns Custom Parameters // onCustomParams: function (sCustomParams) { // }, // //Modifies the selection variant to be set to the SFB // modifyStartupExtension: function (oCustomSelectionVariant) { // }, // //method to get custom message and icon for no data and error case // getCustomMessage: function (oResponse, sCardId) { // }, // //default breakout function for dynamic view switch // onBeforeRebindPageExtension: function () { // } }; });
doCustomNavigation
To fix the header navigation we implement the function doCustomNavigation
. The function takes the standard navigation entry details (if present) for a particual card and context and return a new/modified custom navigation entry to the core. The core will then use the navigation entry to perform navigation. This way we can define a different navigation behaviour based on the card and the given context.
In the following example we implement a custom behaviour for the cardId card01
. We check whether a context exists or not via _checkContext
. If there is a context, it is an item level navigation and we specifiy an app specific route. If there is no context, it is header navigation and we just specify the semantic object aswell as the action.
/** * This function takes the standard navigation entry details (if present) for a particular card and context. * Returns a new/modified custom navigation entry to the core. The core will then uses the custom * navigation entry to perform navigation * @param sCardId : Card id as defined in manifest for a card * @param oContext : Context of line item that is clicked (empty for header click) * @param oNavigationEntry : Custom navigation entry to be used for navigation * @returns {object} : Properties are {type, semanticObject, action, url, label} */ doCustomNavigation: function(sCardId, oContext, oNavigationEntry) { var oNewNavigationEntry = {}; if (sCardId === "card01") { if (this._checkContext(oContext)) { //item level navigation var oEntity = oContext && oContext.getProperty(oContext.getPath()); oNewNavigationEntry.label = null; oNewNavigationEntry.type = "com.sap.vocabularies.UI.v1.DataFieldWithUrl"; oNewNavigationEntry.url = "#SemanticObject-display&/Pattern/" + oEntity.PropertyOfEntity1 + "/" + oEntity.PropertyOfEntity2; } else { //header navigation oNewNavigationEntry.type = "com.sap.vocabularies.UI.v1.DataFieldForIntentBasedNavigation"; oNewNavigationEntry.semanticObject = "SemanticObject"; oNewNavigationEntry.action = "display"; } return oNewNavigationEntry; } }, /** * Check if context exists * @param oContext : Context of line item that is clicked (empty for header click) * @returns {boolean} : Y/N */ _checkContext: function(oContext) { return Object.keys(oContext).length > 0 ? true : false; }