nocin.eu

Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[ABAP] Dropdown list on selection screen

PARAMETER p_list  TYPE c AS LISTBOX VISIBLE LENGTH 32.  "Add OBLIGATORY to get rid of the empty line. Use DEFAULT to set the inital selection.

INITIALIZATION.

  CALL FUNCTION 'VRM_SET_VALUES'
    EXPORTING
      id              = 'P_LIST'
      values          = VALUE vrm_values( ( key = '1' text = 'Case 1' )
                                          ( key = '2' text = 'Case 2' )
                                          ( key = '3' text = 'Case 3' ) )
    EXCEPTIONS
      id_illegal_name = 1
      OTHERS          = 2.


AT SELECTION-SCREEN ON p_list.

  DATA(dynprofields) = VALUE dynpread_tabtype( ( fieldname = 'P_LIST' ) ).
  CALL FUNCTION 'DYNP_VALUES_READ'
    EXPORTING
      dyname             = sy-cprog
      dynumb             = sy-dynnr
      translate_to_upper = 'X'
    TABLES
      dynpfields         = dynprofields.  "find your selected value in column fieldvalue or directly in p_list

[ABAP] Activate and deactivate input fields via radiobuttons

*-----------------------------------------------------------------------
* SELEKTIONSBILD
*-----------------------------------------------------------------------
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-001.

* Radiobutton 1 -> Deactivate Fields
SELECTION-SCREEN BEGIN OF LINE.
PARAMETERS: p_radio1 RADIOBUTTON GROUP rad1 DEFAULT 'X' USER-COMMAND rad.
SELECTION-SCREEN COMMENT 6(37) text-t01 FOR FIELD p_radio1.  
SELECTION-SCREEN : END OF LINE.

* Radiobutton 2 -> Activate Fields
SELECTION-SCREEN : BEGIN OF LINE.
PARAMETERS: p_radio2 RADIOBUTTON GROUP rad1.
SELECTION-SCREEN COMMENT 6(37) text-t02 FOR FIELD p_radio2.
SELECTION-SCREEN : END OF LINE.

* Input fields
PARAMETERS:
  p_vorna TYPE vorna OBLIGATORY DEFAULT 'Max',
  p_nachn TYPE nachn OBLIGATORY DEFAULT 'Mustermann'.

SELECTION-SCREEN END OF BLOCK b1.


*----------------------------------------------------------------------
* AT SELECTION-SCREEN
*----------------------------------------------------------------------
AT SELECTION-SCREEN OUTPUT. " PBO
  LOOP AT SCREEN.
    CASE screen-name.
      WHEN 'P_VORNA' OR 'P_NACHN'.
        screen-input = COND #( WHEN p_radio2 = abap_true THEN 1 ELSE 0 ).
        MODIFY SCREEN.
    ENDCASE.
  ENDLOOP.

[ABAP] JSON to ABAP with dereferencing

Recently I had to fetch an attachment from SuccessFactors. The API returns a huge JSON. Here only a few lines of that json.

{
    "d": {
        "__metadata": {
            "uri": "https://apihostname.successfactors.com:443/odata/v2/Attachment(1000)",
            "type": "SFOData.Attachment"
        },
        "attachmentId": "1000",
        "fileName": "Testdokument.pdf",
        "mimeType": "application/pdf",
        "moduleCategory": "HRIS_ATTACHMENT",
        "userId": "10000555",
        "fileExtension": "pdf",
        "fileContent": "JVBERi0xLjcKjp2jtMXW5/gKMiAwIG9iag0KWy9JQ0NCYXNlZCAzIDAgUl0NCmVuZG9iag0KMyAw\r\nIG9iag0KPDwNCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlIA0KL0xlbmd0aCAyNTk2IA0KL04gMyANCj4+\r\nDQpzdHJlYW0NCnicnZZ3VFPZFofPvTe9UJIQipTQa2hSAkgNvUiRLioxCRBKwJAAIjZEVHBEUZGm\r\nCDIo4ICjQ5GxIoqFAVGx6wQZRNRxcBQblklkrRnfvHnvzZvfH/d+a5+9z91n733WugCQ/IMFwkxY\r\nCYAMoVgU4efFiI2LZ2AHAQzwAANsAOBws7NCFvhGApkCfNiMbJkT+Be9ug4g+fsq0z+MwQD/n5S5\r\nWSIxAFCYjOfy+NlcGRfJOD1XnCW3T8mYtjRNzjBKziJZgjJWk3PyLFt89pllDznzMoQ8GctzzuJl\r\n8OTcJ+ONORK+jJFgGRfnCPi5Mr4mY4N0SYZAxm/ksRl8TjYAKJLcLuZzU2RsLWOSKDKCLeN5AOB......"
        ...
    } 
}

Parsing the JSON to ABAP

SAP provides two classes /ui2/cl_json and /ui2/cl_data_access to work with JSONs and there are also many cool tools on https://dotabap.org/ for that.
Because it’s just a simple JSON I tried to avoid importing another class to the system. So I stayed with SAPs classes.

First deserialize the JSON with /ui2/cl_json.

    " Constants for properties
    CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
    CONSTANTS gc_filename    TYPE string VALUE 'FILENAME' ##NO_TEXT.
    CONSTANTS gc_doc_cat     TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
   
    DATA: lr_data TYPE REF TO data.
    /ui2/cl_json=>deserialize( EXPORTING json = lv_responce  "the repsonse contains the json string
                               CHANGING  data = lr_data ).

I found three similiar ways to use the class /ui2/cl_data_access to get the relevant data out of the JSON.

1. Using the class construktor and the value( ) method for earch property.

    DATA: lv_filecontent_base64 TYPE string,
          lv_filename           TYPE string,
          lv_documentcategory   TYPE string.

    /ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
    /ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filename    }| )->value( IMPORTING ev_data = lv_filename).
    /ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_doc_cat     }| )->value( IMPORTING ev_data = lv_documentcategory ).

2. Create an instance as first step and pass the component. Reuse the instance with the value( ) method.

    DATA: lv_filecontent_base64 TYPE string,
          lv_filename           TYPE string,
          lv_documentcategory   TYPE string.

    DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data      = lr_data
                                                        iv_component = |D-| ).
    
    lo_data_access->at( |{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
    lo_data_access->at( |{ gc_filename    }| )->value( IMPORTING ev_data = lv_filename ).
    lo_data_access->at( |{ gc_doc_cat     }| )->value( IMPORTING ev_data = lv_documentcategory ).

3. Use the ref( ) method to get a reference of a property value. Because it returns a generic data type (and you cannot dereference generic references) you have to use CAST combined with dereferencing (->*) to get the concret value.

    DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data      = lr_data
                                                        iv_component = |D-| ).

    DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
    DATA(lv_filename)           = CAST string( lo_data_access->at( gc_filename    )->ref( ) )->*.
    DATA(lv_documentcategory)   = CAST string( lo_data_access->at( gc_doc_cat     )->ref( ) )->*.

In the end I stayed with version 3 because I prefere using inline declarations. The complete result looked like this:

    " Constants for properties
    CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
    CONSTANTS gc_filename    TYPE string VALUE 'FILENAME' ##NO_TEXT.
    CONSTANTS gc_doc_cat     TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.

    DATA: lr_data TYPE REF TO data.
    /ui2/cl_json=>deserialize( EXPORTING json = lv_response  "the repsonse contains the json string
                               CHANGING  data = lr_data ).

    DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data      = lr_data
                                                        iv_component = |D-| ).

    DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
    DATA(lv_filename)           = CAST string( lo_data_access->at( gc_filename    )->ref( ) )->*.
    DATA(lv_documentcategory)   = CAST string( lo_data_access->at( gc_doc_cat     )->ref( ) )->*.

    " Decode BASE64 PDF to XString
    DATA(lv_decodedx) = cl_http_utility=>if_http_utility~decode_x_base64( lv_filecontent_base64 ).
    " XString to binary
    DATA(lt_data)     = cl_bcs_convert=>xstring_to_solix( lv_decodedx ).

    " Download file
    cl_gui_frontend_services=>gui_download( EXPORTING bin_filesize         = xstrlen( lv_decodedx )
                                                      filename             = lv_filename
                                                      filetype             = 'BIN'
                                                      show_transfer_status = ' '
                                             CHANGING data_tab             = lt_data ).

When /ui2/cl_data_access is not available (SAP_UI has to be at least 7.51, see note 2526405), you can do it the oldschool way.

    " Constants for properties
    CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
    CONSTANTS gc_filename    TYPE string VALUE 'FILENAME' ##NO_TEXT.
    CONSTANTS gc_doc_cat     TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.

    DATA: lr_data TYPE REF TO data.
    /ui2/cl_json=>deserialize( EXPORTING json = lv_response  "the repsonse contains the json string
                               CHANGING  data = lr_data ).

    ASSIGN lr_data->* TO FIELD-SYMBOL(<fs_d>).
    ASSIGN COMPONENT 'D' OF STRUCTURE <fs_d> TO FIELD-SYMBOL(<fs_components>).

    ASSIGN <fs_components>->* TO FIELD-SYMBOL(<fs_fields>).
    ASSIGN COMPONENT gc_doc_cat     OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<documentcategory>).
    ASSIGN COMPONENT gc_filename    OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filename>).
    ASSIGN COMPONENT gc_filecontent OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filecontent>).

    ASSIGN <documentcategory>->* TO FIELD-SYMBOL(<documentcategory_value>).
    ASSIGN <filename>->*         TO FIELD-SYMBOL(<filename_value>).
    ASSIGN <filecontent>->*      TO FIELD-SYMBOL(<filecontent_value>).

    "...

[BTP] Difference between Platform Users and Business Users

https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/cc1c676b43904066abb2a4838cbd0c37.html

Platform users are usually developers, administrators or operators who deploy, administer, and troubleshoot applications and services on SAP BTP.”

Business users use the applications that are deployed to SAP BTP. For example, the end users of your deployed application or users of subscribed apps or services, such as SAP Business Application Studio or SAP Web IDE, are business users.”

[ABAP] Convert JavaScript Timestamp to yyyy-MM-dd’T’HH:mm:ss

DATA(lv_js_timestamp) = "/Date(1615161600000)/".

"Extract /Date(1615161600000)/ to 1615161600000
FIND REGEX '([0-9]+)' IN lv_js_timestamp IGNORING CASE SUBMATCHES DATA(js_timestamp).

cl_pco_utility=>convert_java_timestamp_to_abap( EXPORTING iv_timestamp = js_timestamp
                                                IMPORTING ev_date      = DATA(lv_date)
                                                          ev_time      = DATA(lv_time) ).
"2021-03-08T00:00:00
CONVERT DATE lv_date TIME lv_time INTO TIME STAMP DATA(timestamp) TIME ZONE 'UTC'.
rv_datetime = |{ timestamp TIMESTAMP = ISO }|.

[Deutsch] Aliquotierung

https://de.wikipedia.org/wiki/Aliquotierung

Aliquotierung oder das zugehörige Verb aliquotieren bedeuten im allgemeinen Sprachsinn „den aliquoten Teil einer Größe bestimmen“.

https://de.wiktionary.org/wiki/Aliquotierung

[1] Betriebswirtschaft, Personalwirtschaft: die anteilsmäßige, periodenbezogene Berechnung und Ermittlung von Ansprüchen (Urlaub, Sonderzahlungen, Entgelt etc.) für einen Mitarbeiter bei dessen Ein- oder Austritt innerhalb (und nicht am Anfang oder am Ende) der Abrechnungsperiode.

[Deutsch] Abkürzungen

VokuhilaVorne kurz hinten lang
HariboHans Riegel Bonn
HanutaHaselnuss Tafel
AdidasAdolf (Adi) Dassler
NKDNiedrig Kalkuliert Discount
PersilPerborat & Silikat
TaffTäglich, Aktuell, Frisch, Frech
MilkaMilchkakao
AEGAus Erfahrung Gut
Tetraminoaus vier Quadraten
Dominoaus zwei Quadraten
Monominoaus einem Quadrat
LaserLight amplification by stimulated emission of radiation
RadarRadio detection and ranging
UnimogUniversal Motorgerät
Audilateinisch für “Horch!” nach Firmengründer August Horch
TaserThomas A. Swift’s Electric Rifle
LKWLeber Käs Weckle (oder Last Kraft Wagen)
OlibaOberlippenbart
MufutiMultifunktionstisch
Immobilienweil sie nicht mobil, also immobil sind (lateinisch im-mobilis ‚unbeweglich‘)

[SAPUI5] UI5 Web Components

https://blogs.sap.com/2020/12/21/using-ui5-web-components/

“With UI5 Web Components, developers can build the core logic of the application using these popular web frameworks, while using UI5 Web Components as the visual elements (controls).

Each of these web frameworks have different ways of handling data layer, persistence, binding, and integration with APIs, etc. but the nice thing about web components is that is integrates easily, regardless of the framework.”

[nodejs] APIs and Microservices Projects

These are my notes while doing the course APIs and Microservices on https://www.freecodecamp.org. I highly recommend it if you prefer to try things directly rather than watching videos.


Timestamp Microservice

https://repl.it/@nocin/boilerplate-project-timestamp#server.js

app.get("/api/timestamp/", (req, res) => {
  res.json({ unix: Date.now(), utc: Date() });
});

app.get("/api/timestamp/:date?", (req, res) => {

  //utc date?
  let date = new Date(req.params.date)
  if (date != "Invalid Date") {
   res.json({unix: date.getTime(), utc: date.toUTCString()});
  }

  //unix timestamp?
  const dateInt = parseInt(req.params.date);
  date = new Date(dateInt).toUTCString();
  if (date != "Invalid Date") {
    res.json({unix: dateInt, utc: date});
  }
  
  //invalid input
  res.json({ error: date });
});


Request Header Parser Microservice

https://repl.it/@nocin/boilerplate-project-headerparser#server.js

https://www.npmjs.com/package/express-useragent
https://www.npmjs.com/package/express-request-language

var useragent = require('express-useragent');
var cookieParser = require('cookie-parser');
var requestLanguage = require('express-request-language');

// stuff...

app.use(useragent.express());
app.use(cookieParser());
app.use(requestLanguage({
  languages: ['en-US', 'zh-CN'],
  cookie: {
    name: 'language',
    options: { maxAge: 24*3600*1000 },
    url: '/languages/{language}'
  }
}));

app.get("/api/whoami", (req, res) => {
  res.json({"ipaddress": req.ip,
            "language": req.language,
            "software": req.useragent.source });
});


URL Shortener Microservice

https://repl.it/@nocin/boilerplate-project-urlshortener#server.js

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const dns = require('dns');


// Basic Configuration
const port = process.env.PORT || 3000;

app.use(cors());

app.use('/public', express.static(`${process.cwd()}/public`));

app.get('/', function(req, res) {
  res.sendFile(process.cwd() + '/views/index.html');
});

app.use(bodyParser.urlencoded({extended: false}));

let urls = [];

//POST
app.post("/api/shorturl/new", function(req, res) {
  
  const getHostnameFromRegex = (url) => {
  // run against regex
  const matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
  // extract hostname (will be null if no match is found)
  return matches && matches[1];
  }

  hostname = getHostnameFromRegex(req.body.url);
  console.log("Hostname: " + hostname);

  // if no hostname found, return here
  if (!hostname) res.json({ error: 'invalid url' });

  // check if url is valid
  dns.lookup(hostname, (error, addresses) => {
    console.error(error);
    console.log(addresses);

    if (!error) {
       let newUrl = { original_url : req.body.url, short_url : urls.length + 1};
      urls.push(newUrl);
      res.json(newUrl);
    } else {
      res.json({ error: 'invalid url' });
    }

  });

});

//GET
app.get('/api/shorturl/:num', function(req, res) {

  for (let i = 0; i < urls.length; i++) {
    console.log(urls[i].original_url);
    if (urls[i].short_url == req.params.num) {
        res.redirect(urls[i].original_url);
    }
  }

});

app.listen(port, function() {
  console.log(`Listening on port ${port}`);
});


Exercise Tracker

https://repl.it/@nocin/boilerplate-project-exercisetracker#server.js

const express = require('express')
const app = express()
const cors = require('cors')
require('dotenv').config()
const bodyParser = require('body-parser');
const mongoose = require('mongoose');

app.use(cors())
app.use(express.static('public'))
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/views/index.html')
});


const listener = app.listen(process.env.PORT || 3000, () => {
    console.log('Your app is listening on port ' + listener.address().port)
})


//BodyParser
app.use(bodyParser.urlencoded({ extended: false }));

//DB connect
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

const { Schema } = mongoose;

//User Schema
const userSchema = new Schema({
    username: { type: String, required: true },
});
const User = mongoose.model("User", userSchema);

//Exercise Schema
const exerciseSchema = new Schema({
    userId: Schema.Types.ObjectId,
    description: { type: String, required: true },
    duration: { type: Number, required: true },
    date: { type: Date, default: Date.now }
});
const Exercise = mongoose.model("Exercise", exerciseSchema);


//POST user to DB
app.post("/api/exercise/new-user", (req, res) => {

    let user = new User({ username: req.body.username });

    user.save((err, data) => {
        //console.log("created User: " + data);
        if (err) return console.error(err);
        res.json({ username: data.username, _id: data._id });
    });

});


//GET all users from DB
app.get("/api/exercise/users", (req, res) => {
    User.find((err, usersFound) => {
        if (err) return console.error(err);
        //console.error("users found: " + usersFound);
        res.json(usersFound);
    })
});


//POST exercise form data
app.post("/api/exercise/add", (req, res) => {

    let exercise = new Exercise({
        userId: req.body.userId,
        description: req.body.description,
        duration: req.body.duration,
        date: req.body.date ? req.body.date : Date.now()
    });

    exercise.save((err, data) => {
        //console.log("created exercise: " + data);
        if (err) return console.error(err);
        User.findById(exercise.userId, (err, userFound) => {
            if (err) return console.error(err);
            //console.log("userFound " + userFound.username); 
            res.json({
                _id: data.userId,
                username: userFound.username,
                date: data.date.toDateString(),
                duration: data.duration,
                description: data.description
            });
        });
    });
});


//GET exercise log
app.get("/api/exercise/log", (req, res) => {
    console.log(req.query.userId);
    console.log(req.query.from);
    console.log(req.query.to);
    console.log(req.query.limit);

    let userId = req.query.userId;
    let limit = Number(req.query.limit);

    //create query filter
    let filter = {};
    filter.userId = userId;

    if (req.query.from && req.query.to) {
        let fromDate = new Date(req.query.from);
        let toDate = new Date(req.query.to);
        filter.date = { $gte: fromDate, $lte: toDate };
    }

    console.log("Filter " + JSON.stringify(filter));

    const queryExercises = (done) => {
        Exercise.find(filter)
            .limit(limit)
            .exec((err, exercices) => {
                if (err) return console.error(err);
                done(exercices);
            })
    };

    const paseExercises = (exercices) => {
        let logArray = [];

        for (let i = 0; i < exercices.length; i++) {
            var obj = exercices[i];
            logArray.push({
                description: obj.description,
                duration: obj.duration,
                date: obj.date.toDateString()
            });
        }
        console.log(logArray);

        User.findById(userId, (err, userFound) => {
            if (err) return console.error(err);
            let logger = {
                _id: userId,
                username: userFound.username,
                count: logArray.length,
                log: logArray
            };
            res.json(logger);
        });
    }

    //Execute Query
    queryExercises(paseExercises);

});

File Metadata Microservice

https://repl.it/@nocin/boilerplate-project-filemetadata#server.js

https://www.npmjs.com/package/multer

var express = require('express');
var cors = require('cors');
require('dotenv').config()
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' });

var app = express();

app.use(cors());
app.use('/public', express.static(process.cwd() + '/public'));

app.get('/', function (req, res) {
    res.sendFile(process.cwd() + '/views/index.html');
});


const port = process.env.PORT || 3000;
app.listen(port, function () {
  console.log('Your app is listening on port ' + port)
});


//POST 
app.post('/api/fileanalyse', upload.single('upfile'), (req, res, next) => {
  res.json({ name: req.file.originalname, type: req.file.mimetype, size: req.file.size  });
})