Overview
In this example we create an application that displays the measurement points for equipment. The user can enter new measurements and confirm them with a signature, or display measurements that have already been saved.
 
This application is suitable, for example, for service technicians who need easy access to equipment data while on the road and who want to quickly enter measurement values and save them directly in the SAP system. It is also conceivable, however, to make this app directly available to customers, who can then report their own measurement points.

Example
Some data about the equipment are displayed as well as the measurements entered so far:

If a measurement document is selected, details of the measurement point, the recorded measurement and the signature made are displayed. This view can then be processed directly, e.g. by printing it out or forwarding it as a PDF document:

When entering measurements, values can be entered in each case, whereby the S10 Framework automatically uses the correct format according to the unit of measure. Only after a signature has been made, can the document be saved:

Implementation
The application is implemented in the following steps: :
  1. Determine the appropriate SAP tables for the dates to be displayed.
  2. Determine an interface for saving the measurement data
  3. Generating the basic application with the S10 Utilities
  4. Extending the generated application
  5. Implementation of the saving of measurements
  6. Implementation of the saving of the signature

However, this is only to be considered as a suggestion, because the HTML views can in principle be designed freely, whereby only the connection to the ABAP objects must be ensured by certain naming conventions.

1. Determine the appropriate SAP tables for the data to be displayed
We want to map parts of an existing SAP transaction with this S10 application. In this case we start with the transaction IE03 (Display Equipment) and can display the technical information about fields there with the F1 key. As shown below, the table view ITOB is used in IE03, which we could also take as a basis for the application.

 

By double-clicking on the view, however, we see that the SAP table EQUI (Equipment Master Data) is linked here and therefore we use this as the starting point for generating the application:



We proceed in the same way for the measurement points (table view IMPTT) as well as the measurement documents (table IMRG).

Some data are additionally read with the help of SAP function modules:
GET_MEASURING_POINTS_4_EQUIPM
(Read measuring points for an equipment)

MEASUREM_DOCUM_RFC_SINGLE_002
(Measuring points and measuring documents: Dialog)

GET_MEASURING_DOCUMENTS
(Read measurement documents for an equipment) 

2. Determining an interface for saving the input data (measured values)
In principle, function modules should be used for saving data if they are available and suitable for the application. As a further option, you can also record a transaction via batch input and make it available as a function call. Except for your own tables, you should generally not write directly to SAP tables.

You can find suitable functions for saving data via transactions SE37 (Function Builder), SE38 (ABAP Editor), SE24 (Class Builder) or also BAPI (BAPI Explorer) or mostly on the Internet in various places.

In this application we use the function module

MEASUREM_DOCUM_RFC_SINGLE_001
(Saving of measured values)

and the ABAP class

CL_MIME_REPOSITORY_API
(API for MIME Repository)

to save the image file with the signature into the SAP MIME-Repository.
3. Generating the basic application with the S10 Utilities
As a general rule, it is recommended to start with an existing application and to adjust and extend it. This way, basic functions such as the login page are already available. In this example we use the S10 utilities to generate an already executable application from an SAP table. Details can be found in the documentation under the point "Generating application parts".

To do this, we first call transaction /S10/UTIL and switch to the "Generate list display" tab. As stated in step 1, we want to start with the view of the equipment and therefore enter "EQUI" as table. Then we select some fields for displaying the table, search fields as well as for the detail view. After the execution, there is a finished S10 application in the specified output directory, which we will extend.





In principle, an S10 application is built from individual views, i.e. separate HTML pages, each belonging to a specific ABAP class. This means that we can develop and test individual parts of the application separately, and then use them together in a more complex application.

We therefore generate further applications, whereby the output directory may well remain the same, another view with associated ABAP class is then generated. However, the table with the measurements of the measuring points of a piece of equipment is then composed of different columns, so that we edit this part directly in the HTML file.

Basically, a generated S10 application consists of a view on the selected fields of the specified table or table view as well as the possibility to expand individual rows of the table and thus display details of this object. This detail view can be extended as needed by further fields, but also more complex parts like images, further tables or interaction and navigation options.

4. Extending the generated application
In concrete terms, we now insert a table with the previous measurements in the generated detail view as well as a pushbutton that opens a new screen for entering measured values. This screen can in turn be developed as an independent view with an associated class (modular structure). This greatly simplifies the reuse of individual parts as well as testing.

The HTML views
First open the generated HTML file for the view, in our case the file "equi_manager.list.html" in the directory "classes\equi_manager\views.en". The detail view for a line starts with the following code part:
 <!-- detail view -->
<div id="tabequi_detail" class='tabledetail'>

You can find the generated fields in this section and we can add more elements there:

A pushbutton to navigate to a screen where measured values can be entered:
<div class="infoblock">

    <button class="button" type="button" 
            onclick="S10Apply('open_measurepoints');">
            
            Enter measurement values
            
    </button>
</div>

When the user presses this pushbutton, the ABAP method 'open_measurepoints' is called in the active class.

For the table with the measurement values first the headers:
 <div class="colheaders">

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

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

<!-- additional columns ... -->

</div>

Following this, the definition of the table row, where we again specify the assignment to the ABAP object by name=:
<!-- list rows -->
<form class='table' name='myequi.tabimptt'>

    <div class="tablerow">

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

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

        <!-- additional columns ... -->

    </div>
</form>


The ABAP methods

We have defined calls to the ABAP class in two places in the HTML code:
  1. S10Apply('open_measurepoints')   

    calls the ABAP method "open_measurepoints"

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

    defines a link and calls the ABAP method "on_link_mdocm"
The first method basically calls a new view with an input table for measurements, but first reads in the measurement points of this equipment via function call for this purpose:
* open measure points for this equipment
  method open_measurepoints.

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

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

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

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

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

    clear tabimptt_input.

    loop at mydiimpt into mydiimpt_wa.

      create object myimptt.

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

      append myimptt to  tabimptt_input.

    endloop.

    clear saved.

    s10dialog( 'measurepoints').

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


  endmethod.

Hint
After the dialog or the view "measurepoints" has been closed, e.g. in which the user has triggered a "back", the code execution is continued in the next line. In this case, the user might have entered and saved new measurements, so a refresh of the previous view should be triggered.

The second method navigates to a view that displays the details of the respective measurement or measurement document as well as an image of the signature made:
* User clicked on a measurement document link
  method on_link_mdocm.

    data: mymdocm type imrc_mdocm.

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

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

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

    data: my_imrg_ba type imrg.

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

    if sy-subrc <> 0.

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

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

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


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

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


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


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

  endmethod.

Hints
  1. The application is "stateful", i.e. all data read in and entries made by the user are stored in the respective ABAP variables. However, in the event of an interaction, the respective context from which the user acted must be determined. This can be, for example, the table row that was clicked. In the ABAP method, you get information about the context via the "s10fromcontextinfo" method.
     
  2. S10 fields are normally typed. This ensures that, for example, in function calls, user input can be converted to the correct internal format by the S10 framework. An exception, however, are fields that are defined in the HTML view by the CSS class "outputhtml". These are not directly displayed as text, but the content is interpreted as HTML code and the result is displayed. This weakens the separation between application logic and user interface a bit, but offers some possibilities. In this case, the HTML code is generated, which ensures that the image with the signature is displayed in the view.

  3. One advantage of the S10 framework is the exception handling, which ensures that error messages can be passed directly to the frontend and displayed there as an error message. If an error message is displayed with "s10errormessage" , the code execution is aborted at this point.
5. Implementation of the saving of measurements 
In principle, we only need a small table that displays the name of each measuring point and the unit of entry, as well as offering an input field for new measured values. Here you can see the connection between the elements of the HTML interface and the respective variables in the ABAP class (for details see the tropic "Data binding" in the documentation):
<!-- list rows -->
<form class='table' name='tabimptt_input'>
    <div class="tablerow">

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

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

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

The fields in the associated ABAP class:
* View for Measurement Document -> List View
class imptt_short definition inheriting from /s10/any.

  public section.

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

We now need a simple pushbutton with which the user can trigger the saving of the entered measured values:
<div class="toolbar">
    <button type="button" class="toolbarbutton" 
            onclick="S10Apply('measures_save');">
        Speichern
    </button>
</div>

Hint
It should be pointed out once again that no passing of the entered values to the ABAP method is necessary, instead the S10 Framework automatically ensures that the user data entered at the front end is directly available in a called ABAP method. The other way round, data changed in the ABAP method is automatically sent to the front end after execution, so that the current data is displayed there in each case.

The corresponding ABAP method "measures_save" is not very complex:
* We save the measures that the user entered
  method measures_save.

* Already saved?
    if saved is not initial.

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

    endif.

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


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

* Save measures
    loop at tabimptt_input into tabimptt_input_wa.

      if tabimptt_input_wa->recorded_value is not initial.

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

        if sy-subrc <> 0.

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

        endif.

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

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

        s10infomessage( mytext ).

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

    endloop.

* set saved status
    saved = 'X'.

  endmethod.

Note on the data type of the measured values to be entered
The measured value that the user enters does not first have to be converted into another data type in this example, since it is already defined in the type expected for the function call. The type is "rimr0-recdc which belongs to the domain FLTP_INOUT (input/output field for floating point fields, 22 digits) and is basically a CHAR, i.e. text. However, you can inform the S10 framework in the ABAP program which field defines the unit of measure. On the interface, the unit is then formatted accordingly, e.g. without decimal places for ST (pieces), 2 decimal places for currency units and so on:
    data:
      recorded_value      type rimr0-recdc, " Value
      mrngu               type imrc_mrngu, " Unit 
* Units
      unit_recorded_value type string value 'mrngu',

Here we define a field for entering a measurement value, a field for the unit of measure, and a link between the two fields by prefixing the name of the field with "unit_".



Note on translation capabilities
In addition to the possibility of using so-called text IDs in ABAP coding for the translation of texts, the S10 framework also offers the possibility of maintaining small text files and using "s10localize" to determine the text in the user's logon language. For details, see the reference to s10localize() .
6. Implementation of the saving of the signature
The signature feature was included by using an open-source JavaScript library available at github.com/szimek/signature_pad. It offers the possibility to save the signature image directly as a string (Base64). Thus, it can be transferred to the ABAP method very easily in a hidden S10 field. Afterwards, this text is converted back into binary format with a function call and stored in the MIME repository as an image file.

We first include the JavaScript library in the HTML view:
<script src="../../../scripts/signature_pad.js"></script>

For the signature field, a so-called HTML5 <canvas> element is used, which allows drawing graphics. JavaScript offers a function in the standard with which the graphic can be saved and processed as a text representation (Base64):
<!-- Canvas element to draw graphics -->
<canvas height="200" width="350" style="background-color: white" id="sketchpad"></canvas>

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

After the user has signed, the image is encoded as a Base64 string and inserted into the S10 field. This will automatically transfer it from the S10 framework to the ABAP variable:
var img = signaturePad.toDataURL();
img = img.replace('data:image/png;base64,', '');        
document.getElementById("signature").value=img; 

When saving the measured values, the "save_signature" method is also called. This converts the Base64 text into binary files and saves them in the MIME repository as .PNG image file:
method save_signature.

    clear mybase64.
    clear content.

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

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


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

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

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

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

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

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

    endif.

  endmethod.

Download
You can download all project files here:
s10_service_example.zip

The .ZIP archive contains the HTML views and a text file with the ABAP program "/s10/service_demo". To import the HTML views you can use the report "BSP_UPDATE_MIMEREPOS" as described in the documentation on "Development environment".

If necessary, adjust the number of the client in the "user.logon.html" file. For details, see the documentation on S10Logon()
 

Component: S10 Framework