Moin, Moin aus dem UI5 Maschinenraum!
Heute möchte ich dir das harte Entwicklerleben etwas erleichtern und ein paar meiner OData Model Best Practices mit dir teilen.
Ziel ist es, deine SAPUI5 OData Calls zu stabilisieren und deinen Code durch Standardisierung “cleaner” zu gestalten.
OData Calls
Das SAPUI5 Framework bietet dir die Grundfunktionalitäten um Datensätze zu lesen und zu manipulieren.
Falls du noch nicht weißt, wie genau diese CRUD-Q Methoden funktionieren, dann lies dir bitte zunächst die folgenden zwei Blogs von meinem lieben Kollegen Sebastian Kielhorn durch.
Er erklärt dir schnell und gut wie man OData liest und schreibt. Ich warte hier so lange auf dich…
Schön, nun will ich dein Wissen etwas erweitern und dir zeigen, wie du deine OData Calls standardisieren kannst.
Standardisierung und Best Practices
In einer typischen UI5-Anwendung wirst du OData CRUD-Q Calls und Function Imports an diversen Stellen ausführen. Typischer Weise soll das UI während der Kommunikation mit dem Backend durch einen BusyIndicator gesperrt werden. Außerdem muss die Frontend-Instanz des OData Models synchronisiert werden, falls einer deiner Calls Daten im Backend manipuliert.
Man muss kein Genie sein, um solch redundante Arbeitsschritte in einem eigenen kleinen Hilfscontroller auszulagern. Ich nenne diesen Controller in diesem Blog “Utilities.js”.
Zunächst legst du das Grundgerüst deines Controllers an und definierst dessen Abhängigkeiten.
sap.ui.define([ "cgi/controller/BusyHelper" ], function(BusyHelper) { "use strict"; return { }; } );
Es fällt dir bestimmt direkt auf, dass innerhalb deines Controllers eine Abhängigkeit namens “BusyHelper” existiert. Dabei handelt es sich um einen zweiten Controller, der den BusyDialog steuert.
BusyHelper.js
Den BusyHelper kannst du Beispielsweise wie folgt implementieren:
sap.ui.define(["sap/m/BusyDialog"], function(BusyDialog) { "use strict"; var _oBusyDialog, _bOpen; return { _init: function() { _oBusyDialog = new BusyDialog(); _bOpen = false; }, show: function() { if (!_oBusyDialog) { this._init(); } if (!_bOpen) { _oBusyDialog.open(); _bOpen = true; } }, hide: function() { if (_bOpen) { _oBusyDialog.close(); _bOpen = false; } } };
OData Model read()
Nun erweiterst du deinen Utilities.js Controller um eine standardisierte Methode um damit Daten zu laden.
Diese Methode heißt readFromOdata(). Sie sollte unabhängig von jeglichen Modellnamen oder auch Views sein, sodass sie praktisch von überall in deiner Applikation aufgerufen werden kann. Außerdem sollte die Methode über Parameter verfügen, mit denen man Filter und Pfade für ein eventuellen OData Expands verarbeiten kann.
Deine Methode zum Laden könnte zum Beispiel so aussehen:
readFromOData: function(oModel, sPath, sExpand, aFilters, bNoBusyIndicator) { //deferred for asynchronous resolving var sUrlParam; var dfdResult = jQuery.Deferred(); if (sExpand.length > 0) { sUrlParam = "$expand=" + sExpand; } else { sUrlParam = null; } if(!bNoBusyIndicator){ BusyHelper.show(); } // Parameter map for odata service call var mParameters = { context: null, urlParameters: sUrlParam, filters: aFilters, sorters: null, success: function(oData, oResponse) { //close loading indicator if(!bNoBusyIndicator){ BusyHelper.hide(); } if (oData) { if (oData.results && oData.results.length > 0) { dfdResult.resolve(oData.results); } else if (typeof(oData) === "object" && !oData.results && !$.isEmptyObject(oData)){ dfdResult.resolve(oData); } else { dfdResult.resolve(null); } } else { dfdResult.reject(oResponse); } }, error: function(oResponse) { //close loading indicator if(!bNoBusyIndicator){ BusyHelper.hide(); } dfdResult.reject(oResponse); } }; // get data from model with parameters oModel.read(sPath, mParameters); return dfdResult.promise(); }
Wie du siehst, wird die readFromOdata() mit Hilfe eines jQuery Deferred Objects als Promise ausgeprägt. Dadurch kriegen wir das Timing-Problem, das durch die asynchrone Natur eines OData Calls entsteht, in den Griff und machen die aufrufende Methode zusätzlich noch sehr viel lesbarer. Der Aufruf aus einem View-Controller sieht nämlich in etwas so aus:
... var oModel = this.getView().getModel(); Utilities.readFromOdata(oModel, "/EntitySet('123456')", "/ToSubEntity", aFilters, true) .done( function(OData){ //here is what you want to do with you data }) .fail( function(oRespone){ //here is you error handling }); ...
Schön übersichtlich, oder?
OData Model callFunction()
Die zweite Methode, die ich dir zeigen möchte ist doFunctionImport(). Dabei handelt es sich um einen standardisierter Aufruf eines Function Imports. Sie funktioniert eigentlich nach dem selben Prinzip wie die readFromOdata(), natürlich ohne Filter und Expands.
doFunctionImport: function(oModel, sFunction, mInputData, bNoBusyIndicator) { var dfdResult = jQuery.Deferred(); if(!bNoBusyIndicator){ BusyHelper.show(); } var mParameters = { method: "GET", urlParameters: mInputData, success: function(oData, oResponse) { //close Busy indicator BusyHelper.hide(); if (oData) { dfdResult.resolve(oData); } else { dfdResult.resolve(null); } }, error: function(oResponse) { //close loading indicator BusyHelper.hide(); dfdResult.reject(oResponse); } }; oModel.callFunction(sFunction, mParameters); return dfdResult.promise(); }
So lassen sich Function Imports bequem ausführen, ohne dass du dir jedes mal aufs neue Gedanken über Asynchronität oder Busy Handling machen musst.
Ich hoffe meine Code Snippets helfen dir etwas weiter und verbessern deine UI5 Entwicklung.
In ein paar Tagen findest du auf unserem Blog auch noch Best Practices für die Standardisierung vom create() und update() des OData Models.
Bis dahin!
In Hamburg sagt man Tschüss!
Du interessierst dich für Fiori und hast Lust coole Projekte mit uns zu machen? Wir suchen dich! Schau doch mal in unserer Stellenbeschreibung vorbei.