Skip to Content
Technical Articles
Author's profile photo Ajit Kumar Panda

CAP with Fiori Elements: Side Effects, Custom Actions, Dynamic Expressions

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.

Folder or File Purpose
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

annotations.cds:
contains annotations for List page and Object Page

annotations_se.cds:
contains annotations related side effects

annotations_ca_de.cds:
contains annotations related to custom actions & dynamic expressions

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%20-%20List%20Page Orders%20-%20Detail%20Page
Orders – List Page 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]}}

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

 

Assigned Tags

      24 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Alejandro Sensejl
      Alejandro Sensejl

      Great content, thank you! Looking forward to more blogs about CAP

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Thank you. 🙂

      Author's profile photo Mio Yasutake
      Mio Yasutake

      Thank you for writing this in such an easy to understand manner!

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Thank you. 🙂

      Author's profile photo Thorsten Klingbeil
      Thorsten Klingbeil

      Excellent Blog - thanks Ajit!

      Very inspiring! 🙂

      I wonder, if this can be transferred to the RAP-World, as well 🤔

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Thorsten,

      Thank you for reading the blog post!!
      I believe it should work for RAP world too. Hopefully some RAP enthusiasts will help us in this case. 🙂
      Regards, Ajit

      Author's profile photo Rohit Patil
      Rohit Patil

      hello Ajit Kumar Panda 

      Thank you for writing this in such an easy-to-understand blog!

       

      but I can't open the git hub link

       

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Rohit Patil ,

      Thank you for noticing. It is fixed now.

      Best Regards, Ajit

      Author's profile photo Yogesh Gupta
      Yogesh Gupta

      Very Informative Blog. Thank you for sharing!!

      Author's profile photo Peter Wörner
      Peter Wörner

      Hi Ajit,

      thanks for your enlightening blog. I am trying to get the app working also, but to no avail so far.

      I get errors from sqlite. Is there anything I have to do to initialise the databases beforehand?

      TIA

      Peter

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Peter Wörner,
      Thank you for reading the blog.
      Can you try this command in terminal:

      cds deploy --to sqlite

       

      Let me know if it works.
      Best Regards, Ajit

      Author's profile photo Peter Wörner
      Peter Wörner

      Thanks for your quick reply.

      No more sql errors, but still no data in the app. Any hints?

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Peter Wörner ,

      There is no data OOB. You can add data and see how the features work.

      Best Regards, Ajit

      Author's profile photo Peter Wörner
      Peter Wörner

      Hi Ajit,

      I would like to enter data, but the app requires customer data in an obligatory field. How would I be able to enter those in the first place?

      TIA

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Peter Wörner ,

      You can open customer ui as well. Application include customer ui as well. You can use that and create a customer.

      Regards, Ajit

      Author's profile photo Eric Sheng
      Eric Sheng

      Hi Ajit, do the SideEffects with Custom Action works for oData v2? I tried but can't get it working on my oData v2 project. The documentation didn't explicitly stating it doesn't work for v2. So just want to confirm this bit.

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      Hi Eric Sheng,

      Side Effects with Custom Action works for OData V2 as well. You can refer the help document here.

      Regards, Ajit

      Author's profile photo Helmut Tammen
      Helmut Tammen

      Awesome, thank you.

      Regards Helmut

      Author's profile photo Willem PARDAENS
      Willem PARDAENS

      Very informative, well documented and easy to apply to our own development. Thank you!

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      I am glad that it helped you. 🙂

      Author's profile photo VICTOR GONZALEZ
      VICTOR GONZALEZ

      A very useful blog to implement a commonly used custom code. Thank you very very much!

      Author's profile photo Venkatesh Hulekal
      Venkatesh Hulekal

      Very clear and really helpful. Thank you very much!

      Author's profile photo Ajit Kumar Panda
      Ajit Kumar Panda
      Blog Post Author

      I am glad that it helped you.  🙂

      Author's profile photo Krishna Chaitanyadas
      Krishna Chaitanyadas

      Hi Ajit Kumar Panda

      I am using dynamic expressions to hide/unhide a reference facet dynamically.

      It does not seem to be working.

      I have raised a question on community (https://answers.sap.com/questions/14005912/dynamic-expressions-for-hiding-a-reference-facet-i.html).
      Can you kindly provide your expert opinion?

       

      Regards,

      Krishna