Serving Fiori UIs

CAP provides out-of-the-box support for SAP Fiori elements frontends. 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, etc. In the following sections, we are always referring to SAP Fiori Elements when mentioning Fiori.


Adding Fiori Apps to CAP Projects

As showcased in cap/samples, Fiori apps should be added as sub folders to the app/ of a CAP project. Each sub folder constitutes an individual Fiori application, with local annotations, manifest.json, etc. So, a typical folder layout would look like this:

Folder / Sub Folder Description
app/ all Fiori apps should go in here…
    browse/ Fiori app for end users
    orders/ Fiori app for order mgmt
    admin/ Fiori app for admins
    index.html For sandbox tests
srv/ all services
db/ domain models, and db stuff

By Copying from cap/samples

For example you can copy the fiori apps from cap/samples as a template and modify the content as appropriate.

Adding Fiori Annotations

The main content to be added is service definitions annotated with information how to render respective data.

What Are Fiori Annotations?

SAP Fiori elements apps are generic frontends, 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: price},
      {Value: currency.symbol, Label:' '},

Find this source and many more in cap/samples Learn more on OData Annotations in CDS

Where to Put Them?

While CDS in principle allows 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:
      fiori-service.cds #> annotating ../srv/admin-service.cds
      fiori-service.cds #> annotating ../srv/cat-service.cds
./srv  #> all service definitions should stay clean in here:

See this also in cap/samples/fiori

Reasoning: This recommendation essentially follows best practices and guiding principles of Conceptual Modeling and Separation of Concerns.

Prefer @title and @description

Influenced by 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

It’s recommended to prefer 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';

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 ...     // element-level

is equivalent to:

entity Foo @(Capabilities:{
  // entity-level
  InsertRestrictions.Insertable: false
  UpdateRestrictions.Updatable: false
  DeleteRestrictions.Deletable: false
}) {
  // element-level
  @Core.Computed foo ...

Similar recommendations apply to @mandatory and others → see Common Annotations.

Draft-Based Editing

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 Fiori elements provide out-of-the-box support for Drafts as outlined below… It’s recommended to always use Draft when your application needs data input by end users.

For details and guidelines about that, 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;

See that live in cap/samples

Enabling Draft for Localized Data

Additionally 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;

Background: Fiori Drafts requires 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 annotation @fiori.draft.enabled tells the compiler to add such a technical primary key element named ID_texts.

See that live in cap/samples

Validating Drafts…

You can add custom handlers to add specific validations as usual. In addition, in case of Draft, you can register handlers to the PATCH events to validate input per field, during the edit session, as follows.

… in Java

If you save a new draft entity in the UI, the validation logic you’ve written in the method annotated with @BeforeCreate kick in. Similarly, if you save an entity in the edit draft state, the validation logic you’ve written in the method annotated with @BeforeUpdate gets triggered.

… in Node.js

You can add your validation logic in the before 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

In addition, you can add field-level validations on the individual PATCH events:

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.

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 Fiori.

Convenience option @cds.odata.valuelist

Simply add annotation @cds.odata.valuelist to an entity, and all Associations targeting this entity will automatically receive Value Lists in Fiori clients. For example:

entity 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;
context sap.common {
  entity Currencies : CodeList {...};
  entity CodeList { name : localized String; ... }
annotate sap.common.CodeList with @(
   UI.Identification: [name],

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;

Find that also in our cap/samples

Still, all 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 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">
               <Record Type="Common.ValueListParameterInOut">
                  <PropertyValue Property="ValueListProperty" String="code"/>
                  <PropertyValue Property="LocalDataProperty" PropertyPath="currency_code"/>
               <Record Type="Common.ValueListParameterDisplayOnly">
                  <PropertyValue Property="ValueListProperty" String="name"/>