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.