Im SAP System werden Dokumente zu einem Objekt meistens über die "Objekt Services" als "Attachments" abgelegt. In Transaktion IE02 (Ändern Equipment) sieht das zum Beispiel wie folgt aus:

Zahlreiche Dateitypen sind dabei möglich; gebräuchlich sind PDFs, Word- und Excel-Dateien sowie Bilddateien im .jpg oder .png Format.

In diesem Tutorial integrieren wir die Dokumentenanzeige sowie den Upload in eine S10 Anwendung. Unser Startpunkt ist eine Liste von Equipments, die wir uns mit den S10 Utilities, Transaktion /s10/util, generiert haben:

In diese Basisliste bauen wir die Dokumentenanzeige ein. Bei Klick auf eine Equipmentzeile erscheinen einige Details zum jeweiligen Equipment sowie die Anlagenliste:

In der Liste der Dokumente wird bei einem Klick das Dokument entweder extern angezeigt (Word- und Exceldateien) oder inline in die Liste eingebettet (Bilddateien, PDF, Video). Der Browser bietet dabei in der Regel die Möglichkeit, das Dokument nachträglich statt in der Liste als eigenständige Anwendung anzuzeigen. Bei Word und Excel ist zur Zeit in den gängigen Browsern keine eingebettete Anzeige unterstützt.

Hier einige Beispiele:

Die Dokumente können jeweils einzeln auf- und zugeklappt werden. Bei Excel und Word wird die entsprechende Anwendung auf dem mobilen Gerät oder auf dem Desktop geöffnet:

Nun zur Implementierung. Wir nutzen die globale Klasse /s10/attachment, die Teil des S10 Demo-Pakets ist; sie stellt einige generelle Services bereit, um Dokumente zu SAP Objekten zu lesen. Damit wird das ABAP-Programm recht übersichtlich:

ABAP
program zzequi00.

class equi_detail definition inheriting from /s10/any.

  public section.

* Geräteigenschaften
    data:
      equnr type v_equi-equnr, " Gerätenummer
      tplnr type v_equi-tplnr, " Technischer Platz
      eqtyp type v_equi-eqtyp, " Gerätetyp
      inbdt type v_equi-inbdt, " Inbetriebnahmedatum
      sernr type v_equi-sernr. " Seriennummer

* Anhänge
    data:
      attachment  type ref to /s10/attachment,
      attachments type table of ref to /s10/attachment.

* Name der Datenbanktabelle
    constants:
      dbtablename type string value 'v_equi'.

    methods:
      build_attachments
        importing
          equnr       type equnr
        exporting
          attachments type table,

      on_detail_attachments.

endclass.

class equi_detail implementation.

  method build_attachments.

    /s10/attachment=>list_attachments(
      exporting
        instid = equnr
        typeid = 'EQUI'
        catid  = 'BO'
      importing
        attachments = attachments ).

* Anhänge sortieren
    s10sort(
      exporting
        foldername = 'attachments'
        columns    = 'doctitle' ).

  endmethod.

  method on_detail_attachments.

    create object attachment.
    attachment->dockey =
        s10contextinfo( )->getvalue( 'dockey' ).

  endmethod.

endclass.


class equi_short definition inheriting from /s10/any.

  public section.

* Tabellenfelder für Detailansicht sowie Schlüsselfelder
    data:
      equnr type v_equi-equnr,
      tplnr type v_equi-tplnr.

* Name der Datenbanktabelle
    constants:
      dbtablename type string value 'v_equi'.

endclass.

class equi_short implementation.
endclass.


class equi_manager definition inheriting from /s10/any.

  public section.

* Tabellenfelder für Listenansicht sowie Schlüsselfelder
    data:
      search_equnr       type string,
      search_equnr_upper type string,
      search_tplnr       type v_equi-tplnr,
      search_tplnr_upper type string,
      search_sernr       type v_equi-sernr,
      search_sernr_upper type string,
      search_maxrowcount type string value '100'.

    data:
      tabequi type table of ref to equi_short,
      myequi  type ref to equi_detail.

    methods:
      list,
      on_detail_tabequi,

* Methode zum Aufbau von tabequi
      build_tabequi
        importing
          search_equnr       type string
          search_tplnr       type v_equi-tplnr
          search_sernr       type v_equi-sernr
          search_maxrowcount type string
        exporting
          tabequi            type table.

endclass.


class equi_manager implementation.

* Listbildschirm anzeigen
  method list.
    s10nextscreen( 'list' ).
  endmethod.


* Datenbankwerte selektieren und Tabelle tabequi füllen
  method build_tabequi.

    data: condition type string,
          join      type string,
          search    type string.

    search_equnr_upper = to_upper( search_equnr ).
    search_tplnr_upper = to_upper( search_tplnr ).
    search_sernr_upper = to_upper( search_sernr ).

    if search_equnr is not initial.
      search = search_equnr.
      if join is initial.
        join = |v_equi |.
      endif.

      join = join &&
        | join eqkt on eqkt~EQUNR = v_equi~equnr and
        eqkt~SPRAS = @sy-langu and upper( eqkt~EQKTX )
        like '%| && search_equnr_upper && |%'|.
    endif.

    if search_tplnr is not initial.
      search = search_tplnr.
      if condition is not initial.
        condition = condition && | and |.
      endif.

      condition = condition &&
        |v_equi~TPLNR EQ '| && search_tplnr && |'|.
    endif.

    if search_sernr is not initial.
      search = search_sernr.
      if condition is not initial.
        condition = condition && | and |.
      endif.

      condition = condition &&
        |v_equi~SERNR EQ '| && search_sernr && |'|.
    endif.

    data: maxrows type i.
    maxrows = search_maxrowcount.

* Daten lesen
    s10databaseselect(
      exporting
        dbtablename = join
        condition   = condition
        maxrows     = maxrows
        distinct    = 'X'
        fieldlist   = 'v_equi~equnr,v_equi~tplnr'
      changing
        folder      = tabequi ).

* Tabelle sortieren
    s10sort(
      foldername = 'tabequi'
      columns    = 'equnr,tplnr'
      userformat = 'X' ).

  endmethod.


* Details in der Liste anzeigen (Zeilenauswahl)
  method on_detail_tabequi.

* Aktuelle Tabellenzeile lesen
    data: tabindex type i.
    tabindex = s10actionparameter( ).

    read table tabequi
      index tabindex
      assigning field-symbol(<row>).

* Tabellenschlüssel setzen und Detailattribute lesen
    create object myequi.
    myequi->equnr = <row>->equnr.
    myequi->tplnr = <row>->tplnr.

    myequi->s10databaseread(
      keylist = 'equnr,tplnr' ).

  endmethod.

endclass.


* Hauptklasse für diese Anwendung
class main definition inheriting from /s10/any.

  public section.

* Manager-Objekt
    data:
      my_equi_manager type ref to equi_manager.

    methods:
      logon.

endclass.


class main implementation.

* Benutzer anmelden
  method logon.

* S10-Lizenz setzen
    s10setlicense(
      'Synactive GmbH demo license number=100
       role=s10demo_role maxusers=10
       signature=821.126.87.7' ).

* Manager-Objekt erzeugen
    create object my_equi_manager.

* Listenanzeige starten
    my_equi_manager->list( ).

  endmethod.

endclass.

Zur Erläuterung ist die Verbindung zur HTML-Seite an einigen Stellen erforderlich:

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>Equipments</title>
</head>

<body style="width: 100%;
             margin: 0px;
             padding: 0px;"
      onload='init();'
      class="colorscheme9">

    <div class="headerarea"
         style="width: 100%;
                padding: 10px;">

        <b>Equipments</b>

    </div>

    <div class="toolbar">

        <button type="button"
                class="toolbarbutton"
                onclick="S10Enter();">
            Start
        </button>

        <button type="button"
                class="toolbarbutton"
                style="float: right;"
                onclick="S10Logoff();">
            End
        </button>

    </div>

    <!-- filter -->
    <div class="tablefilteractive">

        <div class="infoblock"
             style="width: 150px;
                    height: 50px;">

            <label class="label">
                Equipmenttext
            </label>

            <br>

            <input type="search"
                   class="input"
                   name="search_equnr"
                   style="width: 140px;">

        </div>

        <div class="infoblock"
             style="width: 150px;
                    height: 50px;">

            <label class="label">
                Technischer Platz
            </label>

            <br>

            <input type="search"
                   class="input"
                   name="search_tplnr"
                   style="width: 140px;">

        </div>

        <div class="infoblock"
             style="width: 150px;
                    height: 50px;">

            <label class="label">
                Serialnummer
            </label>

            <br>

            <input type="search"
                   class="input"
                   name="search_sernr"
                   style="width: 140px;">

        </div>

        <br />

        <div class="infoblock"
             style="width: 150px;
                    height: 50px;">

            <label class='label'>
                Maximale Anzahl
            </label>

            <br />

            <select class="inputselect"
                    name="search_maxrowcount"
                    size="1"
                    style='width: 120px;'
                    onchange="S10Enter();">

                <option value="100">100</option>
                <option value="400">400</option>
                <option value="1000">1000</option>
                <option value="4000">4000</option>
                <option value="10000">10000</option>
                <option value="40000">40000</option>
                <option value="100000">100000</option>

            </select>

        </div>

        <div class="infoblock"
             style="width: 150px;
                    height: 50px;">

            <label class='label'>
                Anzahl selektiert
            </label>

            <br />

            <div class='output'
                 style="font-size: 14px;
                        padding-top: 6px;
                        font-weight: bold;"
                 name='tabequi@rowcount'>
            </div>

        </div>

        <div class="processingmessage">
            Daten wurden selektiert ...
        </div>

    </div>

    <!-- column headers -->
    <div class="colheaders">

        <div class='colhead colheadup output'
             style="width: 100px;
                    max-width: 100px;"
             name="equnr">
        </div>

        <div class='colhead output'
             style="width: 260px;"
             name="equnr@text">
        </div>

        <div class='colhead output landscape'
             style="width: 300px;"
             name="tplnr@text">
        </div>

        <div class="colhead"
             style="width: 20px;
                    float: right;
                    margin-right: 4px;"
             onclick="S10FilterTable(this);">

            <img src="../../../icons/filter.png"
                 style="width: 18px;
                        height: 18px;">

        </div>

    </div>

    <!-- list rows -->
    <form class='table'
          name='tabequi'>

        <div class="tablerow"
             onclick="S10ToggleDetail(this);">

            <div class='outputcelldiv'
                 style="width: 100px;
                        max-width: 100px;"
                 name="equnr">
            </div>

            <div class='outputcelldiv'
                 style="width: 260px;"
                 name="equnr@text">
            </div>

            <div class='outputcelldiv landscape'
                 style="width: 300px;"
                 name="tplnr@text">
            </div>

            <div class='tablerequestdetail'
                 style='float: right;'>
            </div>

        </div>

    </form>

    <!-- nothing selected -->
    <div class="tablenocontent"
         style="display: block;">
        Es wurde nichts selektiert
    </div>

    <!-- detail view -->
    <div id="tabequi_detail"
         class='tabledetail'>

        <div class="infoblock">
            <label class='label output'
                   name="myequi.eqtyp">
            </label>

            <br />

            <span class='output'
                  name='myequi.eqtyp'>
            </span>

            <span class='output'
                  name='myequi.eqtyp@text'>
            </span>

        </div>

        <div class="infoblock">
            <label class='label output'
                   name="myequi.inbdt">
            </label>

            <br />

            <span class='output'
                  name='myequi.inbdt'>
            </span>

        </div>

        <div class="infoblock">
            <label class='label output'
                   name="myequi.sernr">
            </label>

            <br />

            <span class='output'
                  name='myequi.sernr'>
            </span>

        </div>

        <div class="infoblock"
             style="width: 100%;
                    max-width: 800px;
                    height: auto;
                    display: block;">

            <label class='label'>
                Dokumente
            </label>

            <br />

            <form class='table'
                  name='myequi.attachments'
                  style="width: 360px;
                         --landscape-width: 480px;">

                <div class="tablerow"
                     onclick="S10ToggleDetail();">

                    <div class='outputcelldiv linkkey'
                         name="dockey">
                    </div>

                    <div class='outputcelldiv'
                         style="width: 220px;
                                --landscape-width: 320px;"
                         name="doctitle">
                    </div>

                    <div class='outputcelldiv'
                         style="width: 40px;"
                         name="doctype">
                    </div>

                    <div class='outputcelldiv'
                         style="width: 80px;"
                         name="docsize_out">
                    </div>

                </div>

            </form>

            <div class="tablenocontent">
                Keine Dokumente abgelegt
            </div>

            <br />

        </div>

        <div class='tabledetail'
             id="myequi.attachments_detail">

            <div class="outputhtml"
                 name="myequi.attachment.display_html">
            </div>

        </div>

</body>

</html>

Erläuterungen

Die Zeilennummern A... beziehen sich auf das ABAP-Coding, H... auf die HTML-Datei.

Zeile A3

ABAP

class equi_detail definition inheriting from /s10/any.
 

Die Klasse "equi_detail" beschreibt die Detailsicht des Equipments, nach Selektion einer Zeile in der Liste. Die Attribute (Zeilen A9 bis A13) werden in HTML ab H155 angezeigt. Wenn Sie weitere Infos zu dem Equipment anzeigen wollen, die in dem Datenbank-View V_EQUI enthalten sind, genügt es, diese in der Klasse noch dazuzunehmen und in der HTML-Datei auszugeben. Das Lesen aller Attribute geschieht durch s10databaseread( ) in Zeile A192.

Zeile A15

ABAP
* attachments
    data:
      attachment  type ref to /s10/attachment,
      attachments type table of ref to /s10/attachment.

 Definiert ein attachment-Objekt und die Tabelle der Attachments. Die Ausgabe der Tabelle erfolgt in HTML ab Zeile H181 bis H209. Die Spalten doctitle, doctype und docsize_out sind als Attribute der Klasse /s10/attachment definiert.

Neben den sichtbaren Spalten wird der Dokumentenschlüssel unsichtbar in der Tabelle vermerkt: H191 <div class='outputcelldiv linkkey' name="dockey"></div> Bei Klick auf eine Attachmentzeile wird in ABAP dieser Schlüssel durch s10contextinfo() gelesen:

ABAP
method on_detail_attachments.

    create object attachment.
    attachment->dockey =  s10contextinfo( )->getvalue( 'dockey').

endmethod.

 Zeile A37

Die Tabelle der Attachments pro Equipment ist als build-Methode realisiert. Zur Impelmentierung wird die Methode "list_attachments" der Klasse /s10/attachment aufgerufen:

ABAP
/s10/attachment=>list_attachments(
      exporting
         instid =  equnr
         typeid  = 'EQUI'
         catid   = 'BO'
      importing
        attachments = attachments ).

Für andere Objekte müssen Sie hier die richtige "typeid" mitgeben, das ist der "Business Object Type" (Transaktion SWO2). Für eine Bestellanforderung wäre es zum Beispiel "BUS2105". Die Katalog-Id ist immer "BO" für "Business Object".

Zeile A64

ABAP
class equi_short definition inheriting from /s10/any.

Die Klasse "equi_short" beschreibt die Zeile der Equipemntliste. WIr benötigen hier nur die Equipementnummer und den Technischen Platz. Die entsprechenden Texte werden durch das S10 Framework in der Ausgabe durch die Notation "...@text" ergänzt. In der HTML Datei finden Sie die Ausgabe der Tabelle ab Zeile H125, beginnend mit

HTML
<form class='table' name='tabequi'>

Ab Zeile A119

ABAP
class equi_manager implementation.

Der Rest des Programms ist unabhängig von der Anzeige der Dokumente; er ist durch die S10 Utilities generiert.

Der zentrale Abschnitt in der HTML-Datei ist die Anzeige des Dokuments ab Zeile H211:

HTML
<!-- Document display area -->
<div class='tabledetail' id="myequi.attachments_detail">
    <div class="outputhtml" name="myequi.attachment.display_html">
    </div>
</div>

 Diese Abschnitt wird automatisch hinter die ausgewählte Zeile der Attachment-Liste eingeblendet, sobald der Benutzer auf eine Attachment-Zeile klickt, da

HTML
<div class="tablerow" onclick="S10ToggleDetail();">

definiert ist in Zeile H188.

Es wird zunächst die ABAP-Methode

ABAP

method on_detail_attachments.

    create object attachment.
    attachment->dockey =  s10contextinfo( )->getvalue( 'dockey').

endmethod. 

des Objekts "myequi" gerufen, da die Tabelle "attachments" im Objekt "myequi" definiert ist. Allerdings passt das Objekt "myequi"  im ABAP Programm immer zur letzten ausgewählten Equipmentzeile; es ist ja nur einmal vorhanden. Wenn der Benutzer zunächst einige Equipments aufklappt und dann in einem der aufgeklappten Bereiche ein Attachment anzeigt, nutzt uns die Zeilennummer aus HTML im ABAP Programm nichts, da die aktuelle attachment-Tabelle zu einem anderen Equipment gehören kann. Diese Schwierigkeit entsteht durch die geschachtelte Struktur der Liste, also Liste der Equipments und pro Equipment eine Liste der Attachments.

Aus diesem Grund nutzen wir nicht die Zeilennummer und die aktuelle Tabelle "attachments", sondern greifen durch

attachment->dockey =  s10contextinfo( )->getvalue( 'dockey').

auf den in der HTML-Zeile versteckten Dokumentenkey zu und setzen ihn in das attachment-Objekt. Da in der HTML-Seite das Attribut "display_html" ausgegeben wird:

<div class="outputhtml" name="myequi.attachment.display_html">

läuft nun die build-Methode des Attributs "display_html" der globalen Klasse "/s10/attachment" ab. Die build-Methode liest das Dokument ein und baut den richtigen HTML-Code zur Anzeige des Objekts auf. In unserer HTML-Datei ist es hier wichtig, mit class="outputhtml"statt mit class="output" zu arbeiten, da andernfalls der HTML-Code angezeigt würde, statt ihn als HTML zu interpretieren. 

Unsere Dokumentenanzeige wäre einfacher zu implementieren, wenn wir die Informationen pro Equipment und pro Attachment nicht in die Liste integrieren würden, sonden jeweils in separat aufgerufenen Seiten anzeigen würden, aus denen der Benutzer dann zur Liste zurückgeht. Der Vorteil der gewählten Darstellung ist, dass die einmal aufgeklappten Teile, zum Beispiel zwei Fotos, in der Liste simultan sichtbar bleiben oder zumindest durch Blättern leicht erreichbar sind. Zum Beispiel wären zwei Fotos zu einer Schadensmeldung dann unmittelbar untereinander sichtbar.

 

Upload von Dokumenten

Nun zum Hochladen von Dokumenten und Abspeichern als Attachment. Das Vorgehen hierzu ist wie folgt:

  1. In HTML die Auswahl der gewünschten hochzuladenden Datei aufnehmen
  2. Mit Javscript-Mitteln den Inhalt der ausgewählten Datei in eine versteckte S10 Variable übertragen
  3. In ABAP den Dateinhalt aus der S10 Variablen übernehmen und mit der Klasse "attachment" des S10 Frameworks als Attachment zum Equipment abspeichern

 

Schritt 1: In HTML die Auswahl der gewünschten hochzuladenden Datei aufnehmen

HTML
<label class='label'>Titel für neues Dokument</label>
<br />
<input class="input" 
    data-locid="attachment_title_input"
    style="width: 380px;">
<input type="file" 
    data-locid="attachment_input" 
    accept=".jpg,.jpeg,.png,.pdf,.docx,.xlsx,.txt" 
    style="display: none" 
    onchange="upload_attachment(this)">
<br />
<button type="button" class="button" 
     data-locid="attachment_button" 
     style="margin-top: 5px;" 
     onclick="return start_upload_attachment(this)">
    Dokument auswählen und hochladen
    <span class="processingmessage">Upload läuft</span>
</button>

Erläuterungen

Zeilen 4, 8, 15: data-locid=

In unserer Anwendung wird die Upload-Möglichkeit unterhalb der Attachment-Liste innerhalb des Detailbereichs der Tabellenzeile angezeigt. Es kann also, wenn der Benutzer für mehrere Zeilen nacheinander den Detailbereich öffnet, mehrfach erscheinen. Aus diesem Grund können wir zur Identifizierung der  HTML-Elemente aus JavaScript nicht das gebräuchliche id= verwenden, da in HTML die vergebene id in der HTML-Seite eindeutig sein muss, um mit document.getElementById() zu funktionieren. Das Attribut data-locid gestattet uns, ein Element innerhalb des geöffneten Detailbereichs mit der S10-Farmework-Funktion S10 ElementByLocid() zu adressieren (siehe JavaScript in Schritt 2), es ist sozusagen eine lokal verwendbare Id.

Zeile 7: <input type=file>

Hierduch wird in HTML die Auswahl einer Datei durch den Benutzer ermöglicht. Mit accept=... geben wir die Dateikennungen an, die bei der Auwahl möglich sein sollen. In Zeile 10 wird das Element mit style="display:none" versteckt, da wir nicht die HTML-Standarddarstellung nutzen wollen, sonden im Anschluss in Zeile 14 einen eigenen Button zur Dateiauswahl vorsehen.

Der oben angegebene HTML-Teil kommt in den Detaibereich der Tabellenzeile. Zusätzlich benötigen wir ausserhalb des Dateilbereichs ein einzelnes verstecktes <input>-Element für die S10 Variable, mit der wir den Dateiinhalt in unser ABAP-Programm transportieren lassen:

HTML
<input type="hidden" 
  class="input" 
  name="data_url" 
  id="data_url" />
In unserem ABAP-Programm wird hierzu (Schritt 3) eine Variable "data_url" definiert, in der wir den Dateiinhalt erhalten:
ABAP
data:
  data_url type string.

Schritt 2: Mit Javscript-Mitteln den Inhalt der ausgewählten Datei in eine versteckte S10 Variable übertragen

Fügen Sie folgendes JavaScrpt-Coding in der HTML-Seite ein. Wo es sich befindet, spielt keine Rolle, also in <head> oder <body>. Falls Sie das Hochladen von Dokumenten in mehreren HTML-Seite benötigen, am besten als eigene Datei ablegen und mit <script src="..."> </script> referieren.

HTML
<script>

var attachment_reader = null;
var attachment_input = null;
var attachment_button = null;
var attachment_title = "";
var attachment_filename = "";

function start_upload_attachment(f) {

    attachment_title =
        S10ElementByLocid(
            f,
            'attachment_title_input'
        ).value;

    if (!attachment_title) {
        alert(
            'Bitte geben Sie zunächst einen Titel ein'
        );
        return;
    };

    attachment_input =
        S10ElementByLocid(
            f,
            'attachment_input'
        );

    attachment_button =
        S10ElementByLocid(
            f,
            'attachment_button'
        );

    // reset value, otherwise onchange is not triggered
    // for same filename
    attachment_input.value = "";

    attachment_input.click();

    return false;
};


function upload_attachment(f) {

    var files = f.files;

    // anything selected?
    if (!files) {
        return;
    };

    var fileobject = files[0];

    attachment_filename =
        fileobject.name;

    attachment_reader =
        new FileReader();

    attachment_reader.addEventListener(
        "load",
        complete_upload_attachment
    );

    attachment_reader.readAsDataURL(
        fileobject
    );
}


function complete_upload_attachment() {

    var result =
        attachment_reader.result;

    attachment_reader = null;

    // check file size
    if (result.length > 1024 * 1024) {

        S10ErrorMessage(
            attachment_button,
            "Synactive Demo-System: Bitte nur Dateien < 1MB hochladen"
        );

        return;
    };

    document.getElementById(
        'data_url'
    ).value = result;

    S10Apply(
        'save_attachment',
        attachment_filename + '\t' +
        attachment_title,
        attachment_button
    );
};

</script>

Erläuterungen

Zeile 9 Funktion "start_upload_attachment"

Die Funktion wird von den Upload-Button aufgerufen. Sie prüft, ob der Benutzer einen Titel eingegeben hat, setzt einige globale Variablen und ruft dann die Dateiauswahl auf, indem das versteckte <input type='file'> Element geklickt wird.

Zeile 32 Funktion "upload_attachment"

Diese Funktion wird aus dem <input type='file'> Element durch onchange= aufgerufen, siehe HTML-Ausschnitt oben Zeile 11, sobald der Benutzer eine Datei ausgewählt hat. WIr besorgen uns das "File object" der ausgewählten Datei und lesen die Datei im Format "Data URL" in Zeile 47 ein. Das Dateilesen führt das Javascript "File Object" asynchron aus. Wir geben deshalb in Zeile 45 davor die JavaScript-Funktion an, die aufgerufen wird, wenn die Datei vollständig eingelesen ist und wir auf den gesamten Inhalt zugreifen können.

Zeile 52 Funktion "complete_upload_attachment"

Diese Funktion wird aufgerufen, sobald die Datei vollständig eingelesen ist.  Wir können auf den Inhalt der Datei über die Komponente "result" des JavaScript File Readers zugreifen. In Zeile 59 prüfen wir die Dateigrösse und geben bei zu grosser Datei eine Fehlermeldung aus.

Hinweis: In mobilen Geräten steht bei der Dateiauswahl meist auch die Möglichkeit zur Verfügung, ein Foto zu machen und hochzuladen. Durch die grosse Auflösung heutiger Geräte entstehen dabei unter Umständen sehr grosse jpeg-Dateien. Sie können derartige Bilddateien zunächst intern stärker komprimieren und dann hochladen (canvas-Element in HTML verwenden).

JavaScript mit Komprimierung der Bilddateien HTML
<script>

var attachment_reader = null;
var attachment_input = null;
var attachment_button = null;
var attachment_title = "";
var attachment_filename = "";

function start_upload_attachment(f) {

    attachment_title =
        S10ElementByLocid(
            f,
            'attachment_title_input'
        ).value;

    if (!attachment_title) {
        alert(
            'Bitte zunächst einen Titel eingeben'
        );
        return;
    };

    attachment_input =
        S10ElementByLocid(
            f,
            'attachment_input'
        );

    attachment_button =
        S10ElementByLocid(
            f,
            'attachment_button'
        );

    // reset value, otherwise onchange is not triggered
    // for same filename
    attachment_input.value = '';

    attachment_input.click();

    return false;
};


function upload_attachment(f) {

    var files = f.files;

    // anything selected?
    if (files) {

        // 1st file (multiple files not supported)
        var fileobject = files[0];

        attachment_filename =
            fileobject.name;

        attachment_reader =
            new FileReader();

        attachment_reader.addEventListener(
            "load",
            complete_upload_attachment
        );

        attachment_reader.readAsDataURL(
            fileobject
        );
    };
}


function complete_upload_attachment() {

    var result =
        attachment_reader.result;

    attachment_reader = null;

    // compress large jpeg images (camera)
    if (result.startsWith('data:image/jpeg') &&
        result.length > 400 * 1024) {

        var image = new Image();

        image.src = result;

        image.onload = function () {
            upload_image(image);
        };

        return;
    };

    // check file size
    if (result.length > 1024 * 1024) {

        S10ErrorMessage(
            attachment_button,
            "Synactive Demo-System: Bitte nur Dateien < 1MB hochladen"
        );

        return;
    };

    // upload content as data url
    document.getElementById(
        'data_url'
    ).value = result;

    S10Apply(
        'save_attachment',
        attachment_filename + '\t' +
        attachment_title,
        attachment_button
    );
};


function compressImage(image, compression) {

    var canvas = document.createElement("canvas");

    canvas.width = image.width;
    canvas.height = image.height;

    var ctx = canvas.getContext("2d");

    ctx.drawImage(image, 0, 0);

    return canvas.toDataURL("image/jpeg", compression);
}


function upload_image(image) {

    var data_uri =
        compressImage(image, 0.7);

    var k = 0.5;

    while (k > 0 &&
        data_uri.length > 1024 * 1024) {

        data_uri =
            compressImage(image, k);

        k = k - 0.1;
    };

    // check file size
    if (data_uri.length > 1024 * 1024) {

        S10ErrorMessage(
            attachment_button,
            "Synactive Demo-System: Bitte nur Bilder < 1MB (komprimiert) hochladen"
        );

        return;
    };

    // upload content as data url
    document.getElementById(
        'data_url'
    ).value = data_uri;

    S10Apply(
        'save_attachment',
        attachment_filename + '\t' +
        attachment_title,
        attachment_button
    );
};

</script>

Den Inhalt der Datei setzen wir in Zeile 66 in die versteckte Variable "data_url". Anschliessen rufen wir in Zeile 68 die ABAP-Funktion "save_attachment" auf, der wir den Dateinamen und, durch ein TAB-Zeichen getrennt, den vom Benutzer eingegebenen TItel mitgeben. Nun kann auf ABAP-Ebene das Abspeichern der Datei als Attachment erfolgen.

Schritt 3: In ABAP den Dateinhalt aus der S10 Variablen übernehmen und mit der Klasse "attachment" des S10 Frameworks als Attachment zum Equipment abspeichern

Ergänzen Sie Ihr ABAP-Programm um folgendes Coding:

ABAP
* Variable für Upload-Dateiinhalt
     public section.
      data:
          data_url type string.


    * In "class equi_manager implementation":
      method save_attachment.

        data: parms type string.
        parms  = s10actionparameter( ).

        data: docfilename type string,
              doctitle    type string,
              doctype     type string.

        split parms at cl_abap_char_utilities=>horizontal_tab
           into docfilename doctitle.

    * doctype: file extension
        if docfilename ca '.'.
          data: strs type standard table of string,
                str  type string.

          split docfilename at '.' into table strs.
          read table strs index lines( strs ) into str.
          doctype = to_lower( str ).
        endif.



    * current equipment
        data: rownumber type i.
        rownumber = s10contextinfo( )->rownumber.
        read table tabequi index rownumber  assigning field-symbol(<equi>).

        call method /s10/attachment=>create_attachment
          exporting
            objtype     = 'EQUI'
            objkey      = <equi>->equnr
            docfilename = docfilename
            doctitle    = doctitle
            doctype     = doctype
            data_url    = data_url.

    * refresh detail view
        <equi>->s10detailview = 'X'.

    * set table key and read detail attributes
        create object myequi.
        myequi->equnr = <equi>->equnr.
        myequi->tplnr = <equi>->tplnr.
        myequi->s10databaseread( keylist = 'equnr,tplnr' ).

    * clear data
        clear: data_url.

      endmethod.

Erläuterungen

Zeilen 3 und 4

Die Variable "data_url" in der "public section" der Klasse anlegen.

Zeile 17

Den in JavaScript zusammengesetzten Parameter zerlegen wir wieder in die Bestandteile Dateiname und Titel. Alternativ hätten wir in JavaScript wie beim Dateiinhalt vorgehen können und zwei weitere versteckte <input> Element füllen, die jeweils ABAP Attributen zugeordnet sind.

Zeile 32

Wir benötigen die aktuelle Equipmentnummer, die wir der aktiven Tabellenzeile entnehmen.

Zeile 37

Nun wird das Attachment hinzugefügt. Der Objekttyp, hier "EQUI", und der Aufbau des Schlüssels, hier nur die Equipmentnummer, hängt von dem jeweiligen SAP Objekt ab.

Zeilen 46-53

Wir lesen die Equipmentdetails neu und sorgen dafür, dass der Detailbereich aktualisiert wird (Zeile 47). Damit erscheint das gerade hinzugefügte Attachment nun in der Attachmentliste.