In der Anleitung Tabellen wird die Tabellentechnik des S10 Frameworks beschrieben. In diesem Tutorial wollen wir die Technik anhand eines Beispiels kennenlernen:
In einer Tabelle sollen beschädigte Lagerartikel erfasst werden. Die Eingabemaske sieht wie folgt aus:
Zur Eingabe der Materialnummer kann neben der Tastatureingabe auch ein Barcode oder QR-Code abfotografiert werden. Die Spalten "Menge" (Anzahl beschädigter Artikel) sowie "Schadenstyp" (Dropdown-Liste) werden manuell erfasst. Materialkurztext, Lagermengeneinheit und der Lagerbestand werden aus dem SAP-System dazugelesen.
Die Anwendung kann auf dem Smartphone wie oben angezeigt im Querformat oder auch im Hochformat bedient werden. Im Hochformat sind die Anzeigespalten "Materialkurztext", "Bestand" und "ME" (Mengeneinheit) ausgeblendet, aber die Erfassung ist auch dann problemlos möglich:
Falls zusätzliche Informationen angezeigt oder eingegeben werden sollen, etwa eine Chargen- oder Serialnummer oder der genaue Lagerplatz, kann die Tabelle um eine mehrzeilige Ausgabe oder ein gesondert aufrufbares Detailbild pro Zeile ergänzt werden. Wir beschränken uns hier auf die einzeilige Ansicht.
Die Entwicklung beginnen wir mit der Definition einer lokalen ABAP-Klasse zur Beschreibung der Tabellenzeile. Generell beruht die Tabellendarstellung im S10 Framework darauf, jeder Tabellenzeile ein ABAP Objekt zuzuordnen. Das hat den Vorteil, dass die Techniken der "build"- und "validate"-Methoden (Tutorials 2 und 4) nun pro Tabellenzeile zur Verfügung stehen, was wir gleich verwenden werden.
Wir können die lokale Klasse für die Tabellenzeilen entweder per Hand eingeben oder den Klassengenerator aus den S10 Utilities nutzen. Die Klasse sieht wie folgt aus:
ABAPclass mdr definition inheriting from /s10/any.
public section.
data:
matnr type mard-matnr, " Materialnummer
werks type mard-werks, " Werk
lgort type mard-lgort, " Lagerort
speme type mard-speme, " Anzahl beschädigte Artikel
meins type mara-meins, " Mengeneinheit
labst type mard-labst, " Lagerbestand
ecode type string. " Schadenscode
* Einheiten zuordnen
constants:
unit_speme type string value 'meins',
unit_labst type string value 'meins'.
endclass.
Der Klassenname, hier "mdr" für "Material Damage Report" ist frei wählbar. Die Konstanten "unit_speme" und "unit_labst" sorgen dafür, dass die Ausgabe mit der zur Mengeneinheit passenden Anzahl Dezimalstellen erfolgt.
Verglichen mit der angezeigten Tabelle, haben wir
Den Materialtext können wir direkt in HTML durch die Notation "matnr@text" ausgeben, siehe s10standardname, sodass wir hierfür kein eigenes Klassenattribut benötigen.
Die Informationen "Werk" und "Lagerort" sind für den Datenbankzugriff auf die Lagerortdaten des Materials (SAP-Tabelle MARD) gedacht. Alternativ könnten wir in jeder Tabellenzeile auch eine Referenz zu dem Kopfobjekt unterbringen und darüber auf Werk und Lagerort zugreifen.
Die Lagermengeneinheit und der aktuelle Lagerbestand werden aus SAP-Tabellen gelesen. Dazu implementieren wir wie in Tutorial 2 beschrieben zwei "build"-Methode, denen wir die Namen "build_meins" und "build_labst"geben:
ABAPmethods:
build_meins
importing
matnr type mard-matnr
exporting
meins type mara-meins,
build_labst
importing
matnr type mard-matnr
exporting
labst type mard-labst.
...
class mdr implementation.
method build_meins.
clear meins.
if matnr is initial.
return.
endif.
select single meins from mara into meins
where matnr = matnr.
endmethod.
method build_labst.
clear labst.
if matnr is initial.
return.
endif.
select single labst from mard into labst
where matnr = matnr
and werks = werks
and lgort = lgort.
endmethod.
Die beiden build-Methoden werden vom S10 Framework automatisch pro Tabellenzeile aufgerufen. Damit ist sichergestellt, dass die Mengeneinheit und der Lagerbestand jeweils korrekt für die eingegebene Materialnummer besorgt und angezeigt werden.
Jede eingegebene Materialnummer soll auf Gültigkeit geprüft werden, was wir durch Implementierung der folgenden Methode "validate_matnr" erreichen (siehe Tutorial 4):
ABAPmethods:
validate_matnr
importing
matnr type mard-matnr.
...
class mdr implementation.
method validate_matnr.
data: mymatnr type mara-matnr.
select single matnr from mard into mymatnr
where matnr = matnr
and werks = werks
and lgort = lgort.
if sy-subrc ne 0.
s10errormessage( |Materialnummer | && s10getuservalue( 'matnr' ) &&
| existiert nicht in Werk/Lagerort |).
endif.
endmethod.
In der Fehlermeldung arbeiten wir mit der S10-Methode "s10getuservalue", um die Materialnummer korrekt formatiert anzuzeigen, also zum Beispiel "98" statt des internen Formats "000000000000000098". Die Positionierung der Meldung auf die richtige Zeile und das Setzen des Eingabefokus auf die ungültige Materialnummer erfolgen automatisch durch das S10 Framework, das die Verbindung zwischen der Tabellenzeile und dem mdr-Objekt kennt:

class mdr_manager definition inheriting from /s10/any.
public section.
data:
werks type mard-werks,
lgort type mard-lgort,
tabmdr type table of ref to mdr.
methods:
start,
addrow,
delrow,
check,
save.
endclass.
DIe eigentliche Anwendung besteht aus den Methoden start, addrow, delrow, check und save.method start.
* user parameters
get parameter:
id 'WRK' field werks,
id 'LAG' field lgort.
* add 5 empty rows
do 5 times.
addrow( ).
enddo.
* display main screen
s10nextscreen( 'create').
endmethod.
Durch "addrow" wird eine leere Zeile in der Erfassungstabelle hinzugefügt. Wir können die Methode wie oben in "start" intern aufrufen, aber auch der Benutzer kann durch einen Klick auf die Drucktaste "Neue Zeile" eine leere Zeile hinzufügen.
* add row in item table
method addrow.
data: mymdr type ref to mdr.
create object mymdr.
* set general key values
mymdr->werks = werks.
mymdr->lgort = lgort.
append mymdr to tabmdr.
endmethod.
"delrow" wird vom Benutzer aufgerufen, indem er auf das Icon rechts in der Tabellenzeile klickt. In der Methode müssen wir zum Löschen der richtigen Zeile wissen, in welcher Zeile der Benutzer geklickt hat. Das geht über die einen Aufruf von s10contextinfo( ):
ABAP* delete row in item table
method delrow.
delete tabmdr index s10contextinfo( )->rownumber.
endmethod.
Bei "check" prüfen wir bei allen belegten Zeilen der Tabelle, ob Menge und Schadenstyp eingegeben wurden:
ABAPmethod check.
loop at tabmdr assigning field-symbol(<mdr>)
where table_line->matnr is not initial.
if <mdr>->speme le 0.
<mdr>->s10setfocus( 'speme' ).
s10errormessage( 'Bitte die Menge eingeben' ).
endif.
if <mdr>->ecode is initial.
<mdr>->s10setfocus( 'ecode' ).
s10errormessage( 'Bitte den Schadenstyp auswählen' ).
endif.
endloop.
s10infomessage( 'Daten geprüft' ).
endmethod.
Zwei Details sind dabei interessant:
In der where-Bedingung der loop-Anweisung können wir in ABAP durch "table_line" auf das Zeilenobjekt zugreifen:
ABAPloop at tabmdr assigning field-symbol(<mdr>))
where table_line->matnr is not initial.
Der Aufruf von s10setfocus erfolgt über das Zeilenobjekt:
ABAP<mdr>->s10setfocus( 'speme' ).

Wenn wir stattdessen nur s10setfocus( 'speme' ) aufrufen, versucht das S10 Framework, ein Eingabefeld "speme" des aktuellen Objekts der Klasse "mdr_manager" zu finden, was es aber nicht gibt. Die Fehlermeldung erscheint dann bei dem auslösenden "Prüfen"-Button, was dem Benutzer die Zuordnung zu der fehlenden Mengeneingabe erschwert:

Die "save" Methode ist für das Tutorial nicht näher ausgeführt, da es uns im Moment um die Dialogtechnik der Erfassungstabelle geht:
ABAP* save data
method save.
* check data first
check( ).
s10infomessage( 'Save: to be implemented' ).
endmethod.
Denkbar ist hier der Aufruf des SAP-BAPI "BAPI_GOODSMVT_CREATE" zum Erstellen eines Materialbelegs.
Die HTML-Seite "create" enthält den Kopfbereich mit Titel, den Drucktasten "Prüfen", "Sichern" und "Logoff", das Werk und den Lagerort, dann die Erfassungstabelle sowie den "Neue Zeile" Button. Da das Erstellen der HTML-Datei einige Schreibarbeit bedeutet, können Sie das S10 Generierungstool dazu nutzen und sich eine halbwegs passende Tabelle als Ausgangspunkt generieren lassen. Fast alle HTML-Optionen sind HTML5-Standard. Das S10 Framework interpretiert nur die speziellen CSS-Klassen der Elemente, um die Verbindung mit den ABAP-Objekten herzustellen.
HTML<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<meta name="viewport"
content="width=400">
<link rel='stylesheet' type='text/css'
href='../../../style/s10.style.css'>
<link rel='stylesheet' type='text/css'
href='../../../style/custom.style.css'>
<script
src='../../synactiveS10/synactiveS10.java.js'>
</script>
<title>Lagermaterial: Schaden erfassen</title>
</head>
<body style="width: 100%; margin: 0px; padding: 0px;"
onload='init();'
class="colorscheme9">
<div class="headerarea"
style="width: 100%;
font-size: 20px;
font-weight: bold;
padding: 10px;">
Lagermaterial: Schaden erfassen
</div>
<div class="toolbar">
<button type="button"
class="toolbarbutton"
onclick="S10Apply('check');">
Prüfen
</button>
<button type="button"
class="toolbarbutton"
onclick="S10Apply('save');">
Sichern
</button>
<button type="button"
class="toolbarbutton"
style="float: right;"
onclick="S10Logoff();">
Logoff
</button>
</div>
<!-- header fields -->
<div style="background-color: lightgray;">
<div class="infoblock"
style="width: 200px;
height: 50px;">
<label class="label output"
for="werks"
name="werks">
</label>
<br>
<span class='output'
name="werks">
</span>
<span class='output'
name='werks@text'>
</span>
</div>
<div class="infoblock"
style="width: 200px;
height: 50px;">
<label class="label output"
for="lgort"
name="lgort">
</label>
<br>
<span class='output'
name="lgort">
</span>
<span class='output'
name='lgort@text'>
</span>
</div>
</div>
<!-- column headers -->
<div class="colheaders nosort">
<!-- Material -->
<div class='colhead output'
style="width: 100px;"
name="matnr">
</div>
<div class='colhead output landscape'
style="width: 180px;"
name="matnr@text">
</div>
<!-- Menge -->
<div class='colhead'
style="width: 60px;"
name="speme">
Menge
</div>
<!-- Schadenscode-->
<div class='colhead'
style="width: 170px;"
name="ecode">
Schadenstyp
</div>
<!-- Lagerbestand -->
<div class='colhead landscape'
style="width: 60px;"
name="labst">
Bestand
</div>
<!-- ME -->
<div class='colhead landscape'
style="width: 60px;"
name="meins">
ME
</div>
</div>
<!-- list rows -->
<form class='table'
name='tabmdr'>
<div class="tablerow">
<input type="text"
class="inputcell valuehelp"
name="matnr"
style="float: left;
width: 100px;">
<div class='outputcelldiv landscape'
style="width: 180px;
font-size: 13px;
font-weight: normal;"
name="matnr@text">
</div>
<input type="text"
class="inputcell"
name="speme"
style="float: left;
width: 60px;">
<select class="inputcellselect"
name="ecode"
style="float: left;
width: 170px;">
<optgroup label="Wasserschaden">
<option value="W01">
Wasserschaden
</option>
<option value="W02">
Wasserspuren
</option>
</optgroup>
<optgroup label="Verpackung">
<option value="V01">
Verpackung beschädigt
</option>
<option value="V02">
Verpackung verschmutzt
</option>
<option value="V03">
Verpackung fehlt
</option>
<option value="V04">
Verpackung leer
</option>
</optgroup>
<optgroup label="Sonstiges">
<option value="S01">
Sonstiger Schaden
</option>
</optgroup>
</select>
<div class='outputcelldiv landscape'
style="width: 60px;"
name="labst">
</div>
<div class='outputcelldiv landscape'
style="width: 60px;"
name="meins@text">
</div>
<!-- delete row -->
<img style="width: 16px;
height: 16px;
margin-left: 10px;"
src="../../../icons/delete_64x64.png"
onclick="S10Apply('delrow')" />
</div>
</form>
<button type="button"
class="button-small"
style='margin: 4px;
background-color: #937a7a;
color: white;'
onclick="S10Apply('addrow');">
Neue Zeile
</button>
</body>
</html>
Die Erfassungstabelle beginnt in Zeile 59 mit den Spaltenüberschriften.
Zeile 59: <div class="colheaders nosort">
Die Option "nosort" sorgt dafür, dass die Umsortierung der Tabellenzeilen, die normalerweise durch Klick auf eine Spaltenüberschrift möglich ist, unterbleibt. Bei Erfassungstabellen macht es meist nicht viel Sinn, eine Umsortierung anzubieten, und der Pfeil für die Sortierreihenfolge verbraucht etwas Platz in der Überschrift.
Zeile 63: <div class='colhead output ' style="width: 100px;" name="matnr"></div>
Bei der CSS-Klasse "colhead output" entnimmt das S10 Framework die Überschrift aus dem SAP Repository gemäss dem bei name= genannten Klassenattribut .
Zeile 64 <div class='colhead output landscape'
style="width: 180px;" name="matnr@text">
</div>
Durch "landscape" wird diese Spalte im Hochformat ("portrait") nicht angezeigt
Zeile 93 <form class='table' name='tabmdr'>
Nach den Überschriften nun die eigentliche Tabelle, die durch ein <form>-Tag mit class='table' eingeleitet wird. Bei name= den ABAP-Namen der Tabelle nennen, hier "tabmdr". Alle in der nachfolgenden Tabellenzeile stehenden Elemente werden pro Tabellenzeile wiederholt und mit den Werten des Tabellenzeilen-Objekts gefüllt.
Ab Zeile 96 die einzelnen Spalten der Tabelle. Wichtig ist hier, dass die Breite genau mit der Breite der jeweiligen Spaltenüberschrift identisch ist. Für Hoch- und Querformat können Sie abweichende Breiten wählen, oder Spalten ganz unterdrücken. Das ist in der Anleitung "Responsive Webdesign" beschrieben.
Zeile 96 <input type="text" class="inputcell valuehelp" name="matnr"
style="float: left; width: 100px;">
Die Spalte "matnr" ist eingabebereit und gestattet die Auswahl über eine Suchhilfe, siehe die Anleitung "Suchhilfe".
Zeile 99 <div class='outputcelldiv landscape'
style="width: 180px; font-size: 13px; font-weight: normal;"
name="matnr@text">
</div>
Der Kurztext zur eingegebenen Materialnummer wird durch name="matnr@text" in dieser Spalte angezeigt. Im Hochformat ist die Spalte wegen der CSS-Klasse "landscape" unterdrückt.
Zeile 107 <select class="inputcellselect" name="ecode"
style="float: left; width: 170px;">
<optgroup label="Wasserschaden">
<option value="W01">Wasserschaden</option>
<option value="W02">Wasserspuren</option>
</optgroup>
<optgroup label="Verpackung">
<option value="V01">Verpackung beschädigt</option>
<option value="V02">Verpackung verschmutzt</option>
<option value="V03">Verpackung fehlt</option>
<option value="V04">Verpackung leer</option>
</optgroup>
<optgroup label="Sonstiges">
<option value="S01">Sonstiger Schaden</option>
</optgroup>
</select>
Eingabe über eine fest in der HTML-Seite programmierte 2-stufige Dropdown-Liste. Alternativ können Sie Dropdown-Listen auch in ABAP bereitstellen oder das S10 Repository als Grundlage nehmen, siehe die Anleitung "Dropdown-Listen".
Zeile 129 <div class='outputcelldiv landscape'
style="width: 60px;" name="meins@text">
</div>
Zur Mengeneinheit geben wir die sprachabhängige Bezeichnung aus, die durch name="meins@text" durch das S10 Framework zur Verfügung gestellt wird.
Zeile 133
<!-- delete row -->
<img style="width: 16px; height: 16px; margin-left: 10px;"
src="../../../icons/delete_64x64.png"
onclick="S10Apply('delrow')" />
Ausgabe eines Icons zum Löschen der Zeile. Über S10Apply( "delrow" ) wird die ABAP-Methode "delrow" aufgerufen.
Zeile 142 <button type="button" class="button-small"
style='margin: 4px; background-color: #937a7a; color: white;'
onclick="S10Apply('addrow'); ">
Neue Zeile
</button>
Der "Neue Zeile" Button, der im Anschluss an die Erfassungstabelle erscheint. Alternativ können wir in jeder Tabellenzeile ein Icon "Neue Zeile einfügen" und dann vor der betreffenden Zeile eine neue Zeile einfügen. Da es bei dieser Anwendung nicht auf die Reihenfolge der erfassten Materialien ankommt, haben wir uns auf die platzsparende Variante, Zeilen immer am Ende anzufügen, beschränkt.