Präsentation von Tabellen: die Datenquelle

Das <z:table>-Tag hat drei verschiedene Schnittstellen, über die es ABAP-Daten entgegennehmen kann: Die direkte Datenübergabe, die Referenz mittels Datenbindung sowie die Übergabe eines Datenobjekts. Welche dieser Schnittstellen am besten verwendet wird, ergibt sich aus der Situation. Wir wollen hier die drei möglichen Parametrisierungen des <z:table>-Tags diskutieren.

Dieser Artikel ist eine Fortsetzung des Beitrags zur Darstellung interner Tabellen mit CSS-Mitteln.

Direkte Datenübergabe

Der einfachste Weg ist sicher die direkte Angabe der Datenquelle über das data-Attribut. Wir haben diesen Weg im zweiten Beispiel der Tag Library-Demoanwendung demonstriert. Wenn Sie im View über eine interne Tabelle lt_data verfügen, können Sie diese mit folgendem Aufruf des <z:table>-Elements präsentieren:
<z:table data="<%= lt_data %>">
Das demonstriert die einfache Aufrufbarkeit des <z:table>-Elements: Daher steht diese Aufrufart am Anfang dieses Artikels. Dennoch sollte dies nicht der normale Anwendungsfall sein. Denn üblicherweise wollen Sie Anwendungsdaten darstellen – und Anwendungsdaten sind nicht im View, sondern in einem Model zu Hause. Die Daten werden daher üblicherweise nur vom Model an das Tabellen-Tag weitergereicht, ohne dass eine lokale interne Tabelle innerhalb des Views nötig wäre.

Sinnvoll ist die Verwendung einer lokalen internen Tabelle im View, wenn die Daten vor der Präsentierung noch aufbereitet werden sollen. Der View wird dann zwar parametrisiert aufgerufen, wobei einer seiner Parameter die darzustellende Tabelle ist. Aus dieser Tabelle wird jedoch eine für die Präsentierung besser geeignete Tabelle aufgebaut - und diese erst wird an das <z:table>-Element übergeben.

Ein Beispiel dieser Art findet sich übrigens auch in der Z Extension: Das Element <z:messages> stellt nicht eine einzelne Meldung, sondern sämtliche in einem Log gesammelten Meldungen dar. Hierfür ruft es intern einen View auf, den View sap/zsrs_taglib/messages.htm, und übergibt ihm auf dem Wege über ein Viewcontext-Objekt die Tabelle der Meldungen. Viewcontexte sind ABAP-Objekte mit viewnahem Code (Präsentationslogik):

Die von der Anwendung übergebene Tabelle vom Typ zsrs_messages_tab enthält lediglich

Daraus wird nun in einem Viewcontext (der Klasse zcl_ctx_z_messages) die Ausgabetabelle präpariert: Der Typ wird umgewandelt in ein passendes Ampel-Icon (rot, gelb oder grün), und wenn ein Langtext vorhanden ist, wird der HTML-Code für einen Anker mit Hilfe-Icon auf diesen Langtext vorbereitet. Dies geschieht in der folgenden Contextmethode:
method generate_messages_ui.

  data: ls_messages type zsrs_messages_ui.

  field-symbols: <ls_messages> type zsrs_messages.

* Die Contentstruktur ZSRS_MESSAGES ist benannte Substruktur von ZSRS_MESSAGES_UI
  assign ls_messages-messages to <ls_messages>.

* Tabelle um Ausgabedaten anreichern
  loop at it_messages->* into <ls_messages>. " sic! Nicht 'assigning'!

* Icons für die Ausgabe
    case ls_messages-type.
      when 'E' or 'A' or 'X'.
        ls_messages-icon = 's_s_tl_r.gif'.
      when 'W'.
        ls_messages-icon = 's_s_tl_y.gif'.
      when 'I' or 'S'.
        ls_messages-icon = 's_s_tl_g.gif'.
      when others.
        ls_messages-icon = 's_outlig.gif'.
    endcase.
    concatenate
      '<img src="../public/bc/Icons/'
      ls_messages-icon
      '" width="32" height="15">'
      into ls_messages-icon.

* Langtext-Hilfe vorhanden?
    if not ls_messages-longtextref is initial.
* Ja, dann Verweis auf Hilfe in neuem Fenster generieren
      concatenate 'javascript:windowOpen('''
                  ls_messages-longtextref
                  ''');' into ls_messages-longtextref.
      concatenate '<a onClick="' ls_messages-longtextref '">'
                  '<img src="../public/bc/Icons/s_f_help.gif" width="16" height="14" border="0"></a>'
        into ls_messages-help.
    else.
* Nein, dann Verweiscode löschen
      clear ls_messages-help.
    endif.

    insert ls_messages into table messages_ui.

  endloop.


endmethod.

Diese Methode wurde kurz vor der Ausgabe des Views messages.htm durchlaufen. Im MVC-Framework braucht man sich um die Initialisierung des View-Contexts nicht zu kümmern. Wenn ein Seitenattribut existiert, das auf den Typ zif_view_context abgebildet werden kann, wird vom Framework automatisch eine Instanz erzeugt und die Methode zif_view_context~set_view() aufgerufen. Die Details hierzu sind im Kapitel 6.11 des BSP-Praxisbuchs beschrieben.

Als nächstes wird nun der View messages.htm durchlaufen. Die im Context aufgebaute interne Tabelle messages_ui wird nun mittels direkter Datenübergabe an das <z:table>-Element durchgereicht:

<%@page language="abap" %>
<%@extension name="Z" prefix="z" %>
<z:table data     = "<%= ctx->messages_ui  %>"
         noHeader = "true" >
  <z:column name      = "type"
            invisible = "true" />
  <z:column name      = "longtextref"
            invisible = "true" />
  <z:column name      = "text"
            tdClass   = "message" />
</z:table>
Das Ergebnis dieses Views sehen Sie in der Taglib-Demo-Anwendung:

Modeldaten übergeben

In manchen Fällen übergeben Sie die Referenz eines Models als Seitenattribut an einen View. Unser MVC-Framework bietet hierbei einen auf Namensgleichheit basierenden Mechanismus. Wenn Sie in Ihrer Flow Logic einem Model die ID x geben, etwa indem die Flow Logic die Zeile
<model id="x" class="zcl_model_x"/>
enthält, dann bekommen Sie die Instanz des Models automatisch im View angeliefert, wenn dieser ein Attribut des Namens x vom Typ des Models (also in diesem Beispiel vom Typ ref to zcl_model_x) aufweist. Das können Sie sich zunutze machen, um dem <z:table>-Element mit Scripting interne Tabellen durchzureichen:
<z:table data="<%=x->gt_employees%>"/>
Nun werden Sie selten die Daten 1:1 so darstellen wollen, wie sie das Model vorhält. Üblicherweise sind die Tabellen des Models zu breit, enthalten zu viele Spalten. Mit dem <z:column>-Element können Sie die Menge der darzustellenden Komponenten einschränken, ebenso mit dessen listPos-Attribut die Reihenfolge der Komponenten verändern. Mit dem Substitutionsmechanismus und den Tabellen-Exits können Sie auch komplexere Präsentationslogik programmieren.

Datenbindung

Der gerade beschriebene Weg, Models als View-Attribute bekanntzumachen, enthält eine gewisse Redundanz. Denn das Model ist ja über die Flow Logic eindeutig mit einer gewissen ID assoziiert. Es sollte also möglich sein, das Model bei Kenntnis dieser ID auch direkt anzusprechen, ohne dass die Instanz auf dem Umweg über ein View-Attribut an den View geleitet werden müsste.

Genau hierfür ist der Mechanismus der Datenbindung gedacht. Die Syntax der Datenbindung, der das ganze 7. Kapitel des BSP-Praxisbuchs gewidmet ist, sieht für die obige Tabelle gt_employees des Models x den Bindungsausdruck

//x/gt_employees
vor. Auch das <z:table>-Element kann Datenbindungsausdrücke entgegennehmen. Hierfür dient das Attribut binding. Die Deklaration
<z:table binding="//x/gt_employees/">
hat also dieselbe Wirkung wie die obige mit data="<%=x->gt_employees%>".

Dem Vorteil, dass der Umweg über ein Seitenattribut vermieden wird, steht allerdings der Nachteil entgegen, dass die vollständige Überprüfung des Bindungsausdrucks erst zur Laufzeit möglich ist und die Verwendung des Models x im View nicht mehr im Verwendungsnachweis ersichtlich ist.

Ein Ding, das sich wie eine Indextabelle verhält

Um seine Arbeit zu tun, braucht das <z:table>-Element eine Datenquelle, von der es die folgenden Informationen erhält: Darüberhinaus muss es bei Verwendung des <z:toolbar>-Elements noch möglich sein, die Tabelle nach einigen Komponenten umzusortieren.

Ein Objekt, das diese Informationen auf Anfrage herausgibt, muss nicht unbedingt eine interne Tabelle sein. Es kann auch ein ABAP-Objekt sein, das ein geeignetes Interface implementiert. Das Interface zif_table_exit ist genau auf die Beschaffung der oben genannten Informationen zugeschnitten. Wenn Sie also statt einer internen Tabelle <gt_employees> ein Model-Attribut <go_employees> haben, das das Interface <zif_index_table> implementiert, so können Sie dieses Objekt, das sich wie eine interne Tabelle verhält, aber nicht notwendig eine ist, dem <z:table>-Element über das Attribut dataObject mitgeben:

<z:table dataObject="<%=x->go_employees%>"/>
Wenn Sie das Attribut dataObject verwenden, ist klar erkennbar, dass Sie mit einem Objekt arbeiten, das sich wie eine interne Tabelle verhält. Sie können go_employees aber auch über das binding- und sogar über das data-Attribut übergeben:
<% data: lo_emp type ref to data.
get reference of x->go_employees into lo_emp.
<z:table data="<%=lo_emp%>"/>
oder
<z:table binding="//x/go_employees"/>
Wie im Kapitel 7 des BSP-Praxisbuchs erläutert, muss eine Datenbindung, die auf ein ABAP-Objekt verweist, mit einer doppelte Referenzierung arbeiten, sozusagen mit ref to ref to object, weil es in ABAP keinen gemeinsamen Oberbegriff für ref to data und ref to object gibt. Das ist der Grund, warum in obigem Beispiel eine Variable lo_emp an das data-Attribut übergeben werden muss, die ihrerseits auf die Referenzvariable x->go_employees verweist.

Welchen Nutzen hat diese Schnittstelle? Ein Beispiel aus der Praxis sei angeführt, die Sortimentsliste im SAP Retail Store. Wenn Sie sich eine interne Tabelle mit vielen Einträgen vorstellen, für die zwar die Anzahl und die Schlüssel leicht zu beschaffen sind, pro Zeile aber ein Datenteil aufwendig zu ermitteln ist, dann sind Sie ungefähr bei der Problematik der Sortimentsliste.

Das für eine Filiale vorgesehene Sortiment ist zunächst eine lange Liste von Artikeln. Die Artikelnummern sind die Schlüssel, und eine Tabelle dieser Schlüssel (das blaue Rechteck in der nebenstehenden Graphik) ist verhältnismässig leicht zu beschaffen. Für die jeweils angezeigten Artikel möchte der Filialmitarbeiter aber detailliertere Informationen haben. Neben einfach zu beschaffenden Daten wie dem Artikeltext gehören hierzu auch Informationen wie die zuletzt zugeteilten, gelieferten bzw. bestellten Mengen, die aufwendiger zu ermitteln sind, da verschiedene abhängige Datenbanktabellen befragt werden müssen. Andererseits gibt es nur ein kleines Fenster von etwa zehn Zeilen, das jeweils im Focus des Anwenders ist - weil er durch Suchen, Blättern oder Scannen eines Artikels genau auf den für ihn relevanten Teil der Tabelle positioniert hat. Dies ist in der Graphik das rote Rechteck.

Der Benutzer sieht nur einen Ausschnitt von zehn Zeilen der Tabelle, er kann die Tabelle auch durchsuchen oder bei einem Schlüssel aufsetzen, es macht aber wenig Sinn, ihm die ganze Tabelle zu präsentieren, da er sonst mit Daten überschüttet wird. Wenn aber die Datenteile von sämtlichen Tabellenzeilen aufwendig ermittelt werden, obwohl der Benutzer nur zehn Zeilen davon sieht, ist das ineffizient, erzeugt vermeidbare Systemlast und schlechte Dialog-Antwortzeiten.

Genau hier half uns "das Ding, das sich wie eine Standardtabelle verhält": Das <z:table>-Element kennt ja die Nummern der zehn jeweils darzustellenden Zeilen. Wenn es nun statt einer internen Tabelle ein auf zif_index_table abbildbares Objekt erhält, führt es die (aufwändige) Lesemethode nicht für alle vielleicht 10.000 Sätze der Tabelle aus, sondern nur für die zehn darzustellenden Sätze.

Der Trick ist also, dass man mit Hilfe dieser Schnittstelle den Zeitpunkt des Lesens der Schlüssel von dem Zeitpunkt trennen kann, zu dem die gesamte Datenzeile gefüllt, ermittelt und zurückgeliefert wird.

Zurück