Developing Fiori CRUD App with BOPF and FIORI Elements
2023-12-9 03:30:6 Author: blogs.sap.com(查看原文) 阅读量:8 收藏

In this tutorial, we will build a Fiori Elements application to perform CRUD operations with the BOPF Framework using CDS annotations, without writing a single line of UI5 code.

The Fiori application that we will build today will be focused on visitor management. We will create a database table called “VISITOR” to store visitor information. The table will include fields such as Visit ID, First Name, Last Name, Company Name, Telephone, E-Mail, Reason, Host Information, Visit Date, Check-In Date, Check-Out Date, and Status.

Pre-requisites:

  • S/4 HANA System
  • ECLIPSE with ABAP Development Tools
  • VSCode with SAP Fiori Tools – Extension Pack

Steps:

  • Step 1Creating Table
  • Step 2Creating CDS views with BO Enabled
    • Step 2.1 Basic CDS View
    • Step 2.2 Projection CDS View
    • Step 2.3 CDS Views for dropdown domain values
  • Step 3Testing the BO that have been created by CDS
  • Step 4Registering the oData and testing in gateway client
  • Step 5Creating Fiori Elements App
    • Step 5.1 – Creating Fiori Elements App
    • Step 5.2 – Previewing Fiori Elements App
  • Step 6 Adding a custom action to the BO
    • Step 6.1Creating New Action to the BO
    • Step 6.2Add action to Fiori App as Button by using CDS Annotations
    • Step 6.3 Previewing and Testing Button

Step 1Creating Table

There are a few fields in the table that will be created which contains fixed values so first step we need to do is creating data elements and domains for this fields.

Now, let’s create simple table to store visitor records.

VISITOR TABLE KEY Data Element Domain Fixed Value
MANDT X MANDT
GUID GUID X GUID
Visit ID VISITID VISITID
First Name FNAME FNAME
Last Name LNAME LNAME
Company Name CMPNY CMPNY
Telephone Country Code TELCODE TELCODE
Telephone TELNUM TELNUM
E-Mail EMAIL EMAIL
Reason REASON REASON ZXXX_DO_VIS_REASON Meeting, Interview, Maintance, Other
Host Information VHOST VHOST
Visit Date VDATE VDATE
Check-In Date INTIME INTIME
Check-Out Date OUTTIME OUTTIME
Status STATUS STATUS ZXXX_DO_VIS_STATUS Done, Postponed, Canceled, In Progress

Step 2 – Creating CDS views with BO Enabled

We need to create two CDS views which called Basic and Projection Views.

Basic View: interacts with database for fetching data

Projection View: created on top of basic views and contains Annotations

Using the steps shown below, create two CDS Views as follows:

Step 2.1 Basic CDS View

@AbapCatalog.sqlViewName: 'ZXXXCDSVIS01'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Visitor CDS'
define view ZXXX_CDS_VIS_01
  as select from zxxx_visitor2
{
  key guid    as Guid,
      visitid as Visitid,
      fname   as Fname,
      lname   as Lname,
      cmpny   as Cmpny,
      telcode as Telcode,
      telnum  as Telnum,
      email   as Email,
      reason  as Reason,
      vhost   as Vhost,
      vdate   as Vdate,
      intime  as Intime,
      outtime as Outtime,
      status  as Status

}

Step 2.2 Projection CDS View

@AbapCatalog.sqlViewName: 'ZXXXCDSVIS02'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Visitor CDS View'

@ObjectModel:{
modelCategory: #BUSINESS_OBJECT,
compositionRoot: true,
representativeKey: 'Guid',
semanticKey: ['Visitid'],
transactionalProcessingEnabled: true,
writeActivePersistence: 'ZXXX_VISITOR2',
// enable crud
createEnabled: true,
updateEnabled: true,
deleteEnabled: true
}

@UI.headerInfo: {typeName: 'Visitor', typeNamePlural: 'Visitors', title: {value: 'Visitid'}}

@UI: {
    presentationVariant: [{
        sortOrder: [{
            by: 'Visitid',
            direction: #ASC
        }],
        visualizations: [{
            type: #AS_LINEITEM
        }]
    }]
}


@OData.publish: true

define view ZXXX_CDS_VIS_02
  as select from ZXXX_CDS_VIS_01
  association [0..1] to ZXXX_CDS_VIS_REASON_DOM as _ReasonValueHelp on $projection.Reason = _ReasonValueHelp.Low
  association [0..1] to ZXXX_CDS_VIS_STATUS_DOM as _StatusValueHelp on $projection.Status = _StatusValueHelp.Low
{
      @UI.facet: [
          // Section General Information
          {
              label: 'General Information',
              position: 10,
              type: #COLLECTION,
              id: 'GeneralInfo'
          },
          {
              label: 'General',
              purpose: #STANDARD,
              position: 10,
              type: #IDENTIFICATION_REFERENCE,
              parentId: 'GeneralInfo'
          }
      ]



      @UI.hidden: true
  key Guid,

      @EndUserText.label: 'Visit ID'
      @UI: {
          lineItem: [{ position: 10}],
          identification: [{position: 10}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      @ObjectModel.readOnly: true
      Visitid,

      @EndUserText.label: 'First Name'
      @Consumption.valueHelpDefinition: [ {
          entity: {
            name: 'ZXXX_CDS_VIS_02',
            element: 'Fname'
          }
      } ]
      @UI: {
          lineItem: [{ position: 20}],
          identification: [{position: 20}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Fname,

      @EndUserText.label: 'Last Name'
      @Consumption.valueHelpDefinition: [ {
          entity: {
            name: 'ZXXX_CDS_VIS_02',
            element: 'Lname'
          }
      } ]
      @UI: {
          lineItem: [{ position: 30}],
          identification: [{position: 30}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @UI.selectionField: [{ position: 11 }]
      @ObjectModel.mandatory: true
      Lname,

      @EndUserText.label: 'Company'
      @Consumption.valueHelpDefinition: [ {
          entity: {
            name: 'ZXXX_CDS_VIS_02',
            element: 'Cmpny'
          }
      } ]
      @UI: {
          lineItem: [{ position: 40}],
          identification: [{position: 40}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @UI.selectionField: [{ position: 12 }]
      @ObjectModel.mandatory: true
      Cmpny,

      @EndUserText.label: 'Tel.No. Country Code'
      @UI.hidden: true
      @UI: {
          lineItem: [{ position: 50}],
          identification: [{position: 50}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Telcode,

      @EndUserText.label: 'Tel.No.'
      @UI: {
          lineItem: [{ position: 60}],
          identification: [{position: 60}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Telnum,

      @EndUserText.label: 'E-Mail'
      @UI: {
          lineItem: [{ position: 70}],
          identification: [{position: 70}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Email,

      @EndUserText.label: 'Reason'
      @Consumption.valueHelpDefinition: [ {
          entity: {
            name: 'ZXXX_CDS_VIS_REASON_DOM',
            element: 'Low'
          }
      } ]
      @ObjectModel.text.element: ['ReasonText']
      @UI: {
          lineItem: [{ position: 80}],
          identification: [{position: 80}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      @UI.textArrangement: #TEXT_ONLY
      @UI.selectionField: [{ position: 20 }]
      Reason,
      _ReasonValueHelp.Text as ReasonText,

      @EndUserText.label: 'Host Information'
      @UI: {
          lineItem: [{ position: 90}],
          identification: [{position: 90}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Vhost,

      @EndUserText.label: 'Date'
      @UI: {
          lineItem: [{ position: 100}],
          identification: [{position: 100}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Vdate,

      @EndUserText.label: 'Check-In Time'
      @UI: {
          lineItem: [{ position: 110}],
          identification: [{position: 110}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Intime,

      @EndUserText.label: 'Check-Out Time'
      @UI: {
          lineItem: [{ position: 120}],
          identification: [{position: 120}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      Outtime,

      @EndUserText.label: 'Status'
      @Consumption.valueHelpDefinition: [ {
          entity: {
            name: 'ZXXX_CDS_VIS_STATUS_DOM',
            element: 'Low'
          }
      } ]
      @ObjectModel.text.element: ['StatusText']
      @UI: {
          lineItem: [{ position: 81}],
          identification: [{position: 81}],
          fieldGroup: [{qualifier: 'Basic'}]
      }
      @ObjectModel.mandatory: true
      @UI.textArrangement: #TEXT_ONLY
      @UI.selectionField: [{ position: 30 }]
      Status,
}

Step 2.3 CDS views for dropdown domain values

base cds for fetching fixed values of any domain

@AbapCatalog.sqlViewName: 'ZXXXCDSDOMVAL'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Domain Value Fetch'
define view ZXXX_CDS_DOMVAL
  as select from    dd07l as FixedValue
    left outer join dd07t as ValueText on  FixedValue.domname    = ValueText.domname
                                       and FixedValue.domvalue_l = ValueText.domvalue_l
                                       and FixedValue.as4local   = ValueText.as4local

{
      @UI.hidden
  key FixedValue.domname    as DomainName,
      @UI.hidden
  key FixedValue.as4local   as Status,
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.8
  key FixedValue.domvalue_l as Low,
      @Semantics.text: true -- identifies the text field
      ValueText.ddtext      as Text
}

where
      FixedValue.as4local  = 'A' --Active
  and ValueText.ddlanguage = $session.system_language

projection view for fetching fixed values of our domains

@AbapCatalog.sqlViewName: 'ZXXXVISRSNDOM'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Reason Domain Values'
@Search.searchable: true
@ObjectModel.resultSet.sizeCategory: #XS
define view ZXXX_CDS_VIS_REASON_DOM
  as select from ZXXX_CDS_DOMVAL as DomValues
{
      @UI.hidden
  key DomValues.DomainName,
      @EndUserText.label: 'Status' -- Custom label text
      @UI.textArrangement: #TEXT_ONLY
      @ObjectModel.text.element: [ 'Text' ]
  key DomValues.Low,
      @Semantics.text: true            -- identifies the text field
      @Search.defaultSearchElement: true
      @Search.fuzzinessThreshold: 0.8
      @UI.hidden: true
      DomValues.Text
}

where
  DomValues.DomainName = 'ZXXX_DO_VIS_REASON'

Step 3Testing the BO that have been created by CDS

Step 4Registering the oData and testing in gateway client

Register the service in the backend on Tcode – /n/IWFND/MAINT_SERVICE

Select the Gateway client to test the Odata Service.

Step 5Creating Fiori Elements App

It’s time to create Fiori Elements App by using VSCode.

Step 5.1 – Creating Fiori Elements App

Select Application type and template

Select Data source and Service

Select Main Entity

Provide necessary details

Step 5.2 – Previewing Fiori Elements App

App has been created so We can check CRUD operations of our app.

Step 6 Adding a custom action to the BO

Step 6.1Creating New Action to the BO

Open BOBX Tcode and BO

Create New Action

Enter action name and description then create Implementing Class by double clicking after entering Implementing Class Name

Open execute method of Implementing Class

Purpose of this action is setting status done of selected rows. According to this purpose, firstly we need to read the data we want to change, then update the status with call the UPDATE method of io_modify.

DATA:
      lt_head    TYPE ztxxx_cds_vis_02,
      lv_nextnum TYPE numc10.
    io_read->retrieve(
      EXPORTING
      iv_node = is_ctx-node_key
      it_key = it_key
      iv_fill_data = abap_true
      IMPORTING
      et_data = lt_head
    ).

    LOOP AT lt_head REFERENCE INTO DATA(lr_head).

      IF lr_head->status NE 'D'.
        lr_head->status = 'D'.

        io_modify->update(
          EXPORTING
            iv_node = is_ctx-node_key
            iv_key = lr_head->key
            iv_root_key = lr_head->root_key
            is_data = lr_head
          ).
      ENDIF.
    ENDLOOP.

Step 6.2Add action to Fiori App as Button by using CDS Annotations

Open Eclipse and Add below code block to our Projection CDS View.

 @UI: {
          lineItem: [{ type: #FOR_ACTION, position: 1,
          dataAction: 'BOPF:SET_STATUS_DONE',
          label: 'Set Status: Done' }],
          identification: [{type: #FOR_ACTION,
          position: 1,
          dataAction: 'BOPF:SET_STATUS_DONE',
          label: 'Set Status: Done' }]
      }

Step 6.3 Previewing and Testing Button

Conclusion

Creating a Fiori Elements app for using CDS annotations and the BOPF Framework is a powerful way to build apps without writing extensive UI5 code. If you’re interested in exploring more about this or diving deeper into modern ABAP development, let’s chat in the comments! Also, consider checking out SAP RAP (Restful ABAP Programming) for further insights into streamlined development approaches.


文章来源: https://blogs.sap.com/2023/12/08/developing-fiori-crud-app-with-bopf-and-fiori-elements/
如有侵权请联系:admin#unsafe.sh