Skip to Content
Technical Articles
Author's profile photo Mio Yasutake

Controlling CAP actions on Fiori UI

Motivation

I’m going develop a call center app (just for demonstration purpose), where incoming queries are posted into the system. When an operator receives a new query, they set its status to “started”. When the query is resolved, they set its status to “closed”.

The key here is how to enable/disable action buttons according to the query’s status.
I have achieved this using CAP annotations and service implementations.

 

Actions and Functions

Actions and Functions allow encapsulating logic for modifying or requesting data that goes beyond simple CRUD. In the OData specification document, Functions and Actions are defined as below.

Actions

Actions are operations exposed by an OData service that MAY have side effects when invoked. Actions MAY return data but MUST NOT be further composed with additional path segments. 

Functions

Functions are operations exposed by an OData service that MUST return data and MUST have no observable side effects.

Simply put, Actions can be used to modify data, while Functions are used to get specific (set of) data.

Bound Actions/Functions and Unbound Actions/Functions

There are two types of Actions and Functions: Bound Actions/Functions and Unbound Actions/Functions.
Bound Actions/Functions are associated with individual entities. Bound Actions/Functions receive key fields of the entity which they are bound to, and you can use those keys to get detail of the entity or manipulate the entity.
Unbound Actions/Functions are not associated with particular entity.
How to define Actions and Functions is described in the cap document.

 

Development

In this section, I’m going to show you how to implement bound actions and enable/disable the actions according to the query’s status.

1. Schema definition

I have defined one entity for inquires and two for value help (category & status).

namespace demo.callcenter;
using { cuid, managed, sap.common } from '@sap/cds/common';

entity Inquiries: cuid, managed{
    category: Association to Category @title: 'Category';
    inquiry: String(1000) @title: 'Inquiry';
    startedAt: DateTime @title: 'Started At'; 
    answer: String(1000) @title: 'Answer';
    status: Association to Status @title: 'Status';
    satisfaction: Integer @title: 'Satisfacton' 
        enum {
            veryUnsatisfied = 1;
            unsatisfied = 2;
            neutral = 3;
            satisfied = 4;
            verySatisfied = 5
        };
    virtual startEnabled: Boolean;
    virtual closeEnabled: Boolean;    
}

entity Category: common.CodeList {
    key code   : String(1)
}

entity Status: common.CodeList {
    key code: String(1)
}

 

The important part is below. These are virtual elements for controlling actions.
As Oliver Klemenz suggested in his comment, virtual elements don’t create fields on persistence layer, thus suit the purpose.

I’ll implement the logic for filling these fields later.

    virtual startEnabled: Boolean;
    virtual closeEnabled: Boolean;    

 

An inquiry has 3 statuses. These statuses will be used for controlling actions.

  1. New
  2. In Process
  3. Closed

 

2. Service definition

I have defined two bound actions: start and close.

using { demo.callcenter as call } from '../db/schema';

service CallCenterService {
    entity Inquiries as projection on call.Inquiries
    actions {
        @sap.applicable.path: 'startEnabled'
        action start();
        @sap.applicable.path: 'closeEnabled'
        action close(satisfaction: Integer);
    }   
}

 

The important part is the following annotation. Based on the filed specified here, the service will determine if the action should be enabled. I fond this in UI5 documentation.

@sap.applicable.path: 'startEnabled'

 

3. UI annotations

Below part is relevant to showing actions on Fiori UI.

        LineItem: [
            { $Type: 'UI.DataFieldForAction', Action: 'CallCenterService.EntityContainer/Inquiries_start', Label: 'Start',  Visible, Enabled},
            { $Type: 'UI.DataFieldForAction', Action: 'CallCenterService.EntityContainer/Inquiries_close', Label: 'Close',  Visible, Enabled},
            { Value: category_code },
            { Value: inquiry },
            { Value: status_code },
            { Value: startedAt },
            { Value: satisfaction },
            { Value: createdAt },
            { Value: modifiedAt }
        ]

 

4. Service implementation

In the service implementation, I have three methods.

The first one is for toggling the actions. As defined in the service definition, if “startEnabled” is true, then the start action will be enabled. I want the start action to be enabled only for new inquires (status: “1”). Similarly, I want the close action to be enabled only for active inquires (status: not closed(3)).

const cds = require('@sap/cds')

module.exports = function () {
    const { Inquiries } = cds.entities 

    this.after('READ', 'Inquiries', (each) => {
        //for new inquires only
        if (each.status_code === '1' ) { 
            each.startEnabled = true
        }
        //for active inquires only
        if (each.status_code !== '3') {
            each.closeEnabled = true
        }
    })      
    ...
}

 

The second and the third methods are action implementations. For bound actions, entity’s key is passed via request parameters.

    this.on ('start', async (req)=> {
        const id = req.params[0]
        const n = await UPDATE(Inquiries).set({ 
            status_code:'2',
            startedAt: Date.now()
        }).where ({ID:id}).and({status_code:'1'})
        n > 0 || req.error (404) 
    })

    this.on('close', async (req) => {
        const id = req.params[0]
        const { satisfaction } = req.data
        const n = await UPDATE(Inquiries).set({ 
            satisfaction: satisfaction,
            status_code: '3'
        }).where ({ID:id}).and({status_code:{'<>':'3'}})
        n > 0 || req.error (404)        
    })

 

I can get the key with the following code and use it for updating the entity.

const id = req.params[0]

 

5. Actions on UI

I used Fiori Tools and OData v2 adapter for creating a list report app. How to achieve this is written in my previous blog.
All you have to do is to generate a list report app using Fiori Tools. No local annotations nor extensions are required. Here is the resulting app.

When I select a record with status “1”, both start and close buttons are enabled.

When I select a record with status “2”, only close action is enabled.

For closed record, no action is enabled.

 

Summary

Using annotation@sap.applicable.path: 'startEnabled' and a virtual field, you can control actions on Fiori UI. You can implement the logic to enable actions in the CAP service implementation.

 

References

Assigned Tags

      15 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Oliver Klemenz
      Oliver Klemenz

      Nice Blog. You can also try CDS virtual elements (https://cap.cloud.sap/docs/cds/cdl#virtual-elements) to avoid empty/unused fields on persistence layer... Also good to see, that the CDS OData V2 Adapter Proxy works well for all your scenarios...

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Oliver,

      Thank you for your suggestion. Virtual elements are indeed better for the purpose.

      I've updated the post.

      Author's profile photo Pierre Dominique
      Pierre Dominique

      Another great blog post, thanks for sharing this!

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Pierre,

      Thanks for your comment!

      Author's profile photo Afshin Irani
      Afshin Irani

      Hi,

      Another excellent blog on CAP. Keep me them coming and one of the best in the space.

      A blog on demystifying the world CDS annotations would be great.

      Thanks

      Afshin

       

       

       

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Afshin,

      Thanks for your comment. I'm interested in CAP and Fiori elements integration, and hope to write more blogs on this topic.

      Author's profile photo Michael Belenki
      Michael Belenki

      Hi Mio,

      I was looking for how to disable / enable action in OData V4 and found out that it can be done with another annotation @Core.OperationAvailable: { $value: true }. Unfortunately all my tries to use a path as $value failed. Do you have any idea, how to do it? 

      @Core.OperationAvailable: { $value: 'startEnabled' } does not work ('String' not allowed here. Expected: 'Edm.Boolean'CDS (annotations)

      @Core.OperationAvailable: { $value: startEnabled } returns an error in visual studio code "

      Path bookEnabled leads to action. The path should lead to type Edm.Boolean.CDS (annotations)"

      thanks and kind regards,

      Michael

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Michael,

      I could reproduce the issue, however, I don't know how to achieve this in OData V4.

      I'd like to be informed once you have solved it.

      Regards,

      Mio

      Author's profile photo Hieu Ngo Xuan
      Hieu Ngo Xuan

      Hi Michael Belenki

      Maybe there is a solution for you

      Step 1: Add the following code into your schema CDS

      type TechnicalBooleanFlag : Boolean @(
          UI.Hidden,
          Core.Computed
      );

      Step 2: At the exact entity, you want to custom this logic, add the code

      entity MyEntity: cuid, managed {
          name                    : String(200) @mandatory;
          sort                    : String(100) @title:'Sort Name';
          // === allowButton property to custom logic
          allowButton          : TechnicalBooleanFlag not null default true;
      
      }

      Step 3: At fiori cds file, annotate for your action as below

      annotate YourService.Entity actions {
          
          
          @(
              Common.SideEffects : {
                  TargetEntities : [
                      _it,
                  ]
              },
              cds.odata.bindingparameter.name: '_it',
              Core.OperationAvailable : _it.allowButton,
              UI.FieldGroup
          )
          yourAction(
              email @title : 'Email' @FieldControl.Mandatory,
              position @title : 'Position'
              // something else
          );
      
      }

       

      Hope useful for you!

      Author's profile photo Dominik Espitalier
      Dominik Espitalier

      Hey Mio,

      thanks for this great bog! I have one question. Let's say I have a boolean field named "toBeValidated" in my entity, which I want to use for my @sap.applicable.path. And I have two actions which I want to enable/disable accordingly to this boolean field "toBeValidated". Do you know if there is some way to add a ! operator to the path like so:

      entity ValidationRules @(
              Capabilities : {
                  Insertable : false,
                  Updatable  : true,
                  Deletable  : false
              },
              restrict     : [{
                  grant : [
                      'enable',
                      'disable'
                  ],
                  to    : [
                      'superadmin',
                      'cockpitadmin'
                  ]
              }, ]
          ) as projection on ds.Validation_Customizing actions {
              @sap.applicable.path:'toBeValidated'
              action enable() returns  ValidationRules;
              @sap.applicable.path:'!toBeValidated'
              action disable() returns ValidationRules;
          };

      Please have a look on the second action "disable".

       

      Best Regards

      Dominik

      Author's profile photo Mio Yasutake
      Mio Yasutake
      Blog Post Author

      Hi Dominik,

      Thanks for your comment!

      Unfortunately, I don't know if it is possible to set an "expression" for a boolean property.

      A workaround would be to define a virtual property (let's say, "disable") and set its value in the read handler.

          this.after('READ', 'ValidationRules', (each) => {
              each.disabled = !each.toBeValidated
          }) 

      Best regrads,

      Mio

      a

      Author's profile photo Dominik Espitalier
      Dominik Espitalier

      Hey Mio,

      thanks for your answer. Thats what I did. And its working pretty good, but it would be awesome to use an expression in the datamodel 🙂

       

      Best Regards

      Dominik

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      Hi @Mio Yasutake, Oliver Klemenz ,

      thank you for this very useful blog and suggestions.

      I am mainly working with OData V4 and as I know and tried out, @applicable path is only meant for OData V2 annotations. Thus, I´d like to know how to implement this kind of action button control for V4? Using a dynamically determined boolean property works on header level, but it seems not to work on item level - when specific line items are clicked, the button should be set either to hidden or disabled (@UI.Hidden annotation for data field action). It works when hard coded or when used with hasActiveEntity property (for drafts), but not with a custom boolean property that is set to true as default in data model.

      So, any ideas on how to solve this issue?

      Thank you in advance!

      Cheers,

      Rufat

      Author's profile photo Rufat Gadirov
      Rufat Gadirov

      Hi there, ok I got it

      in OData V4

      The following annotation controls if a button of the corresponding action is available or not!

       @Core.OperationAvailable : in.cancelOperationAvailable (bound data element as boolean flag!)
              action cancelBooking();
      data model:
       cancelOperationAvailable : common.TechnicalBooleanFlag not null default false;
      Cheers,
      Rufat
      Author's profile photo Tim Anlauf
      Tim Anlauf

      Hello Mio Yasutake,

      thanks for that helpful blog. Maybe it would be a good idea to add a some information when working  with buttons on object page with ODATA v4 (draft mode).

      I did not found a solution for the following problem / question.

      How do I disable an action button in the object page when the object is in edit / draft mode?