Skip to Content
Technical Articles

SAP Fiori Overview Page – Beginner Guide

Background  

In this post, we will discuss some useful information we learned while making an Overview Page Application and provide beginner level instructions on how to start an SAP Fiori Overview Page project like the one pictured below. We will discuss common problems faced during the process, and the solutions which may be otherwise difficult to discover. I worked on this project with my colleague Jarrod Lane

 

1. Creating a New Project

Once you are logged into the SAP WebIDE, you can create a new project from a project template, start with a sample application, or import an existing project from git or your local file system. The sample application has a pre-built overview page you can use to gain insights into how the cards work, annotation structure requirements and so on. We began our first Overview Page development using the sample application and trying to add the cards we wanted to it, for practise.  

Note: The WebIDE has inbuilt git functionality, but we have found it to be finicky at best, and we recommend avoiding it whenever possible, instead we used a git-bash command line on our local computers. 

For now, we will open a new project from a project template, however when making your Overview Page, it helps to have a Sample Application project in your project workspace as well as your actual project. 

You will need to select the Overview Page template, which can be found under the Category “SAP Fiori Elements”. 

The namespace you select will later dictate the prefix of your data entities as they appear to the WebIDE, so we will call our namespace NameSpace, so you can see how it affects the metadata.xml file later. 

To set up the project’s OData connection, you will need your account authorised to access your backend data service gateway (assuming you already have a backend data source).  

 

Select your system from the Service Catalogue and then scroll down the list of options to find the service which contains your data that will be displayed on the Overview Page. 

At this point you will be asked to select an annotation file for your project. If you do not have one already, you can create one later, as shown in section 3: Annotations. 

Then, under Template Customisation, you will need to decide an alias for your data source. This alias will be used as a name for your database when you reference Entities and EntitySets.

The line reading “EntityType for Filter” defines a global filter for your Overview Page. This is a filter bar at the top of the application that filters the data that goes to every card on the page. If only one of your Entity types needs filter capability, then you can select that Entity. If multiple Entity types need to be filtered, then you may want to have a dedicated Entity type whose fields describe which Entity properties can be filtered, as pictured below.

If you do not yet have an Entity type for filters, then you can leave the EntityType for Filter blank, and after the Entity type for filter has been created, add a reference to it in you manifest.json file, next to “globalFilterEntityType”. 

Some extra work will need to be done on the backend before the filter works, detailed in section 4: Global Filtering.

 

2. Creating a Card

SAP Overview pages display information in the form of SAP Cards. These take various forms, such as tables, graphs, lists, and contact cards. Adding a card can be done by writing code into the manifest.json file itself, or by using the WebIDE’s Add Card wizard. For the purposes of this blog, cards will be added using the wizard, and the resulting manifest.json file changes will be explained. 

Note: Analytical Cards cannot be created using the wizard however, and the method for creating them will be included in the appendix. 

Either right click your desired project, or with the same project selected open the File menu, then navigate to the New option and select Card. 

From here, the card creation wizard will open and the first option you will have to decide upon is what type of card to select. In this example, a simple Table card will be created. 

Next, a data source must be selected. Generally, the default option is likely to be the one you want, as it will be the data source set up with the project. 

The next tab is where most of the important details of the card are filled out. Here you decide which data the card is going to be working with from your dataset, as well as setting its title, subtitle and so on. Most of the options in this tab are discussed in the annotations section below and can be added manually without the wizard, so it’s fine to leave them blank for now. 

Next you have the option to set a default size for your card, this is mostly for aesthetics and can be edited later regardless. If you select Disable Resizing, then users will not be able to alter the size of the card when running the application. 

After this, the final tab simply asks you to select Finish and assuming the previous steps have been done correctly your card will be created. This can be checked in the manifest.json, and assuming this is your first card, it will look as follows: 

"sap.ovp": {
    "globalFilterModel": "Alias",
    "globalFilterEntityType": "OVPFilter",
    "containerLayout": "resizable",
    "enableLiveFilter": false,
    "considerAnalyticalParameters": false,
    "cards": {
        "card00": {
            "model": "Alias",
            "template": "sap.ovp.cards.table",
            "settings": {
                "title": "{{card00_title}}",
                "subTitle": "{{card00_subTitle}}",
                "entitySet": "OrdersStats",
                "sortBy": "EquipmentNumber",
                "sortOrder" "ascending",
                "addODataSelect": false
            }
        }
    }
}

If this is not your first card, then it will appear at the bottom of the list of cards. Cards can be added by typing directly into manifest.json in this same fashion. 

So, when the project is run now, we see the following: 

The card currently doesn’t display in the application, and the reason is simply that SAP doesn’t know what data from your data entity you want it to display. If you click around on the page, you’ll likely highlight the outline of the card, revealing that it is there but currently invisible. In order to make the card truly work, the annotations for the card must be correctly created/modified. 

 

3. Annotations

Annotations functionally tell your application what data to display in your cards. Annotations are in xml form in the application but can be edited using a simpler editor that SAP provides. If you didn’t already have an annotations file for your project, simply right click on your webapp folder in your project and under New select Annotation File. Here you can select a name for the file, and the appropriate data source, which should default to the one you want. 

For this example, the annotation file has simply been named annotation.xml. Upon opening your new annotation file, you should see something like this: 

<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
	<edmx:Reference Uri="/sap/opu/odata/sap/ZREPAIR_PROCESSING_SRV/$metadata"/>
	<edmx:DataServices>
		<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm"/>
	</edmx:DataServices>
</edmx:Edmx>

Here you can type directly into the file to create annotations, using examples found on SAP blogs and other sources, or you can select the Annotation Modeller option in the bottom left corner of the editor. 

The screen should change to look similar to the image below: 

So, from here annotations can be added much more simply than writing them yourself. Clicking Select Targets will open a menu of all your data source’s data entity types. In this example, since the card created in section 2 was targeting Order Stats, that is going to be selected. 

And so now the table on the annotation modeller page should look something like this: 

From here, selecting the + on the right side of the screen allows you to add annotations for a data entity type. There are many options for annotations, each one has a purpose for certain types of cards. Important types of annotations for the available cards will be discussed further below. 

For the purpose of our simple Table card, we only need to select LineItem hit OK. Next, click the + on the row where your new LineItem has been added, and a new list of annotation options will appear. Again, each of these serves its own purpose, but in this case all we need to add is a basic DataField 

Your DataField annotation should appear like this: 

Clicking the dropdown on Property opens a list of the data types within the selected entity (OrderStats in this example). Select whichever one you want to be displayed in your Table card created in part 2. From here you can add multiple DataField entries in the same way the first was added, resulting in something like this: 

Make sure to save your changes and now when you run your project, your first card should appear with data being displayed. 

From this point on in this section, details about specific annotation types will be explained. 

Some of the annotations you need can be written using information easily found on the SAP blog forums, but some information of specific annotations is not easily found. These difficult annotations are explained here. 

 

Selection Variant 

The selection variant pre-filters the data that goes to a particular card, overriding the global filter. For example, when first making a stack card showing information on our Order class, we found that our database had too many Orders recorded and if we try to read all of them, then the OverView Page times out and crashes, so the data must be filtered before the card is displayed.  

<Annotations Target="Metadata.Order">
    <Annotation Term="UI.SelectionVariant" Qualifier="Filter2">
        <Record Type="UI.SelectionVariantType">
            <PropertyValue Property="SelectOptions">
                <Collection>
                    <Record Type="UI.SelectOptionType">
                        <PropertyValue Property="PropertyName" PropertyPath="SystemStatus"/> 
                        <PropertyValue Property="Ranges">
                            <Collection>
                                <Record Type="UI.SelectionRangeType">										 
                                    <PropertyValue Property="Sign" EnumMember="UI.SelectionRangeSignType/I"/>
                                    <PropertyValue Property="Option" EnumMember="UI.SelectionRangeOptionType/EQ"/>
                                    <PropertyValue Property="Low" String="REL"/>
                                </Record>
                            </Collection>
                        </PropertyValue>
                    </Record>
                    <Record Type="UI.SelectOptionType">
                        <PropertyValue Property="PropertyName" PropertyPath="Plant"/>
                        <PropertyValue Property="Ranges">
                            <Collection>
                                <Record Type="UI.SelectionRangeType">
                                    <PropertyValue Property="Sign" EnumMember="UI.SelectionRangeSignType/I"/>
                                    <PropertyValue Property="Option" EnumMember="UI.SelectionRangeOptionType/EQ"/>
                                    <PropertyValue Property="Low" String="8500"/>
                                </Record>
                            </Collection>
                        </PropertyValue>
                    </Record>
                </Collection>
            </PropertyValue>
        </Record>
    </Annotation>
</Annotations>

Here, we have 2 SelectOptionTypes. The first refers to our Order PropertyPath=”SystemStatus which describes whether the Order is complete or not. The Property=”Low” describes the value that the data can take. In our database, the string REL is given to orders that have been released, but not completed, so if we want our stack card to show only those orders which are not yet completed, we give the String=”REL”. 

The second select option refers to the Plant property of an Order. Here we want to display only the Orders that come from Plant 8500, so we dictate that each Order displayed must have the Plant property equal 8500. 

Our selection variant is described for cards that are based on the EntityType ‘Order’. The Qualifer=”Filter2” identifies this selection variant, so when we want a card to be restricted by this filter, we need to give the card a reference to this filter in the card settings, found in the manifest.json. 

"card01": {
    "model": "Alias",
    "template": "sap.ovp.cards.stack",
    "settings": {
        "title": "{{Open Work Orders, Plant 8500}}",
        "entitySet": "Orders",
        "addODataSelect": false,
        "selectionAnnotationPath": "com.sap.vocabularies.UI.v1.SelectionVariant#Filter2",
        "annotationPath": "com.sap.vocabularies.UI.v1.FieldGroup#Note/Data"
    }
},

After applying this Selection Variant to a Stack Card for Orders, we see that there are 3 open work orders from Plant 8500.  

Stack Cards 

Stack cards show detail information on up to 20 entries from a given entity set. 

Firstly, the annotation for HeaderInfo, applies to all cards with a header, so this will also apply to the stack card. This defines the information that appears in the title of each Work Order card and the description, which appears directly underneath the header. 

Below, we see the PropertyValue TypeName assigned the string “Work Order”. Then, the PropertyValue “Title” is assigned a DataField whose Value is given as “OrderNumber”. This causes the Header of each card to display Work Order: <Order Number>and also causes the Order Number to appear as a title at the top of each card’s body. 

<Annotation Term="UI.HeaderInfo">
    <Record Type="UI.HeaderInfoType" Qualifier="OpenWorkOrders">
        <PropertyValue Property="TypeName" String="Work Order"/>
        <PropertyValue Property="TypeNamePlural" String="Work Orders"/>
        <PropertyValue Property="Title">
            <Record Type="UI.DataField">
                <PropertyValue Property="Label" String="Order Number"/>
                <PropertyValue Property="Value" Path="OrderNumber"/>
            </Record>
        </PropertyValue>
        <PropertyValue Property="Description">
            <Record Type="UI.DataField">
                <PropertyValue Property="Label" String="Description"/>
                <PropertyValue Property="Value" Path="Description"/>
            </Record>
        </PropertyValue>
    </Record>
</Annotation>

The Header Description Property defined here causes the description of each order to appear directly below the title. This is what generates the lines “service 1”, “WARRANTY SERVICE SCHEDULE”, and “Test for Work+” in each respective card. 

 

The rest of the information is a little trickier to display and requires a few extra steps. First, the data is described in a FieldGroup annotation. The annotation must be given a Qualifier for later, here we have given it the Qualifer “Note”.  

<Annotation Term="com.sap.vocabularies.UI.v1.FieldGroup" Qualifier="Note">
    <Record Type="UI.FieldGroupType">
        <PropertyValue Property="Data">
            <Collection>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="Order Type"/>
                    <PropertyValue Property="Value" Path="OrderType"/>
                </Record>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="ActTypeDescription"/>
                    <PropertyValue Property="Value" Path="ActTypeDesc"/>
                </Record>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="Work Centre"/>
                    <PropertyValue Property="Value" Path="WorkCentreId"/>
                </Record>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="Equipment Number"/>
                    <PropertyValue Property="Value" Path="EquipmentNumber"/>
                </Record>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="Vehicle Type"/>
                    <PropertyValue Property="Value" Path="VehicleType"/>
                </Record>
                <Record Type="UI.DataField">
                    <PropertyValue Property="Label" String="Manufacturer"/>
                    <PropertyValue Property="Value" Path="Manufacturer"/>
                </Record>
            </Collection>
        </PropertyValue>
    </Record>
</Annotation>

If you try to display the stack card with only these annotations, you will find that the body of the card is empty. Unfortunately, the FieldGroup annotation is not enough to display the body of the card. You need a Facet annotation which tells the Stack card to actually use the FieldGroup annotation. 

<Annotation Term="UI.Facets">
    <Collection>
        <Record Type="UI.ReferenceFacet">
            <Annotation Term="UI.IsSummary"/>
            <PropertyValue Property="Label" String="Note"/>
            <PropertyValue Property="Target" AnnotationPath="@com.sap.vocabularies.UI.v1.FieldGroup#Note"/>
        </Record>
    </Collection>
</Annotation>

The PropertyValue “Label” is given the String “Note”, which is the same as the Qualifier we defined earlier, and the PropertyValue “Target” is given the AnnotationPath of the FieldGroup which describes our Stack card. 

Finally, we return to this line of the card definition in manifest.json 

annotationPath“: “com.sap.vocabularies.UI.v1.FieldGroup#Note/Data” 

The Qualifier #Note points to our FieldGroup annotation, and specifically /Data is required to point to the section of the annotation which describes the body of the card, and is written under PropertyValue Property=”Data”. 

 

List Card 

The List Card is similar to the Table Card, but is slightly more complicated. It uses Line Items as well as a DataPoint in the annotations file to display data. 

The first thing required is a DataPoint, which looks like the following:

<Annotations Target="Metadata.AssetStat">
    <Annotation Term="UI.DataPoint" Qualifier="CostAnno">
        <Record Type="UI.DataPointType">
            <PropertyValue Property="Value" Path="Cost"/>
            <PropertyValue Property="Criticality" EnumMember="UI.CriticalityType/Negative"/>
        </Record>
    </Annotation>
</Annotations>

Note, for this example the AssetStat data source has been used. It is also worth noting that the DataPoint has a qualifier of “CostAnno”. A qualifier is required for making a List Card, as will be explained below. Furthermore, the DataPointType element containing the Criticality property determines the colour of the List Card. In this case Negative was chosen, which is why the card elements are red. So the next required element is a LineItem.

<Annotation Term="UI.LineItem" Qualifier="Cost">
    <Collection>
        <Record Type="UI.DataField">
            <PropertyValue Property="Value" Path="Assetclass"/>
            <PropertyValue Property="Label" String="{@i18n&gt;ASSET_CLASS}"/>
        </Record>
        <Record Type="UI.DataField">
            <PropertyValue Property="Value" Path="Plant"/>
            <PropertyValue Property="Label" String="{@i18n&gt;STAT_NUM}"/>
        </Record>
        <Record Type="UI.DataFieldForAnnotation">
            <PropertyValue Property="Target" AnnotationPath="@UI.DataPoint#CostAnno"/>
        </Record>
        <Record Type="UI.DataField">
            <PropertyValue Property="Value" Path="StatMonth"/>
        </Record>
        <Record Type="UI.DataField">
            <PropertyValue Property="Value" Path="OrderType"/>
        </Record>
    </Collection>
</Annotation>

Similar to the Table Card, the Line Item is filled with DataFields. As you can see when comparing this to the completed card, the first two DataFields are the heading and description of each list element. The difference is the DataFieldForAnnotation record in this Line Item. Here, the annotation path must reference the DataPoint created above, this is what lets the List Card know which element is to fill the bar section for each list element. 

Again, this annotation has a qualifier, this is only required if you plan on making multiple List Cards. In this case, all that is required is to add the annotationPath variable to the card definition in manifest.json as follows: 

"card03": {
    "model": "Alias",
    "template": "sap.ovp.cards.list",
    "settings": {
        "title": "{{Worst Performing Assets by Cost}}",
        "entitySet": "AssetStats",
        "listType": "extended",
        "annotationPath": "com.sap.vocabularies.UI.v1.LineItem#Cost",
        "listFlavor": "bar",
        "sortBy": "Cost",
        "sortOrder": "descending",
        "addODataSelect": false,
        "defaultSpan": {
            "rows": 6,
            "cols": 2
        }
    }
},

At this point, the List Card is complete. As a side note, List Cards without the bar can be made, and as such the DataPoint and DataFieldForAnnotation would not be required. Furthermore, the List Card in this example is what is referred to as an Extended List Card, the other option is a Condensed List Card. The primary difference is that an Extended List Card can display more information in each list element, which has been shown in this example. 

 

4. Global Filtering

Filtering in SAP Overview Page applications can be handled by a filter bar at the top of your application page. This filter bar can contain multiple fields and filters all applicable cards, ignoring those that the filter does not apply to. This section details how a filter bar can be applied, including any back end and annotation settings to be applied. 

Setting up the Global Filter during the creation of the project is enough to have the filter bar appear at the top of your app. It does not, however, filter anything until a filter method is defined in your backend getEntitySet method. Below is a method we used to filter OrderStats by plant. This method is written in the getEntitySet method of our OrderStats class, and so it only filters cards that draw from the OrderStats class. Any other class that has a reference to ‘Plant’ will not be filtered until another filter method is written in their respective getEntitySet methods.  

*Filter


    if it_filter_select_options is not initial.
      data lr_pl type range of werks_d.

      read table it_filter_select_options with key property = 'Plant'
       assigning field-symbol(<lsline>).
      if <lsline> is assigned.
        read table <lsline>-select_options index 1 assigning field-symbol(<lsop>).
        if <lsop> is assigned.
          append initial line to lr_pl assigning field-symbol(<lslr>).
          <lslr>-sign = <lsop>-sign.
          <lslr>-option = <lsop>-option.
          <lslr>-low = <lsop>-low.
        endif.
      endif.

      delete et_entityset where plant not in lr_pl.

What this is doing is – if filter select option have not been initialised, then create a set of data (called lr_pr, this is just a variable name), whose type is ‘werks_d’. werks_d is the ABAP data type of our Plant property. The data type for your property can found by opening the data model of your backend data source, and viewing the properties of your entity type you wish to be filtered.

Click on the white square that appears next to the ABAP Field Name entry, and you will see this table, which describes your data fields. Here we see that the Plant property is of the type ‘werks_d’, so that is the type our filtered data set must take.  

After creating the new data set named lr_pr, the function reads the filter select options for the key word ‘Plant’If a filter option for Plant has been entered, then append the matching data to the data set lr_pr 

Finally, delete everything in et_entityset that doesn’t match the filtered data. This ensures that only data entries that fit the given criteria are sent to the cards. 

 

Note: our getEntitySet method begins with this line  

field-symbols: <ls_es> like line of et_entityset. 

Also note that whenever we filtered data, our app only showed the first data entry that matched the criteria and ignored every other tuple, forcing us to arrange our backend data source in such a way that all the information we needed to display could be found in one tuple. This filter method is a bit of a hack and may be the cause of this bug, so keep that in mind if you encounter the same problem.  

 

 

Appendix

Creating an Analytical Card

Analytical Cards cannot be created using the Card Wizard and must instead be typed out manually in the manifest.json file. If you try to use the card wizard, you will find that you must select a KPI annotation path, but you will have no options from which to select.  

You must have this code in your manifest.json, in the section that describes your cards:

"card03": {
    "model": "Alias",
    "template": "sap.ovp.cards.charts.analytical",
    "settings": {
        "itemText": "{{card00_itemText}}",
        "title": "{{Corrective Work Orders}}",
        "subTitle": "{{Corrective Work Orders}}",
        "category": "{{card00_category}}",
        "entitySet": "OrderStats",
        "chartAnnotationPath": "com.sap.vocabularies.UI.v1.Chart#CorrectiveWorkOrders",
        "selectionAnnotationPath": "com.sap.vocabularies.UI.v1.SelectionVariant#Filter",
        "dataPointAnnotationPath": "com.sap.vocabularies.UI.v1.DataPoint#CorrectiveWorkOrders"
    }
},

You may find that the variable text for the card title  

    “title”: “{{card00_title}}”, 

Does not produce an actual title on your card, as you define it in the annotation file. If this is the case, you must hard-code the title as seen here in the manifest.json 

The annotation paths defined at the end point to the annotations you must write in your annotation file, with the identifier after the # matching the annotation qualifier 

The dataPointAnnotation is optional, if you need to display fields with special formatting, described here: https://help.sap.com/viewer/468a97775123488ab3345a0c48cadd8f/7.52.5/en-US/65731e6b823240398e33133908efdaa1.html 

The selectionAnnotationPath is optional, if you need the card to be pre-filtered, overriding the global filter. The annotation for Selection Variants is discussed at the end of Section 3: Annotations. 

The important line here is chartAnnotationPath, which must be included for the card to work. This points to the annotation for your Analytical Card. Here we have an annotation for a stacked column chart, which displays the number of Corrective Work Orders (CORR) raised by each Plant (Plant), in each month (StatMonth).  

Note: The EntitySet from which this example chart is produced sends through a table, where each tuple represents a month and a plant – these are the keys of the table – and the CORR column contains a count of all the corrective work orders that have been raised by that plant in that month. During our project, when trying to make a card show specific data from that table using filters, we found that the OverView Page only displayed the first tuple in the table that met the criteria and ignored every other tuple that met the criteria. To get around this, we had to organise the backend data source in such a way that all the data we need can be displayed in a single tuple. 

<Annotation Term="UI.Chart" Qualifier="CorrectiveWorkOrders">
    <Record Type="UI.ChartDefinitionType">
        <PropertyValue Property="ChartType" EnumMember="UI.ChartType/ColumnStacked"/>
        <PropertyValue Property="Title" String="Corrective Work Orders Raised / Month"/>
        <PropertyValue Property="MeasureAttributes">
            <Collection>
                <Record Type="UI.ChartMeasureAttributeType">
                    <PropertyValue Property="Measure" PropertyPath="CORR"/>
                        <PropertyValue Property="Role" EnumMember="UI.ChartMeasureRoleType/Axis1"/>
                </Record>
            </Collection>
        </PropertyValue>
        <PropertyValue Property="DimensionAttributes">
            <Collection>
                <Record Type="UI.ChartDimensionAttributeType">
                    <PropertyValue Property="Dimension" PropertyPath="Plant"/>
                    <PropertyValue Property="Role" EnumMember="UI.ChartDimensionRoleType/Series"/>
                </Record>
                <Record Type="UI.ChartDimensionAttributeType">
                    <PropertyValue Property="Dimension" PropertyPath="StatMonth"/>
                    <PropertyValue Property="Role" EnumMember="UI.ChartDimensionRoleType/Category"/>
                </Record>
            </Collection>
        </PropertyValue>
    </Record>
</Annotation>

Under the ChartDefinitionType, in addition to the card’s ChartType and Title, you must also define MeasureAttributes and DimensionAttributes for your chart.  

MeasureAttributes will be the Y-axis of a column chart. Here, we want to measure the number of Corrective Work Orders raised, which is recorded in our backend database as a number (the value of CORR is derived by the backend using database using SELECT COUNT statements, not described in this post). 

DimensionAttributes describe the X-axis of a column chart and the stacks of each column. Here, the Category dimension role refers to the Month (the X-axis) and the Series dimension role refers to the Plant (the column stacks), so each column of the chart will display the number of corrective work orders raised by each plant in a particular month. 

 

 

I hope this was helpful in creating an Overview Page with SAP Fiori. 

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