Übersicht
In diesem Beispiel erstellen wir eine Anwendung, die die Messpunkte zu Equipments anzeigt. Der Benutzer kann neue Messungen eintragen und diese mit einer Unterschrift quittieren, oder bereits gespeicherte Messungen anzeigen.
 
Diese Anwendung ist zum Beispiel für Servicetechniker geeignet, die unterwegs einen einfachen Zugriff auf Equipmentdaten benötigen sowie schnell Messwerte eintragen und direkt im SAP-System speichern möchten. Denkbar ist aber auch, diese App direkt Kunden zur Verfügung zu stellen, die so eigene Messpunkte rückmelden können.

Beispiel
Es werden einige Daten zum Equipment angezeigt sowie die bisher eingetragenen Messungen:

Wird ein Messbeleg ausgewählt, werden Details zum Messpunkt, zur erfassten Messung sowie die getätigte Unterschrift angezeigt. Diese Ansicht kann dann direkt weiterverarbeitet werden, z.B. durch Ausrucken oder Weiterversand als PDF-Dokument:

Bei der Erfassung von Messungen können jeweils Werte eingetragen werden, wobei vom S10 Framework automatisch das richtige Format entsprechend der Mengeneinheit verwendet wird.  Erst nach dem eine Unterschrift erfolgt ist, kann der Beleg gespeichert werden:

Implementierung
Die Implementierung der Anwendung erfolgt in folgenden Schritten:
  1. Ermitteln der entsprechenden SAP-Tabellen für die anzuzeigenden Daten
  2. Ermitteln einer Schnittstelle zum Speichern der Messdaten
  3. Generieren der Basisanwendung mit den S10 Utilities
  4. Erweiterung der generierten Anwendung
  5. Implementierung der Speicherung von Messungen
  6. Implementierung der Speicherung der Unterschrift

Dies ist jedoch nur als Vorschlag zu betrachten, denn die HTML-Views können prinzipiell frei gestaltet werden, wobei nur die Verbindung zu den ABAP Objekten durch bestimmte Namenskonventionen sichergestellt werden muss.

1. Ermitteln der entsprechenden SAP Tabellen für die anzuzeigenden Daten
Wir möchten mit dieser S10 Anwendung Teile einer bestehenden SAP Transaktion abbilden. In diesem Fall starten wir mit der Transaktion IE03 (Equipment anzeigen) und können dort mit der F1-Taste die technischen Informationen zu Feldern anzeigen. Wie unten gezeigt, wird in der IE03 der Tabellenview ITOB verwendet, den wir auch als Basis für die Anwendung nehmen könnten.

 

Per Doppelklick auf den View sehen wir jedoch, dass hier die SAP-Tabelle EQUI (Equipment Stammdaten) verknüpft wird und nehmen daher diese als Ausgangspunkt für die Anwendungsgenerierung:



Genauso verfahren wir für die Messpunkte (Tabellenview IMPTT) sowie die Messbelege (Tabelle IMRG).

Einige Daten werden zusätzlich mit Hilfe von SAP-Funktionsbausteinen gelesen:
GET_MEASURING_POINTS_4_EQUIPM
(Messpunkte zu einem Equipment lesen)

MEASUREM_DOCUM_RFC_SINGLE_002
(Messpunkte und Messbelege: Dialog)

GET_MEASURING_DOCUMENTS
(Messbelege zu einem Equipment lesen) 

2. Ermitteln einer Schnittstelle zum Speichern der Eingabedaten (Messwerte)
Für das Speichern von Daten sollten prinzipiell Funktionsbausteine verwendet werden, falls diese für den Anwendungsfall vorhanden und geeignet sind. Als weitere Möglichkeit können Sie auch eine Transaktion per Batch-Input aufzeichnen und als Funktionsaufruf verfügbar machen. Außer in eigene Tabellen sollte in SAP-Tabellen generell nicht direkt geschrieben werden.

Geeignete Funktionen zum Speichern von Daten können Sie über die Transaktionen SE37 (Function Builder), SE38 (ABAP Editor), SE24 (Class Builder) oder auch BAPI (BAPI Explorer) finden oder zumeist auch im Internet an diversen Stellen.

In dieser Anwendung verwenden wir den Funktionsbaustein

MEASUREM_DOCUM_RFC_SINGLE_001
(Speichern von Messwerten)

sowie die ABAP-Klasse

CL_MIME_REPOSITORY_API
(API für MIME Repository)

zum Speichern der Bilddatei mit der Unterschrift.
3. Generieren der Basisanwendung mit den S10 Utilities
In der Regel ist es empfehlenswert, mit einer bestehenden Anwendung zu starten und diese anzupassen und zu erweitern. Dadurch sind bereits grundlegende Funktionen wie die Login-Seite vorhanden. In diesem Beispiel verwenden wir die S10 Utilities, um aus einer SAP-Tabelle eine bereits lauffähige Anwendung zu generieren. Details dazu finden Sie in der Dokumentation unter dem Punkt "Generierung".

Wir rufen dazu zunächst die Transaktion /S10/UTIL auf und wechseln auf den Reiter "Listanzeige generieren". Wie in Schritt 1 festgestellt, möchten wir zunächst mit der Sicht auf die Equipments starten und geben daher als Tabelle "EQUI" ein. Anschließend wählen wir einige Felder für Anzeige der Tabelle, Suchfelder sowie für die Detailansicht aus. Nach dem Ausführen befindet sich in dem angegeben Ausgabeverzeichnis dann eine fertige S10 Anwendung, die wir noch erweitern werden.





Eine S10 Anwendung ist im Prinzip aus einzelnen Views aufgebaut, das heißt separaten HTML-Seiten, die jeweils zu einer bestimmten ABAP-Klasse gehören. Das bedeutet, dass wir einzelne Teile der Anwendung getrennt entwickeln und testen können, um sie anschließend in einer komplexeren Anwendung gemeinsam zu verwenden.

Wir generieren daher weitere Anwendungen, wobei das Ausgabeverzeichns durchaus gleich bleiben kann, es wird dann ein weiterer View mit zugehöriger ABAP-Klasse generiert. Die Tabelle mit den Messungen der Messpunkte eines Equipments setzt sich dann allerdings aus unterschiedlichen Spalten zusammen, so dass wir diesen Teil direkt im HTML editieren.

Grundsätzlich besteht eine generierte S10 Anwendung aus einer Sicht auf die gewählten Felder der angegebenen Tabelle oder des Tabellenviews sowie der Möglichkeit, einzelne Zeilen der Tabelle aufzuklappen und so Details dieses Objekts anzuzeigen. Diese Detailansicht kann beliebig erweitert werden durch weitere Felder, aber auch komplexere Teile wie Bilder, weitere Tabellen oder Interaktions- und Navigationsmöglichkeiten.

4. Erweitern der generierten Anwendung
Konkret fügen wir in der generierten Detailansicht nun eine Tabelle mit den bisherigen Messungen ein sowie einen Pushbutton, der einen neuen Screen zur Eingabe von Messwerten öffnet. Dieser Screen kann wiederum als eigenständiger View mit zugehöriger Klasse entwickelt werden (modularer Aufbau). So wird das Wiederverwenden einzelner Teile sowie das Testen stark vereinfacht.

Die HTML-Views
Öffnen Sie zunächst die generierte HTML-Datei für den View, in unserem Fall die Datei "equi_manager.list.html" im Verzeichnis "classes\equi_manager\views.de". Die Detailansicht zu einer Zeile beginnt mit folgendem Code Teil:
 <!-- detail view -->
<div id="tabequi_detail" class='tabledetail'>

Sie finden in diesem Abschnitt die generierten Felder und wir können dort weitere Elemente einfügen:

Ein Pushbutton, um zu einem Screen zu navigieren, auf dem Messwerte eingetragen werden können:
<div class="infoblock">

    <button class="button" type="button" 
            onclick="S10Apply('open_measurepoints');">
            
            Messwerte eintragen
            
    </button>
</div>

Wenn der Benutzer auf diesen Pushbutton drückt, wird die ABAP-Methode 'open_measurepoints' in der aktiven Klasse aufgerufen.

Für die Tabelle mit den Messwerten zunächst die Überschriften:
 <div class="colheaders">

       <!-- Meßbeleg -->
       <div class='colhead  output' 
      	    style="width:80px; --landscape-width:100px;" name="mdocm">
       </div>

       <!-- Meßpunkt -->
       <div class='colhead output landscape' 
       	    style="width:90px; " name="point">
       </div>

<!-- weitere Spalten ... -->

</div>

Anschließend die Definition der Tabellenzeile, wobei wir die Zuordnung zu dem ABAP-Objekt wieder per name= angeben:
<!-- list rows -->
<form class='table' name='myequi.tabimptt'>

    <div class="tablerow">

        <div class='outputcelldiv link' 
             style="width:80px; --landscape-width:100px;" 
              name="mdocm">
       </div>

        <div class='outputcelldiv landscape' 
             style="width:90px;" 
             name="point">
       </div>

        <!-- weitere Spalten ... -->

    </div>
</form>


Die ABAP-Methoden

Wir haben in dem HTML-Coding an zwei Stellen Aufrufe in die ABAP-Klasse definiert:
  1. S10Apply('open_measurepoints')   

    ruft die Methode "open_measurepoints" auf

  2. <div name="mcdocm" class="outputcelldiv link">    

    definiert einen Link und ruft die Methode "on_link_mdocm" auf
Die erste Methode ruft im Wesentlichen einen neuen View mit einer Eingabetabelle für Messungen auf, liest zunächst jedoch dafür die Messpunkte dieses Equipments per Funktionsbaustein ein:
* open measure points for this equipment
  method open_measurepoints.

* Get number of row the user clicked in
    data:
    rownumber   type i.
    rownumber = s10contextinfo( )->rownumber.

* Read the object of this row
    read table tabequi index s10contextinfo( )->rownumber assigning field-symbol(<row>).

    data: mydiimpt type table of diimpt.
    data: mydiimpt_wa type diimpt.

* Get measure points for the equipment in this selected row
    call function 'GET_MEASURING_POINTS_4_EQUIPM'
      exporting
        i_equnr   = <row>->equnr
      tables
        et_diimpt = mydiimpt.

* Copy the data
    data: myimptt type ref to imptt_short.

    clear tabimptt_input.

    loop at mydiimpt into mydiimpt_wa.

      create object myimptt.

      myimptt->mrngu = mydiimpt_wa-unitc.
      myimptt->point = mydiimpt_wa-point.
      myimptt->pttxt = mydiimpt_wa-pttxt.

      append myimptt to  tabimptt_input.

    endloop.

    clear saved.

    s10dialog( 'measurepoints').

* The user might have saved some new measures,
* so update the table
    read_detail_tabequi( <row>->equnr  ).
    <row>->s10detailview = 'X'.


  endmethod.

Hinweis
Nachdem der Dialog bzw. der View "measurepoints" geschlossen wurde, z.B. in dem der Benutzer ein "zurück" ausgelöst hat, wird die Codeausführung in der nächsten Zeile fortgesetzt. In diesem Fall könnte der Benutzer neue Messungen eingetragen und gespeichert haben, so dass ein Auffrischen der vorherigen Anzeige angestoßen werden sollte.

Die zweite Methode navigiert zu einem View, der die Details zu der jeweiligen Messungen bzw. dem Messbeleg anzeigt sowie ein Bild der geleisteten Unterschrift:
* User clicked on a measurement document link
  method on_link_mdocm.

    data: mymdocm type imrc_mdocm.

* Get the measurement document for this row
    call method s10fromcontextinfo
      exporting
        key    = 'mdocm'
      changing
        result = mymdocm.

* Create a new object for the measurement document
    data: my_imptt_short type ref to imptt_short.
    create object my_imptt_short.

* Key = measurement document
    my_imptt_short->mdocm = mymdocm.

    data: my_imrg_ba type imrg.

* Read the data for this measurement document
    call function 'MEASUREM_DOCUM_RFC_SINGLE_002'
      exporting
        measurement_document = mymdocm
        with_dialog_screen   = ''
      importing
        imrg_ba              = my_imrg_ba.

    if sy-subrc <> 0.

* Something went wrong
      s10errormessage(
             exporting
               msgid = sy-msgid
               msgno = sy-msgno
               par1  = sy-msgv1
               par2  = sy-msgv2
               par3  = sy-msgv3
                ).
    endif.

* Copy the read data from the function call
    my_imptt_short->s10copy( my_imrg_ba  ).

* We read additional data from the database
    my_imptt_short->s10databaseread( ).


* read details
    if not my_imptt_short->myimrg is bound.
      create object my_imptt_short->myimrg.
    endif.

    my_imptt_short->myimrg->mdocm = my_imrg_ba-mdocm.
    my_imptt_short->myimrg->s10databaseread( ).


* Create the HTML code for the image of the signature
    my_imptt_short->signature_html =  |<img style='padding:5px;border-radius:15px;background-color:white; max-height:200px' 
    src='../../../signatures/|
        && mymdocm && |.png' onError="this.width=1; this.onError=null;">|.


* Open the detail view as new dialog
    my_imptt_short->s10dialog( 'details' ).

  endmethod.


Hinweise
  1. Die Anwendung ist zwar "stateful", das heißt, dass alle eingelesenen Daten sowie gemachte Eingaben vom Benutzer in den jeweiligen ABAP-Variablen gehalten werden. Jedoch muss bei einer Interaktion der jeweilige Kontext ermittelt werden, aus dem heraus der Benutzer agiert hat. Dies kann z.B. die geklickte Tabellenzeile sein. In der ABAP-Methode erhalten Sie Informationen zum Kontext über die "s10fromcontextinfo" Methode.
     
  2. S10 Felder sind normalerweise typisiert. Dadurch ist gewährleistet, dass z.B. bei Funktionsaufrufen Eingaben des Anwenders in das richtige interne Format durch das S10 Framework umgewandelt werden können. Eine Ausnahme sind jedoch Felder, die in dem HTML-View durch die CSS-Klasse "outputhtml" definiert werden. Diese werden nicht direkt als Text ausgegeben, sondern der Inhalt wird als HTML-Code interpretiert und das Ergebnis angezeigt. Dies weicht zwar die Trennung zwischen Anwendungslogik und Benutzeroberfläche etwas auf, bietet aber einige Möglichkeiten. In diesem Fall wird der HTML-Code generiert, der dafür sorgt, dass das Bild mit der Unterschrift in dem View angezeigt wird.

  3. Ein Vorteil des S10 Frameworks ist die Ausnahmebehandlung, die dafür sorgt, dass Fehlernachrichten direkt an das Frontend weitergereicht und dort als Fehlermeldung angezeigt werden können. Bei einer Fehlernachricht, die mit "s10errormessage" angezeigt wird, wird die Codeausführung an dieser Stelle abgebrochen.
5. Implementierung der Speicherung von Messungen 
Wir benötigen im Prinzip nur eine kleine Tabelle, die pro Messpunkt dessen Namen und die Erfassungseinheit anzeigt, sowie ein Eingabefeld für neue Messwerte anbietet. Hier sehen Sie die Verbindung zwischen den Elemente der HTML-Oberfläche und den jeweiligen Variablen in der ABAP Klasse (Details dazu unter dem Punkt "Datenbindung" in der Dokumentation):
<!-- list rows -->
<form class='table' name='tabimptt_input'>
    <div class="tablerow">

        <div class='outputcelldiv landscape' 
             style="width:100px;" name="point"></div>

        <div class='outputcelldiv' 
             style="width:calc(100% - 255px); 
                    --landscape-width:calc(100% - 355px);" 
             name="pttxt">
        </div>

        <input class="inputcell" type="text" 
               style="width:100px;" 
               name="recorded_value">
        
        <div class='outputcelldiv' 
             style="width:100px;" 
             name="mrngu"></div>
    </div>
</form>

Die Felder in der dazugehörigen ABAP-Klasse:
* View for Measurement Document -> List View
class imptt_short definition inheriting from /s10/any.

  public section.

* table fields for list view, plus key fields
    data:
      point          type imptt-point, " measuring point
      pttxt          type imptt-pttxt, " designation
      mrngu          type imrc_mrngu, " Unit using text
      recorded_value type rimr0-recdc, " Value using Text

Wir benötigen nun noch einen einfachen Pushbutton, mit dem der Benutzer das Speichern der eingetragenen Messwerte auslösen kann:
<div class="toolbar">
    <button type="button" class="toolbarbutton" 
            onclick="S10Apply('measures_save');">
        Speichern
    </button>
</div>

Hinweis
Hier sei noch einmal betont, dass keine Übergabe der eingegebene Werte an die ABAP-Methode notwendig ist, sondern das S10 Framework automatisch dafür sorgt, dass die jeweils am Front-End eingegebenen Daten des Benutzers in einer aufgerufenen ABAP-Methode direkt zur Verfügung stehen. Umgekehrt werden in der ABAP-Methode geänderte Daten nach Ablauf automatisch an das Front-End geschickt, so dass dort jeweils die aktuellen Daten angezeigt werden.

Die entsprechende ABAP-Methode "measures_save" ist nicht besonders umfangreich:
* We save the measures that the user entered
  method measures_save.

* Already saved?
    if saved is not initial.

      s10errormessage( s10localize( id =  'measure_saved_already' ) ).

    endif.

* Signature?
    if signature is initial.
      s10errormessage( s10localize( id =  'measure_signature_missing' ) ).
    endif.


    data: tabimptt_input_wa type ref to imptt_short.
    data: mymeasurement_document type imrg-mdocm.

* Save measures
    loop at tabimptt_input into tabimptt_input_wa.

      if tabimptt_input_wa->recorded_value is not initial.

        call function 'MEASUREM_DOCUM_RFC_SINGLE_001'
          exporting
            measurement_point    = tabimptt_input_wa->point
            recorded_value       = tabimptt_input_wa->recorded_value
          importing
            measurement_document = mymeasurement_document
          exceptions
            others               = 1.

        if sy-subrc <> 0.

* something went wrong
          s10errormessage(
            exporting
              msgid      =     sy-msgid
              msgno      =     sy-msgno
              par1       =     sy-msgv1
              par2       =     sy-msgv2
              par3       =     sy-msgv3
          ).

        endif.

* Save signature to mime repository
        data: filename type string.
        filename = '/signatures/' && mymeasurement_document && '.png'.
        save_signature( filename = filename ).

        data: mytext type string.
        mytext = s10localize( id =  'measure_saved_for_point').
        replace '%1' in mytext with tabimptt_input_wa->point.

        s10infomessage( mytext ).

* Add CSS class -> input cannot be edited anymore
        tabimptt_input_wa->s10addcss(
          exporting
            attrname     =     'recorded_value'
            cssclassname =     'inputdisabled'
        ).
      endif.

    endloop.

* set saved status
    saved = 'X'.

  endmethod.

Hinweis zum Datentyp der einzugebenden Messwerte
Der Messwert, den der Benutzer eingibt, muss in diesem Beispiel nicht erst in einen anderen Datentyp umgewandelt werden, da er bereits in dem für den Funktionsbaustein erwarteten Typ definiert ist. Der Typ ist "rimr0-recdc welcher zur Domäne FLTP_INOUT (Ein-/Ausgabefeld für Gleitkommafelder, 22-stellig) gehört und im Prinzip ein CHAR, also Text ist. Sie können jedoch in dem ABAP-Programm dem S10 Framework mitteilen, welches Feld die Mengeneinheit definiert. Auf der Oberfläche wird dann die Einheit entsprechend formatiert, z.B. ohne Dezimalstellen bei ST (Stück), 2 Dezimalstellen bei Währungseinheiten und so weiter:
    data:
      recorded_value      type rimr0-recdc, " Value
      mrngu               type imrc_mrngu, " Unit 
* Units
      unit_recorded_value type string value 'mrngu',

Hier definieren wir ein Feld für die Eingabe eines Messwerts, ein Feld für die Mengeneinheit sowie eine Verbindung zwischen beiden Feldern durch ein vorangestelltes "unit_" vor dem Namen des Feldes.



Hinweis zu Übersetzungsmöglichkeiten
Neben der Möglichkeit, im ABAP-Coding sog. Text-IDs zu verwenden für die Übersetzung von Texten, bietet das S10 Framework auch die Möglichkeit, kleine Textdateien zu pflegen und per "s10localize" den Text in der Anmeldesprache des Benutzers zu ermitteln. Details dazu finden Sie in der Referenz zu s10localize() .
6. Implementierung der Speicherung der Unterschrift
Die Möglichkeit zur Unterschrift wurde in Form einer JavaScript-Bibliothek, die als Open-Source Software unter github.com/szimek/signature_pad verfügbar ist, eingebunden.  Sie bietet die Möglichkeit, das Bild der Unterschrift direkt als Zeichenfolge zu speichern (Base64). Somit kann es sehr einfach in einem versteckten S10 Feld an die ABAP-Methode übertragen werden. Anschließend wird dieser Text mit einem Funktionsbaustein wieder in das binäre Format umgewandelt und im MIME-Repository als Bilddatei abgelegt.

Wir binden zunächst die JavaScript-Bibliothek in dem HTML-View ein:
<script src="../../../scripts/signature_pad.js"></script>

Für das Feld für die Unterschrift wird ein sog. HTML5 <canvas> Element verwendet, welches das Zeichnen von Grafiken ermöglicht. JavaScript bietet im Standard einen Befehl an, mit dem die Grafik als Textrepräsentation (Base64) gespeichert und weiterverarbeitet werden kann:
<!-- Canvas element to draw graphics -->
<canvas height="200" width="350" style="background-color: white" id="sketchpad"></canvas>

<!-- Hidden S10 field for transfering data to ABAP-->
<input type="hidden" class="input" id="signature" name="signature">

Nach dem der Benutzer unterschrieben hat, wird das Bild als Base64 String codiert und in das S10 Feld gestellt.
Dadurch wird es automatisch vom S10 Framework in die ABAP Variable übertragen:
var img = signaturePad.toDataURL();
img = img.replace('data:image/png;base64,', '');        
document.getElementById("signature").value=img; 

Beim Speichern der Messwerte wird zusätzlich die Methode "save_signature" aufgerufen. Diese wandelt den Base64 Text in Binärdateien um und speichert diese im MIME-Repository als .PNG Bilddatei ab:
method save_signature.

    clear mybase64.
    clear content.

* Image of the signature as Base64 string
    mybase64 = signature.

* Convert to xstring
    call function 'SSFC_BASE64_DECODE'
      exporting
        b64data = mybase64
      importing
        bindata = content
      exceptions
        others  = 8.


* Get API for the MIME repository
    data(o_mime_rep) = cl_mime_repository_api=>get_api( ).

* Get the path of our application as root for the image files
    data: mypath type string.
    mypath = projectpath.

* append the filename
    mypath = mypath && '/' && filename.

* Save image in MIME repository.
* Object catalog entry is created, any existing files are overwritten
*
* -> local object in this case, no transport

    o_mime_rep->put(  i_url = mypath
                     i_content = content
                     i_check_authority = abap_false
                     i_suppress_dialogs = 'X'
                     i_suppress_package_dialog = 'X'
                     i_dev_package = '$TMP'
                     ).
    if sy-subrc <> 0.

* something went wrong
      s10errormessage(
        exporting
          msgid      =     sy-msgid
          msgno      =     sy-msgno
          par1       =     sy-msgv1
          par2       =     sy-msgv2
          par3       =     sy-msgv3
      ).

    endif.

  endmethod.

Download
Sie können alle Projektdateien hier herunterladen:
s10_service_example.zip

Das .ZIP Archiv enthält die HTML-Views sowie eine Textdatei mit dem ABAP Programm "/s10/service_demo". Zum Importieren der HTML-Views können Sie den Report "BSP_UPDATE_MIMEREPOS" verwenden wie in der Dokumentation unter dem Punkt Entwicklungsumgebung" beschrieben.

Bitte passen Sie ggf. noch Nummer des Mandanten in der Datei "user.logon.html" an. Details dazu finden Sie in der Dokumentation unter dem Punkt S10Logon()
 

Komponente: S10 Framework