Serving Fiori UIs
This guide explains how to add one or more SAP Fiori elements apps to a CAP project, how to add SAP Fiori elements annotations to respective service definitions, and more. In the following sections, when mentioning Fiori, we always mean SAP Fiori elements.
Learn more about developing SAP Fiori elements and OData V4 (since 1.84.)
SAP Fiori Preview
When starting your application, for example, using cds watch
, there is an SAP Fiori preview for development purposes. You can use this to see the effect of annotations in a quick roundtrip. Be aware that this is just a preview. There can be differences between the preview and an implemented SAP Fiori application.
The preview is only active with the development profile.
TIP
To also enable this preview for the production profile, add the following configuration to your project's package.json: cds.features.fiori_preview:true
If you add this to your cdsrc.json omit the cds
section.
Adding SAP Fiori Apps to CAP Projects
As showcased in cap/samples, SAP Fiori apps should be added as sub folders to the app/
of a CAP project. Each sub folder constitutes an individual SAP Fiori application, with local annotations, manifest.json, etc. So, a typical folder layout would look like this:
Folder/Sub Folder | Description |
---|---|
app/ | All SAP Fiori apps should go in here |
browse/ | SAP Fiori app for end users |
orders/ | SAP Fiori app for order management |
admin/ | SAP Fiori app for admins |
index.html | For sandbox tests |
srv/ | All services |
db/ | Domain models, and db stuff |
Using SAP Fiori Tools
The SAP Fiori tools provide advanced support for adding SAP Fiori apps to existing CAP projects as well as a wealth of productivity tools, for example for adding SAP Fiori annotations, or graphical modeling and editing. They can be used locally in Visual Studio Code (VS Code) or in SAP Business Application Studio.
Learn more about how to install SAP Fiori tools.
By Copying from cap/samples
For example, you can copy the SAP Fiori apps from cap/samples as a template and modify the content as appropriate.
By Copying the SAP Fiori Elements Sample Service
This is a sample to create an incident management app with SAP Fiori elements for OData V4.
Adding SAP Fiori Annotations
The main content to add is service definitions annotated with information about how to render respective data.
What Are SAP Fiori Annotations?
SAP Fiori elements apps are generic front ends, which construct and render the pages and controls based on annotated metadata documents. The annotations provide semantic annotations used to render such content, for example:
annotate CatalogService.Books with @(
UI: {
SelectionFields: [ ID, price, currency_code ],
LineItem: [
{Value: title},
{Value: author, Label:'{i18n>Author}'},
{Value: genre.name},
{Value: price},
{Value: currency.symbol, Label:' '},
]
}
);
annotate CatalogService.Books with @(
UI: {
SelectionFields: [ ID, price, currency_code ],
LineItem: [
{Value: title},
{Value: author, Label:'{i18n>Author}'},
{Value: genre.name},
{Value: price},
{Value: currency.symbol, Label:' '},
]
}
);
Find this source and many more in cap/samples. [Learn more about OData Annotations in CDS.][OData Annotations]
Where to Put Them?
While CDS in principle allows you to add such annotations everywhere in your models, we recommend putting them in separate .cds files placed in your ./app/* folders, for example, as follows.
./app #> all your Fiori annotations should go here, for example:
./admin
fiori-service.cds #> annotating ../srv/admin-service.cds
./browse
fiori-service.cds #> annotating ../srv/cat-service.cds
index.cds
./srv #> all service definitions should stay clean in here:
admin-service.cds
cat-service.cds
...
./app #> all your Fiori annotations should go here, for example:
./admin
fiori-service.cds #> annotating ../srv/admin-service.cds
./browse
fiori-service.cds #> annotating ../srv/cat-service.cds
index.cds
./srv #> all service definitions should stay clean in here:
admin-service.cds
cat-service.cds
...
See this also in cap/samples/fiori.
Reasoning: This recommendation essentially follows the best practices and guiding principles of Conceptual Modeling and Separation of Concerns.
Maintaining Annotations
Maintaining OData annotations in .cds files is accelerated by the SAP Fiori tools - CDS OData Language Server @sap/ux-cds-odata-language-server-extension in the SAP CDS language support plugin. It helps you add and edit OData annotations in CDS syntax with:
- Code completion
- Validation against the OData vocabularies and project metadata
- Navigation to the referenced annotations
- Quick view of vocabulary information
- Internationalization support
These assisting features are provided for OData annotations in CDS syntax and can’t be used yet for the core data services common annotations.
The @sap/ux-cds-odata-language-server-extension module doesn’t require any manual installation. The latest version is fetched by default from npmjs.com as indicated in the user preference setting CDS > Contributions: Registry.
Learn more about the CDS extension for VS Code.
Code Completion
The CDS OData Language Server provides a list of context-sensitive suggestions based on the service metadata and OData vocabularies. You can use it to choose OData annotation terms, their properties, and values from the list of suggestions in annotate directives applied to service entities and entity elements. See annotate directives for more details.
Using Code Completion
To trigger code completion, choose ⌘ + ⎵ (macOS) or Ctrl + ⎵ (other platforms). The list of suggested values is displayed.
You can filter the list of suggested values by typing more characters.
Navigate to the desired value using the up or down arrows or your mouse. Accept the highlighted value by pressing Enter or by clicking the mouse. Use code completion to add and change individual values (word-based completion) and to add small code blocks containing annotation structures along with mandatory properties (micro-snippets). In an active code snippet, you can use the ⇥ (tab) key to quickly move to the next tab stop.
Example: Annotating Service Entities
(cursor position indicated by |
)
Place cursor in the
annotate
directive for a service entity, for exampleannotate Foo.Bar with ;
and trigger code completion.Type
u
to filter the suggestions and choose{} UI
. Micro-snippet@UI : {|}
is inserted:annotate Foo.Bar with @UI : {|};
Use code completion again to add an annotation term from the UI vocabulary, in this example
SelectionFields
. The micro snippet for this annotation is added and the cursor is placed directly after the term name letting you define a qualifier:annotate Foo.Bar with @UI : {SelectionFields | : []};
Press the ⇥ (tab) key to move the cursor to the next tab stop and use code completion again to add values. Because the
UI.SelectionFields
annotation is a collection of entity elements (entity properties), all elements of the annotated entity are suggested.TIP
To choose an element of an associated entity, first select the corresponding association from the list and type . (period). Elements of associated entity are suggested.
You can add multiple values separated by comma.
annotate Foo.Bar with @UI : { SelectionFields : [
description, assignedIndividual.lastName|
],
};
annotate Foo.Bar with @UI : { SelectionFields : [
description, assignedIndividual.lastName|
],
};
Add a new line after
,
(comma) and use code completion again to add another annotation from the UI vocabulary, such asLineItem
. Line item is a collection ofDataField
records. To add a record, select the record type you need from the completion list.cdsannotate Foo.Bar with @UI : { SelectionFields : [ description, assignedIndividual.lastName ], LineItem : [{ $Type:'UI.DataField', Value : |, }, };
annotate Foo.Bar with @UI : { SelectionFields : [ description, assignedIndividual.lastName ], LineItem : [{ $Type:'UI.DataField', Value : |, }, };
For each record type, two kinds of micro-snippets are provided: one containing only mandatory properties and one containing all properties defined for this record (full record). Usually you need just a subset of properties. So, you either select a full record and then remove the properties you don’t need, or add the record containing only required properties and then add the remaining properties.
Use code completion to add values for the annotation properties.
cdsannotate Foo.Bar with @UI : { SelectionFields : [ description, assignedIndividual.lastName ], LineItem : [ { $Type:'UI.DataField', Value : description, }, { $Type:'UI.DataFieldForAnnotation', Target : 'assignedIndividual/@Communication.Contact', },| ] };
annotate Foo.Bar with @UI : { SelectionFields : [ description, assignedIndividual.lastName ], LineItem : [ { $Type:'UI.DataField', Value : description, }, { $Type:'UI.DataFieldForAnnotation', Target : 'assignedIndividual/@Communication.Contact', },| ] };
To add values pointing to annotations defined in another CDS source, you must reference this source with the
using
directive. See Theusing
Directive for more details.
Example: Annotating Entity Elements
(cursor position indicated by |
)
- Place the cursor in the
annotate
directive, for exampleannotate Foo.Bar with {|};
, add a new line and trigger code completion. You get the list of entity elements. Choose the one that you want to annotate.
annotate Foo.Bar with {
code|
};
annotate Foo.Bar with {
code|
};
- Press the ⎵ key, use code completion again, and choose
{} UI
. The@UI : {|}
micro-snippet is inserted:
annotate Foo.Bar with {
code @UI : { | }
};
annotate Foo.Bar with {
code @UI : { | }
};
- Trigger completion again and choose an annotation term from the UI vocabulary, in this example: Hidden.
annotate Foo.Bar with {
code @UI : {Hidden : |}
};
annotate Foo.Bar with {
code @UI : {Hidden : |}
};
- Press the ⇥ (tab) key to move the cursor to the next tab stop and use code completion again to add the value. Because the
UI.Hidden
annotation is of Boolean type, the values true and false are suggested:
annotate Foo.Bar with {
code @UI : {Hidden : false }
};
annotate Foo.Bar with {
code @UI : {Hidden : false }
};
Diagnostics
The CDS OData Language Server validates OData annotations in .cds files against the service metadata and OData vocabularies. It also checks provided string content for language-dependent annotation values and warns you if the format doesn’t match the internationalization (i18n) key reference. It shows you that this string is hard coded and won’t change based on the language setting in your application. See Internationalization support for more details.
Validation is performed when you open a .cds file and then is retriggered with each change to the relevant files.
You can view the diagnostic messages by hovering over the highlighted part in the annotation file or by opening the problems panel. Click on the message in the problems panel to navigate to the related place in the annotation file.
If an annotation value points to the annotation defined in another CDS source, you must reference this source with a
using
directive to avoid warnings. See Theusing
Directive for more details.
Navigation to Referenced Annotations
CDS OData Language Server enables quick navigation to the definition of referenced annotations. For example, if your annotation file contains a DataFieldForAnnotation
record referencing an Identification
annotation defined in the service file, you can view which file it’s defined in and what fields or labels this annotation contains. You can even update the Identification
annotation or add comments.
You can navigate to the referenced annotation using the Peek Definition and Go To Definition features.
If the referenced annotation is defined in another CDS source, you must reference this source with the
using
directive to enable the navigation. See Theusing
Directive for more details.
Peek Definition
Peek Definition lets you preview and update the referenced annotation without switching away from the code that you’re writing. It’s triggered when your cursor is inside the referenced annotation value.
- Using a keyboard: choose ⌥ + F12 (macOS) or Alt + F12 (other platforms)
- Using a mouse: right-click and select Peek Definition If an annotation is defined in multiple sources, all these sources are listed. You can select which one you want to view or update. Annotation layering isn't considered.
Go to Definition
Go To Definition lets you navigate to the source of the referenced annotation and opens the source file scrolled to the respective place in a new tab. It’s triggered when your cursor is inside the referenced annotation value.
Place your cursor inside the path referencing the annotation term segment or translatable string value, and trigger Go to Definition:
- Using a keyboard: choose F12 in VS Code, or Ctrl + F12 in SAP Business Application Studio
- Using a mouse: right-click and select Go To Definition
- Using a keyboard and mouse: ⌘ + mouse click (macOS) or Ctrl + mouse click (other platforms)
If an annotation is defined in multiple sources, a Peek definition listing these sources will be shown instead. Annotation layering isn't considered.
Documentation (Quick Info)
The annotation language server provides quick information for annotation terms, record types, and properties used in the annotation file, or provided as suggestions in code completion lists. This information is retrieved from the respective OData vocabularies and can provide answers to the following questions:
- What is the type and purpose of the annotation term/record type/property?
- What targets can the annotation term apply to?
- Is the annotation term/record type/property experimental? Is it deprecated?
- Is this annotation property mandatory or optional?
The exact content depends on the availability in OData vocabularies.
To view the quick info for an annotation term, record type, or property used in the annotation file, hover your mouse over it. The accompanying documentation is displayed in a hover window, if provided in the respective OData vocabularies.
To view the quick info for each suggestion in the code completion list, either pressing ⌘ + ⎵ (macOS) or Ctrl + ⎵ (other platforms), or click the info icon. The accompanying documentation for the suggestion expands to the side. The expanded documentation stays open and updates as you navigate the list. You can close this by pressing ⌘ + ⎵ / Ctrl + ⎵ again or by clicking on the close icon.
Internationalization Support
When you open an annotation file, all language-dependent string values are checked against the i18n.properties file. Each value that doesn’t represent a valid reference to the existing text key in the i18n.properties file, is indicated with a warning. A Quick Fix action is suggested to generate a text key in i18n file and substitute your string value with the reference to that entry.
Prefer @title
and @description
Influenced by the [JSON Schema], CDS supports the [common annotations] @title
and @description
, which are mapped to corresponding [OData annotations] as follows:
CDS | JSON Schema | OData |
---|---|---|
@title | title | @Common.Label |
@description | description | @Core.Description |
We recommend preferring these annotations over the OData ones in protocol-agnostic data models and service models, for example:
annotate my.Books with { //...
title @title: 'Book Title';
author @title: 'Author ID';
}
annotate my.Books with { //...
title @title: 'Book Title';
author @title: 'Author ID';
}
Prefer @readonly
, @mandatory
, ...
CDS supports @readonly
as a common annotation, which translates to respective [OData annotations] from the @Capabilities
vocabulary. We recommend using the former for reasons of conciseness and comprehensibility as shown in this example:
@readonly entity Foo { // entity-level
@readonly foo : String // element-level
}
@readonly entity Foo { // entity-level
@readonly foo : String // element-level
}
is equivalent to:
entity Foo @(Capabilities:{
// entity-level
InsertRestrictions.Insertable: false,
UpdateRestrictions.Updatable: false,
DeleteRestrictions.Deletable: false
}) {
// element-level
@Core.Computed foo : String
}
entity Foo @(Capabilities:{
// entity-level
InsertRestrictions.Insertable: false,
UpdateRestrictions.Updatable: false,
DeleteRestrictions.Deletable: false
}) {
// element-level
@Core.Computed foo : String
}
Similar recommendations apply to @mandatory
and others → see Common Annotations.
Draft-Based Editing
SAP Fiori supports edit sessions with draft states stored on the server, so users can interrupt and continue later on, possibly from different places and devices. CAP, as well as SAP Fiori elements, provide out-of-the-box support for drafts as outlined in the following sections. We recommend to always use draft when your application needs data input by end users.
For details and guidelines, see SAP Fiori Design Guidelines for Draft.
Find a working end-to-end version in cap/samples/fiori.
Enabling Draft with @odata.draft.enabled
To enable draft for an entity exposed by a service, simply annotate it with @odata.draft.enabled
as in this example:
annotate AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books with @odata.draft.enabled;
WARNING
You can't project from draft-enabled entities, as annotations are propagated. Either enable the draft for the projection and not the original entity or disable the draft on the projection using @odata.draft.enabled: null
.
Enabling Draft for Localized Data
Annotate the underlying base entity in the base model with @fiori.draft.enabled
to also support drafts for [localized data]:
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
Background: SAP Fiori drafts require single keys of type
UUID
, which isn’t the case by default for the automatically generated_texts
entities (→ see the Localized Data guide for details). The@fiori.draft.enabled
annotation tells the compiler to add such a technical primary key element namedID_texts
.
If you're editing data in multiple languages, the General tab in the example above is reserved for the default language (often "en"). Any change to other languages has to be done in the Translations tab, where a corresponding language can be chosen from a drop-down menu as illustrated above. This also applies if you use the URL parameter sap-language
on the draft page.
Validating Drafts
You can add custom handlers to add specific validations, as usual. In addition, for a draft, you can register handlers to the PATCH
events to validate input per field, during the edit session, as follows.
... in Java
You can add your validation logic before operation event handlers. Specific events for draft operations exist. See Java > Fiori Drafts > Editing Drafts for more details.
... in Node.js
You can add your validation logic before the operation handler for the CREATE
or UPDATE
event (as in the case of nondraft implementations) or on the SAVE
event (specific to drafts only):
srv.before ('CREATE','Books', (req)=>{ ... }) // run before create
srv.before ('UPDATE','Books', (req)=>{ ... }) // run before create
srv.before ('SAVE','Books', (req)=>{...}) // run at final save only
srv.before ('CREATE','Books', (req)=>{ ... }) // run before create
srv.before ('UPDATE','Books', (req)=>{ ... }) // run before create
srv.before ('SAVE','Books', (req)=>{...}) // run at final save only
In addition, you can add field-level validations on the individual PATCH
events:
srv.before ('PATCH','Books', (req)=>{...}) // run during editing
srv.before ('PATCH','Books', (req)=>{...}) // run during editing
These get triggered during the draft edit session whenever the user tabs from one field to the next, and can be used to provide early feedback.
You can also add custom logic for the initial creation of drafts:
srv.before ('NEW','Books', (req)=>{...}) // run during creation of a draft from scratch
srv.before ('EDIT','Books', (req)=>{...}) // run during creation of a draft for existing instance
srv.before ('NEW','Books', (req)=>{...}) // run during creation of a draft from scratch
srv.before ('EDIT','Books', (req)=>{...}) // run during creation of a draft for existing instance
Query Drafts Programmatically
To access drafts in code, you can use the .drafts
reflection.
SELECT.from(Books.drafts) //returns all drafts of the Books entity
SELECT.from(Books.drafts) //returns all drafts of the Books entity
Learn how to query drafts in Java.
Value Help Support
In addition to supporting the standard @Common.ValueList
annotations as defined in the [OData Vocabularies], CAP provides advanced, convenient support for Value Help as understood and supported by SAP Fiori.
Convenience Option @cds.odata.valuelist
Simply add the @cds.odata.valuelist
annotation to an entity, and all managed associations targeting this entity will automatically receive Value Lists in SAP Fiori clients. For example:
@cds.odata.valuelist
entity Currencies {}
@cds.odata.valuelist
entity Currencies {}
service BookshopService {
entity Books { //...
currency : Association to Currencies;
}
}
service BookshopService {
entity Books { //...
currency : Association to Currencies;
}
}
Pre-Defined Types in @sap/cds/common
The reuse types in [@sap/cds/common] already have this added to base types and entities, so all uses automatically benefit from this. This is an effective excerpt of respective definitions in @sap/cds/common
:
type Currencies : Association to sap.common.Currencies;
type Currencies : Association to sap.common.Currencies;
context sap.common {
entity Currencies : CodeList {...};
entity CodeList { name : localized String; ... }
}
context sap.common {
entity Currencies : CodeList {...};
entity CodeList { name : localized String; ... }
}
annotate sap.common.CodeList with @(
UI.Identification: [name],
cds.odata.valuelist,
);
annotate sap.common.CodeList with @(
UI.Identification: [name],
cds.odata.valuelist,
);
Usages of @sap/cds/common
In effect, usages of [@sap/cds/common] stay clean of any pollution, for example:
using { Currency } from '@sap/cds/common';
entity Books { //...
currency : Currency;
}
using { Currency } from '@sap/cds/common';
entity Books { //...
currency : Currency;
}
Find this also in our cap/samples.
Still, all SAP Fiori UIs, on all services exposing Books
, will automatically receive Value Help for currencies. You can also benefit from that when deriving your project-specific code list entities from sap.common.CodeList.
Resulting Annotations in EDMX
Here is an example showing how this ends up as OData Common.ValueList
annotations:
<Annotations Target="AdminService.Books/currency_code">
<Annotation Term="Common.ValueList">
<Record Type="Common.ValueListType">
<PropertyValue Property="CollectionPath" String="Currencies"/>
<PropertyValue Property="Label" String="Currency"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="Common.ValueListParameterInOut">
<PropertyValue Property="ValueListProperty" String="code"/>
<PropertyValue Property="LocalDataProperty" PropertyPath="currency_code"/>
</Record>
<Record Type="Common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty" String="name"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotation>
<Annotations Target="AdminService.Books/currency_code">
<Annotation Term="Common.ValueList">
<Record Type="Common.ValueListType">
<PropertyValue Property="CollectionPath" String="Currencies"/>
<PropertyValue Property="Label" String="Currency"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="Common.ValueListParameterInOut">
<PropertyValue Property="ValueListProperty" String="code"/>
<PropertyValue Property="LocalDataProperty" PropertyPath="currency_code"/>
</Record>
<Record Type="Common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty" String="name"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
</Annotation>
Actions
In our SFLIGHT sample application, we showcase how to use actions covering the definition in your CDS model, the needed custom code and the UI implementation.
Learn more about Custom Actions & Functions.
We're going to look at three things.
- Define the action in CDS and custom code.
- Create buttons to bring the action to the UI
- Dynamically define the buttons status on the UI
First you need to define an action, like in the travel-service.cds file.
entity Travel as projection on my.Travel actions {
action createTravelByTemplate() returns Travel;
action rejectTravel();
action acceptTravel();
action deductDiscount( percent: Percentage not null ) returns Travel;
};
entity Travel as projection on my.Travel actions {
action createTravelByTemplate() returns Travel;
action rejectTravel();
action acceptTravel();
action deductDiscount( percent: Percentage not null ) returns Travel;
};
To define what the action actually is doing, you need to write some custom code. See the travel-service.js file for example:
this.on('acceptTravel', req => UPDATE(req._target).with({TravelStatus_code:'A'}))
this.on('acceptTravel', req => UPDATE(req._target).with({TravelStatus_code:'A'}))
req._target
is a workaround that has been introduced in SFlight. In the future, there might be an official API for it.
Create the buttons, to bring this action onto the UI and make it actionable for the user. There are two buttons: On the overview and in the detail screen. Both are defined in the layouts.cds file.
For the overview of all travels, use the @UI.LineItem
annotation.
annotate TravelService.Travel with @UI : {
LineItem : [
{ $Type : 'UI.DataFieldForAction',
Action : 'TravelService.acceptTravel',
Label : '{i18n>AcceptTravel}' }
]
};
annotate TravelService.Travel with @UI : {
LineItem : [
{ $Type : 'UI.DataFieldForAction',
Action : 'TravelService.acceptTravel',
Label : '{i18n>AcceptTravel}' }
]
};
For the detail screen of a travel, use the @UI.Identification
annotation.
annotate TravelService.Travel with @UI : {
Identification : [
{ $Type : 'UI.DataFieldForAction',
Action : 'TravelService.acceptTravel',
Label : '{i18n>AcceptTravel}' }
]
};
annotate TravelService.Travel with @UI : {
Identification : [
{ $Type : 'UI.DataFieldForAction',
Action : 'TravelService.acceptTravel',
Label : '{i18n>AcceptTravel}' }
]
};
Now, the buttons are there and connected to the action. The missing piece is to define the availability of the buttons dynamically. Annotate the Travel
entity in the TravelService
service accordingly in the field-control.cds file.
annotate TravelService.Travel with actions {
acceptTravel @(
Core.OperationAvailable : {
$edmJson: { $Ne: [{ $Path: 'in/TravelStatus_code'}, 'A']}
},
Common.SideEffects.TargetProperties : ['in/TravelStatus_code'], ) };
annotate TravelService.Travel with actions {
acceptTravel @(
Core.OperationAvailable : {
$edmJson: { $Ne: [{ $Path: 'in/TravelStatus_code'}, 'A']}
},
Common.SideEffects.TargetProperties : ['in/TravelStatus_code'], ) };
This annotation uses dynamic expressions to control the buttons for each action. And the status of a travel on the UI is updated, triggered by the @Common.SideEffects.TargetProperties
annotation.
If you've the need for a more complex calculation, then the interesting parts in SFLIGHT are virtual fields in field-control.cds (also lines 37-44) and custom code in travel-service.js.
Locked by Another User
The scenario Locked by Another User of the draft choreography has been adjusted since SAP Fiori elements v1.91. The new implementation can only be served by backend versions cds^5
(Node.js) and 1.12.0 (Java), respectively.