Skip to Content

SAP Fiori Overview Pages introduced a new concept to the user interaction, all the detailed information that the user needs in a single page with the ability to filter and navigate to different applications, and the most important, information displayed in the way that the user wants.

In the current version of SAPUI5 (SAP Innovation 1.46), if we want to create a new card through the WebIDE wizard, these options are available:

  • List
  • Link List
  • Table
  • Stack
  • Analytic Card

But what if we want to implement our own custom card? For example, include a map, an integration with a 3rd party component or even a different UI5 control, is it possible to achieve that?

The answer is Yes! Based on the concept of extension is possible to create your own card from a generic card provided by SAP. To understand the concepts behind the Overview pages and Cards I’m going to split this article in 3 different sections:

  1. Creating a new List card
  2. Debugging the OVP to understand the Generic card concept
  3. Creating a new Custom card

If you already have experience with Overview Pages & Cards you can jump directly to the section 2, if you never had any contact with this Fiori Element I advise you to read all the article.

Let’s start our exercise creating a new List card. In this example I will use the Northwind OData Service to connect the data with our application.

 

Create a new List card

1. Create a new project from template and select the Overview Page Application.

2. Set the name as ZOVPCUSTOMCARD.

 

3. Select the Northwind Odata Services, if you have any questions about how to configure the destination, you can look for further information in this link: https://blogs.sap.com/2014/07/07/how-to-use-northwind-odata-service-with-sap-river-rde/

  • URL: /V2/northwind/northwind.svc

 

4. Leave the annotation selection in blank for now, we’ll generate the annotation in a different step. In the end, provide the information about the Template Customization and click Finish.

 

5. This should be your project structure:

 

6. Right click in the project folder, select New -> Card.

 

7. Define northwind as the Datasource.

 

8. Select the List card option.

 

9. Fill the information in the General section and leave the sections Annotations and Card Properties in blank (no need for this exercise).

 

10. Notice that the project structure continues the same, but if we open the manifest.json it’s possible to check a new section in the end of the file called “sap.ovp”, this is the code generated automatically by the WebIDE wizard:

	"sap.ovp": {
		"globalFilterModel": "northwind",
		"globalFilterEntityType": "Customer",
		"cards": {
			"zovpcustomcard_card00": {
				"model": "northwind",
				"template": "sap.ovp.cards.list",
				"settings": {
					"title": "{{zovpcustomcard_card00_title}}",
					"subTitle": "{{zovpcustomcard_card00_subTitle}}",
					"entitySet": "Customers",
					"addODataSelect": "false",
					"annotationPath": "com.sap.vocabularies.UI.v1.LineItem"
				}
			}
		}
	}

Attention: We need to adjust manually the name of the globalFilterEntityType, the wizard uses the EntitySet instead the EntityType, so just remember to change the name from Customers to Customer (without the S in the end).

 

11. The last step is to create an Annotation file and set the values for the Global Filter and the List.

To achieve that click with the right button in the localService folder and select New -> Annotation File.

 

12. Create a new annotation file with the name annotation_list, this should be your project structure after you finish:

 

13. Edit the annotation_list.xml, first of all we need to annotate the Customers entity set. To define the fields available in the Global filter we need to populate the UI.SelectionFields annotation and for the fields in the list we need to populate the UI.LineItem annotation. Check the example in the XML file below, the fields CustomerID and CompanyName were configured in the filter and in the list.

<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
	<edmx:Reference xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"
		Uri="https://webide-p998901trial.dispatcher.hanatrial.ondemand.com/destinations/northwind/V2/northwind/northwind.svc/$metadata">
		<edmx:Include xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Namespace="NorthwindModel"/>
		<edmx:Include xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Namespace="ODataWeb.Northwind.Model"/>
	</edmx:Reference>
	<edmx:Reference Uri="http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs02/vocabularies/Org.OData.Aggregation.V1.xml">
		<edmx:Include Alias="Aggregation" Namespace="Org.OData.Aggregation.V1"/>
	</edmx:Reference>
	<edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/vocabularies/Org.OData.Capabilities.V1.xml">
		<edmx:Include Alias="Capabilities" Namespace="Org.OData.Capabilities.V1"/>
	</edmx:Reference>
	<edmx:Reference Uri="https://wiki.scn.sap.com/wiki/download/attachments/448470974/Common.xml?api=v2">
		<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
	</edmx:Reference>
	<edmx:Reference Uri="https://wiki.scn.sap.com/wiki/download/attachments/448470971/Communication.xml?api=v2">
		<edmx:Include Alias="vCard" Namespace="com.sap.vocabularies.Communication.v1"/>
	</edmx:Reference>
	<edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/vocabularies/Org.OData.Core.V1.xml">
		<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
	</edmx:Reference>
	<edmx:Reference Uri="http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/vocabularies/Org.OData.Measures.V1.xml">
		<edmx:Include Alias="CQP" Namespace="Org.OData.Measures.V1"/>
	</edmx:Reference>
	<edmx:Reference Uri="https://wiki.scn.sap.com/wiki/download/attachments/448470968/UI.xml?api=v2">
		<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
	</edmx:Reference>
	<edmx:DataServices>
		<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm">
			<Annotations Target="NorthwindModel.Customer">
				<Annotation Term="UI.SelectionFields">
					<Collection>
						<PropertyPath>CustomerID</PropertyPath>
						<PropertyPath>CompanyName</PropertyPath>
					</Collection>
				</Annotation>
				<Annotation Term="UI.LineItem">
					<Collection>
						<Record Type="UI.DataField">
							<PropertyValue Property="Value" Path="CustomerID"/>
						</Record>
						<Record Type="UI.DataField">
							<PropertyValue Property="Value" Path="CompanyName"/>
						</Record>
					</Collection>
				</Annotation>
			</Annotations>
		</Schema>
	</edmx:DataServices>
</edmx:Edmx>

 

14. This is the OVP final result:

 

Debugging the OVP to understand the Generic card concept

Let’s start activating the debug mode in our application. In the UI5 application screen click CTRL + ALT + SHIFT + P, this command opens the Technical Information pop-up and allows the use of the Debug Source. Select only the OVP module, close the pop-up and refresh your browser.

 

SAPUI5 Technical Info:

 

Debug Source:

 

After the browser refresh, open the Chrome Inspector and select Sources in the top menu.

Open the folder resources -> sap -> ovp -> cards. Three folders are loaded under this main folder:

  • generic
  • list
  • loading

 

Notice that only the folders related with the cards used in our application were loaded by the framework.

  • The list folder is available because we used as template for our card.
  • The loading folder contains the Busy Indicator logic during the load of the data.
  • The generic folder is the generic template used by all the OVP cards.

Comparing the files in all the folder we can see a pattern, basically every card folder have at least a Component.js. Inside this file we can check a metadata with the configuration of the card. So let’s compare the Generic and List Components codes to discover more details about the OVP architecture:

Generic Component:

metadata: {
	properties: {
		"contentFragment": {
			"type": "string"
		},
		"headerExtensionFragment": {
			"type": "string"
		},
		"contentPosition": {
			"type": "string",
			"defaultValue": "Middle"
		},
		"footerFragment": {
			"type": "string"
		},
		"identificationAnnotationPath": {
			"type": "string",
			"defaultValue": "com.sap.vocabularies.UI.v1.Identification"
		},
		"selectionAnnotationPath": {
			"type": "string"
		},
		"filters": {
			"type": "object"
		},
		"addODataSelect": {
			"type": "boolean",
			"defaultValue": false
		}
	},
	version: "1.44.11",

	library: "sap.ovp",

	includes: [],

	dependencies: {
		libs: ["sap.m"],
		components: []
	},
	config: {}
},

List Component:

metadata: {
    properties: {
        "contentFragment": {
            "type": "string",
            "defaultValue": "sap.ovp.cards.list.List"
        },
        "annotationPath": {
            "type": "string",
            "defaultValue": "com.sap.vocabularies.UI.v1.LineItem"
        },
        "footerFragment": {
            "type": "string",
            "defaultValue": "sap.ovp.cards.generic.CountFooter"
        },
        "headerExtensionFragment":{
            "type": "string",
            "defaultValue": "sap.ovp.cards.generic.KPIHeader"
        }
    },

    version: "1.44.11",

    library: "sap.ovp",

    includes: [],

    dependencies: {
        libs: [ "sap.m" ],
        components: []
    },
    config: {},
    customizing: {
        "sap.ui.controllerExtensions": {
            "sap.ovp.cards.generic.Card": {
                controllerName: "sap.ovp.cards.list.List"
            }
        }
    }
}

 

We can absorb a few concepts of this code comparison:

  • The properties available in the metadata are the same configured inside the manifest.json.
  • The List Component executes a controller extension of the Generic Component using a new node inside the metadata called customizing.
  • The List Component provides a Fragment XML with the UI5 controls through properties -> contentFragment.

With these concepts in mind we discover that is possible to create a fragment and control the business logic through a controller extension. Let’s put this idea in practice and see how can we generate our custom card.

 

Creating a new Custom card

For this exercise we’re going to create a Button and an event to display a simple message in the controller extension, let’s start creating a new folder cards in the root of the application and inside this folder a new one called mycustomcard.

Inside the folder mycustomcard create 3 files:

  • Component.js (Card controller)
  • MyCustomCard.controller.js (Extension controller)
  • MyCustomCard.fragment.xml (fragment XML)

This should be your application structure after the changes:

 

In the MyCustomCard.fragment.xml, create an UI5 button and associate to an event called “onPressCustomCard”:

<core:FragmentDefinition 
	xmlns:core="sap.ui.core"
	xmlns="sap.m" >
	
	<Button text="{@i18n>MyCustomCardBtn}" press="onPressCustomCard" />
	
</core:FragmentDefinition>

In the MyCustomCard.controller.js, create the method “onPressCustomCard” and place a console log message inside:

(function () {
	"use strict";

	sap.ui.controller("zovpcustomcard.cards.mycustomcard.MyCustomCard", {
		
		onPressCustomCard: function(oEvent) {
			console.log("MyCustomCard - onPressCustomCard");	
		}
		
	});
})();

In the Component.js, create the metadata attribute and configure the contentFragment and the controller extension through the customizing node. Special attention to the  to the sap.ui.declare and sap.ui.require commands in the header, they are responsible for the Generic component load and the declaration of our new Custom Card component:

(function () {
    "use strict";

    jQuery.sap.declare("zovpcustomcard.cards.mycustomcard.Component");
	jQuery.sap.require("sap.ovp.cards.generic.Component");

	sap.ovp.cards.generic.Component.extend("zovpcustomcard.cards.mycustomcard.Component", {
		metadata: {
			properties: {
				"contentFragment": {
					"type": "string",
					"defaultValue": "zovpcustomcard.cards.mycustomcard.MyCustomCard"
				}
			},

			version: "1.44.10",

			library: "sap.ovp",

			includes: [],

			dependencies: {
				libs: ["sap.m"],
				components: []
			},
			config: {},
			customizing: {
				"sap.ui.controllerExtensions": {
					"sap.ovp.cards.generic.Card": {
						controllerName: "zovpcustomcard.cards.mycustomcard.MyCustomCard"
					}
				}
			}
		}
	});
})();

Last step, in the manifest.json, copy the configuration of the List card and adapt the properties to point to the new custom card.

	"sap.ovp": {
		"globalFilterModel": "northwind",
		"globalFilterEntityType": "Customer",
		"cards": {
			"zovpcustomcard_card00": {
				"model": "northwind",
				"template": "sap.ovp.cards.list",
				"settings": {
					"title": "{{zovpcustomcard_card00_title}}",
					"subTitle": "{{zovpcustomcard_card00_subTitle}}",
					"entitySet": "Customers",
					"addODataSelect": "false",
					"annotationPath": "com.sap.vocabularies.UI.v1.LineItem"
				}
			},
			"zovpcustomcard_card01": {
				"model": "northwind",
				"template": "zovpcustomcard.cards.mycustomcard",
				"settings": {
					"title": "{{zovpcustomcard_card01_title}}",
					"subTitle": "{{zovpcustomcard_card01_subTitle}}",
					"entitySet": "Customers"
				}
			}
		}
	}

Don’t forget to place the relevant texts in the i18n file:

zovpcustomcard_card01_title=My Custom Card
zovpcustomcard_card01_subTitle=Just a simple button

MyCustomCardBtn=Click here

 

Execute the application, two cards are displayed now (List and Custom).

 

Click in the button and check the log in the Inspector console:

To report this post you need to login first.

10 Comments

You must be Logged on to comment or reply to a post.

  1. Яхья Картоев

    Hi, Felipe!

    Thank you very much for this post, it really helped me!

    Do you know how to change the width of my custom card?

    For example, I need only one card and want it fill all available width.

    As we can see on screenshot below, we can change the width of each card, so it fills twice width. But I can’t find out how to do that.

    Will be very glad to hear your answer, and thank you in advance.

    (1) 
    1. Felipe de Mello Rodrigues Post author

      Hi Yahya,

       

      I never used this functionality before, but since you sent the screenshots I started to search more about the subject.

       

      I’ve started with a research in the SAP Help, but after a debug session in one of my devs I discovered this particular code in the OVP Component.js:

       

      Notice that we have a property called containerLayout responsible for a change in the standard Card Container. I’ve tested this property changing my manifest.json and I was able to resize all my cards. This is the final result:

       

       

       

      Just include this single line of code in your app descriptor and the user will have the option to resize the cards in the way that he wants.

       

      Let me know if you have any questions.

       

      Cheers,

      Felipe

      (1) 
  2. Tanveer Shaikh

    Hi Felipe,

    Thanks for the excellent blog.

    Can you please help with some additional information on Navigation from Cards to other Apps with  data selection/filters ? I saw some documents suggesting use of datafieldforintendbased navigation but I am not able to pass filter/selection parameters to receiver app. Any hint on how to pass filter/selection values to receiver app with while navigation ?

    Thanks,

    Tanveer

    (0) 
    1. Felipe de Mello Rodrigues Post author

      Hi Tanveer,

      Thanks for the feedback!

      DataFieldForIntentBasedNavigation is exactly the annotation you are looking for!

      You can configure this annotation through an ABAP CDS view or directly as a local annotation in your UI5 application. In the example below I create a CDS view with two fields (Company and Plant) and I configure an Intent Based Navigation using the following parameters:

      • Semantic Object: MySemanticObject
      • Action: display
      define view CDS_MyCustomView
        as select from CDS_MyCustomViewTable
      {
            @Consumption.semanticObject: 'MySemanticObject'
            @UI.identification: {
                type: #FOR_INTENT_BASED_NAVIGATION,
                semanticObjectAction: 'display'
            }
            Company,
            
            Plant
      }

      This piece of code will generate the following annotation in the end:

      <Annotation Term="UI.Identification">
        <Collection>
          <Record Type="UI.DataFieldForIntentBasedNavigation">
            <PropertyValue String="MySemanticObject" Property="SemanticObject"/>
            <PropertyValue String="display" Property="Action"/>
            <PropertyValue Property="RequiresContext" Bool="false"/>
          </Record>
        </Collection>
      </Annotation>

      If you don’t use ABAP CDS in your scenario just copy the code above and place inside your Annotation.xml file. You can configure through the Annotation Modeler as well (Web IDE).

      When the user hits the list, the system will collect all the fields from the same Entity Set and append as parameters in the URL (Company and Plant in my example) and execute the navigation to the Semantic Object and Action that we configured before. It will generate an URL like:

      #MySemanticObject-display?Company=XXXX&Plant=YYYY

      Remember to declare your field names using the same name as defined by the Target Application. If the Target is expecting Company it will work perfectly, but if the Target expects BUKRS the filter won’t work properly.

      If you can’t use the same field names in both applications, there is one last option when you include an extra configuration through the Fiori Launchpad Designer. Just search for the Target Mapping configuration of your Target Application and include a new parameter configuration like the one below:

      This configuration will convert the Company field of the URL and place the value inside the field BUKRS.

      Following these tips your navigation and filtering will work fine!

      Cheers,

      Felipe

       

      (0) 
  3. Tanveer Shaikh

    Thanks a lot Felipe,

    This helped a lot and I am able to navigate from Overview page to a list report /Analytic list page  !!!  I am using Hana XSodata with local annotations, and its work just fine using DataFieldForIntentBasedNavigation in Webide Annotation modeler.

    Have a follow up question – Can we use same annotations to navigate from a line item of a list report or Analytic list page to some other app? For Example If I have list report for all Sales Order for a customer , built using Fiori element template and webide annotation modeler, and from the line items of the list report I want to navigate to a Sales Order tracking app using the Sales order number from the list report line item.

    Tried to look for some documentation and looks like need to extent the list report + local annotation to achieve that ?

    Can you please point in the right direction to look for  ? Do we need extension or just local annotation can achieve it ? My setup is same Using webide annotation modeler with Hana xsodata as backend data source. So, no ABAP CDS.

    Thanks,

    Tanveer

    (0) 
    1. Felipe de Mello Rodrigues Post author

      Hi Tanveer,

      I think the best option is to reuse the annotation DataFieldForIntentBasedNavigation in one of the fields contained inside your ListItem annotation, this way the List Report will generate a link for the field like the example below (check the column Document Number):

      If you still need to use the item navigation for this purpose you will probably need to extend your List Report.

      Cheers,

      Felipe

      (0) 

Leave a Reply