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:

 

In the next post I will explain how to connect the global filter with your custom card and probably provide a few different ideas of UI controls who can be implemented as a card.

To report this post you need to login first.

6 Comments

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

  1. Yahya Kartoev

    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

      (0) 

Leave a Reply