Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert

Introduction

In a recent assignment, I had the opportunity to use the Cloud Application Programming Model and Fiori Elements to design and create an enterprise-ready application. I used a few concepts like dynamic expressions, side effects, and custom actions on object pages. However, finding appropriate material or references with examples and understanding the concepts required a lot of time. I'll try to briefly describe these ideas in this blog post so that others can benefit and time can be saved.

Short explanation of POC scenario and its resources

We will utilise the example project "cap-fe-se-ca-de" that I posted on GitHub here to explain the topics.  Let's quickly review the project and some of its key files.

Ajit_K_Panda_0-1707190948596.png
  •  app/ : This has 2 ui frontends i.e. Customers and Orders
  • db/ : Data model is defined in schema.cds
  • srv/ : Services are defined in service.cds and logic is added via service.js. Also capabilities.cds adds/restricts different capabilities of the services
  • app/orders: Fiori Elements based UI app for Orders

Both the "Customers" and the "Orders" applications in the app folder were created using Fiori Elements. To better comprehend the aforementioned topics, we will concentrate on the Orders application. The first view in the Orders application is a List Page, and the second view, which is the details view, is an Object Page, as illustrated below:

Orders - List Page

Ajit_K_Panda_1-1707191766614.png
Orders - Object Page

Concept-1: Side Effects

If a user modifies the content of a field or carries out an action, this modification may have an impact on other fields on the UI. This system behaviour is called a side effect.

example:  In order, if you enter quantity and price, total value gets updated automatically. This behaviour is called a side effect.

There are different variations to Side effects. Let’s explore one by one using annotations_se.cds file in Orders application.

Side Effect with Single Source Property and Target Entities:  In order detail page, when user choose customer, automatically the customer details like first name, last name and its address should also be updated.  To achieve this, following annotation is added in annotations_se.cds
using orderservice as service from '../../srv/service';

annotate service.Orders @(Common : {
    SideEffects #CustomerChanged : {
        SourceProperties : ['customer_ID'],
        TargetEntities   : [customer, customer.addresses]
    }
});

Based on above annotations, whenever customer_ID field is updated by user, this side effect #CustomerChanged get triggered and the field related to customer and customer address entity is updated.

Technically, this side effect results in one update call to update the customer_ID field and one get call to fetch customer and customer address data.

Batch Request with 2 Calls


In this example, we saw how we can impact ui filelds referencing to navigation entities i.e. Customer and its addresses.

Side Effect with Single Source Property and Target Properties: When a user adds a new item to an order and provides the product id, the price and currency should be automatically fetched from the product master and filled in for the item entity. Also when both product and quantity is filled, net price should also be calculated. The annotations below are used to accomplish this:
annotate service.OrderItems @(Common : {
    SideEffects #ProductChanged  : {
        SourceProperties : ['product_id'],
        TargetProperties : ['price', 'currency_code', 'netprice']
    }
});

annotate service.OrderItems @(Common : {
    SideEffects #QuantityChanged : {
        SourceProperties : ['quantity'],
        TargetProperties : ['netprice']
    }
});

Keep in mind that side effect causes an additional get call and update the user interface with the call's outcome. But it makes no logic addition. In order to complete the functionality we need, we will additionally add logic to the first patch call that

  • will query the product master and fill in the price and currency
  • calculate net price and fill it in item entity
Refer to the following code defined in service.js.
this.before('PATCH', OrderItems, async req => {
  let productInfo, price, quantity, dbItemInfo;

  //When product is available, fill price and currency
  if (req.data.product_id != undefined && req.data.product_id != null) {
    productInfo = await cds.read(Products).byKey(req.data.product_id);
    req.data.price = productInfo.price;
    req.data.currency_code = productInfo.currency_code;
  }

  // Calculate Net Price
  dbItemInfo = await cds.read(OrderItems.drafts).where({ ID: req.data.ID });
  req.data.price > 0 ? price = req.data.price : price = dbItemInfo[0].price;
  req.data.quantity > 0 ? 
  quantity = req.data.quantity : quantity = dbItemInfo[0].quantity;
  if (price > 0 && quantity > 0) {
    req.data.netprice = price * quantity;
    req.data.netprice = Math.round((req.data.netprice + Number.EPSILON)*100);
    req.data.netprice = req.data.netprice / 100;
  }
});
Side Effect on a Source Entity: The order's total amount need to be updated automatically as items are added. In other words, anytime order items are updated, a side effect should be started to retrieve the order's total. To achieve this, use the annotations below:
annotate service.Orders @(Common : {
    SideEffects #ItemChanged     : {
        SourceEntities   : [items],
        TargetProperties : ['totamount', 'currency_code']
    }
});
As seen in the code below, the total amount for the order needs to be updated in the initial patch call of the side effect.
this.after('PATCH', OrderItems, async data => {
  let orderInfo = { totamount: 0 };
  let result = await cds.read(OrderItems.drafts)
                        .where({ ID: data.ID }).columns(['order_ID']);
  let dbItemInfos = await cds.read(OrderItems.drafts)
                             .where({ order_ID: result[0].order_ID });

  for (let i = 0; i < dbItemInfos.length; i++) {
    if (dbItemInfos[i].netprice > 0) {
      orderInfo.totamount = orderInfo.totamount + dbItemInfos[i].netprice;
    }
    orderInfo.currency_code = dbItemInfos[i].currency_code;
  }

  await cds.update(Orders.drafts, result[0].order_ID).set(orderInfo);
});

Concept-2: DefaultValuesFunction

Default values can be supplied using a "DefaultValueFunction" when creating a new entity or item.
For example, an order's initial status is always set to "Open" when it is created.

Annotation to provide 'DefaultValueFunction' is as shown below:

annotate service.Orders with @( Common.DefaultValuesFunction : 'getOrderDefaults' );

Here, 'getOrderDefaults' is a function of orders entity under orderservice. It is defined in service.cds  and service.js file.

 

//Definition [service.cds] 
function getOrderDefaults() returns Orders; 

//Implementation [service.js] 
this.on('getOrderDefaults', async req => { return {status: 'Open'}; });

 

Concept-3: Custom Actions

The initial status of an order is 'Open'. We can define custom action to change the status from 'Open' to 'In Process' and vice-versa.

Two bound actions and its correspoding logic are provided in service.cds and service.js respectively as shown below:

//Definition [service.cds] 

entity Orders as projection on db.Orders
  actions {
    action setOrderProcessing();
    action setOrderOpen();
};
//Implementation [service.js] 
this.on('setOrderProcessing', Orders, async req => {
await cds.update(Orders, req.params[0].ID).set({status: 'In Process'});
});

this.on('setOrderOpen', Orders, async req => {
await cds.update(Orders, req.params[0].ID).set({status: 'Open'});
});


These 2 actions can be added in UI as Buttons using annotations as provided in annotations_ca_de.cds file.

annotate service.Orders with @(UI.Identification : [ 
{
$Type : 'UI.DataFieldForAction',
Label : 'Set to In Process',
Action : 'orderservice.setOrderProcessing'
},
{
$Type : 'UI.DataFieldForAction',
Label : 'Set to Open',
Action : 'orderservice.setOrderOpen'
}
]);

Concept-4: Dynamic Expressions

Dynamic expressions are small logics defined in annotation to control the behavior of UI.

Example: If status of order is Open, then only 'Set to In Process' button is visible and if status of order is In Process, then only 'Set to Open' button is visible. To achieve this, we can use dynamic expressions.

Note 1: OData supports dynamic expressions in annotations. In CDS, these dynamic expressions are provides using JSON representations. For more information, please refer this document.

 

 

annotate service.Orders with @(UI.Identification : [ 
  {
    $Type : 'UI.DataFieldForAction', 
    Label : 'Set to In Process', 
    Action : 'orderservice.setOrderProcessing', 
    ![@UI.Hidden] : {$edmJson : {$Ne : [{$Path : 'status'}, 'Open']}} 
  }, 
  { 
    $Type : 'UI.DataFieldForAction', 
    Label : 'Set to Open', 
    Action : 'orderservice.setOrderOpen', 
    ![@UI.Hidden] : {$edmJson : {$Eq : [{$Path : 'status'},'Open']}} 
  } 
]);

 

 

![@UI.Hidden] : {$edmJson : {$Ne : [{$Path : 'status'}, 'Open']}}

  • $Path: Refers to the property of the entity
  • $Ne: Not equal to operator, Other values are $Eq - Equal to, $Le - Less Than or Equal to , $Ge - Greater Than or Equal to

Above dynamics expression checks if status is not open then it returns true.

Note 2: Dynamic expressions can also be composite. Lets look at an example:

Criticality: {$edmJson :{$If :[{$Eq :[{$Path : 'status'},'Open']}, 1, 3]}}

Above dynamic expression checks if status is open then return 1 else return 3 for Criticality.

Concept-5: Custom Actions with Side Effects

As explained in previous concept, We have used two action for changing the status of order. However once the action is executed, the status field on ui should also change instantly. To achieve this, we can define side effects for custom action as shown below.

In this case, side effects annotations are provided in service.cds file.

 

 

entity Orders as projection on db.Orders 
  actions { 
    @( cds.odata.bindingparameter.name : '_it',
    Common.SideEffects : {TargetProperties : ['_it/status']} ) 
    action setOrderProcessing(); 

    @( cds.odata.bindingparameter.name : '_it', 
    Common.SideEffects : {TargetProperties : ['_it/status']} ) 
    action setOrderOpen(); 
  };

 

 

Conclusion

Together, the Cloud Application Programming Model and Fiori Elements improve developer experience while also boosting productivity and accelerating the development of enterprise-ready applications.

More information about Fiori Elements with cloud application programming model can be found here. You can follow my profile to get notification of the next blog post on CAP or Fiori Elements. Please feel free to provide any feedback you have in the comments section below and ask your questions about the topic in sap community using this link.

29 Comments