Monday 2 December 2019

Smartform folders: change their sequence at runtime

On a customer project, we were asked to add some flexibility to the display of customer “Factsheets”: ability to suppress some sections, and possibility to change sequence of the displayed ones.

A “Factsheet” is a SAP SmartForm that provides in PDF format a customer overview of various data, including sales, marketing, service and logistics data. Factsheet is requested from a Fiori app, and is provided either online or by e-mail.

In our case, “Factsheet” SmartForm is made of a total of 20 sections/folders (“Quotations”, “Sales details”, “Latest Complaints”, etc…), but only a subset of those sections may be of interest to a specific sales person, for a specific customer.

While it is easy to manage suppression of a section, sequence change is more challenging. Purpose of this post is to present the solution we developed.

This is how solution looks like from an end-user perspective:


In the frontend, we implemented Drag-and-Drop with jQuery, but it is now enabled for all UI5 controls in standard, since version 1.56. Sort sequence chosen by user is saved into a transparent backend table using a custom oData entity.

In the backend, objective was to mimic the folder sequence chosen by the user. While the smartform designer tool (transaction “smartforms”) allows to change easily sequence of folders, this applies only during design phase. As soon as smartform is generated, it becomes static (as regards to folder sequence).

Technically, smartform generation creates a function-group, and a function-module that is used to invoke smartform at runtime.

We decided to manipulate the code of the function-group, and to generate at runtime a temporary program using ABAP statement “GENERATE SUBROUTINE POOL”. One constraint was that we then had to convert the function-module into a form, in order to trigger the changed ABAP code.

Advantage of this solution, is that changed sequence is only valid at runtime for a specific user. Each end-user can have her own folder sequence, and can change it whenever desired.

In the SmartForm definition, blocks of data to be sorted need to appear under root folders (defined just under main window). Folder names (here: FOLDER_A, FOLDER_B and FOLDER_C) are used as anchors by the SmartForm handler routine to rearrange folder sequence. All smartform elements defined below root folders participate in the sequence change. Nested folders are supported.

SAP ABAP Study Materials, SAP ABAP Certifications, SAP ABAP Learning, SAP ABAP Online Exam

Limitations: no folder conditions at root folder level are supported. And, in the form interface, only table assignments of type “TYPE” are supported (no “LIKE” assignments).

SAP ABAP Study Materials, SAP ABAP Certifications, SAP ABAP Learning, SAP ABAP Online Exam

This is a test report that compares classical generation of SmartForm via function-module with a custom generation “on-the-fly” after change of sequence of folders, using a FORM subroutine.

*&---------------------------------------------------------------------*
*& Report ZSMARTFORM_SECTION_RT_SORT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zsmartform_section_rt_sort.

* text elements

* Sym  Text
* 001 Generation type
* 002 Standard (SmartForm function)
* 003 Sorted (dynamic subroutine)
* 004 Preferred section sequence
* 005 FOLDER_A
* 006 FOLDER_B
* 007 FOLDER_C

SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-001.
* output using standard function-module
SELECTION-SCREEN: BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(32) TEXT-002 FOR FIELD pa_stdfm.
SELECTION-SCREEN  POSITION 34.
PARAMETERS: pa_stdfm RADIOBUTTON GROUP typ DEFAULT 'X'.
SELECTION-SCREEN: END OF LINE.
* sorted output using custom form
SELECTION-SCREEN: BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(32) TEXT-003 FOR FIELD pa_sortd.
SELECTION-SCREEN  POSITION 34.
PARAMETERS: pa_sortd RADIOBUTTON GROUP typ.
SELECTION-SCREEN: END OF LINE.
SELECTION-SCREEN END OF BLOCK bl01.

SELECTION-SCREEN: BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-004.
* preference folder A
SELECTION-SCREEN: BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(32) TEXT-005 FOR FIELD pa_prefa.
SELECTION-SCREEN  POSITION 34.
PARAMETERS: pa_prefa TYPE numc2 DEFAULT 2.
SELECTION-SCREEN: END OF LINE.
* preference folder B
SELECTION-SCREEN: BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(32) TEXT-006 FOR FIELD pa_prefb.
SELECTION-SCREEN  POSITION 34.
PARAMETERS: pa_prefb TYPE numc2 DEFAULT 3.
SELECTION-SCREEN: END OF LINE.
* preference folder C
SELECTION-SCREEN: BEGIN OF LINE.
SELECTION-SCREEN COMMENT 1(32) TEXT-007 FOR FIELD pa_prefc.
SELECTION-SCREEN  POSITION 34.
PARAMETERS: pa_prefc TYPE numc2 DEFAULT 1.
SELECTION-SCREEN: END OF LINE.
SELECTION-SCREEN: END OF BLOCK bl02.

TYPES:
  BEGIN OF ty_section_indexes,
    folder_a TYPE numc2,
    folder_b TYPE numc2,
    folder_c TYPE numc2,
  END OF ty_section_indexes.

DATA:

  lv_form_name              TYPE          tdsfname,
  ls_user_preferences       TYPE          ty_section_indexes,

  lv_fm_name                TYPE          rs38l_fnam,
  ls_archive_index          TYPE          toa_dara,
  lt_archive_index_tab      TYPE          tsfdara,
  ls_archive_parameters     TYPE          arc_params,
  ls_control_parameters     TYPE          ssfctrlop,
  ls_mail_appl_obj          TYPE          swotobjid,
  ls_mail_recipient         TYPE          swotobjid,
  ls_mail_sender            TYPE          swotobjid,
  ls_output_options         TYPE          ssfcompop,
  ls_user_settings          TYPE          tdbool,
  lv_document_output_info   TYPE          ssfcrespd,
  ls_job_output_info        TYPE          ssfcrescl,
  ls_job_output_options     TYPE          ssfcresop,

  lv_demo_param             TYPE          boolean,
  lt_demo_tab               TYPE          text_line_tab,

  lr_smartform_editor       TYPE REF TO   zsmartform_in_memory_editor,
  ls_section_target_index   TYPE          zgw_factsheet_section_idx,
  lt_section_target_indexes TYPE          zgw_factsheet_section_idx_tab,
  lt_top_source             TYPE TABLE OF string,
  lt_form_source            TYPE TABLE OF string,
  lt_function_source        TYPE TABLE OF string,
  lr_struct_descr           TYPE REF TO   cl_abap_structdescr,
  ls_component              TYPE          abap_compdescr,
  ls_section_target_indexes TYPE          zgw_factsheet_section_idx,
  lv_starting_from_index    TYPE          numc2,
  lv_subrc                  TYPE          sysubrc,
  lv_program_name           TYPE          char08.

FIELD-SYMBOLS:
  <index>                   TYPE          numc2.

lv_form_name = 'ZZ_DEMO_SF_1'.

* user preferences are typically stored in a transparent table
ls_user_preferences-folder_a = pa_prefa.
ls_user_preferences-folder_b = pa_prefb.
ls_user_preferences-folder_c = pa_prefc.

* control parameters
ls_control_parameters-langu     = sy-langu.
ls_control_parameters-no_open   = abap_false.
ls_control_parameters-no_close  = abap_false.
ls_control_parameters-no_dialog = abap_true.
ls_control_parameters-preview   = abap_true.

* output options
ls_output_options-tddest    = 'LOCL'.
ls_output_options-tdnoprint = abap_false.

* get FM name
CALL FUNCTION 'SSF_FUNCTION_MODULE_NAME'
  EXPORTING
    formname           = lv_form_name
  IMPORTING
    fm_name            = lv_fm_name          "e.g. /1BCDWB/SF00000100
  EXCEPTIONS
    no_form            = 1
    no_function_module = 2
    OTHERS             = 3.
CHECK sy-subrc IS INITIAL.

IF pa_stdfm EQ abap_true. "standard function-module

* run standard function-module
  CALL FUNCTION lv_fm_name
    EXPORTING
      control_parameters   = ls_control_parameters
      output_options       = ls_output_options
      demo_import_param    = lv_demo_param " <-- custom parameter
    IMPORTING
      document_output_info = lv_document_output_info
      job_output_info      = ls_job_output_info
      job_output_options   = ls_job_output_options
    TABLES
      demo_import_tab      = lt_demo_tab  " <-- custom table, defined in smartform with type assignment 'TYPE' (not 'LIKE')
    EXCEPTIONS
      formatting_error     = 1
      internal_error       = 2
      send_error           = 3
      user_canceled        = 4
      OTHERS               = 5.

ELSEIF pa_sortd EQ abap_true. "sorted output using custom form

*-------------------------------------------------------------------------------------*
* instantiate smartform in-memory editor
*--------------------------------------------------------------------------------------*

  CREATE OBJECT lr_smartform_editor
    EXPORTING
      iv_form_name = lv_form_name.

*--------------------------------------------------------------------------------------*
* get source includes
*-------------------------------------------------------------------------------------*

  lr_smartform_editor->get_source_includes(
                         IMPORTING et_top_source      = lt_top_source
                                   et_function_source = lt_function_source
                                   et_form_source     = lt_form_source
                         EXCEPTIONS include_not_found = 1
                                    OTHERS            = 2                   ).
  lv_subrc = sy-subrc.
  IF lv_subrc IS INITIAL.

*--------------------------------------------------------------------------------------*
*   convert FUNCTION to FORM, keeping same name
*-------------------------------------------------------------------------------------*

    lr_smartform_editor->convert_function_source(
                           CHANGING   ct_function_source = lt_function_source
                           EXCEPTIONS conversion_failed = 1
                                      OTHERS            = 2                    ).
    lv_subrc = sy-subrc.
    IF lv_subrc IS INITIAL.

*-------------------------------------------------------------------------------------*
*    sort root sections
*--------------------------------------------------------------------------------------*

*     transpose structure to table
      lr_struct_descr ?= cl_abap_typedescr=>describe_by_data( ls_user_preferences ).
      LOOP AT lr_struct_descr->components INTO ls_component.
        ASSIGN COMPONENT ls_component-name OF STRUCTURE ls_user_preferences TO <index>.
        IF sy-subrc IS INITIAL.
          ls_section_target_index-section_name = ls_component-name.
          ls_section_target_index-index        = <index>.
          APPEND ls_section_target_index TO lt_section_target_indexes.
        ENDIF.
      ENDLOOP.

*     process all sections (but could protect n first sections)
      lv_starting_from_index = 1.

*     sort root sections
      lr_smartform_editor->sort_root_sections(
                             EXPORTING  iv_starting_from_section_index = lv_starting_from_index
                                        it_section_target_indexes      = lt_section_target_indexes
                             CHANGING   ct_form_source                 = lt_form_source
                             EXCEPTIONS root_section_sort_failed      = 1
                                        OTHERS                        = 2                          ).

      lv_subrc = sy-subrc.
      IF lv_subrc IS INITIAL.

*--------------------------------------------------------------------------------------*
*       generate transient program
*---------------------------------------------------------------------------------------*

        lr_smartform_editor->generate_transient_program(
                               EXPORTING  it_top_source      = lt_top_source
                                          it_function_source = lt_function_source
                                          it_form_source     = lt_form_source
                               IMPORTING  ev_program_name    = lv_program_name      " --> OUT
                               EXCEPTIONS generation_failed  = 1
                                          OTHERS             = 2                  ).
        lv_subrc = sy-subrc.

      ENDIF.
    ENDIF.
  ENDIF.

  IF lv_subrc IS NOT INITIAL.
*   could call standard function-module, as fallback
  ELSE.

*-------------------------------------------------------------------------------------*
* invoke smartform
*-------------------------------------------------------------------------------------*

*   function module was converted into FORM subroutine, keeping same name
    lv_form_name = lv_fm_name.

    PERFORM (lv_form_name) IN PROGRAM (lv_program_name)
      USING
        lv_program_name        " <-- new parameter, to pass name of generated program
        ls_archive_index
        lt_archive_index_tab
        ls_archive_parameters
        ls_control_parameters
        ls_mail_appl_obj
        ls_mail_recipient
        ls_mail_sender
        ls_output_options
        ls_user_settings
        lv_demo_param          " <-- custom import parameter(s) of function
        lt_demo_tab            " <-- custom table(s) of function, defined in smartform with type assignment 'TYPE' (not 'LIKE')
      CHANGING
        lv_document_output_info
        ls_job_output_info
        ls_job_output_options.

  ENDIF.

ENDIF.

For those who would like to test this solution on their own SAP Netweaver environment, there are 3 files available here: https://github.com/Walter-B/sap-smartforms

◉ a test program. Create report ZSMARTFORM_SECTION_RT_SORT with transaction SE38, and paste code above, (or code available on Github) into it. Add following text symbols, under “Text elements”, and activate.

Sym Text
001 Generation type
002 Standard (SmartForm function)
003 Sorted (dynamic subroutine)
004 Preferred section sequence
005 FOLDER_A
006 FOLDER_B
007 FOLDER_C

◉ a smartform XML file, zz_demo_sf_1.xml. In Github, click on “Raw” button, select whole XML code, and save it into notepad file zz_demo_sf_1.xml (careful with extension). In SAP transaction SMARTFORMS, create new form ZZ_DEMO_SF_1, then go to “Utilities” > “Upload”, accept warning and activate.

◉ the class containing the sort magic. In transaction SE24, create class ZSMARTFORM_IN_MEMORY_EDITOR, choose object type “Class”, add a description, press “Save”. In class builder, press button “Source Code-based”, go to Edit mode, and overwrite with Github code from file ZSMARTFORM_IN_MEMORY_EDITOR. Activate.

Now, you can test the solution on a simple smartform. When selecting radio-button “Sorted (dynamic subroutine”, you can specify any folder sequence, that will be rendered at runtime.

SAP ABAP Study Materials, SAP ABAP Certifications, SAP ABAP Learning, SAP ABAP Online Exam

No comments:

Post a Comment