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:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
program zzequi00.

class equi_detail definition inheriting from /s10/any.

  public section.

* Equipment properties
    data:
      equnr type v_equi-equnr, " Equipment Number
      tplnr type v_equi-tplnr, " Functional Location
      eqtyp type v_equi-eqtyp, " Equipment type
      inbdt type v_equi-inbdt, " Start-up Date
      sernr type v_equi-sernr. " Serial Number

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

* database table name
    constants:
      dbtablename type string value 'v_equi'. " equipments with functional locations

    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 ).

* sort attachments
    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.

* table fields for detail view, plus key fields

    data:
      equnr type v_equi-equnr, " Equipment number
      tplnr type v_equi-tplnr. " Functional location

* database table name
    constants:
      dbtablename type string value 'v_equi'. " for select

endclass.

class equi_short implementation.

endclass.

class equi_manager definition inheriting from /s10/any.

  public section.

* table fields for list view, plus key fields
    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,

* method to build up 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.

* display list screen
  method list.
    s10nextscreen( 'list').
  endmethod.

* select database values and fill table tabequi
  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.

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

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

  endmethod.

* show details in list (line selection)
  method on_detail_tabequi.

* read current table row.
    data: tabindex type i.
    tabindex = s10actionparameter( ).
    read table tabequi index tabindex assigning field-symbol(<row>).

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

  endmethod.
endclass.

* main class for this application
class main definition inheriting from /s10/any.

  public section.

* manager object
   data: my_equi_manager type ref to equi_manager.
    methods:
      logon.
endclass.

class main implementation.

* logon user
  method logon.

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

*  create manager object
    create object my_equi_manager.

* start list display
    my_equi_manager->list( ).

  endmethod.
endclass.

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<!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;">

        <!-- title from data dictionary -->
        <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">

        <!-- Equipment -->
        <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>
        <!-- Functional location -->
        <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);">

            <!-- Equipment -->
            <div class='outputcelldiv '
                style="width: 100px; max-width: 100px;" name="equnr">
            </div>
            <div class='outputcelldiv'
                style="width: 260px;" name="equnr@text">
            </div>
            <!-- Functional location -->
            <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'>

        <!-- Equipment type-->
        <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>

        <!-- In operation from -->
        <div class="infoblock">
            <label class='label output' name="myequi.inbdt"></label>
            <br />
            <span class='output' name='myequi.inbdt'></span>
        </div>

        <!-- Serial number -->
        <div class="infoblock">
            <label class='label output' name="myequi.sernr"></label>
            <br />
            <span class='output' name='myequi.sernr'></span>
        </div>


        <!-- Attachments -->
        <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();">

                    <!-- additional keys -->
                    <div class='outputcelldiv linkkey' name="dockey"></div>

                    <!-- columns -->
                    <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>

        <!-- Document display area -->
        <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

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

* 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:

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:

/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

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

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

Ab Zeile A119

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:

<!-- 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

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

definiert ist in Zeile H188.

Es wird zunächst die ABAP-Methode

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<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:

<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:
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
    <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) {

                // 1st file (nultiple 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;

            // 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);
        };


    </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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
 <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 (nultiple 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-Systen: 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
* 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.