Seien wir offen miteinander
Ein spannendes und großes Thema in der Fiori ist die Offenheit des Frameworks. Diese Offenheit ermöglicht es unter Anderem eigene Controls, also die individuelle Erweiterung der vorhandenen grafischen Elemente wie beispielsweise sap.m.List, und eigene Styles entwickeln zu können. Es ist ein sehr interessanteres, mächtiges aber auch umfangreiches Thema. Daher möchte ich es über mehrere Teile hinweg in Form einer neuen Blog-Reihe vorstellen.
Grundlage wird meine Erfahrung aus einem Projekt sein, wo ich genau diese Anpassungen machen konnte. Da ich die verwendete Fiori nicht zeigen darf, werden die kommenden Entwicklungen auf Basis einer Schulungs Fiori statt finden. Ausgangspunkt war eine Fiori, die diverse Einträge in einer (Master-) Liste angezeigt hat. Die Einträge waren bereits gruppiert und sortiert. Sie sah grob so aus, wie im folgenden Screenshot.
Der Kunde hatte an für diese Liste noch ein paar Änderungswünsche:
- Die gruppierten Elemente sollen gezählt werden
- Bei einem Klick auf den Gruppierer sollen die Einträge der Liste ein- bzw. ausgeklappt werden
- Mittels eines Pfeil-Symbols soll angedeutet werden, ob eine Gruppe ein- oder ausgeklappt ist
Leider konnten diese Anforderungen nicht mit den vorhanden Möglichkeiten der Standard Controls realisiert werden. Der Gruppierer (Klasse sap.m.GroupHeaderListItem) besitzen zwar ein count-Attribut, dieses wird aber nicht automatisch gefüllt. Die Gruppierer erben zwar die onPress()-Methode des allgemeinen ListItem, haben diese aber explizit deaktiviert. Es ist im Grunde nicht vorgesehen, dass die Gruppierer anklickbar sind. Ferner werden keine Icons unterstützt, so dass auch die Pfeile nicht direkt realisierbar waren.
jQuery only
Auch wenn erweiterte Controls und eigene CSS-Anpassungen kern der Reihe werden, möchte ich in diesem ersten Beitrag einen anderen Weg aufzeigen. Dieser “ignoriert” die Möglichkeiten der Erweiterung des SAPUI5-Frameworks weitestgehend und löst die oben gestellten Anforderungen überwiegend mittels jQuery. Mögliche Vorteile des Weges über das Framework (Wiederverwendbarkeit, Update-Sicherheit, etc.) wurden hier bewusst ignoriert. Die folgenden Codings sollen aber als Vergleichsgrundlage für den empfohlenen Weg über das Framework dienen können.
Da die vorhandenen Controls die Anforderungen nicht erfüllen konnten entschloss ich mich für den folgenden Ansatz: die Liste wird wie gehabt fertig geladen, gruppiert und dann von mir anschließend überarbeitet. Soweit konnte mich das Framework unterstützen, da die List ein updateFinished Event anbietet. Dieses Event wird genau dann aufgerufen, wenn die Liste vollständig über OData geladen wurde. Ab hier konnte ich meine Erweiterung “einklinken”.
<List id="productsList" headerText="{i18n>productListTitle}" updateFinished="_enhanceListGrouper" items="{ path: '/ProductSet', sorter: { path: 'Category', group: true }, groupHeaderFactory: '.getGroupHeader', parameters : { expand: 'ToSupplier' } }" growing="false" growingThreshold="20" growingScrollToLoad="false" >
Die _enhanceListGrouper() Methode, welche wohl kein Clean Code Review überstehen dürfte, durchläuft alle Einträge (Items) der Liste. Sollte ein Eintrag vom Typ Gruppierer sein (_isGrouper), dann wird ein click Event auf dieses Element gesetzt. Dieses Event stammt aus der jQuery-Bibliothek und ersetzt das von SAPUI5 gesperrte press Event des Gruppierers. Sollte der Eintrag der Liste kein Gruppierer, sondern ein normales Element, sein, dann wird dies dem letzten aktuellen Gruppierer zugewiesen und der Zähler des Gruppierers um Eins erhöht. Sobald ein neuer Gruppierer gefunden wurde, werden alle folgenden Einträge dem neuen Gruppierer zugewiesen und die Zählung beginnt erneut.
_enhanceListGrouper: function(oEvent) { var oList = oEvent.getSource(); var that = this; var oGrouper; var iCount = 0; var aItems = oList.getItems(); for (var i = 0; i < aItems.length; i++) { if (that._isGrouper(aItems[i])) { oGrouper = aItems[i]; oGrouper.aItems = []; oGrouper.open = true; oGrouper.attachBrowserEvent("click", that._handleGrouperClick, that); iCount = 0; } else { if (oGrouper) { oGrouper.aItems.push(aItems[i]); iCount++; oGrouper.setCount(iCount); } } } }, _isGrouper: function(oItem) { return oItem.getMetadata()._sClassName === "sap.m.GroupHeaderListItem"; },
Um den Gruppierer einem Pfeil geben zu können, habe ich einfach auf bereits vorhandene UTF-8 Zeichen zurück gegriffen. Diese wurden in einer Hash-Map controller-weit unter icons abgelegt. Bei der Definition des Titels des Gruppierer konnte ich wiederum auf eine von SAPUI5 unterstützte Funktionalität zurück greifen. Die Liste kennt eine “groupHeaderFactory” Aggregation. In dieser können die Kopfdaten nach den Anforderungen des Entwicklers selber gestaltet werden. Hier setze ich den Titel des Gruppierers mit einem Pfeil und den eigentlichen Titel (in diesem Beispiel ist es die Kategorie eines Produktes) zusammen.
icons: { UP: "▲", DOWN: "▼", RIGHT: "►", LEFT: "◄" }, getGroupHeader: function(oGroup) { var that = this; return new sap.m.GroupHeaderListItem({ title: that.icons.DOWN + " " + oGroup.key }); },
Das auf- und zuklappen musste ich ebenfalls selber entwickeln. _handleGrouperClick() verarbeitet das Click Event auf einen der Gruppierer. Von hier aus werden zwei weitere Methoden ausgelöst. In _changeGrouperIcon() wird das Pfeil-Icon ausgetauscht. Sollte der Gruppierer bislang offen sein, wird der Pfeil nach rechts zeigen. Sollte er geschlossen sein, wird der neue Pfeil nach unten zeigen. Die _tooggleGrouperItems() dient dem eigentlichen auf- bzw. zuklappen der Einträge. Mittels $() werden diese in ein jQuery-Objekt übertragen. Anschließend lässt sich die jquery-Methode slideToggle() ausführen, die die Einträge entsprechen öffnet oder schließt.
_handleGrouperClick: function(oEvent) { var oGrouper = sap.ui.getCore().byId(oEvent.currentTarget.id); this._changeGrouperIcon(oGrouper); this._tooggleGrouperItems(oGrouper); }, _changeGrouperIcon: function(oGrouper) { var sNewIcon; if (oGrouper.open) { oGrouper.open = false; sNewIcon = this.icons.RIGHT; } else { oGrouper.open = true; sNewIcon = this.icons.DOWN; } var sNewText = sNewIcon + oGrouper.getTitle().slice(1); oGrouper.setTitle(sNewText); }, _tooggleGrouperItems: function(oGrouper) { var aItems = oGrouper.aItems; for (var i = 0; i < aItems.length; i++) { aItems[i].$().slideToggle(); } },
Eine verschlossene Gruppe
Das Ergebnis meiner Bemühungen zeigt das folgende Bild.
Mittels jQuery, den Pfeil-Symbolen und dem Zurückgreifen der nutzbaren API des SAPUI5 Frameworks konnte ich die Anforderungen des Kunden erfüllen. Die Lösung funktioniert, hat aber den Controller sehr umfangreich mit Funktionalität zugebaut, die nur zur Erstellung und Steuerung der erweiterten Liste dient. Im nächsten Teil dieser Reihe zeige ich daher, wie ich aus dem oben gezeigten Coding ein eigenes SAPUI5 Control erstelle, welches die gleichen Features bietet, diese aber abkapselt, und in jeder beliebigen Fiori einsetzbar sein wird.
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.
Eller sagt:
Ich finde es super, dass Du diesen Blog betreibst. Mehr davon, bitte. FIori hat erhebliche Schwächen und es ist wichtig, dass SAP Partner wissen, was möglich ist und aufzeigen, wie man sie UX weiter verbessern kann. Spannend wäre ein Beitrag über das Thema “how to make it offline”.