SAPUI5 UploadCollection Example

Das SAPUI5-Framework bietet eine einfach zu implementierende Downloadmöglichkeit für Media-Entities eines OData-Services. Dazu kommt die UploadCollection zum Einsatz. Diese wird in Form der Klasse sap.m.UploadCollection ausgeliefert.

Voraussetzungen:

  1. Ein korrekt eingerichteter OData-Service mit einer Media-Entiy.
  2. Redefinition und korrekte Neu-Implementierung der GET_STREAM, (CREATE_STREAM für Upload) und DEFINE-Methode der Provider-Klassen.
  3. UI5-Version des Beispiels: 1.76

Media-Entity

Angenommen wir haben folgenden Aufbau unserer Media-Entity in ODataV2.

<EntityType Name="Document" m:HasStream="true" sap:content-version="1">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.String" Nullable="false" MaxLength="32" sap:unicode="false" sap:label="ID" sap:creatable="false" sap:updatable="false"/>
<Property Name="DocumentName" Type="Edm.String" Nullable="false" MaxLength="254" sap:unicode="false" sap:label="Document Name"/>
<Property Name="DocumentType" Type="Edm.String" Nullable="false" MaxLength="128" sap:unicode="false" sap:label="Document Type"/>
<Property Name="Content" Type="Edm.String" Nullable="false" sap:unicode="false" sap:label="Content"/>
<Property Name="ArchivId" Type="Edm.String" Nullable="false" MaxLength="2" sap:unicode="false" sap:label="Archiv ID"/>
<Property Name="ArchivDocId" Type="Edm.String" Nullable="false" MaxLength="40" sap:unicode="false" sap:label="Archiv Document ID"/>
<Property Name="ArchivDocType" Type="Edm.String" Nullable="false" MaxLength="20" sap:unicode="false" sap:label="Archiv Document Typ"/></EntityType>
			

Die Voraussetzung bei dieser MediaEntity ist, dass die GET_STREAM-, CREATE_STREAM– und die DEFINE-Methode redefiniert und korrekt implementiert wurden.

Nun möchten wir eine Funktionalität in unsere Applikation einbauen, um die Dokumente aus dem DocumentSet zu downloaden und anzuzeigen. Um dies zu bewerkstelligen, benötigen wir das sap.m.UploadCollection-Control.

View-Implementierung

Zunächst muss die UploadCollection samt dazugehörigen UploadCollectionItems implementiert werden.

<UploadCollection id="detail_uploadcollection" items="{fileModel>/files}" mode="MultiSelect" uploadEnabled="false"						uploadButtonInvisible="true" selectionChange="onDocumnetSelectionChange">						<toolbar>
    <OverflowToolbar>
        <ToolbarSpacer/>
        <Button enabled="{viewModel>/downloadEnabled}" text="{i18n>btn_download}" press="onDownloadDocuments" type="Transparent"/>
        <UploadCollectionToolbarPlaceholder/>
        </OverflowToolbar>
</toolbar>
<items>
    <UploadCollectionItem documentId="{ID}" url="{path: 'ID', formatter: '.formatUrl'}" fileName="{DocumentName}" mimeType="{DocumentType}" enableEdit="false" enableDelete="false" visibleDelete="false" visibleEdit="false"/>
</items>
</UploadCollection>

Das UploadCollectionItem hat die Property „url„. Diese Property muss mit einer validen URL befüllt werden. In unserem Fall nehmen wir uns die aktuelle, auf den Service bezogene URL des Dokuments. Diese steht nicht direkt als Property in unserer Entiy, sondern muss über einen Umweg ausgelesen werden.

Wir behelfen uns hier mit einem Custom-Formatter, um die korrekte und valide URL unserer Media-Entity aufzulösen.

Eine valide URL, die auf den Steam unserer Media-Entity zeigt, wäre zum Beispiel:
https://<hostname>:<port>/sap/opu/odata/<servicename>/DocumentSet(ID=’566F112400211EDA9CD176726FCF0230′)/$value

Und genau diese URL möchten wir per Aggregation-Binding passend für jedes Dokument in die UploadCollection bekommen. Denn mit dem URL-Parameter „$value“ bekommen wir den Media-Stream unseres Dokuments.

Controller-Implementierung

Im Controller brauchen wir zunächst eine Formatter-Funktion, die bei jedem Binding auf die „url„-Property aufgerufen wird. In dieser Funktion holen wir uns die Service-Url des OData-Services, hängen unseren Schlüssel an und zusätzlich den „$value„-Parameter.

formatUrl: function (sSrId, sSeqnr) {
			let sUrl = this.getModel().sServiceUrl;
			sUrl += "/" + this.getModel().createKey("DocumentSet", {
				ID: sSrId
			});
			sUrl += "/$value";
			return sUrl;
		},

Nachdem wir nun das Code-Snippets implementiert haben, bekommen wir schon Items in unserer UploadCollection richtig angezeigt.

Würden wir jetzt auf ein Dokument klicken, würde in einem neuen Tab das Dokument geladen.

Wir möchten aber Dokumente selektieren und per Button downloaden. Hierfür bietet die UploadCollection die passende Funktion „downloadItem„.

Zuerst definieren wir den Eventhandler für das selectionChanged-Event der UploadCollection, um den Download-Button ein/auszublenden wenn wir Items selektieren. Die Sichtbarkeit wird über ein JSONModel namens ViewModel und der Property „downloadEnabled“ gesteuert.

onDocumnetSelectionChange: function (oEvent) {
			let oUploadCollection = oEvent.getSource();
			if (oUploadCollection.getSelectedItems().length > 0) {
				this._oViewModel.setProperty("/downloadEnabled", true);
			} else {
				this._oViewModel.setProperty("/downloadEnabled", true);
			}
		},

Anschließend wird noch der Eventhandler für das press-Event des Buttons benötigt. Hier werden die selektierten Items heruntergeladen.

onDownloadDocuments: function (oEvent) {
			let oUploadCollection = this.getView().byId("detail_uploadcollection"),
				aSelectedItems = oUploadCollection.getSelectedItems();
			for (var i = 0; i < aSelectedItems.length; i++) {
				oUploadCollection.downloadItem(aSelectedItems[i], true);
			}
		},

Anschließend können wir die Applikation ins ABAP-Backend deployen.

Nun können wir die selektierten Dokumente per Button-Press herunterladen und anzeigen. Hierfür nimmt sich die UploadCollection die Property „url“ des jeweiligen UploadCollectionItems um eine Download-URL zu definieren.

Wir sehen also, das UI5-Framework bietet hier schon einen leichten Weg, um den Dokumentendownload zu ermöglichen. Die Voraussetzung hierfür ist jedoch die richtige Implementierung des Backends und des OData-Services. Wenn dies jedoch sauber gemacht wurde, steht unserem Dokumentendownload nichts im Weg.

ABAP OData $top und $skip Implementierung

OData ABAP Implementierung sind grundsätzlich sehr einfach umzusetzen. Die performante Darstellung von großen Datenmengen in SAPUI5 und Fiori Apps erfordert die Implementierung eines Paging Mechanismus. Dieser Paging Mechanismus ist im OData Standard mittels der URL Parameter $top und $skip vorgesehen. In diesem Knowledgebase-Artikel zeigen Ihnen die Experten der CloudDNA GmbH wie Sie ein Paging mittels der $top und $skip Parameter in der GET_ENTITYSET Methode implementieren können.

METHOD customer_get_entityset.
    DATA(lv_top) = io_tech_request_context->get_top( ).
    DATA(lv_skip) = io_tech_request_context->get_skip( ).
    DATA(lv_max) = 0.
 
    IF lv_top > 0.
      lv_max = lv_top + lv_skip.
    ENDIF.
 
    SELECT FIRSTNAME,LASTNAME,EMAIL,PHONE FROM ZCUSTOMER INTO TABLE @et_entityset UP TO @lv_max ROWS.
 
    IF lv_skip IS NOT INITIAL.
      DELETE et_entityset TO lv_skip.
    ENDIF.
 
ENDMETHOD.

OData – Read Operation

Eine Read Operation in OData liefert immer genau einen Datensatz als Ergebnis. Die Read Operation erfordert, dass alle Schlüsselfelder der Entität vollständig übergeben werden und somit der gewünschte Datensatz eindeutig identifiziert werden kann.

Sobald eine Entity in der SEGW angelegt und aktiviert wird, entsteht in der Model Provider Class (_MPC) der entsprechende Type Eintrag. Der Return Parameter er_entity der ###_GET_ENTITY Methode wird damit typisiert. Die Implementierung der Methode auf Basis der Tabelle SFLIGHT könnte im ABAP wie folgt aussehen:

METHOD flightset_get_entity.
  DATA: ls_entity like er_entity.
  
* Auslesen der Schlüsselfelder
  io_tech_request_context->get_converted_keys(
    IMPORTING
      es_key_values = ls_entity 
  ).
* Ermitteln der Daten - Select aus der Tabelle mit den Schlüsselfeldern
  SELECT * FROM SFLIGHT INTO er_entity 
    WHERE carrid = ls_entity-carrid
      AND connid = ls_entity-connid
      AND fldate = ls_entity-fldate.
ENDMETHOD.