Optimierung der SAPUI5-Code-Dokumentation durch JSDoc

In umfangreichen SAPUI5-Projekten kann das dynamische Verhalten von JavaScript in den Controllern zu Herausforderungen bezüglich Lesbarkeit und Wartbarkeit führen. Durch die Nutzung von JSDoc, einem standardisierten Dokumentationstool, wird diesem Problem entgegengewirkt.

Was ist JSDoc?

JSDoc ist ein Hilfsmittel in der JavaScript-Entwicklung, das dazu dient, Code durch spezielle Kommentare zu dokumentieren. Es ermöglicht Entwicklern, Funktionen, Klassen und Variablen klar zu beschreiben und automatisch gut strukturierte Dokumentationsseiten zu generieren.

Welche Vorteile bietet JSDoc?

Die Verwendung von JSDoc in einem SAPUI5-Projekt bietet einige Vorteile, unter anderem: 

  • Lesbarkeit: JSDoc verbessert die Lesbarkeit von JavaScript-Code, indem es Entwicklern ermöglicht, Funktionen, Klassen und Variablen mit Kommentaren zu dokumentieren.
  • Typsicherheit: Durch das Hinzufügen von Typinformationen in JSDoc-Kommentaren wird die Sicherstellung von Typkorrektheit unterstützt und die Fehlererkennung während der Entwicklung erleichtert.
  • Automatische Generierung: JSDoc ermöglicht die automatische Generierung von gut strukturierten Dokumentationsseiten, die Parameter, Rückgabetypen etc. übersichtlich darstellen.

Installation von JSDoc und Generierung der Code Dokumentation

Um JSDoc über die Konsole zu installieren, kann npm (Node Package Manager) verwendet werden. Folgender Konsolenbefehl dient zur Installation von JSDoc:

npm install jsdoc

Zum Erstellen des Dokumentations-Files kann auf zwei unterschiedliche Varianten zurückgegriffen werden. Es ist möglich, mithilfe des Konsolenbefehls jsdoc die Standardeinstellungen zu verwenden. Nach Eingabe des Befehls wird die Dokumentation automatisch erstellt.

jsdoc webapp/controller

Die zweite Möglichkeit wäre eine Konfigurationsdatei zu benutzen. Diese ermöglicht es Entwicklern, die JSDoc-Ausgabe an die Anforderungen des Projekts anzupassen

Konfigurationsdatei anlegen

Die Konfigurationsdatei befindet sich üblicherweise im Hauptverzeichnis der UI5-App.

{
 "source": {
 "include": ["webapp/controller"],
 "includePattern": ".js$"
 },
 "plugins": ["plugins/markdown"],
 "templates": {
 "recurse": true,
 "monospaceLinks": true
 },
 "opts": {
 "recurse": true,
 "destination": "./docs/"
 }
}

Nach Anlegen der Datei muss dementsprechend die package.json Datei im selbigen Projekt erweitert werden.

"scripts": {
 "doc": "jsdoc -c jsdoc.json"
 ...
 },

Im Anschluss kann nun folgender Konsolenbefehl benutzt werden

npm run doc

Wichtig zu Beachten hierbei ist, dass beide Commands nach Änderungen im Code immer neu ausgeführt werden müssen um eine neue Dokumenation zu erstellen. 

Preview

Nach Ausführung des entsprechenden Befehls zur Dokumentationserstellung wird im Hauptverzeichnis ein Ordner erstellt. Standardmäßig lautet der Ordnername out, kann jedoch mithilfe der Konfigurationsdatei beliebig verändert werden. Dieser Folder enthält die gesamte Dokumentation, einschließlich aller generierten HTML-Dateien, die im Browser eingesehen werden können.

Code-Dokumentation

Wie bereits erwähnt, startet ein JSDoc-Kommentar immer mit /**. Es ist wichtig zu beachten, dass genau zwei * eingefügt werden müssen. Andernfalls wird die Syntax nicht erkannt, und das beschriebene Element wird nicht in die generierte Dokumentation aufgenommen.

Aufbau von JSDoc

Der allgemeine Aufbau eines JSDoc Kommentars besteht aus einer Beschreibung und Block Tags

/**
 *Kommentar
 *@block-tag
 */
funktionsName: function(parameter){
 ...
}

Welche Block Tags gibt es?

Auf der offiziellen JSDoc-Website findet sich eine umfassende Dokumentation zu Block Tags. Die SAP hat jedoch in ihren SAPUI5-Richtlinien bezüglich JSDoc das Spektrum der zulässigen Block Tags etwas begrenzt.

JSDoc in UI5 implementieren

Die nachfolgenden Beispiele veranschaulichen eine konkrete Implementierung von JSDoc in SAPUI5.

Dokumentation von Klassen

Gemäß den SAP Design Guidelines sollten Klassen immer die folgenden Block-Tags enthalten:

  • @class: Fügt der Kategorie „Classes“ in der API ein Element hinzu.
  • @extends: Gibt an, ob und von welcher Klasse geerbt wird.
  • @author: Verantwortliche(r) Autor(in).
  • @since: Version, in der die Klasse erstellt wurde.
sap.ui.define([
 "at/clouddna/controller/BaseController",
 "sap/m/MessageBox",
 "sap/ui/model/json/JSONModel",
 "sap/ui/core/Fragment",
 "sap/ui/core/Item"
],
 function (BaseController, MessageBox, JSONModel, Fragment, Item) {
 "use strict";
 /**
 * This class manages a Customer
 * 
 * Detailed description of the class
 * @class Customer
 * @extends BaseController
 * @author Example Author
 * @since 1.96.0
 */
 return BaseController.extend("at.clouddna.training.controller.Customer", {
 ...
 });

Dokumentation von Funktionen und Parametern

Folgende Block-Tags sollten laut SAP Design Guidelines immer für die Beschreibung von Funktionen und Parametern vorhanden sein:

  • @public/@protected/@private: Gibt die Sichtbarkeit der Methoden in der API an. Private-Methoden werden NICHT in der API angezeigt.
  • @param: Gibt den Typen des Parameters an.
  • @returns: Gibt den Return-Type an.
/**
 * Creates a Path where Documents can be uploaded and returns it
 * @public
 * @param {string} sDocId - Document ID
 * @param {string} sCustomerId - Customer ID
 * @returns {string}
 */
 formatUrl: function(sDocId, sCustomerId){
 let sPath = this.getView().getModel().createKey("/CustomerDocumentSet", {
 DocId: sDocId,
 CustomerId: sCustomerId
 });
 return this.getView().getModel().sServiceUrl + sPath + "/$value";
 },
/**
 * Refreshs the Model
 * @public
 */
 onUploadCompleted: function(){
 this.getView().getModel().refresh(true);
 },
/**
 * Removes an Item from the UploadSet
 * @param {object} oEvent - Event object
 * @public 
 */
 onRemovePressed: function(oEvent){
 oEvent.preventDefault();
 let sPath = oEvent.getSource().getBindingContext().getPath();
 this.getView().getModel().remove(sPath);
 }

Fazit

In SAPUI5 ist die Nutzung von JSDoc ein echter Gamechanger. Durch die Dokumentation von Klassen und Methoden nach den SAP Design Guidelines wird nicht nur der Code verständlicher, sondern auch die Zusammenarbeit im Team deutlich verbessert. Die Verwendung von Block-Tags wie @public oder @private wird die Sichtbarkeit gesteuert, und das Ganze trägt dazu bei, dass SAPUI5 Projekte nicht nur effizienter, sondern auch leichter wartbar sind. JSDoc ist sozusagen der zuverlässige Begleiter für einen gut strukturierten und transparenten SAPUI5-Code.

TypeScript für Fortgeschrittene: Vertiefen Sie Ihre Grundkenntnisse

In der dynamischen Welt der Webentwicklung ist es entscheidend, nicht nur neue Technologien zu erlernen, sondern auch das Wissen kontinuierlich zu vertiefen. TypeScript, als mächtige Erweiterung von JavaScript, bietet eine Fülle an Möglichkeiten, die Entwicklung von Anwendungen zu verbessern und zu vereinfachen. Wenn ihr bereits die Grundlagen von TypeScript gemeistert habt und bereit seid, eure Kenntnisse zu erweitern, seid ihr hier genau richtig.

In den kommenden Abschnitten werdet ihr lernen, wie ihr die Grundlagen von TypeScript weiter ausbaut, komplexe Typen handhabt, fortgeschrittene Funktionen nutzt und die Typsicherheit eurer Anwendungen auf ein neues Niveau hebt. Wir werden euch anhand praktischer Beispiele und tiefergehender Erklärungen zeigen, wie ihr eure Fähigkeiten in TypeScript weiterentwickeln könnt.

Die folgenden fünf Abschnitte bieten euch vertiefte Einblicke in erweiterte TypeScript-Techniken. Sie sind speziell darauf ausgelegt, euer Verständnis zu festigen und euch dabei zu unterstützen, komplexere und leistungsstärkere Anwendungen zu entwickeln. Lasst uns diese Reise fortsetzen und die Macht von TypeScript voll ausschöpfen!

Erweiterte Interface-Nutzung

Interfaces in TypeScript können verwendet werden, um die Struktur von komplexeren Objekten zu definieren, einschließlich optionaler Eigenschaften und Methoden:

interface Employee {
 id: number;
 name: string;
 role?: string; // Optional property
 updateRole(newRole: string): void; // Method
}
let employee: Employee = {
 id: 1,
 name: "Anna Schmidt",
 updateRole: function (newRole: string) {
 this.role = newRole;
 }
};
employee.updateRole("Developer");

Verwendung von Enums

Enums sind eine Möglichkeit, eine Menge von benannten Konstanten zu definieren. Sie können das Lesen des Codes erleichtern und Fehler reduzieren:

enum Direction {
 Up,
 Down,
 Left,
 Right
}
function move(direction: Direction) {
 // ...
}
move(Direction.Up);

Typschutz und Typassertionen

TypeScript erlaubt euch, Typen zu überprüfen und sicherzustellen, dass eine Variable von einem bestimmten Typ ist:

function isString(test: any): test is string {
 return typeof test === "string";
}
function example(foo: any) {
 if (isString(foo)) {
 // Hier weiß TypeScript, dass 'foo' ein String ist
 console.log("It's a string: " + foo);
 }
}
// Typassertion
let someValue: any = "Das ist ein String";
let strLength: number = (someValue as string).length;

Komplexe Generics

Generics können mit Interfaces und anderen Typen kombiniert werden, um noch mächtigere und wiederverwendbare Code-Komponenten zu erstellen:

interface Returnable<T> {
 data: T;
 returnData: () => T;
}
function createReturnable<T>(data: T): Returnable<T> {
 return {
 data,
 returnData() {
 return this.data;
 }
 };
}
const returnableNumber = createReturnable(10);
console.log(returnableNumber.returnData()); // Ausgabe: 10

Asynchrone Funktionen mit TypeScript

Asynchrone Funktionen in TypeScript können genauso definiert werden wie in modernem JavaScript, jedoch mit der zusätzlichen Sicherheit von Typen:

async function fetchData(url: string): Promise<any> {
 const response = await fetch(url);
 const data = await response.json();
 return data;
}
fetchData("https://api.example.com/data")
 .then(data => console.log(data))
 .catch(error => console.error(error));

Erweiterte Typen und Utility-Types

TypeScript bietet eine Reihe von nützlichen Utility-Typen, die euch helfen können, eure Typen flexibel zu gestalten:

type ReadOnlyEmployee = Readonly<Employee>;
let employee: ReadOnlyEmployee = {
 id: 1,
 name: "Anna Schmidt",
 updateRole: (newRole: string) => {
 // Diese Funktion kann nicht aufgerufen werden, wenn 'employee' als 'ReadOnlyEmployee' typisiert ist
 }
};
// 'employee' kann nicht verändert werden, da es ein Readonly-Typ ist
// employee.id = 2; // Error

Diese Beispiele sollten euch einen tieferen Einblick in die Fähigkeiten von TypeScript geben und wie ihr sie in euren eigenen Projekten nutzen könnt. TypeScript bietet eine reichhaltige Typensyntax, die es euch ermöglicht, eure Anwendungen präzise zu modellieren und die Vorteile der statischen Typüberprüfung voll auszuschöpfen.

Von JavaScript zu TypeScript: Grundlagen für Einsteiger

TypeScript fügt JavaScript die dringend benötigte Typisierung hinzu und hilft Entwicklern, sicherere und verständlichere Codebasen zu erstellen. Wenn ihr von JavaScript zu TypeScript wechselt, werdet ihr einige neue Konzepte kennenlernen müssen. Dieser Artikel führt euch durch die Grundlagen von TypeScript, damit ihr schnell loslegen könnt.

Variablentypen

Der vielleicht größte Vorteil von TypeScript ist die Fähigkeit, Variablen explizite Typen zuzuweisen. Diese Typen werden zur Kompilierzeit überprüft, was bedeutet, dass viele Fehler erkannt werden, bevor der Code überhaupt ausgeführt wird. Hier ein einfaches Beispiel:

let message: string = "Hello, TypeScript";
let count: number = 10;
let isActive: boolean = false;

Funktionstypisierung

In TypeScript könnt ihr nicht nur Variablen, sondern auch Funktionen Typen zuweisen. Ihr könnt sowohl den Typen der Argumente als auch den Typen des Rückgabewerts definieren:

function add(x: number, y: number): number {
 return x + y;
}

Interfaces

Interfaces sind ein mächtiges Feature von TypeScript, das euch erlaubt, die Form von Objekten zu definieren. Sie sind besonders nützlich, um sicherzustellen, dass bestimmte Objekte immer bestimmte Eigenschaften haben:

interface User {
 name: string;
 age: number;
}
const user: User = {
 name: "Max Mustermann",
 age: 28
};

Klassen und Vererbung

TypeScript unterstützt Klassen und Vererbung, ähnlich wie andere objektorientierte Sprachen. Dies ermöglicht es euch, euren Code in einer strukturierten Weise zu organisieren:

class Animal {
 name: string;
 constructor(name: string) {
 this.name = name;
 }
 move(distanceInMeters: number = 0) {
 console.log(`${this.name} moved ${distanceInMeters}m.`);
 }
}
class Snake extends Animal {
 constructor(name: string) {
 super(name);
 }
 move(distanceInMeters = 5) {
 console.log("Slithering...");
 super.move(distanceInMeters);
 }
}

Generics

Generics sind eine Möglichkeit, Komponenten zu erstellen, die mit verschiedenen Typen arbeiten können, ohne dabei den spezifischen Typ anzugeben. Sie geben euch Flexibilität sowie Typsicherheit:

function identity<T>(arg: T): T {
 return arg;
}
let output = identity<string>("myString");

Typinferenz und Union-Typen

TypeScript ist intelligent genug, um in vielen Fällen Typen selbst zu erkennen (Typinferenz). Außerdem könnt ihr Union-Typen nutzen, um eine Variable zu definieren, die mehr als einen Typ annehmen kann:

let uncertain: string | number;
uncertain = "Maybe a string?";
uncertain = 42; // Auch okay!

Fazit

Diese Grundlagen sind der erste Schritt, um TypeScript effektiv in euren Projekten einzusetzen. Durch das Erlernen dieser Konzepte werdet ihr in der Lage sein, Anwendungen zu schreiben, die nicht nur leistungsfähig, sondern auch einfacher zu warten und zu debuggen sind. Mit diesen Werkzeugen ausgestattet, könnt ihr beginnen, die tieferen, fortgeschritteneren Teile von TypeScript zu erkunden.

TypeScript in Visual Studio Code: Ein umfassender Startguide für Entwickler

Erste Schritte mit TypeScript in Visual Studio Code

Visual Studio Code (VS Code) ist ein beliebter Editor unter Entwicklern und bietet ausgezeichnete Unterstützung für TypeScript. Wenn du von JavaScript umsteigst, wirst du die TypeScript-Unterstützung in VS Code sehr hilfreich finden. Hier erfährst du, wie du TypeScript in VS Code einrichten kannst.

Schritt 1: Node.js und npm installieren

Bevor du mit TypeScript arbeiten kannst, musst du Node.js und npm installiert haben. Sie werden zusammen gebündelt und können von der offiziellen Node.js-Website heruntergeladen werden.

Schritt 2: TypeScript installieren

Mit installiertem npm kannst du nun TypeScript global auf deinem Rechner installieren, indem du den folgenden Befehl in deinem Terminal ausführst:

npm install -g typescript

Schritt 3: Dein Projekt in VS Code öffnen

Starte VS Code und öffne den Ordner, der dein Projekt enthält. Wenn du von Grund auf neu beginnst, kannst du einen neuen Ordner erstellen und ihn in VS Code öffnen.

Schritt 4: Ein neues TypeScript-Projekt initialisieren

Gib im integrierten Terminal in VS Code (das du mit Strg+ öffnen kannst) den folgenden Befehl ein, um eine tsconfig.json-Datei zu erstellen, welche die Konfigurationsdatei für TypeScript ist:

tsc --init
Dieser Befehl erzeugt eine tsconfig.json-Datei mit Standardeinstellungen, die du bei Bedarf anpassen kannst.

Schritt 5: TypeScript-Code schreiben

Erstelle eine neue Datei mit der Endung .ts. VS Code erkennt sie automatisch als TypeScript-Datei. Du kannst jetzt beginnen, deinen TypeScript-Code zu schreiben. Zum Beispiel:

function gruessen(person: string): string {
 return `Hallo, ${person}!`;
}
const benutzer = "Entwickler";
console.log(gruessen(benutzer));

Schritt 6: TypeScript in JavaScript kompilieren

Um deine TypeScript-Datei in JavaScript zu kompilieren, kannst du den TypeScript-Compiler im Terminal ausführen:

tsc
Wenn du eine tsconfig.json-Datei in deinem Projekt hast, wird tsc ohne Angabe von Dateien alle in dieser Konfiguration angegebenen Dateien kompilieren.

Schritt 7: Automatische Kompilierung aktivieren

Um den Entwicklungsprozess zu vereinfachen, kannst du die automatische Kompilierung von TypeScript-Dateien beim Speichern aktivieren. Drücke dazu Strg+Shift+P, um die Befehlspalette zu öffnen und tippe ‚Tasks: Configure Default Build Task‘. Wähle tsc: watch – tsconfig.json. Dadurch wird dein TypeScript-Code jedes Mal kompiliert, wenn du ihn speicherst.

Schritt 8: TypeScript-Funktionen in VS Code erkunden

VS Code bietet leistungsstarke Funktionen für die TypeScript-Entwicklung, wie IntelliSense, Code-Navigation und Refactoring-Tools. Nutze diese Funktionen, um deine Produktivität zu steigern.

Schritt 9: TypeScript-Deklarationsdateien installieren

Für die Verwendung bestimmter JavaScript-Bibliotheken mit TypeScript musst du möglicherweise TypeScript-Deklarationsdateien installieren, um Typdefinitionen zu erhalten. Du kannst sie mit npm installieren. Zum Beispiel für die beliebte Bibliothek lodash würdest du ausführen:

npm install --save @types/lodash

Fazit

Visual Studio Code und TypeScript zusammen schaffen eine robuste Umgebung für die Entwicklung skalierbarer und wartbarer JavaScript-Anwendungen. Indem du diesen Schritten folgst, wirst du den vollen Umfang der Leistungsfähigkeit von TypeScript in deinem Entwicklungsworkflow nutzen können.

Viel Spaß beim Programmieren mit TypeScript!

SAP Fiori Elements ABAPConf 2021 Report – CDS Tipps Tricks

SAP Fiori Elements ABAPConf 2021 Report – CDS Tipps Tricks

SAP Fiori Elements List Reports

CDS Tipps & Tricks

Im Rahmen der ABAPConf 2021 durfte die CloudDNA GmbH einen Vortrag zum Thema SAP Fiori Elements – CDS Tipps und Tricks halten.

Dabei zeigen wir Ihnen gängige Anwendungsfälle in der Entwicklung von SAP Fiori Elements Apps. 

Listendarstellung (@UI.lineItem)

@Metadata.layer: #CUSTOMER
annotate view ZC_AC_FLIGHTS with
{
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical; 
}

Listendarstellung mit Header (@UI.headerInfo)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical; 
}

Listendarstellung mit Suche (@Search)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical; 
}

Listendarstellung mit Filtern (@SelectionFields)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 @UI.selectionField: [{ position: 10 }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 @UI.selectionField: [{ position: 20 }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 @UI.selectionField: [{ position: 30 }]
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical; 
}

Listendarstellung mit Value Help (@Consumption.valueHelp)

Listendarstellung mit Select Filter (@ObjectModel.resultSet.sizeCategory)

Listendarstellung mit Date Range (@Consumption.filter.selectionType)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 @UI.selectionField: [{ position: 10 }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 @UI.selectionField: [{ position: 20 }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 @UI.selectionField: [{ position: 30 }]
 @Consumption.filter.selectionType: #INTERVAL
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical;
}

Listendarstellung LineItem Status Indicator (@UI.lineItem.criticality)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
@UI.lineItem: [{criticality: 'SeatsCritical'}]
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH }]
 @UI.selectionField: [{ position: 10 }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 @UI.selectionField: [{ position: 20 }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 @UI.selectionField: [{ position: 30 }]
 @Consumption.filter.selectionType: #INTERVAL
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical;
}

Listendarstellung LineItem Link (@UI.lineItem.type: #WITH_URL)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
@UI.lineItem: [{criticality: 'SeatsCritical'}]
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH, type: #WITH_URL, url: 'Url' }]
 @UI.selectionField: [{ position: 10 }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 @UI.selectionField: [{ position: 20 }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 @UI.selectionField: [{ position: 30 }]
 @Consumption.filter.selectionType: #INTERVAL
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM }]
 Seatsocc;
 @UI.hidden: true
 SeatsCritical; 
}

Listendarstellung LineItem Progress Indicator (@UI.datapoint.visualization)

@Metadata.layer: #CUSTOMER
@Search.searchable: true
@UI.headerInfo.typeName: 'Flight'
@UI.headerInfo.typeNamePlural: 'Flights'
@UI.lineItem: [{criticality: 'SeatsCritical'}]
annotate view ZC_AC_FLIGHTS with
{
 @Search.defaultSearchElement: true
 @UI.lineItem: [{ position: 10, importance: #HIGH, type: #WITH_URL, url: 'Url' }]
 @UI.selectionField: [{ position: 10 }]
 Carrid;
 @UI.lineItem: [{ position: 20, importance: #HIGH }]
 @UI.selectionField: [{ position: 20 }]
 Connid;
 @UI.lineItem: [{ position: 30, importance: #HIGH }]
 @UI.selectionField: [{ position: 30 }]
 @Consumption.filter.selectionType: #INTERVAL
 Fldate;
 @UI.lineItem: [{position: 40, importance: #MEDIUM }]
 Price;
 @UI.lineItem: [{position: 50, importance: #MEDIUM, type: #AS_DATAPOINT }]
 @UI.dataPoint: {targetValueElement: 'Seatsmax', visualization: #PROGRESS, criticality: 'SeatsCritical'}
 Seatsocc;
 @UI.hidden: true
 SeatsCritical;
}

Solltet ihr die ABAPConf 2021 verpasst haben kommt Ihr hier zu You tube , ab 5:22:40 gehts mit unserem Beitrag CDS Tipps & Tricks für Fiori Elements Entwicklung

Unsere Empfehlung SAP Fiori Elememts das Handbuch klicken sie hier: SAP Fiori Elements das Praxishandbuch

SAP Fiori Elements - Das Praxishandbuch
SAPUI5 Custom Controls – Teil 2

SAPUI5 Custom Controls – Teil 2

SAPUI5 Custom Control Teil 2

Der Weg zum eigenen Control

Da wir nun im letzten Teil alles über den generellen Aufbau eines SAPUI5 Custom Controls gelernt haben, möchten wir das ganze in der Praxis anwenden.

 

Wie lege ich im SAPUI5 Custom Control eine Liste von Personen?

Unsere Aufgabenstellung ist folgende:

Wir möchten einen Weg finden, um eine Liste von Personen gemäß hypothetischen Firmen-Design-Guidelines darzustellen. Leider reicht das UI5 Framework für die angenommene Einschränkung nicht aus.

Daher benötigen wir ein Custom Control. Genauer gesagt, benötigen wir folgende Komponenten:

  • PersonList – Darstellung unserer Liste
  • PersonListItem – Eine einzelne Zeile
  • style.css – Custom Styling

Optional können noch folgende Files angelegt werden:

  • PersonListRenderer – Zum ausgelagerten Rendern der Liste
  • PersonListItemRenderer – Zum ausgelagterten Rendern eines Items

PersonListItem im SAPUI5 Custom Control

Wir arbeiten uns von unten nach oben. D.h. wir fangen mit unserem aggregierten Control an, welches eben das einzelne Listitem wäre.

Ausgeben möchten wir Vorname, Nachname und das Alter der Person. Um noch ein Custom-CSS reinzubekommen, werden wir Personen mit einem Alter geringer als 18 anders formattieren.

Erstellen wir zunächst ein neues UI5 Projekt und legen die Files für die Custom-Controls an.

Anschließend definieren wir ein Template für das PersonListItem:

Wir wissen, dass wir mit sap.ui.define ein neues Modul anlegen, das zur Laufzeit dynamisch von unserem UI5 Framework geladen wird. Und genau so definieren wir eben auch ein Custom Control. Dieses inkludiert das sap.ui.core.Control, welches wir per .extend erweitern und zurückliefern.

Unsere PersonListItem soll 3 verschiedene Personaleigenschaften ausgeben. Daher brauchen wir 3 Properties (Vorname, Nachname, Alter).

Aggregations soll unser PersonListItem nicht haben, denn wir möchten das PersonListItem selbst als zu aggregierendes Control verwenden.

Properties anlegen

Wir benötigen 3 Properties:

  • firstName: string
  • lastName: string
  • age: int

Auf die Properties kann per get<PropertyName>() zugegriffen werden.

 

Events anlegen

Wir möchten, dass bei einem Click auf das PersonListItem ein Event ausgeführt wird. Dieses liefert eine Hallo-Message zurück.

  • sayHello
    • parameter: message(string)

Anschließend definieren wir eine Eventhandler für das HTML-onclick-Event. Falls dieses auftritt, triggern wir das sayHello-Event und liefern eine Message bestehend aus Name und „says hello“ per message-Parameter zurück.

 

CSS Styling

Wir möchten unsere 3 Properties per HTML-ul-Element anzeigen. Da ist jedoch das Problem, dass die Liste standartmäßig untereinander ihre Items anordnet. Um das zu umgehen, sagen wir per CSS-Klasssenselektor, das alle li-Elemente unter der Klasse die CSS-Property display mit dem Wert inline bekommen.

Und zusätzlich definieren wir noch ein padding von 10 Pixel und die Schriftfarbe black;

 

Renderer

Der Renderer ist das Herzstück unseres Controls. Hier wird definiert, was wann und wie in das DOM eingtragen werden soll.

Die wichtigsten Renderer-Funktionen sind dem letzten Blog-Teil zu entnehmen.

Wir möchten unser Control als div-Element und einer ul ausgeben. Zusätzlich möchten wir noch das Alter dementsprechend farblich hervorheben, basierend auf dem Kriterium, dass das Alter > 18 ist.

1: div ausgeben

Zu aller erst erstellen wir per openStart ein öffnendes div-Tag. Dieses div-Tag wird per writeControlData die Control-Referenz geschrieben und anschließend fügen wir noch unsere CSS-Klasse hinzu. Dann wird das öffnende div-Tag mit > geschlossen.

2: Liste aufbauen

Wir erstellen eine neue unordered-list. Als erstes List-Item geben wir den Vornamen der Person aus und dann den Nachnamen.

3: Alter ausgeben

Je nach dem ob die Person ein Alter > 18 hat, erstellen wir ein List-Item mit dem Style colorred oder color: green.

4: Alles schließen

Zu letzt schließen wir die Liste und abschließend das div-Tag.

 

Somit ist unser PersonListItem komplett. Im nächsten Schritt werden wir nun die PersonList definieren und unser PersonListItem aggregieren.

PersonList im SAPUI5 Custom Control

Die PersonList soll einen Titel bekommen und unsere Personen per PersonListItem anzeigen.

Hierfür benötigen wir Properties und Aggregations.

Metadata definieren

Wir benötigen folgende Property für unseren Titel:

  • title (string) – default: Persons

Zusätzlich möchten wir noch PersonListItems aggregieren:

  • items (PersonListItem) – multiple: true

CSS Styling

per list-style: none sagen wir, dass unsere Personenliste keine Aufzählungszeichen haben sollte.

Dann selektieren wir alle Items mit einem ungeraden Index und färben diese grau ein.

Zusätzlich geben wir noch ein seitliches margin von 15 Pixel.

Renderer definieren

In unserem Renderer wollen wir den Titel der Personenliste und aggregiert die PersonListItems ausgeben.

1: div öffnen

Gleich wie beim PersonListItem öffnen wir einen div-Container und vergeben CSS-Klasse und Control-Data.

2: Titel ausgeben

Der Titel wird innerhalb eines h2-Tags ausgebeben.

3: Items-Aggregation rendern

Per getAggregation(„items“) können wir uns den Inhalt der Items-Aggregation in einem Array speichern. Dieses Array laufen wir durch und erstellen für jeden Eintrag ein neues li-Tag mit der myPersonListItemSelector-Klasse. Dieses bekommt ihren Inhalt per renderControl()-Funktion, welche die render-Methode des übergebenen Controls aufruft. In unserem Fall ist das das PersonListItem.

4: alles schließen

 Abschließend fügen wir noch die schließenden ul- und div-Tags hinzu.

Jetzt sind unsere Custom-Controls fertig und können in den Views verwendet werden.

Controls verwenden

Die beiden Custom Controls können nun in unserer View verwendet werden.

1: Namespace einbinden

Um Controls aus einem anderen Namensraum einzubinden, muss für diesen ein Alias angelegt werden. Wir verweisen hier auf unseren Folder, wo wir die Controls angelegt haben.

2: PersonList einfügen

Wir legen eine neue PersonList an. Dort vergeben wir die title-Property und legen eine items-Aggregation an.

3: PersonListItems hinzufügen

In der items-Aggregation legen wir nun mehrere PersonListItems an. Das würde natürlich auch über Aggregation-Binding in der PersonList per Model funktionieren.

Wir legen aber z.B. 3 PersonListItems an und vergeben allen Properties Werte. Zusätzlich definieren wir eine Funktion für unser sayHello-Event.

Dieses müssen wir dann noch im zugehörigen Controller ausprogrammieren.

Also definieren wir eine neue Methode namens onSayHelloHandler, wo wir den Eventparameter message auslesen und per alert anzeigen.

App ausführen

Unsere App ist nun funktionsfähig und zeig unsere Custom-Controls richtig an.

Wir sehen nun unseren Titel und 3 darunterliegende Personen, die farblich abwechselnd hinterlegt sind.

Zusammenfassung

In 2 Blogteilen haben wir nun alles Notwendige gelernt, um mit dem Entwickeln von eigenen Custom-Controls durchzustarte, falls die Anforderungen nicht durch das umfangreiche SAPUI5 Controls-Paket abgedeckt wird.

Eine weitere Vorgehensweise könnte sein, meine eigenen Custom-Controls in eine UI5-Library/Git Repo zu packen und zu deployen. Dann kann ich diese in weiteren Applikationen verwenden und kann mir meine eigene, umfangreiche Library bauen.

Nur sollte man nicht für jede Anforderung ein neues Control entwickeln oder ein bestehendes zu erweitern. Man sollte so gut und so weit wie möglich mit dem arbeiten, was einem das SAPUI5 Framework zur Verfügung stellt und sich so weit wie möglich in die Materie einarbeiten.

Wer also gerne mehr bezüglich UI5 wissen möchte, kann sich gerne unseren zahlreichen Blogs bedienen.

Die interessantesten für das Controls-Thema sind:

Vielen Dank für das Lesen dieser Blogreihe und wenn Fragen auftreten, können sie gerne in den Kommentaren gestellt werden.