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
21,852

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.

31 Comments
alejiandro_sensejl
Active Participant
Great content, thank you! Looking forward to more blogs about CAP
MioYasutake
Active Contributor
Thank you for writing this in such an easy to understand manner!
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Thank you. 🙂
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Thank you. 🙂
termanico
Participant
Excellent Blog - thanks Ajit!

Very inspiring! 🙂

I wonder, if this can be transferred to the RAP-World, as well 🤔
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
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
Rohit_Patil
Explorer
0 Kudos

hello ajitkpanda 

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

 

but I can't open the git hub link

 

Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi rohit_patil ,

Thank you for noticing. It is fixed now.

Best Regards, Ajit
yogeshgupta
Explorer
Very Informative Blog. Thank you for sharing!!
PWoerner
Explorer

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

Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi pe.woerner,
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

PWoerner
Explorer
0 Kudos
Thanks for your quick reply.

No more sql errors, but still no data in the app. Any hints?
jump_912
Explorer
0 Kudos
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.
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi pe.woerner ,

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

Best Regards, Ajit
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi eric.sheng,

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

Regards, Ajit
PWoerner
Explorer
0 Kudos
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
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi pe.woerner ,

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

Regards, Ajit
htammen
Active Contributor
Awesome, thank you.

Regards Helmut
Willem_Pardaens
Product and Topic Expert
Product and Topic Expert
Very informative, well documented and easy to apply to our own development. Thank you!
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
I am glad that it helped you. 🙂
victorgonzalez
Explorer
A very useful blog to implement a commonly used custom code. Thank you very very much!
venkatesh_r_hulekal
Product and Topic Expert
Product and Topic Expert
Very clear and really helpful. Thank you very much!
Ajit_K_Panda
Product and Topic Expert
Product and Topic Expert
0 Kudos
I am glad that it helped you.  🙂
0 Kudos
Hi ajitkpanda

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
AakashN24
Participant
0 Kudos
Hi ajitkpanda

I was trying do similar with side effects for the orders in my new project,

but the 'PATCH' handler is not working,

then i found out hat 'PATCH' is not supported anymore in the new version,

refer : https://cap.cloud.sap/docs/node.js/fiori


Did you find any new method or workaround as the this.before and this.after 'PATCH' handlers are not working,

Regards,

Aakash.
dejan_kostic3
Associate
Associate
0 Kudos
Hi aakash_n

You can use UPDATE event with entity.drafts, eq. Orders.drafts.

Regards,

Dejan
Aryan_Raj_Sinha
Product and Topic Expert
Product and Topic Expert

Great blog Ajit! Really helped me in my development!

swati_maste
Product and Topic Expert
Product and Topic Expert
0 Kudos

Thank you @Ajit_K_Panda for making this topic easy to understand.

Cmdd
Participant
0 Kudos

Hi @Ajit_K_Panda !

I'm asking you because your blog posts teached me almost all I know about actions.
I need to pass a collection of entities to my (bound) action because I need to add the same amount in the same filed of each of such entities: I'm preferring bound over unbound both conceptually and technically (no side effect for unbound actions!).
I'm very confused  about the usage of the keyword "many $self" in a bound action: I thought it was a way to pass a Collection of entities (selected from a table) to the action but I'm not sure I got it right because I can't find the collection in the action implementation parameters...

What is its purpose? How can I achieve my goal? I think the documentations is not very clear about it.
I also published a question on sap dev and stackoverflow but no answers at all.... 
Thanks a lot,
Chiara

https://community.sap.com/t5/application-development-discussions/how-to-perform-a-cap-bound-action-o... 

https://stackoverflow.com/questions/78244073/how-to-perform-a-sap-cap-bound-action-on-a-collection 

Jaydeepgiri
Discoverer
0 Kudos

Hello @Ajit_K_Panda ,

 

Thank you for an informative blog, it helps a lot.

shf
Explorer
0 Kudos

Great blog! Your explanation should be part of Fiori documentation.