In the SAP system, documents for an object are usually stored as "attachments" via the "Object Services". In transaction IE02 (Change Equipment), for example, this looks like this:

Numerous file types are possible; common are PDFs, Word and Excel files as well as image files in .jpg or .png format.

In this tutorial we will integrate the document display into an S10 application. Our starting point is a list of equipment that we have generated with the S10 utilities, transaction /s10/util:

We build the document display into this basic list. When you click on an equipment line, some details about the respective equipment and the list of installations appear:

In the list of documents, a click either displays the document externally (Word and Excel files) or embeds it inline in the list (image files, PDF, video). The browser usually offers the option of subsequently displaying the document as an independent application instead of in the list. In the case of Word and Excel, embedded display is not currently supported in the current browsers.

Here are a few examples:

The documents can each be opened and closed individually. For Excel and Word, the corresponding application is opened on the mobile device or on the desktop:

Now to the implementation. We use the global class /s10/attachment, which is part of the S10 demo package; it provides some general services to read documents for SAP objects. This makes the ABAP programme quite clear:

  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.

For explanation, the connection to the HTML page is required in some places:

  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">Equipment text</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">Functional location </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">Serial number </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'>Max count</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'>Selected</label><br />
            <div class='output'
                style="font-size: 14px; padding-top: 6px; font-weight: bold;"
                name='tabequi@rowcount'>
            </div>
        </div>

        <div class="processingmessage">Reading data...</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;">
        Nothing selected

    </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'>Documents</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">No documents attached</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>

 

Explanations

The line numbers A... refer to the ABAP code, H... to the HTML file.

Line  A3

class equi_detail definition inheriting from /s10/any.

The class "equi_detail" describes the detailed view of the equipment after selecting a line in the list. The attributes (lines A9 to A13) are displayed in HTML from H155. If you want to display further information on the equipment that is contained in the database view V_EQUI, it is sufficient to add these to the class and output them in the HTML file. The reading of all attributes is done by s10databaseread( ) in line A192.

Line A15

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

Defines an attachment object and the table of attachments. The table is output in HTML from line H181 to H209. The columns doctitle, doctype and docsize_out are defined as attributes of the class /s10/attachment.

In addition to the visible columns, the document key is noted invisibly in the table: H191 <div class='outputcelldiv linkkey' name="dockey"></div> When clicking on an attachment line, this key is read in ABAP by s10contextinfo():

method on_detail_attachments.

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

endmethod.

Line A37

The table of attachments per equipment is realised as a build method. For implementation, the method "list_attachments" of the class /s10/attachment is called:

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

For other objects, you must enter the correct "typeid" here, which is the "Business Object Type" (transaction SWO2). For example, for a purchase requisition it would be "BUS2105". The catalogue ID is always "BO" for "Business Object".

Line A64

class equi_short definition inheriting from /s10/any.

The class "equi_short" describes the line of the equipment list. We only need the equipment number and the functional location here. The corresponding texts are supplemented in the output by the S10 Framework with the notation "...@text". In the HTML file, you will find the output of the table from line H125, beginning with

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

From line A119

class equi_manager implementation.

The rest of the programme is independent of the document display; it is generated by the S10 Utilities.

The central section in the HTML file is the display of the document from line H211:

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

 This section is automatically displayed behind the selected line of the attachment list as soon as the user clicks on an attachment line, as

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

is defined in line H188.

First, the ABAP method

method on_detail_attachments.

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

endmethod.

of the object "myequi" is called, since the table "attachments" is defined in the object "myequi". However, the object "myequi" in the ABAP programme always matches the last selected equipment line; after all, it only exists once. If the user first expands some equipment and then displays an attachment in one of the expanded areas, the line number from HTML in the ABAP programme is of no use to us, because the current attachment table can belong to another piece of equipment. This difficulty arises from the nested structure of the list, i.e. list of equipment and a list of attachments for each piece of equipment.

For this reason, we do not use the line number and the current table "attachments", but reach through

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

to the document key hidden in the HTML line and set it in the attachment object. Since the attribute "display_html" is output in the HTML page:

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

 the build method of the attribute "display_html" of the global class "/s10/attachment" will now run. The build method reads the document and builds the correct HTML code to display the object. In our HTML file it is important here to work with class="outputhtml "instead of class="output", otherwise the HTML code would be displayed instead of interpreted as HTML. 

Our document display would be easier to implement if we did not integrate the information per piece of equipment and per attachment into the list, but instead displayed them in separate pages from which the user then returns to the list. The advantage of the chosen display is that the parts that have been expanded once, for example two photos, remain visible simultaneously in the list or are at least easily accessible by scrolling. For example, two photos of a damage report would then be visible directly below each other. 

 

Uploading documents

Now for uploading documents and saving them as attachments. The procedure for this is as follows:

  1. Add the selection of the desired file to be uploaded in HTML
  2. Transfer the content of the selected file into a hidden S10 variable using Javscript means
  3. In ABAP, transfer the file content from the S10 variable and save it as an attachment to the equipment using the "attachment" class of the S10 framework

 

Step 1: Add the selection of the desired file to be uploaded in HTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<label class='label'>Title for new docuement</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)">
    Select and upload a document
    <span class="processingmessage">Uploading...</span>
</button>

 

Remarks

Lines 4, 8, 15: data-locid=

In our application, the upload option is displayed below the attachment list within the detail area of the table line. So it may appear multiple times if the user opens the detail area for multiple lines in succession. For this reason we cannot use the common id= to identify the HTML elements from JavaScript, because in HTML the assigned id must be unique in the HTML page to work with document.getElementById(). The attribute data-locid allows us to address an element within the opened detail area with the S10 Framework function S10 ElementByLocid() (see JavaScript in Step 2), it is a locally usable id, so to speak.

Line 7: <input type=file>

This allows the user to select a file in HTML. With accept=... we specify the file extensions that should be possible for the selection. In line 10 the element is hidden with style="display:none", because we do not want to use the HTML standard display, but rather provide a separate button for file selection in line 14.

The HTML part specified above goes into the detail area of the table row. In addition, we need a single hidden <input> element outside the file area for the S10 variable, which we use to transport the file contents into our ABAP program:

<input type="hidden" 
     class="input" 
     name="data_url" 
     id="data_url" />

In our ABAP program, a variable "data_url" is defined for this purpose (Step 3), in which we get the file content:
data:
  data_url type string.

Step 2: Transfer the content of the selected file into a hidden S10 variable using Javscript means

Insert the following JavaScrpt code in the HTML page. Whether it is located in <head> or in <body> does not matter. If you need to upload documents in multiple HTML page, you may put it into a separate file and reference it with <script src="..."> </script>.

 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('Please first enter a title');
                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: Please upload only files < 1MB");
                return;
            };

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

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


    </script>

Remarks

Line 9 Function "start_upload_attachment"

The function is called from the upload button. It checks if the user has entered a title, sets some global variables and then calls the file selection by clicking the hidden <input type='file'> element.

 

Line 32 Function "upload_attachment"

This function is called from the <input type='file'> element by onchange=, see HTML snippet above line 11, as soon as the user has selected a file. We get the "file object" of the selected file and read the file in the format "data URL" in Line 47. The file reading is done asynchronously by the javascript "File Object". Therefore, we specify in line 45 the JavaScript function that will be called when the file is completely read and we can access the entire content.

 

Line 52 Function "complete_upload_attachment"

This function is called as soon as the file is completely read. We can access the contents of the file via the "result" component of the JavaScript File Reader. In line 59 we check the file size and give an error message if the file is too large.

Note: In mobile devices, the file selection usually also provides the option to take a photo and upload it. Due to the large resolution of today's devices, this may result in very large jpeg files. You can first compress such image files internally and then upload them (use canvas element in HTML).

JavaScript with image file compression
  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('Please first enter a title');
             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 system: Please upload only files < 1MB");
             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: Please upload only images < 1MB");
             return;
         };

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

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

     };
 
   
 </script>

In line 66 we put the content of the file into the hidden variable "data_url". Then we call the ABAP function "save_attachment" in Line 68, giving it the file name and, separated by a TAB character, the title entered by the user. Now the file can be saved as an attachment on the ABAP level.

Step 3: In ABAP, transfer the file content from the S10 variable and save it as an attachment to the equipment using the "attachment" class of the S10 framework

Add the following code to your ABAP program:

 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 for file content
 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.

Remarks

Lines 3 und 4

Create the "data_url" variable in the "public section" of the class.

Line 17

We split the parameter composed in JavaScript again into the components filename and title. Alternatively, we could have proceeded in JavaScript as with the file content and filled two hidden <input> element, each assigned to ABAP attributes.

Line 32

We need the current equipment number, which we take from the active table row.

Line 37

Now the attachment is added. The object type, here "EQUI", and the structure of the key, here only the equipment number, depends on the respective SAP object.

Lines 46-53

We re-read the equipment details and make sure that the details area is updated (line 47). This means that the attachment just added now appears in the attachment list.