SAPUI5 Custom Controls – Teil 1

SAPUI5 Custom Controls – Teil 1

Das Konzept

In SAP UI5 ist es möglich, bestehende Controls zu erweitern, sobald die normalen Funktionalitäten eines Controls nicht mehr ausreichen. Ebenso lassen sich ganz neue Controls erstellen, um den wachsenden Anforderungsschwierigkeiten gewachsen zu sein.

Leider weicht man jedoch dann vom Fiori-Standard ab und ist nicht mehr Guideline-Konform. Jedoch kann es trotzdem sein, dass man eine Aufgabenstellung zu bewältigen hat, die mit dem Standard nicht abzubilden ist.

Genau hier tritt das Custom-Control-Konzept ins Spiel.

 

Der Aufbau

Grundsätzlich ist ein UI5 Control nichts anderes als ein Modul, das zur Laufzeit dynamisch nachgeladen wird. Ein solches Modul wird mit sap.ui.define erstellt und kann anschließend angesprochen werden.

Des Weiteren wird ein UI5 Control immer als HTML-Element gerendered. Wie genau das aussieht, wird immer im Control-spezifischen render-Methode über den RenderManager definiert. Diese Methode wandelt das Custom Control in die HTML-Elemente um, wie es der Entwickler gerne haben möchte. So wird zum Beispiel beim Rendern einer sap.m.Table eine HTML-table mit td- und tr-Elementen in das DOM eingetragen und mit SAP-spezifischen CSS-Klassen gestyled.

Der Aufbau von Eigenschaften eines Controls wird immer in seinen Metadaten definiert. Ein Control hat ein dementsprechendes metadata-Object, wo Properties, Events, Aggregations und Associations definiert werden. Diese können dann in der View und über den Controller angesprochen werden.

Dies sieht wie folgt aus:

Sobald ich die Methode extend von dem sap.ui.core.Control aufrufe, erstelle ich ein neues Control. Wenn ich das extend von einem bestehenden Control aufrufen, z.B. dem sap.m.Button, würde ich dieses erweitern.

Nach dem ich nun ein neues Control erstellt habe, kann ich die Metadaten definieren:

 

Properties

Control-Properties bekommen ein Object zugewiesen, in dem ich den Datentyp zuweise und gegebenenfalls einen Default-Wert hinterlege.

Folgende Einstellungen kann ich also vornehmen:

  • type – Default type ist string.
  • defaultValue

Zulässige Datentypen für die type-Property sind:

  • string
  • int
  • float
  • string[]

Ich kann auch eigene Konfigurationsproperties innerhalb meiner Property erstellen und diese dann zur Datenverarbeitung nutzen.

Aggregations

Aggregations werden mit 3 verschiedenen Konfigurationen erstellt:

  • type – Default type ist sap.ui.core.Control.
  • multiple – Default ist true.
  • singularName
  • visibility 

Type gibt an, welches Control ich gerne aggrigieren möchte. Manchmal sind solche Aggregation-Controls nicht Module die von sap.ui.core.Control erben, sondern von der abstrakten Klasse sap.ui.core.Element. Sie haben daher keinen eigenen Renderer. Das Rendering von diesen Controls übernimmt das Control, das diese Elemente aggregiert. Mein Custom Control kann natürlich auch andere Custom Controls aggregieren.

Mit multiple: true kann ich sagen, ob meine Aggregation 0 bis N Controls aggregieren kann, oder ob nur ein Control in meiner Aggregation zugelassen wird.

Die Property singularName gibt einen String an, der für automatisch generierte Methoden eingefügt werden soll, um z.B. ein neues Item in die Items-Aggregation einzufügen.

Per visibility-Property lege ich fest, ob meine Aggregation in der View benutzt werden kann und ob sie für den Entwickler sichtbar sein sollte. Wenn die visibility auf Verbergen sein sollte, dann muss die Value „hidden“ vergeben werden.

Associations

Mit Associations können Controls miteinander verknüpft werden, bzw. es kann eine Abhängigkeit erstellt werden. Das bekannteste Beispiel ist die labelFor-Property/Association vom sap.m.Label.

Associations haben den gleichen Konfigurations-Aufbau wie Aggregations. Sie haben daher auch:

  • type
  • multiple
  • singularName
  • visibility

Events

Controls können Eventhandler für bestimmte HTML-Events definieren. Für jedes selbstdefinierte Event werden automasich folgende Funktionen erstellt:

  • attach<Name>
  • detach<Name>
  • fire<Name>

Somit würde ich z.B. für mein Custom Event saveForm die Funktionen attachSaveForm, detachSaveForm und fireSaveForm automatisch generiert bekommen.

Sobald ich ein Event erstellt habe, muss ich nur mehr den dementsprechenden HTML-Eventhandler definieren und dort die fire-Methode meines Events aufrufen. Für die Event-Handler-Implementierung erstelle ich innerhalb meines Controls eine neue Methode, die genau so heißt wie das dementsprechende HTML-Event.

Events können auch Parameter definieren, die ich bei einem fireEvent befülle.

myEvent: {

    parameters: { wasClicked: {type: „boolean“}}

}

Zusätze

Zusätzlich zu den 4 Basis-Konfigurationen stehen noch mehr Optionen zur Verfügung.

So kann ich

  • dnd – Drag and Drop
  • designtime – Design

zusätzlich definieren und konfigurieren. Auch kann ich das Metadaten-Objekt um eigene Einstellungen erweitern.

 

Coding

Die wichtigste Methode unseres Controls ist die onInit-Methode. Diese wird beim Initialisieren unseres Controls aufgerufen und dient mir als Startmöglichkeit.

Weitere Funktionen können ganz normal als Objekt-Methoden eingefügt werden. Hier sollte aber auf das Naming der Methoden geachtet werden, damit es keine Überschneidungen zu automatisch generierten Methoden kommt.

Mit Hilfe der automatisch generierten Set– und Get-Methoden meiner Properties kann ich jetzt auf diese zugreifen.

 

Render Manager

Nachdem ich nun die Metadaten und das Coding fertig habe, muss ich das Control auch dementsprechend anzeigen.

Hierfür dient der Render Manager in der renderer-Methode meines Controls. Mit dessen Hilfe kann ich in das DOM eingreifen und mein Control platzieren. Der Render Manager kommt in der renderer-Methode meines Controls zum Einsatz. Benutzt wird normaler HTML-Code, um unser Control in das DOM zu bringen.

Normalerweise wird die renderer-Methode in ein eigenes File ausgelagert.

Die wichtigsten Funktionen des RM’s sind:

  • openStart – Erstellt ein neues öffnendes Element <…
  • openEnd – Schließt das öffnende Element <…>
  • close – Schließt das Element </…>
  • write – Schreibt HTML-Code
  • writeControlData – Schreib die Conrol-ID in das DOM
  • renderControl – Ruft die renderer-Methode eines aggregierten Controls auf
  • class – Fügt eine CSS-Klasse dem HTML-Element hinzu
  • attr – Fügt dem Element ein Attribut mit Value hinzu

Zusammenfassung

Wir sehen, dass das Erstellen eines Custom-Controls einige Möglichkeiten beinhaltet. Wir können Properties, Aggregations, Events und Associations definieren. Zusätzlich benötigen wir noch die renderer-Methode mit dem RenderManager, um unser Control in das DOM zu bringen und anzuzeigen.

Im nächsten Blog-Part 2 werden wir ein Beispiel-Control entwickeln und uns im Detail die Implementierung ansehen.

Falls Fragen auftauchen, können sie gerne unten in den Kommentaren gestellt werden.  Ich freue mich auf Feedback.

RestModel für SAP UI5

RestModel für SAP UI5

Was?

Sie fragen sich vieleicht: RestModel in SAPUI5? Verwenden wir nicht normalerweise OData oder laden uns Daten in ein JSONModel?

Nun, mit dem RestModel für SAPUI5 lassen sich ganz leicht RESTful-Webservices konsumieren.

Warum?

Es gibt immer wieder die Anforderung, UI5 Apps zu bauen, die keinen OData-Service als Backend-Service benutzen. Jedoch ist das SAPUI5 Framework darauf ausgelegt, mit OData zu kommunizieren. Dies merkt man direkt an der tollen Integration der ODataV2(OData in seiner reinsten Form) und ODataV4(es ist noch Luft nach oben)-Models.

Jetzt kann es jedoch sein, dass überhaupt kein OData-Service zur Verfügung steht, sondern nur ein RESTful-Webservice. Standartmäßig könnte man dieses Problem folgendermaßen lösen:

  • JSONModel.loadData(……)
    • Mit der .loadData-Methode des JSONModels könnte man einen GET-Request auf eine externe Ressource abschicken.
  • $.ajax(….)
    • Per ajax und jQuery lassen sich asynchrone Requests per Javascript losschicken. Im Grunde verwenden die bekannten UI5-Models im Hintergrund ajax-Calls.

Jedoch sind beide Lösungen nicht immer die optimalsten, vor allem da zB. das JSONModel eher als client-seitiges Model darauf ausgelegt ist, lokale Daten zu speichern.

Deshalb haben wir ein RestModel auf Basis des HTTP-Clients axios entwickelt.

Bei uns findet es auch Anwendung in unserem CloudDNA OData-Plugin.

Wie?

Das RestModel benutzt den axios-Client, um HTTP-Calls gegen einen RESTful-Webservice abzuschicken. Axios ist ein HTTP-Client für Browser und node.js, der von Matt Zabriskie als Open-Source Projekt unter der MIT-Lizenz angeboten wird. 

Das RestModel baut nun auf den bereits umfangreichen axios-Client auf und schneidert die Funktionalitäten auf UI5-Ansprüche zu. So z.B. das Mapping von Destinations auf selbstdefinierbare Zugriffspunkte in der neo-app.json.

Folgende Funktionen stehen zur Verfügung:

CRUD – Die Basics

  • create – POST um Daten wegzuschicken,
  • read – GET um sich Daten zu holen
  • update – PUT um Daten zu verändern
  • remove – DELETE um Daten zu löschen

Die CRUD-Funktionen des RestModels ähneln sich syntaktisch den ODataModel-CRUD-Funktionen. Dies wurde aus einem speziellen Grund so gewählt: Die Verwendung des RestModels soll sich gewohnt anfühlen.

So wird zB. ein RestModel-Read folgendermaßen abgesendet:



this._oModel.read("/Customer", {
     success: function (oData) {
         oCodeEditor.setValue(JSON.stringify(oData, null, "\t"));
     }.bind(this)
});

Dies sieht im 1. Moment aus wie ein Read des ODataModels.
Jedoch steht hier eine Instanz des RestModels dahinter.


this._oModel = new RestModel({
    url: "&lt;myservice.com/api&gt;",
});

Mit dieser RestModel-Instanz und dem dahinterliegenden axios können jetzt in gewohnter ODataModel-Manier Rest-Calls abgesetzt werden. So werden die CRUD-Methoden plus Parameter und Header unterstützt.

Das Resultat eines Requests lässt sich entweder über Callback-Funktionen oder auch per Promise abarbeiten. Somit ist man nicht mehr auf Success- und Error-Callback gebunden, sondern kann auf das Javascript Promise-Konzept zugreifen. Die zurückgelieferten Daten können z.B. in ein JSONModel geladen werden und an die View gebunden werden.

Zusätzlich – Die ‚Schmankerl‘

  • bearerTokenLogin – Speichern eines Bearer-Tokens
  • setXCSRFTokenHandling – Cross-Site-Ressource-Forging Handling
  • ….

Das RestModel bietet noch weitere nützliche Features an. So gibt es zB. die Unterstützung eines Bearer-Token Logins, sofern der Webservice diesen anbietet.

Ebenfalls kann das X-CSRF-Token gespeichert und als Request-Header für alle weiteren Requests gesetzt werden.  Dieses kennt man als UI5-Entwickler besonders aus dem File-Upload Gebiet.

Open-Source?

Das RestModel ist natürlich Open-Source und kann auf github geklont werden. Dort wird es immer mit neuen Updates versorgt und korrigiert.

So kommen in Zukunft neue Features hinzu, wie zB. die Speicherung der angeforderten Daten per Model, so dass ein Binding betrieben werden kann und Daten per View geladen werden können.

Ebenfalls würden wir uns über die Mitarbeit anderer Entwickler an diesem Open-Source-Projekt freuen, um das bestmögliche aus dem RestModel herauszuholen.

Alles klar

Getreu unserem Leitfaden

-Von Entwickler – Für Entwickler-

 

sollte das RestModel besonders Entwickler in UI5-Umfeld nützlich sein.

Wer also auf REST-Services zugreifen möchte und mit den ODataModel-Methoden vertraut ist, für den ist das RestModel interessant und sicherlich eine Erleichterung bei REST-Calls.

Einen detailierteren Einblick mit Dokumentation gibt es auf der github-Repository-Seite des RestModels.

Wir freuen uns über konstruktive Kritik und Anregungen und falls Fragen bestehen, zögern Sie nicht und kommentieren Sie unterhalb laughing

 

Die UI5-Media-Entity Journey – Part 4

Die UI5-Media-Entity Journey – Part 4

In dieser 4-Teiligen Blogreihe erfahren Sie alles nötige, um Media-Entity-Handling in SAP UI5 laut gängigen Best-Practice-Erfahrungen zu implementieren.

Einen tieferen Einblick in verschiedenste Themen gibt es in unseren Kursen bei Die Entdecker, die offiziell über training.sap.com angeboten werden.

Für diesen Blog interessante Trainings:

  • HOUI5 – Hands-on Foundation zur Entwicklung von SAPUI5 Applikationen
  • HOFIO – Hands-on Deep Dive zur Entwicklung von SAP Fiori Oberflächen
  • WDECPS – SAP Cloud Platform Security Eine ganzheitliche Betrachtung

Inhalt

Im vierten Teil der Blogserie erweitern wir die UploadCollection um eine Download-Funktion.

Schritt 4 – File Download

Nachdem wir nun die Upload-Funktionalität fertig haben, möchten wir unsere Files auch downloaden können. Deshalb müssen wir noch ein paar Änderungen vornehmen.

Hilfreiche Links:

    View anpassen

    Um die UploadCollection Downloadfähig zu machen, müssen wir ein paar Anpassungen machen.

    1. Den List-Modus der UploadCollection auf MultiSelect umstellen.
      1. Wenn wir den Modus auf MultiSelect ändern, können wir mehrere Items, die heruntergeladen werden sollen, auswählen.
    2. Toolbar einfügen.
      1. Wir erweitern die Standart-Toolbar der UploadCollection um einen Button, mit dem wir die selektierten Items herunterladen können. Dieser soll, sofern keine Items ausgewählt sind, disabled sein.
    3. Das UploadCollectionItem erweitern.
      1. Die Property url des UploadCollectionItems wird mit einer formattierten URL befüllt, die auf den URL-Parameter $value des jeweiligen Files zeigt.

    <UploadCollection id=“main_uploadcollection“ items=“{/FileSet}“ beforeUploadStarts=“onBeforeUploadStarts“ change=“onUploadChange“

    mode=“MultiSelect“ selectionChange=“onUploadSelectionChange“>

    <toolbar>

    <OverflowToolbar>

    <ToolbarSpacer/>

    <Button id=“main_downloadbtn“ text=“{i18n>btn.download}“ press=“onDownloadPress“ type=“Transparent“ enabled=“false“/>

    <UploadCollectionToolbarPlaceholder/>

    </OverflowToolbar>

    </toolbar>

    <items>

    <UploadCollectionItem url=“{path: ‚Fileid‘, formatter: ‚.formatUrl‘}“ documentId=“{Fileid}“ fileName=“{Filename}“ mimeType=“{Mimetype}“

    enableEdit=“false“ enableDelete=“false“ visibleDelete=“false“ visibleEdit=“false“/>

    </items>

    </UploadCollection>

    Controller bearbeiten

    Zuerst programmieren wir den Formatter aus, den wir im url-Parameter verwenden. Dieser Formatter gibt einen String zurück, der die URL unseres Models plus URI unseres Files plus dem URL-Parameter $value beinhaltet. Mit dieser URL kann das File heruntergeladen werden.

    Anschließend benötigen wir noch den Eventhandler für den Button. Dieser Handler sucht sich alle selektierten UploadCollectionItems heraus und übergibt diese an die downloadItem-Funktion der UploadCollection.

    Zu guter Letzt fehlt noch der SelectionChange-Eventhandler. Dieser bewirkt das aktivieren und deaktivieren des Download-Buttons wenn Items bzw. keine Items ausgewählt wurden.

    formatUrl: function (sFileid) {

                                                let sUrl = this.getView().getModel().sServiceUrl;

                                                sUrl += „/“ + this.getView().getModel().createKey(„FileSet“, {

                                                               Fileid: sFileid

                                                });

                                                sUrl += „/$value“;

                                                return sUrl;

                                  },

     

                                  onDownloadPress: function (oEvent) {

                                                let oUploadCollection = this.getView().byId(„main_uploadcollection“),

                                                               aSelectedItems = oUploadCollection.getSelectedItems();

                                                for (var i = 0; i < aSelectedItems.length; i++) {

                                                               oUploadCollection.downloadItem(aSelectedItems[i], true);

                                                }

                                  },

     

                                  onUploadSelectionChange: function (oEvent) {

                                                let oUploadCollection = oEvent.getSource(),

                                                               oButton = this.getView().byId(„main_downloadbtn“);

                                                if (oUploadCollection.getSelectedItems().length > 0) {

                                                               oButton.setEnabled(true);

                                                } else {

                                                               oButton.setEnabled(false);

                                                }

                                  },

    Testen

    Wenn nun alle Änderungen korrekt übernommen wurden, können die Files entweder einzeln per Klick auf den Namen oder per Mehrfachselektion heruntergeladen werden.

    Zusammenfassung

    Wir sind fertig!wink

    Im letzen Schritt haben wir unser UploadCollection-Projekt fertig gemacht und können nun Files rauf- und runterladen.

    Nochmals alles von Anfang an:

    1. Wir erstellen uns DDIC-Objekte zur Speicherung und zum Anzeigen von unseren Files.
    2. Wir konfigurieren einen OData-Service mit einer MediaEntity und redefinieren die STREAM-Methoden.
    3. Wir erstellen eine UI5-App und binden die UploadCollection ein und laden Files per OData-Service hoch.
    4. Wir laden Files per downloadItems-Funktion der UploadCollection herunter.

    Ich bedanke mich für das Lesen unseres Blogs über MediaHandling mit der UploadCollection und würde mich freuen, wenn man sich wieder auf einem neuen Blog sieht.

    Falls Fragen zu diesen einzelnen Schritten auftreten, können sie gerne in den Kommentaren gestellt werden.

     

     

    Die UI5-Media-Entity Journey – Part 4

    Die UI5-Media-Entity Journey – Part 3

    In dieser 4-Teiligen Blogreihe erfahren Sie alles nötige, um Media-Entity-Handling in SAP UI5 laut gängigen Best-Practice-Erfahrungen zu implementieren.

    Einen tieferen Einblick in verschiedenste Themen gibt es in unseren Kursen bei Die Entdecker, die offiziell über training.sap.com angeboten werden.

    Für diesen Blog interessante Trainings:

    • HOUI5 – Hands-on Foundation zur Entwicklung von SAPUI5 Applikationen
    • HOFIO – Hands-on Deep Dive zur Entwicklung von SAP Fiori Oberflächen
    • WDECPS – SAP Cloud Platform Security Eine ganzheitliche Betrachtung

    Inhalt

    Im dritten Teil der Blogserie erstellen wir eine SAPUI5-App und integrieren die sap.m.UploadCollection.

    Schritt 3 – File Upload

    Wir erstellen nun eine SAPUI5 Applikation über die SAP WebIDE. Des weiteren binden wir den OData-Service ein, den wir in Schritt 2 erstellt haben, um Files zu speichern und anzuzeigen,

    Hilfreiche Links:

     

    Voraussetzungen

    • Zugang zur SAP Web IDE
    • Ein korrekt eingerichteter Cloud Connector mit dazugehöriger Destination in der SAP CP

    App erstellen

    Wir wechseln in die SAP WebIDE und legen per Create Project from Template ein neues SAPUI5 Projekt an.

    Nennen wir sie ZDemoFile im Namespace at.demo.file und benutzen als Default-View eine View mit dem Namen Main.

    Da wir nur eine View anzeigen und keine Navigation haben, benötigen wir nicht eine weitere View als Navigationscontainer.

    Im i18n.properties-File kann noch der Titel der Page angepasst werden.

    OData-Service hinzufügen

    Sobald die App nun erstellt ist, kann per rechtsklick ein neuer OData-Service hinzugefügt werden. Hier bitte die Voraussetzungen oben beachten.

    Wir suchen, sobald wir die richtige Destination ausgewählt haben, unseren Service und setzen diesen als Default-Model für unsere App.

    UploadCollection implementieren

    Als 1. Schritt implementieren wir die UploadCollection samt UploadCollectionItem.

    Diese beiden Controls befinden sich im sap.m.-Standartnamensraum und können daher ohne Alias definiert werden.

     Wir definieren folgende Properties für die Upload Collection:

    • items=“{/FileSet}“
      • Zeigt auf unser EntitySet.
    • beforeUploadStarts=“onBeforeUploadStarts“
      • Eventhandler für das Event welches aufgerufen wird, bevor der Upload beginnt.
    • change=“onUploadChange“
      • Eventhandler, der zieht sobald ein File zum Upload ausgewählt wird.

    Und für das UploadCollectionItem

    • documentId=“{Fileid}“
    • fileName=“{Filename}“
    • mimeType=““{Mimetype}

    <UploadCollection id=“main_uploadcollection“ items=“{/FileSet}“ beforeUploadStarts=“onBeforeUploadStarts“ change=“onUploadChange“><UploadCollection id=“main_uploadcollection“ items=“{/FileSet}“ beforeUploadStarts=“onBeforeUploadStarts“ change=“onUploadChange“> <items> <UploadCollectionItem documentId=“{Fileid}“ fileName=“{Filename}“ mimeType=“{Mimetype}“ enableEdit=“false“ enableDelete=“false“ visibleDelete=“false“ visibleEdit=“false“/> </items> </UploadCollection>

    Sobald die App nun gestartet wird, sehen wir das in Schritt 2 hochgeladene File.

    Controller bearbeiten

    Wir wechseln nun in den Main-Controller.

    Dort implementieren wir die beiden Eventhandler uns setzten die Upload-URL für die UploadCollection.

    Zuerst speichern wir uns in der onInit-Funktion unsere UploadCollection und setzen die Upload-URL. Diese setzt sich aus der Service-URL unseres Models und dem Entity-Set FileSet zusammen.

    In der onUploadChange-Funktion behandeln wir den XCSRF-Header. Dieser ist für Cross-Site-Resource-Forging zuständig und muss gesetzt werden. Das ODataModel bietet mit der Funktion getSecurityToke() die Möglichkeit, sich ein solches XCSRF-Token zu generieren lassen. Dass muss anschließend noch als HeaderParameter gesetzt werden.

    In der onBeforeUploadStart-Funktion muss der Slug-Header gesetzt werden. Den Filenamen bekommen wir über das Event-Objekt heraus.

    sap.ui.define([

                   „sap/ui/core/mvc/Controller“,

                   „sap/m/UploadCollectionParameter“

    ], function (Controller, UploadCollectionParameter) {

                   „use strict“;

     

                   return Controller.extend(„at.demo.file.ZDemoFile.controller.Main“, {

                                  oFileUploader: null,

     

                                  onInit: function () {

                                                this.oFileUploader = this.getView().byId(„main_uploadcollection“);

                                                 this.oFileUploader.setUploadUrl(this.getView().getModel().sServiceUrl + „/FileSet“);

                                  },

     

                                  onUploadChange: function (oEvent) {

                                                let oCSRFHeader = new UploadCollectionParameter({

                                                               name: „x-csrf-token“,

                                                               value: this.getView().getModel().getSecurityToken()

                                                });

     

                                                this.oFileUploader.removeAllHeaderParameters();

                                                this.oFileUploader.addHeaderParameter(oCSRFHeader);

                                  },

     

                                  onBeforeUploadStarts: function (oEvent) {

                                                let oHeaderSlug = new UploadCollectionParameter({

                                                               name: „slug“,

                                                               value: oEvent.getParameter(„fileName“)

                                                });

     

                                                oEvent.getParameters().addHeaderParameter(oHeaderSlug);

                                  },

                   });

    });

    .

    Upload testen

    Sobald nun der Controller passt, kann die Upload-Funktionalität getestet werden.

    Mit Hilfe des Hochladen-Buttons kann jetzt einen Daten geuploadet werden. Die UploadCollection zeigt anschließend das hochgeladene File in der Liste an. Solle es nicht angezeigt werden, kann dem mit einem Refresh auf das Model oder die Items-Aggregation abhilfe geschafft werden. 

    Zusammenfassung

    Alles klapptwink

    In diesem Schritt haben wir die Upload-Funktion für unsere Media-Entity implementiert. Aber nur der Upload führt noch nicht zum Ziel, denn wir möchten ja auch downloaden können.

    Dies geschieht in Schritt 4, wo wir die UploadCollection um die Download-Funktion erweitern.

    Falls Fragen zu diesen einzelnen Schritten auftreten, können sie gerne in den Kommentaren gestellt werden.

     

     

    Die UI5-Media-Entity Journey – Part 2

    Die UI5-Media-Entity Journey – Part 2

    In dieser 4-Teiligen Blogreihe erfahren Sie alles nötige, um Media-Entity-Handling in SAP UI5 laut gängigen Best-Practice-Erfahrungen zu implementieren.

    Einen tieferen Einblick in verschiedenste Themen gibt es in unseren Kursen bei Die Entdecker, die offiziell über training.sap.com angeboten werden.

    Für diesen Blog interessante Trainings:

    • HOFIO – Hands-on Deep Dive zur Entwicklung von SAP Fiori Oberflächen
    • WDECPS – SAP Cloud Platform Security Eine ganzheitliche Betrachtung

    Inhalt

    Im zweiten Schritt unserer OData-Journey erfahren wir alles nötige, um einen OData-Service für unsere Medien zu erstellen.

    Wir werden einen SEGW-Projekt erstellen, einen OData-Service einrichten und die in Teil 1 erstellte CDS-View mappen. Außerdem werden wir noch die nötigen STREA-Methoden überschreiben und redefinieren. 

    Voraussetzungen:

     

     

    Schritt 1 – OData-Service erstelleln

    Damit wir unsere Media-Entries auch aus einer UI5-App heraus auslesen können, brauchen wir einen OData-Service, der diese Daten freigibt.

    Dazu gehen wir in die SEGWSAP Gateway Service Builder – und erstellen eine neues lokales Projekt. 

    Struktur importieren

    Sobald der Service nun erstellt wurde, können wir eine neue Entity basieren auf unserer File-Struktur anlegen. Dazu per Rechtsklick eine ABAP-Dictionary-Struktur importieren.

    Wir nennen unsere Entity File und geben unsere File-Struktur an.

    Im folgenden Screen müssen die Felder ausgewählt werden, die in die Entity importiert werden sollen. Hier selektieren wir alle Felder bis auf den Mandanten, da wir diesen nicht freigeben wollen.

    Anschließend wählen wir noch das Schlüsselfeld für unsere Entity aus.

    Damit wir in den Metadaten des Services noch die Info bekommen, welche Entity eine Media-Entity ist, setzten wir das Flag Medium auf true.

    CDS-View mappen

    Damit wir uns die Redefinitionen der GET_ENTITY und der GET_ENTITYSET sparen, können wir die in Teil 1 erstellte CDS-View importieren. Diese nimmt und die Funktionalität der beiden GET-Methoden ab und ermöglicht Filterung und Sortierung.

    Wir wählen eine Business Entity aus und geben unseren DDL-View-Namen an.

    Wenn nun unsere CDS-View gefunden wurde, können die Felder der View den Feldern der Entity zugeordnet werden. Falls Navigation-Properties/Associations existieren, können diese hier ebenfalls gemappt werden.

    DEFINE redefinieren

    Damit unser OData-Service weiß, welche Entity eine Media-Entity ist, bzw ob hier eine Stream-Methode dahinter liegt, muss die DEFINE-Methode der Model-Provider-Class (MPC_EXT) redefiniert werden. Hier sagen wir, welche Entity eine Media-Entity ist.

    Wichtig für uns:

    • set_as_content_type
      • Hier wird das Feld angegeben, das den Mime-Type der Datei beinhaltet.
    • set_as_content_source
      • Hier wird das Feld angegeben, das den Inhalt der Datei beinhaltet.

      method DEFINE.
        DATA: lo_entity   TYPE REF TO /iwbep/if_mgw_odata_entity_typ.
        DATA: lo_property TYPE REF TO /iwbep/if_mgw_odata_property.

        super->define( ).

        lo_entity = model->get_entity_type( iv_entity_name = ‚File‘ ).

        IF lo_entity IS BOUND.

          lo_property = lo_entity->get_property( iv_property_name = ‚Mimetype‘ ).
          lo_property->set_as_content_type( ).

          lo_property = lo_entity->get_property( iv_property_name = ‚Content‘ ).
          lo_property->set_as_content_source( ).
        ENDIF.
      endmethod.

    GET_STREAM redefinieren

    Damit wir unsere Media-Entity als Datei geliefert bekommen, müssen wir die GET_STREAM-Methode der Data-Provider-Class (DPC_EXT) redefinieren. Hier selektieren wir mit dem übergebenen Schlüssel einen Eintrag unserer Tabelle und setzen die Response-Header.

    Für uns wichtig:

    • it_key_tab
      • Tabelle mit den übergebenen Keys.
    • ls_stream
      • Struktur, in der der File-Stream mit Content und MimeType gespeichert werden.
    • ls_header
      • Struktur für den Response-Header

    METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.
        DATA: ls_key      TYPE /iwbep/s_mgw_name_value_pair,
              lv_fileid   TYPE sysuuid_c,
              ls_stream   TYPE ty_s_media_resource,
              ls_header   TYPE ihttpnvp,
              lv_filename TYPE string,
              ls_file     TYPE zdemo_file_s.

    *   Keys lesen
        READ TABLE it_key_tab WITH KEY name = ‚Fileid‘ INTO ls_key.
        lv_fileid = ls_key-value.

    *   File aus Datenbank lesen
        SELECT SINGLE * FROM zdemo_file INTO @ls_file WHERE fileid = @lv_fileid.

        IF sy-subrc <> 0.
          mo_context->get_message_container( )->add_message_text_only(
            iv_msg_type = ‚E‘
            iv_msg_text = TEXT-003
          ).

          RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
            EXPORTING
              textid            = /iwbep/cx_mgw_busi_exception=>business_error
              message_container = mo_context->get_message_container( ).
        ENDIF.

    *   Werte zuweißen
        ls_stream-value = ls_file-content.
        ls_stream-mime_type = ls_file-mimetype.
        lv_filename = ls_file-filename.
        lv_filename = escape(
          val = lv_filename
          format = cl_abap_format=>e_url
        ).

    *   Header setzen
        ls_header-name = |content-disposition|.
        ls_header-value = |inline; filename={ lv_filename }|.
        set_header( is_header = ls_header ).

        copy_data_to_ref(
           EXPORTING
             is_data = ls_stream
           CHANGING
             cr_data = er_stream ).
      ENDMETHOD.

    CREATE_STREAM überschreiben

    Damit wir auch Files speichern können, überschreiben wir die CREATE_STREAM-Methode. In dieser erstellen wir einen neuen Eintrag in unserer Tabelle. Dazu benötigen wir eine GUID, die wir ebenfalls erstellen.

    Für uns wichtig:

    • create_uuid_c32_static
      • Erstellt uns eine gültige UUID als Key für unseren Datensatz.
    • copy_data_to_ref
      • Kopiert die erstellten Daten in die zurückliefernde Struktur.
    • iv_slug
      • In dem sogenannten Slug-Header steht der Dateiname.

      METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream.
        DATA: ls_file           TYPE zdemo_file_s,
              ls_key_tab        TYPE /iwbep/s_mgw_name_value_pair,
              lo_facade         TYPE REF TO /iwbep/if_mgw_dp_int_facade,
              lt_client_headers TYPE tihttpnvp,
              ls_client_header  LIKE LINE OF lt_client_headers,
              lv_file_id        TYPE sysuuid_c.

        lo_facade ?= /iwbep/if_mgw_conv_srv_runtime~get_dp_facade( ).
        lt_client_headers = lo_facade->get_request_header( ).

    *   UUID erstellen
        TRY.
            lv_file_id = cl_system_uuid=>create_uuid_c32_static( ).
          CATCH cx_uuid_error INTO DATA(e_txt).
            mo_context->get_message_container( )->add_message_text_only(
              iv_msg_type = ‚E‘
              iv_msg_text = TEXT-002
            ).

            RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
              EXPORTING
                textid            = /iwbep/cx_mgw_busi_exception=>business_error
                message_container = mo_context->get_message_container( ).
        ENDTRY.

    *   Entity erstellen und mit Werten füllen
        ls_file-fileid = lv_file_id.
        ls_file-filename = iv_slug.
        ls_file-content = is_media_resource-value.
        ls_file-mimetype = is_media_resource-mime_type.
        ls_file-creation_date = sy-datum.
        ls_file-creation_time = sy-uzeit.
        ls_file-created_by = sy-uname.

    *   Neuer Eintrag in die Datenbank
        INSERT INTO zdemo_file VALUES ls_file.

        IF sy-subrc <> 0.
          mo_context->get_message_container( )->add_message_text_only(
             iv_msg_type = ‚E‘
             iv_msg_text = TEXT-001
           ).
          RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
            EXPORTING
              textid            = /iwbep/cx_mgw_busi_exception=>business_error
              message_container = mo_context->get_message_container( ).
        ENDIF.

    *   Daten zurückgeben
        copy_data_to_ref(
           EXPORTING
             is_data = ls_file
           CHANGING
             cr_data = er_entity ).
      ENDMETHOD.

    Service hinzufügen

    Nachdem nun unser Servce fertig ist, müssen wir ihn in der Transation /IWFND/MAINT_SERVICE zu dem Service-Catalog hinzufügen.

    Hier filtern wir nach dem Systemalias LOCAL und dem Technischen Servicenamen unserers Services.

    Sobald er gefunden wurden, fügen wir den Service zu dem Service-Catalog hinzu.

    Wir erstellen einen lokal laufenden Service.

    Service testen

    Sobald der Service im Service-Catalog aufscheint, können wir in im SAP Gateway Client  testen.

    Upload testen

    Um ein File per OData-Service hochzuladen, müssen wir einen POST-Request auswählen.

    Anschließend wählen wir unser FileSet aus.

    Per Datei hinzufügen fügen wir ein Test-File zu unserem Request hinzu. Zusätzlich müssen wir noch den SLUG-Header vergeben.

    Sobald wir den Request versendet haben und der mit dem HTTP-Status 201 durchgelaufen ist, sehen wir eine Response, die das gespeicherte/hochgeladene File enthält.

    Download testen

    Wenn wir einen GET-Request mit einer URI auf eine existierende Entity mit dem Parameter $value versenden, bekommen wir das gespeicherte File zurückgeliefert.

    Der Parameter $value signalisiert, dass die GET_STREAM-Methode dieser Entity aufgerufen wird. Das Resultat können wir uns per Antwort in Browser im Browser anzeigen lassen.

    Testen per OData-Plugin

    Das Testen von OData-Services geht ebenfalls mit dem CloudDNA OData-Plugin bequem aus der WebIDE.

    Hier kann die unsere File-Entity getestet werden. Nach unserem Request bekommen wir auch noch Zusatzinformationen zu dem Request wie Header, Parameters etc.

    Das Testen von Upload-Funktionalitäten geht ebenfalls einfach per File-Uploader. Sollten wir etwas im Backend nicht richtig implementiert haben, bekommen wir hier eine Fehlermeldung. Außerdem brauchen wir uns nicht um das Setzen von Slug und XCSRF-Token kümmern. 

    Zusammenfassung

    Die Hälfte ist geschafft.

    In Teil 2 haben wir nun einen OData-Service basierend auf den Daten von Teil 1 erstellt. Diesen Odata-Service werden wir in Teil 3 benötigen, um die UploadCollection ins Laufen zu bringen.

    Falls Fragen zu diesen einzelnen Schritten auftreten, können sie gerne in den Kommentaren gestellt werden.