Skip to Content
Technical Articles

Fundamental Library for ABAP

Fundamental Library Styles, with Angular, Vue and React UI components, are popular among developers building Fiori front-ends for non-ABAP systems. This blog describes how easy these front-ends and similar frameworks can be integrated with old and new ABAP systems, in scenarios like:

  • Proof of concept or demo app is running with hard-coded data, whose preparation and adaptation is time consuming. ABAP systems on the other hand offer already configured business scenarios, with good quality real data. These data can be consumed with less efforts than the hard-coding, with practically the same performance.
  • Replacing ABAP Dynpro UI with Fundamental UI
  • An existing app, running against non-ABAP system, shall be integrated also with ABAP system (hybrid scenario), consuming ABAP business logic.
  • You enjoy being productive with your favourite web-stack and you are not much or not at all familiar with SAP and ABAP. You would like to consume the ABAP business logic using your web-stack, leveraging skills you already have and not necessarily learning much about ABAP and SAP.

Fundamental Library for ABAP is a new mini toolset that makes these scenarios possible and never stands in the way of developers. Let’s try it! Here’s a breakdown of how we can build a Fundamental Styles app that consumes ABAP business logic.

The blog describes:

  • App scope
  • Deployment models
  • Getting the data from ABAP systems, using
    • ABAP API
    • Connectivity components for NodeJS, Java or Python
    • Middleware
  • Consuming ABAP data by Fundamental Library Styles based
    • Front-end UI Model
    • Front-end UI Views

Fundamental Library for ABAP runtime components provide ABAP connectivity and design-time utilities rfmcall, backend and frontend-model generate Fundamental Library Styles UI components, to be embedded into front-end layouts, forms and dialogs. Design-time utilities are loosely coupled and non-mandatory, leaving the full control to developer.

If you are not very interested in ABAP systems integration, you can jump to Middleware or Front-end sections directly.

For high-level concept check: Web Applications with ABAP, done simple

App

Our app should replace ABAP Dynpro transactions IE03 and IE02, offering SAP Business Object Equipment read and edit functionality. To make the task a bit more challenging let add three more requirements from one real-life use case:

  • Attach (and remove) documents like PDF, AutoCAD drawings etc. to Equipment object, using the Document Management System (DMS)
  • Read and edit Classifications and Characteristics eventually associated with Equipment object
  • Display Mean Time to Repair (MTTR) and Mean Time Between Failure (MTBF) statistics, relevant in certain business scenarios

SAP business object Equipment represents physical assets like the office furniture, devices, machineries etc. Using Classifications and Characteristics, name/value pairs can be added to business object instance, further describing the object. Similar characteristics (name/value pairs) can be grouped under the same group name and several such groups plus ungrouped characteristics may be maintained for Equipment object instance.

Deployment

Our app should be able to run in the cloud or on-premise, connecting to any old or new ABAP system (no backend restrictions). The app code is the same for both deployments, only the NodeJS, Java or Python middleware component is deployed either in the cloud, or on-premise:

The deployment diagram shows also key building blocks of our app, described below:

  • ABAP API
  • Middleware
  • Front-end Model
  • Front-end View

Connectivity

Assume the web-stack is already running, eventually connected to non-ABAP system and should consume also ABAP data with minimum overhead. The interface used here the most are Remote Function Calls (RFCs). This TCP/IP based protocol works with any ABAP system and can expose practically any ABAP business logic, with minimum efforts and technical overhead and with top runtime performance. Other channels like IDoc, HANA Command Network Protocol, SOAP or ODATA web services and other development kits can be used as well, depending on backend system release and use-case.

Fundamental Library for ABAP runtime components providing RFC connectivity are:

SAP NWRFC SDK are closed source pre-built C++ binaries, available on SAP Service Portal free of charge, with SAP customer, partner or developer account.

ūüí°One fundamental difference here is that NodeJS and Python RFC client connections are stateful by default (configuration), while SAP Java Connector is stateless by default and you have to put some effort into it, if stateful connection needed. The NodeJS and Python default programming model is therefore just like in ABAP and calling an Update-BAPI, or some other RFM that stores intermediate results in the current user‚Äôs ABAP session memory, is very easy. For example, your NodeJS or Python program just calls the BAPI or function module, and if it succeeds, just calls BAPI_TRANSACTION_COMMIT on the same connection, running inside the same user session in the backend.

ABAP API

The business logic required for our app is exposed by ABAP API, internally calling following ABAP Function Modules:

  • Equipment Get API
    • BAPI_EQUI_GETDETAIL
  • Equipment Change API
    • BAPI_EQUI_CHANGE
  • Characteristics API
    • BAPI_OBJCL_GETCLASSES
    • BAPI_CLASS_GET_CHARACTERISTICS
    • BAPI_CHARACT_GETDETAIL
    • BAPI_OBJCL_CHANGE
  • Document Management System (DMS) API
    • BAPI_DOCUMENT_GETLIST2
    • BAPI_DOCUMENT_GETDETAIL2
    • BAPI_DOCUMENT_CHANGE2
    • SDOK_PHIO_LOAD_CONTENT
    • CVAPI_DOC_CHECKIN

In addition:

  • BAPI_TRANSACTION_COMMIT and BAPI_TRANSACTION_ROLLBACK are used internally, to commit or rollback the ongoing ABAP session
  • BAPI_USER_GET_DETAIL is used to
    • Expose the user name after logon
    • Collect user SET/GET Parameters, for automatic reuse in application forms

SET/GET parameters are maintained by ABAP transaction SU3 and define ABAP form input fields default values. When the form is opened, if the SET/GET parameter assigned to input field, the field is automatically pre-filled with the SET/GET parameter value, saving the user from typing always the same input (ie. the fixed sales area code). Our Fundamental/ABAP app should behave the same way and automatically fill fields whose default values (SET/GET parameters) are maintained in the backend.

Middleware

Independent of the front-end library, the middleware code of our app can be implemented in NodeJS, Java or Python. The code looks more or less the same so let  take Python and Flask server as example. Alternatives are Pyramid, bottle, tornado etc. for Python, or express, Koa etc. for NodeJS or plenty of other servers available in Java.

import json
from flask import Flask, Response, jsonify
from pyrfc import Connection

def to_json(content, simple=False):
    if simple:
        js = simplejson.dumps(content, default=json_serial)
        return Response(js, status=200, mimetype='application/json')
    return jsonify(content)

app = Flask(__name__)

abap_client = Connection(dest="MME)

# Equipment
@app.route('/equipment/<path:path>', methods=['POST'])
def equipment(path):
    try:
        to_abap = json.loads(request.data)

        if path == 'get':
            from_abap = abap_client.call('/COE/RBP_PAM_EQUIPMENT_GET', to_abap)
        elif path == 'getlist':
            from_abap = abap_client.call('/COE/RBP_PAM_EQUIPMENT_GETL', to_abap)
        elif path == 'change':
            from_abap = abap_client.call('/COE/RBP_PAM_EQUIPMENT_CHANGE', to_abap)
        elif path == 'install':
            from_abap = abap_client.call('/COE/RBP_PAM_EQUIPMENT_INSTALL', to_abap)
        elif path == 'dismantle':
            from_abap = abap_client.call('/COE/RBP_PAM_EQUIPMENT_DISMTLE', to_abap)
        else:
            raise Exception ('not implemented: %s' % path)

        return to_json(from_abap)

    except Exception, e:
        return serverError(e), 50

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8123)

The code above shows middleware server routes for Equipment. Similar route handler is implemented for DMS and no separate route is used for Characteristics, which are optional part of Equipment API.

The code looks short considering not really trivial app but in this case the middleware is not doing much. After App server (Flask app) and ABAP connection (abap_client) are instantiated, the server is listening on front-end requests on /equipment route.

The front-end JSON payload is automatically converted to ABAP data (to_abap), used to invoke our ABAP API. The ABAP call result (from_abap) is automatically converted to JSON and passed back to front-end.

ABAP API data structures can be complex but the middleware does not have to deal with them at detail level, usually only passing them back and forth. That makes the code here is so simple. Of course, if any pre-processing of ABAP input or post-processing of ABAP output makes sense (API orchestration, caching, parallelisation …), it can be easy added here. The front-end, middleware and ABAP system, all operate with the same ABAP data and adaptations can be done at the level where it makes the most sense.

The NodeJS and SAP Java Connector support JSON out of the box and using JSON on these platform is even easier. No need for a dedicated JSON package like in Python.

Using the default stateful mode of NodeJS and Python SAP connectors, ABAP API orchestrations and adaptations can be implemented in middleware, just like they would been done in ABAP, with practically the same performance. This extensibility point can help in customer or industry specific scenarios, when there are no “one fit for all” ABAP backend services/api.

rfmcall

When more pre/post-processing required, the Fundamental Library for ABAP rfmcall utility can generate middleware ABAP API data structures and RFM calls at detail level:

$ npm install rfmcall
 
$ rfmcall MME BAPI_USER_GET_DETAIL
 
The output is a JavaScript (Python, Java) counterpart of ABAP editor CALL FUNCTION template. The generated code starts with RFM parameters initialisation, with optional parameters commented out and ends with ABAP RFM invocation.
//
// BAPI_USER_GET_DETAIL
//
 
let params = {
  // IMPORT PARAMETERS
 
  USERNAME: "", // CHAR (12) User Name
  //CACHE_RESULTS                  :   "", // CHAR (1) Temporarily buffer results in work process
 
  // TABLE PARAMETERS
 
  RETURN: [], // BAPIRET2 Return Structure
  //ACTIVITYGROUPS                 :   [], // BAPIAGR Activity Groups
  //ADDCOMREM                      :   [], // BAPICOMREM BAPI Structure Communication Comments
  //ADDFAX                         :   [], // BAPIADFAX BAPI Structure Fax Numbers
  ...
  ...
};
 
...
...
// ACTIVITYGROUPS BAPIAGR Activity Groups
 
let ACTIVITYGROUPS                    =   []; // BAPIAGR Activity Groups
 
let BAPIAGR = {
  AGR_NAME                         :   "", // CHAR (30) Role Name
  AGR_TEXT                         :   "", // CHAR (80) Role Description
  FROM_DAT                         :   "", // DATS (8) Date of menu generation
  ORG_FLAG                         :   "", // CHAR (1) Indicator: Indirect Assignment of the User to the Role
  TO_DAT                           :   "", // DATS (8) Date of menu generation
};
 
// ADDCOMREM BAPICOMREM BAPI Structure Communication Comments
 
let ADDCOMREM                         =   []; // BAPICOMREM BAPI Structure Communication Comments
 
let BAPICOMREM = {
  COMM_NOTES                       :   "", // CHAR (50) Communication link notes
  COMM_TYPE                        :   "", // CHAR (3) Communication Method (Key) (Business Address Services)
  CONSNUMBER                       :   "", // NUMC (3) Sequence Number
  ERRORFLAG                        :   "", // CHAR (1) Flag: Record not processed
  LANGU                            :   "", // LANG (1) ALPHA=ISOLA Language Key
  LANGU_ISO                        :   "", // CHAR (2) 2-Character SAP Language Code
};

...
...

result = await client.call("BAPI_USER_GET_DETAIL", params);

For each RFM parameter the data type is given in comment, followed by the corresponding initialiser. The mandatory input parameter USERNAME for example is a single CHAR 12 variable and the ADDCOMREM output table rows are defined by BAPICOMREM structure, initialised at field level below in generated JavaScript code. The utility works like ABAP CALL FUNCTION template, enhanced with data structures’ initialisers.

Please note ABAP Conversion (ALPHA) Exit names, in comments of fields like LANGU, just as reminders for ABAP API developers that Conversion Exits logic should be triggered for these fields in ABAP inbound/outbound logic.

Front-end Model

Having the ABAP API and middleware parts of our app done, we can send HTTP/JSON requests to app server /equipment route and get the ABAP API JSON response back. Now we need a front-end model to send these requests and “drive” the front-end view.

The front-end model is manually implemented and has in our case the getlist, get and save (update) methods, mapped to their middleware routes. The naming is a matter of convention:

import { UIApplication, UIHttpService, UIUtils } from '../../resources/index';

export class Equipment {
  static inject = [UIApplication, UIHttpService];
  static __bizDocType = 'BUSEQUI';

  constructor(app, httpService) {
    this.app = app;
    this.httpService = httpService;
    this.reset();
  }

  reset() {
    this.ES_HEADER = {};
    this.ES_SPECIFIC = {};
    this.DMS = false;
    this.KPI = [];
    this.Attachments = [];
    this.Characteristics = [];
    this.selection = {};
    this.url = {};
    this.list = [];
  }

  getlist() {
    this.list = [];
    return this.httpService
      .backend('/equipment/getlist', {
        IV_PLANT: '1000'
      })
      .then(FROM_ABAP => {
        this.list = FROM_ABAP.ET_EQUIPMENT;
      })
      .catch(error => {
        this.reset();
        this.app.toastError(error);
      });
  }

  get(id = false) {
    if (id) this.selection.EQUIID = id;
    return this.httpService
      .backend('/equipment/get', {
        IV_EQUIID: this.selection.EQUIID,
        IV_CHARACTERISTICS: 'X',
        IV_DOCUMENT: 'X',
        IV_WITH_TIMESTATS: 'X'
      })
      .then(FROM_ABAP => {
        // header
        this.ES_HEADER = FROM_ABAP.ES_HEADER;
        this.ES_SPECIFIC = FROM_ABAP.ES_SPECIFIC;

        // clone structures for X-fields change detection
        this.IS_HEADER = UIUtils.abapStructClone(this.ES_HEADER);
        this.IS_SPECIFIC = UIUtils.abapStructClone(this.ES_SPECIFIC);

        // user status
        this.ET_STATUS = FROM_ABAP.ET_USER_STATUS;

        // DMS Attachments
        this.Attachments = FROM_ABAP.ET_DOCUMENT;

        // WebGUI url
        this.href = this.app.webGuiUrl('IE03', false, this.selection.EQUIID);
        this.hrefText = this.IS_HEADER.DESCRIPT + ' (' + this.selection.EQUIID + ')';

        // Characteristics
        this.Characteristics = [];

        // add grouped characteristics
        for (let chGroup of FROM_ABAP.ET_CHARACT_GROUP) {
          this.addCharactGroup(
            FROM_ABAP.ET_CHARACTERISTICS,
            chGroup.CHARACT_GROUP,
            chGroup.CHARACT_GROUP_NAME
          );
        }

        // add ungrouped characteristics
        this.addCharactGroup(FROM_ABAP.ET_CHARACTERISTICS, '', 'Ungrouped');

        // mttr/mtbf kpi, requested by oiltank
        this.KPI = [];
        if (this.IS_HEADER.START_FROM) {
          if (FROM_ABAP.ET_RESULT.length) {
            this.KPI = [
              { id: 'BMON', name: 'No. of breakdowns reported in month', value: '' },
              { id: 'TBR', name: 'Time between repairs', value: '' },
              { id: 'BACT', name: 'No. of actual breakdowns', value: '' },
              { id: 'MTBR', name: 'Mean time between repairs ', value: '' },
              { id: 'TLDM', name: 'Total length of downtime in month', value: '' },
              { id: 'MTTRM', name: 'Mean Time To Repair in Month', value: '' }
            ];
            let result = FROM_ABAP.ET_RESULT[0];
            for (let line of this.KPI) {
              if (line.id === 'BMON') line.value = result.NBDEFF;
              else if (line.id === 'TBR') line.value = result.STBRHRS;
              else if (line.id === 'BACT') line.value = result.SNBDREP;
              else if (line.id === 'TLDM') line.value = result.STTRHRS;
            }
            for (let line of this.KPI) {
              if (line.id === 'MTBR') {
                if (result.SNBDREP) line.value = result.STBRHRS / result.NBDEFF;
              }
              if (line.id === 'TLDM') {
                if (result.SNBDREP) line.value = result.STBRHRS / result.SNBDREP;
              }
              if (line.id === 'MTTRM') {
                if (result.NBDEFF) line.value = result.STTRHRS / result.NBDEFF;
              }
              if (line.id !== 'BMON' && line.id !== 'BACT') {
                line.value += ' H';
              }
            }
          }
        }
      })

      .catch(error => {
        this.reset();
        this.app.toastError(error);
      });
  }

  save() {
    // set X-fields
    let header = [
      'MANFACTURE',
      'MANMODEL',
      'MANPARNO',
      'MANSERNO',
      'MAINTPLANT',
      'OBJECTTYPE',
      'ABCINDIC',
      'WORK_CTR'
    ];
    let specific = ['EQUICATGRY', 'READ_FLOC'];
    this.IS_HEADER_X = UIUtils.abapStructDiff(this.ES_HEADER, this.IS_HEADER, header);
    this.IS_SPECIFIC_X = UIUtils.abapStructDiff(this.ES_SPECIFIC, this.IS_SPECIFIC, specific);
    
    // Characteristics
    let itChar = [];
    for (let chGroup of this.Characteristics) {
      for (let ch of chGroup.CHARLIST) {
        itChar.push({
          CLASS: ch.CLASS,
          CHARACT: ch.NAME_CHAR,
          VALUE: ch.VALUE,
          VALUE_FROM: ch.VALUE_FROM,
          VALUE_TO: ch.VALUE_TO
        });
      }
    }

    return this.httpService
      .backend('/equipment/change', {
        IV_EQUIID: this.selection.EQUIID,
        IS_HEADER: this.ES_HEADER,
        IS_HEADER_X: this.IS_HEADER_X,
        IS_DATA_SPECIFIC: this.ES_SPECIFIC,
        IS_DATA_SPECIFIC_X: this.IS_SPECIFIC_X,
        IT_CHARACTERISTICS: itChar
      })
      .then(FROM_ABAP => {
        this.app.toastSuccess({
          type: 'success',
          message: `Equipment saved: ${this.selection.EQUIID}`
        });
      })
      .catch(error => {
        this.app.toastError(error);
      });
  }

  addCharactGroup(ET_CHARACTERISTICS, groupId, groupName) {
    let chlist = [];

    // add group
    for (let ch of ET_CHARACTERISTICS) {
      if (ch.CHARACT_GROUP === groupId) {
        ch.NUMBER_DIGITS -= ch.NUMBER_DECIMALS; // adapt for MNUMBER :-)
        chlist.push(ch);
      }
    }

    if (chlist.length) {
      this.Characteristics.push({
        groupId: groupId ? groupId : 'GROUPID',
        groupDesc: groupName,
        CHARLIST: chlist
      });
    }
  }
}

Reading the code, we see:

  • The same ABAP data structures used in backend and middleware are used also in a front-end model. Having the backend, middleware and the front-end all speak the same language, makes the troubleshooting easier and collaboration among Web and ABAP developers better and faster.
  • Despite complex ABAP API data structures and not the simplest use case, the model code requires only 180 lines of JavaScript. Just like the middleware the model deals with ABAP data on detail/field level only when necessary, keeping the code short and readable.

Each front-end framework brings own artefacts which more or less blur the clean view of application business logic. This particular example is implemented using Aurelia front-end framework which brings very little of such overhead. Wit such “less verbose” frameworks, ABAP developers with little or no Web experience can also read and follow the ABAP data processing logic in front-end model and especially in the middleware. The application logic in different front-end frameworks must be identical because that logic is defined by business requirements, only frameworks’ technical constructs and patterns are different.

Front-end View

Via ABAP API and middleware the plain ABAP API parameters’ data are exposed in front-end model as:

  • JavaScript variables, representing ABAP variables
  • Simple JavaScript objects, representing ABAP structures
  • Arrays of simple JavaScript objects, representing ABAP tables

The front-end view UI elements, like inputs, date/time-picker, checkbox, combobox etc. shall be bound to ABAP variables or structures/tables` fields and tabular UI elements shall be bound to ABAP tables.

Each UI element shall be also annotated with corresponding text label, optional default value (ABAP SET/GET parameter) and optional value input help, like ABAP F4 Help, Check Table or Field Domain Values lookup. The ABAP field technical type (number, string, boolean, date …) and length should be used by UI element internal logic to handle different data types accordingly (e.g. restrict the input length or the number of decimals, keep or discard leading zeroes etc.).

Once we have such Fundamental Styles UI elements, annotated and bound to front-end model, we can group them inside Fundamental Styles forms, dialogs and layouts and provide the front-end view for the end-user.

Coding these annotations and bindings manually is possible but Fundamental Library for ABAP offers two more utilities to automate this task.

UI elements are bound to front-end model data, which are actually ABAP data disguised in JavaScript. The above mentioned annotations can be therefore automatically extracted from ABAP API metadata which are already available in ABAP. Fundamental Library for ABAP utilities backend and frontend-model do exactly that: generate Angular, React, Vue or Aurelia UI elements, with annotations and bindings relevant for ABAP data consumption:

  • Data type, length
  • Texts (label, caption)
  • Unit of measure
  • Value Input Help
    • Field Domain Values
    • Check Tables and custom lookups
    • Elementary and complex F4 search helps
  • Default value (ABAP SU3 SET/GET parameter)
  • Conversion Exit

Annotations are provided as UI element custom attributes which are internally interpreted by generic internal logic of reusable UI components.

Let generate the front-end UI elements and finalise our app, by running the backend.py utility first:

$ git clone https://github.com/SAP/fundamental-tools

$ cd tools

$ python backend.py EQUIPMENT
Connected to host: coevi51 sid: MME client: 620 kernel: 745 user: DEMO lang: E

Model EQUIPMENT (8)

BAPI_EQUI_CHANGE
BAPI_EQUI_CREATE
BAPI_EQUI_CREATE_BY_REFERENCE
BAPI_EQUI_DISMANTLE
BAPI_EQUI_GETDETAIL
BAPI_EQUI_GETLIST
BAPI_EQUI_GETSTATUS
BAPI_EQUI_INSTALL


rfms:      8
params:   84
vars:     25
strucs:   35
tables:   24
all:      84
fields:   37
helps:    67
    CA:    1
    CH:    2
    CL:    1
    CT:   26
    FV:    5
    SH:   32

The connection to ABAP backend system is required and the output are above mentioned front-end bindings and annotations, cached in local JSON file.

Using the next and the last utility from our toolset, annotated front-end view UI elements can be generated for for Angular, React, Vue or Aurelia front-end. The ABAP system connection is not required any more:

$ python frontend-angular.py # frontend-vue.py frontend-aurelia.py ...

Processed 8 ABAP API of EQUIPMENT model

In data/EQUIPMENT/model/angular folder, two files are generated for each ABAP API:

  • HTML file, the flat list of annotated UI elements, bound to ABAP API data fields
  • JavaScript file, the flat list of frontend-model data initialisers, down to field level

Part of HTML output for Angular

<div fd-form-item>
    <label fd-form-label>Distribution Channel</label>
        <input fd-input fd-form-control
            [(ngModel)]="DATA_GENERAL.DISTR_CHAN"
            data-abap="{type: 'string', length: '2', mid: 'VTW'}"
            data-shlp="{type:'SH', id:'CSH_TVTW'}"
        />
</div>

<div fd-form-item>
    <fd-checkbox label="Referenced Configuration" [(ngModel)]="DATA_SPECIFIC.READ_CUREF"></fd-checkbox>
</div>

<div fd-form-item>
    <label fd-form-label>Validity end date</label>
    <fd-date-picker [type]="single" [(ngModel)]="DATA_FLEET.EXPIRY_DATE"></fd-date-picker>
</div>

<div fd-form-item>
    <label fd-form-label>Cost Center</label>
    <fd-combobox
        [(ngModel)]="DATA_GENERAL.COSTCENTER"
        [dropdownValues]="{type:'CT', id:'CSKS'}"
        data-abap="{type: 'string', length: '10', mid: 'KOS', alpha: 'ALPHA'}"
    >
    </fd-combobox>
</div>

<div fd-form-item>
    <label fd-form-label>Equipment category</label>
    <fd-combobox
        [(ngModel)]="DATA_SPECIFIC.EQUICATGRY"
        [dropdownValues]="{type:'CT', id:'T370T'}"
        data-abap="{type: 'string', length: '1', mid: 'EQT'}"
    >
    </fd-combobox>
</div>

The same for Aurelia

<ui-input value.bind="DATA_GENERAL.DISTR_CHAN" shlp.bind='{"type":"SH", "id":"CSH_TVTW"}'
    data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"2", "mid":"VTW"}'
    label="Distribution Channel">
</ui-input>

<ui-checkbox value.bind="DATA_SPECIFIC.READ_CUREF" label="Referenced Configuration">
</ui-checkbox>

<ui-date date.bind="DATA_FLEET.EXPIRY_DATE" label="Validity end date"></ui-date>

<ui-combo value.bind="DATA_GENERAL.COSTCENTER" shlp.bind='{"type":"CT", "id":"CSKS"}'
    data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"10", "mid":"KOS"}'
    alpha-exit="ALPHA"
    label="Cost Center">
</ui-combo>

<ui-combo value.bind="DATA_SPECIFIC.EQUICATGRY" shlp.bind='{"type":"CT", "id":"T370T"}'
    data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"1", "mid":"EQT"}'
    label="Equipment category">
</ui-combo>

The same for Vue

<fd-form-item>
    <fd-form-label>Distribution Channel</fd-form-label>
        <fd-input v-model="DATA_GENERAL.DISTR_CHAN"
            data-abap="{type: 'string', length: '2', mid: 'VTW'}"
            data-shlp="{type:'SH', id:'CSH_TVTW'}"
        />
</fd-form-item>

<fd-form-item-checkbox label="Referenced Configuration">
    <fd-checkbox v-model="DATA_SPECIFIC.READ_CUREF">
</fd-form-item-checkbox>

<fd-form-item>
    <fd-form-label>Validity end date</fd-form-label>
    <fd-date-picker v-model="DATA_FLEET.EXPIRY_DATE"></fd-date-picker>
</fd-form-item

<fd-form-item>
    <fd-form-label>Cost Center</fd-form-label>
    <fd-select v-model="DATA_GENERAL.COSTCENTER"
        options="{type:'CT', id:'CSKS'}"
        data-abap="{type: 'string', length: '10', mid: 'KOS', alpha: 'ALPHA'}"
    >
    </fd-select>
</fd-form-item>

<fd-form-item>
    <fd-form-label>Equipment category</fd-form-label>
    <fd-select v-model="DATA_SPECIFIC.EQUICATGRY"
        options="{type:'CT', id:'T370T'}"
        data-abap="{type: 'string', length: '1', mid: 'EQT'}"
    >
    </fd-select>
</fd-form-item>

Tabular UI elements look similar to this one for Aurelia

<ui-datagrid data.bind="BAPI_EQUI_CHANGE EXTENSIONIN" title="Reference Structure for BAPI Parameters ExtensionIn/ExtensionOut"
            data-summary="false" default-sort=""
            selectable rowselect.trigger="object.selectObject($event.detail)">
  
    <ui-dg-column sortable field="STRUCTURE" data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"30", "shlp":{"type":"b'CT'", "id":"b'DD02L'"}}' label="Structure name of  BAPI table extension"></ui-dg-column>
    <ui-dg-column sortable field="VALUEPART1" data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"240"}' label="Data part of BAPI extension parameter"></ui-dg-column>
    <ui-dg-column sortable field="VALUEPART2" data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"240"}' label="Data part of BAPI extension parameter"></ui-dg-column>
    <ui-dg-column sortable field="VALUEPART3" data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"240"}' label="Data part of BAPI extension parameter"></ui-dg-column>
    <ui-dg-column sortable field="VALUEPART4" data-abap.bind='{"ddic":"CHAR", "type":"string", "length":"240"}' label="Data part of BAPI extension parameter"></ui-dg-column>
</ui-datagrid>

The JavaScript initialisers are independent of the front-end framework and sometimes helpful in front-end model. They look the same like when generated using NodeJS rfmcall:

//
// BAPI_EQUI_CHANGE 0.2
//

BAPI_EQUI_CHANGE = {

  // INPUT PARAMETERS

  DATA_FLEET: {},                          // BAPI_FLEET : Vehicle-Specific Data
  DATA_FLEETX: {},                         // BAPI_FLEETX : Flag Structure Belonging to BAPI_FLEET (for Change BAPIs)
  DATA_GENERAL: {},                        // BAPI_ITOB : General Data for Technical Objects
  DATA_GENERALX: {},                       // BAPI_ITOBX : Flag Structure for BAPI_ITOB for Fields to be Copied
  DATA_SPECIFIC: {},                       // BAPI_ITOB_EQ_ONLY : Equipment-Specific data
  DATA_SPECIFICX: {},                      // BAPI_ITOB_EQ_ONLYX : Flag Structure for BAPI_ITOB_EQ_ONLY for Fields to be Copied
  EQUIPMENT: '',                           // CHAR(18)   BAPI_ITOB_PARMS EQUIPMENT      Number of Equipment to be Changed
  VALID_DATE: '',                          // DATS(8)    BAPI_ITOB_PARMS INST_DATE      Date for Validity of an Equipment Usage Period Relevant Change
  VALID_TIME: '',                          // TIMS(6)    BAPI_ITOB_PARMS INST_TIME      Time for Validity of a Equipment Usage Period Relevant Change

  // OUTPUT PARAMETERS

  DATA_FLEET_EXP: {},                      // BAPI_FLEET : Vehicle-Specific Data
  DATA_GENERAL_EXP: {},                    // BAPI_ITOB : General Data for Technical Objects
  DATA_SPECIFIC_EXP: {},                   // BAPI_ITOB_EQ_ONLY : Equipment-Specific data
  RETURN: {},                              // BAPIRET2 : Return Parameter

  // TABLE PARAMETERS

  EXTENSIONIN: [],                         // BAPIPAREX : Reference Structure for BAPI Parameters ExtensionIn/ExtensionOut
  EXTENSIONOUT: []                         // BAPIPAREX : Reference Structure for BAPI Parameters ExtensionIn/ExtensionOut
};

Just a couple of remarks here:

  • Using Fundamental Library for ABAP generators is not mandatory. The UI elements bound to ABAP API structures can be manually implemented and generated UI elements can be adapted as needed. Default values, value input helps can be added to any UI input element (or removed), technical format can be changed etc.
  • HTML and JavaScript output expose “expanded” ABAP API structures, with all fields. The fields really needed in front-end depend on business scenario, usually defined by SAP functional expert. Only these fields` HTML snippets and JS initialisers need to be copied into particular app front-end view layouts.
  • The generated HTML UI elements impose no restrictions on view layout and styling, they are fully independent

Let pick up UI elements generated for Angular (fd-form-item) and add them into front-end view panels. View elements without UI components (page, command bar etc.) are not shown here, for the sake of readability:

<!-- Manufacturer -->

<div class="fd-layout-panel">
    <div class="fd-layout-panel__header">
        <div class="fd-layout-panel__head">
            <h3 class="fd-layout-panel__title">
                Manufacturer
            </h3>
        </div>
    </div>

    <div class="fd-layout-panel__body">

        <div fd-form-item>
            <label fd-form-label>Manufacturer of asset</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_HEADER.MANFACTURE"
                data-abap="{type: 'string', length: '30'}" />
        </div>

        <div fd-form-item>
            <label fd-form-label>Manufacturer model number</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_HEADER.MANMODEL"
                data-abap="{type: 'string', length: '20'}" />
        </div>

        <div fd-form-item>
            <label fd-form-label>Manufacturer part number</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_HEADER.MANPARNO"
                data-abap="{type: 'string', length: '30'}" />
        </div>

        <div fd-form-item>
            <label fd-form-label>Manufacturer serial number</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_HEADER.MANSERNO"
                data-abap="{type: 'string', length: '30'}" />
        </div>
    </div>

</div>

<!-- Installed at -->

<div class="fd-layout-panel">
    <div class="fd-layout-panel__header">
        <div class="fd-layout-panel__head">
            <h3 class="fd-layout-panel__title">
                Installed at
            </h3>
        </div>
    </div>

    <div class="fd-layout-panel__body">

        <div fd-form-item>
            <label fd-form-label>Functional Location Label</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_SPECIFIC.READ_FLOC"
                data-abap="{type: 'string', length: '40', mid: 'IFL'}" data-shlp="{type: 'SH', id: 'IFLM'}" />
        </div>

        <div fd-form-item>
            <label fd-form-label>Maintenance plant</label>
            <input fd-input fd-form-control [(ngModel)]="equipment.ES_HEADER.MAINTPLANT"
                data-abap="{type: 'string', length: '4', mid: 'SWK'}" data-shlp="{type:'SH', id:'H_T001W'}" />
        </div>

        <div fd-form-item>
            <label fd-form-label>ABC indicator for technical object</label>
            <fd-combobox [(ngModel)]="quipment.ES_HEADER.ABCINDIC" [dropdownValues]="{type:'CT', id:'T370C'}"
                data-abap="{type: 'string', length: '1'}">
            </fd-combobox>
        </div>
    </div>

</div>

<!-- Equi data -->

<div class="fd-layout-panel">
    <div class="fd-layout-panel__header">
        <div class="fd-layout-panel__head">
            <h3 class="fd-layout-panel__title">
                Equi data
            </h3>
        </div>
    </div>

    <div class="fd-layout-panel__body">

        <div fd-form-item>
            <label fd-form-label>Equipment category</label>
            <fd-combobox [(ngModel)]="equipment.ES_SPECIFIC.EQUICATGRY" [dropdownValues]="{type:'CT', id:'T370T'}"
                data-abap="{type: 'string', length: '1', mid: 'EQT'}">
            </fd-combobox>
        </div>

        <div fd-form-item>
            <label fd-form-label>Type of Technical Object</label>
            <fd-combobox [(ngModel)]="equipment.ES_HEADER.OBJECTTYPE" [dropdownValues]="{type:'CT', id:'T370K'}"
                data-abap="{type: 'string', length: '10'}">
            </fd-combobox>
        </div>


        <div fd-form-item>
            <label fd-form-label>ABC indicator for technical object</label>
            <fd-combobox [(ngModel)]="equipment.ES_HEADER.ABCINDIC" [dropdownValues]="{type:'CT', id:'T370C'}"
                data-abap="{type: 'string', length: '1'}">
            </fd-combobox>
        </div>
    </div>

</div>

 

With all pieces put together our Fundamental Library for ABAP app looks like this:

Here the complete listing of the same front-end view implemented in Aurelia, with all elements:

<template>
    <require from="reuse/ui-attachments"></require>

    <ui-page page-title="Equipment">

        <template replace-part="page-header">
            <ui-row class="navbar header">
                <ui-button class="ui-btn-transparent" icon-prefix="nav-back" click.delegate="app.router.navigateBack()">
                </ui-button>
                <!--ui-button transparent click.delegate="toggleSidebar()" icon-prefix="menu2"></ui-button-->
                <div style="flex: 1; text-align: center;">
                    ${routeTitle}&nbsp;<a href.bind="equipment.href" target="_blank">${equipment.hrefText}</a>
                </div>
            </ui-row>
        </template>

        <template replace-part="page-footer">
            <ui-row class="navbar footer">
                <ui-column pull-right>
                    <ui-button transparent click.delegate="equipment.get()" label="Get"></ui-button>
                    <ui-button transparent click.delegate="equipment.save()" label="Save"></ui-button>
                </ui-column>
            </ui-row>
        </template>

        <ui-content scroll padded class="ui-app-view">

            <ui-row padded>
                <ui-column padded size="sm-6">
                    <h4>
                        <span role="presentation" aria-hidden="true"
                            class="sap-icon sap-icon-supplier ui-color-secondary"></span>
                        Manufacturer
                    </h4>

                    <ui-input ddic-length="30" ddic-type="CHAR" label="Manufacturer of asset"
                        value.bind="equipment.ES_HEADER.MANFACTURE">
                    </ui-input>

                    <ui-input ddic-length="20" ddic-type="CHAR" label="Manufacturer model number"
                        value.bind="equipment.ES_HEADER.MANMODEL">
                    </ui-input>

                    <ui-input ddic-length="30" ddic-type="CHAR" label="Manufacturer part number"
                        shlp.bind="{type: 'SH', id: 'IFLM'}" mid="IFL" value.bind="equipment.ES_HEADER.MANPARNO">
                    </ui-input>

                    <ui-input ddic-length="30" ddic-type="CHAR" label="Manufacturer serial number"
                        value.bind="equipment.ES_HEADER.MANSERNO">
                    </ui-input>
                </ui-column>

                <ui-column padded size="sm-6">
                    <h4>
                        <span role="presentation" aria-hidden="true"
                            class="sap-icon sap-icon-functional-location ui-color-secondary"></span>
                        Installed at
                    </h4>

                    <ui-input ddic-length="40" ddic-type="CHAR" label="Functional Location Label"
                        value.bind="equipment.ES_SPECIFIC.READ_FLOC" shlp.bind="{type: 'SH', id: 'IFLM'}" mid="IFL">
                    </ui-input>

                    <ui-input ddic-length="4" ddic-type="CHAR" label="Maintenance plant"
                        value.bind="equipment.ES_HEADER.MAINTPLANT"
                        shlp.bind="{type: 'SH', id: 'H_T001W', valueColumn: 'WERKS'}" mid="SWK">
                    </ui-input>

                    <ui-combo clear ddic-length="1" ddic-type="CHAR" label="ABC indicator for technical object"
                        value.bind="equipment.ES_HEADER.ABCINDIC" shlp.bind="{type: 'CT', id: 'T370C'}">
                    </ui-combo>

                </ui-column>

                <ui-column padded size="sm-6" show.bind="equipment.ET_STATUS.length">
                    <h4>
                        <span role="presentation" aria-hidden="true"
                            class="sap-icon sap-icon-order-status ui-color-secondary"></span>
                        Status
                    </h4>
                    <ul class="list-group">
                        <li repeat.for="status of equipment.ET_STATUS" class="list-group-item">
                            ${status.DESCRIPTION} ${status.STATUS} ${status.TEXT}
                        </li>
                    </ul>
                </ui-column>

                <ui-column padded size="sm-6" show.bind="equipment.IS_HEADER.START_FROM">
                    <h4><span role="presentation" aria-hidden="true"
                            class="sap-icon sap-icon-kpi-corporate-performance ui-color-secondary"></span>
                        In Operation since ${equipment.IS_HEADER.START_FROM | dateFormat }
                    </h4>
                    <ul class="list-group">
                        <li repeat.for="line of equipment.KPI" class="list-group-item" style="padding: 0.25rem 0">
                            <ui-row>
                                <ui-column size="md-4" class="ui-color-secondary"> ${line.name} </ui-column>
                                <ui-column size="md-2" class="ui-text-end"> ${line.value} </ui-column>
                            </ui-row>
                        </li>
                    </ul>
                </ui-column>

                <ui-column padded size="sm-6">
                    <h4>Equi Data</h4>

                    <ui-combo clear ddic-length="1" ddic-type="CHAR" label="Equipment category"
                        value.bind="equipment.ES_SPECIFIC.EQUICATGRY" shlp.bind="{type: 'CT', id: 'T370T'}" mid="EQT">
                    </ui-combo>

                    <ui-combo clear ddic-length="10" ddic-type="CHAR" label="Type of Technical Object"
                        value.bind="equipment.ES_HEADER.OBJECTTYPE" shlp.bind="{type: 'CT', id: 'T370K'}">
                    </ui-combo>

                    <ui-combo clear ddic-length="1" ddic-type="CHAR" label="ABC indicator for technical object"
                        value.bind="equipment.ES_HEADER.ABCINDIC" shlp.bind="{type: 'CT', id: 'T370C'}">
                    </ui-combo>

                </ui-column>

                <ui-column size="sm-6">
                    <!--img if.bind="image.content"
                         src="data:${image.mimeType};base64,${image.content}" alt="photo"/-->

                    <!--embed src="pdfFiles/Test.pdf" width="600" height="500" alt="pdf" pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/-->
                    <ui-attachments attachments.bind="equipment.Attachments"></ui-attachments>
                </ui-column>

                <ui-column size="sm-6" show.bind="equipment.Characteristics.length">
                    <h4>Characteristics (${equipment.Characteristics.length})</h4>
                    <div class="" repeat.for="charGroup of equipment.Characteristics">

                        <ui-panel>
                            <ui-header light collapse="true">${charGroup.groupDesc} (${charGroup.CHARLIST.length})
                            </ui-header>
                            <ui-body scroll padded>
                                <div repeat.for="ch of charGroup.CHARLIST">
                                    <!--span>${ch.DESCR_CHAR} ${ch.DATA_TYPE} sin: ${ch.SINGLE_VALUE} v: ${ch.VALUE} vfrom: ${ch.VALUE_FROM} unit:${ch.UNIT}</span-->
                                    <ui-input if.one-time="ch.DATA_TYPE === 'CHAR' " ddic-type="CHAR"
                                        label="${ch.DESCR_CHAR}" ddic-length="${ch.NUMBER_DIGITS}"
                                        value.two-way="ch.VALUE"></ui-input>

                                    <ui-input if.one-time="ch.DATA_TYPE === 'NUM' " ddic-type="DEC"
                                        label="${ch.DESCR_CHAR}" ddic-length="${ch.NUMBER_DIGITS}.${NUMBER_DECIMALS}"
                                        unit="${ch.UNIT}" value.two-way="ch.VALUE_FROM"></ui-input>
                                </div>
                            </ui-body>
                        </ui-panel>
                    </div>
                </ui-column>

            </ui-row>

        </ui-content>
    </ui-page>
</template>

Findings

Few findings from prototypes and projects done so far:

  • Minimum lines of code required at each level. The numbers for our real-life app show:
    • ABAP: the pure ABAP business logic exposed, without technical overhead
    • Middleware: ca. 80 lines of JavaScript, Java or Python
    • Front-end model: ca. 170 lines of JavaScript
    • Front-end view: ca. 200 lines of HTML
  • The simple code is not achieved by framework “magic” restricting the flexibility, performance or development freedom. The developer always has the last word in every aspect of using Fundamental Library for ABAP, up to not using and manually overriding some of it components and utilities.
  • The overall simplicity leads to high runtime performance, short time-to-market and easy to read and adapt implementations
  • Modular design makes app elements and apps easy reusable in more complex apps and app elements

Summary

Hope you enjoyed reading and stay tuned for more updates.

For high-level concept check: Web Applications with ABAP, done simple

Contributions to this project are welcome and please feel free to share your thoughts, questions and suggestions.

Be the first to leave a comment
You must be Logged on to comment or reply to a post.