ABAP – Dynamically show and hide ALV (as sidebar)

SAPQuite frequently I’m getting requests to create an ALV report where only the “header data” is displayed…but after clicking on a hotspot area in that ALV, another grid (ALV) is to be displayed with detailed information (displayed as a sidebar on the screen, not as a new, modal screen). And of course, this sidebar should be possible to be closed/hidden.

There is a way using the docking containers being dynamically resized/created/removed and this is what I’m going to show in the following lines of code.

I’ll start with the simplest part / base of the program: START-OF-SELECTION event

START-OF-SELECTION.
  lcl_demo=>get_instance( )->execute( ).

I have created a screen 0100 where all the magic will happen. This screen contain NO elements and its flow is as follows:

PROCESS BEFORE OUTPUT.
  MODULE pbo_0100.
*
PROCESS AFTER INPUT.
  MODULE pai_0100.

The PAI and PBO modules functionality is implemented in my local class’s methods:

MODULE pbo_0100 OUTPUT.
  lcl_demo=>get_instance( )->pbo_0100( ).
ENDMODULE.

MODULE pai_0100 INPUT.
  lcl_demo=>get_instance( )->pai_0100( sy-ucomm ).
ENDMODULE.

Here comes the local class definition:

CLASS lcl_demo DEFINITION.
  PUBLIC SECTION.
    TYPES:
*     Structure of the HEAD data (SFLIGHT) 
*     ...it contains an extra field to hold text of the BUTTON 
*        and an extra STYLE field
      BEGIN OF ty_s_head,
        action(20) TYPE c.
        INCLUDE TYPE sflight.
    TYPES:
        style TYPE lvc_t_styl,
      END OF ty_s_head,
      
      ty_t_head   TYPE TABLE OF ty_s_head WITH DEFAULT KEY,
*     Details are of type SBOOK      
      ty_t_detail TYPE TABLE OF sbook WITH DEFAULT KEY.

    METHODS:
      execute,
      pbo_0100,
      pai_0100 IMPORTING iv_ucomm TYPE sy-ucomm.

*   Signleton implementation to avoid global data
    CLASS-METHODS:
      get_instance RETURNING VALUE(ro_instance) TYPE REF TO lcl_demo.

  PRIVATE SECTION.
    DATA:
*     Data to hold HEAD and DETAILS entries for each ALV
      mt_head            TYPE ty_t_head,
      mt_detail          TYPE ty_t_detail,
      
*     Docking containers for HEAD and DETAILS
      mo_dock_head       TYPE REF TO cl_gui_docking_container,
      mo_dock_detail     TYPE REF TO cl_gui_docking_container,
      
*     ALV grids for HEAD and DETAILS
      mo_grid_head       TYPE REF TO cl_gui_alv_grid,
      mo_grid_detail     TYPE REF TO cl_gui_alv_grid,

*     Indicator whether details screen is displayed
      mv_details_visible TYPE abap_bool.

    METHODS:
*     Data selection methods    
      select_head_data RETURNING VALUE(rt_data) TYPE ty_t_head,
      select_item_data IMPORTING iv_carrid      TYPE s_carr_id
                                 iv_connid      TYPE s_conn_id
                                 iv_fldate      TYPE s_date
                       RETURNING VALUE(rt_data) TYPE ty_t_detail,

*     ALV display/close methods
      display_head,
      display_details,
      close_details.

*     helper methods
      add_grid_buttons,
      get_fcat_4_itab IMPORTING it_table       TYPE ANY TABLE
                      RETURNING VALUE(rt_fcat) TYPE lvc_t_fcat,
                      
*     event handling methods                      
      handle_head_button_click
                    FOR EVENT button_click OF cl_gui_alv_grid
        IMPORTING es_col_id
                    es_row_no,
      handle_detail_toolbar
                    FOR EVENT toolbar OF cl_gui_alv_grid
        IMPORTING e_object
                    e_interactive,
      handle_detail_user_command
                    FOR EVENT user_command OF cl_gui_alv_grid
        IMPORTING e_ucomm.      

*   Singleton implementation to avoid global data 
    CLASS-DATA:
      mo_instance TYPE REF TO lcl_demo.
ENDCLASS.

Now the funnier part…

CLASS lcl_demo IMPLEMENTATION.

To avoid declaring global data I implemented the singleton design pattern to be able to access the one and only instance of the LCL_DEMO:

  METHOD get_instance.
    IF mo_instance IS INITIAL.
      CREATE OBJECT mo_instance.
    ENDIF.

    ro_instance = mo_instance.
  ENDMETHOD.

If you remember the START-OF-SELECTION event, we called method execute of the LCL_DEMO instance where we select the HEAD data and then navigate to screen 0100:

  METHOD execute.
    me->mt_head = me->select_head_data( ).
    CALL SCREEN 0100.
  ENDMETHOD.

Selection of the HEAD data is quite simple:

  METHOD select_head_data.
    SELECT *
      INTO CORRESPONDING FIELDS OF TABLE rt_data
      FROM sflight.
  ENDMETHOD.

PBO module of the screen 0100 calls method pbo_0100 of our LCL_DEMO class. Here we set the PFSTATUS, where I defined just three (usual) user commands BACK, LEAVE and CANCEL. Next we display the HEAD data in an ALV grid stretched over the whole screen

  METHOD pbo_0100.
    SET PF-STATUS 'PFSTATUS'.
    SET TITLEBAR  'TITLEBAR'.

    me->display_head( ).
  ENDMETHOD.

In display_head method we

  1. create the docking container which will hold the ALV grid and we use the parameter extension = 10000 to stretch it over the whole screen
  2. We call method add_grid_buttons which will prepare the necessary structures so cells in one of the ALV columns is displayed as buttons
  3. We use a generic helper method get_fcat_4_itab which returns a field catalog for any given ITAB
  4. We register event handler for handling user clicks on a button
  5. We display the grid

If the grid was already shown (in previous PBO event), we just refresh the grid data

  METHOD display_head.
    DATA:
      lt_fcat    TYPE lvc_t_fcat,
      ls_layout  TYPE lvc_s_layo,
      ls_variant TYPE disvariant,
      ls_stable  TYPE lvc_s_stbl.

    IF me->mo_dock_head IS INITIAL.
      CREATE OBJECT me->mo_dock_head
        EXPORTING
          extension = 10000
          parent    = cl_gui_container=>screen0
          side      = cl_gui_docking_container=>dock_at_left.

      CREATE OBJECT me->mo_grid_head
        EXPORTING
          i_parent = me->mo_dock_head.

      ls_layout-sel_mode   = 'A'.
      ls_layout-no_merging = 'X'.   "No merging of cells
      ls_layout-no_rowmark = 'X'.

*      ALV title
      ls_layout-grid_title = 'Split screen demo'.
      " style field name used to identify the BUTTON column
      ls_layout-stylefname = 'STYLE'.

      me->add_grid_buttons( ).

*     ALV Variant
      ls_variant-report   = sy-repid.
      ls_variant-username = sy-uname.

      lt_fcat = me->get_fcat_4_itab( me->mt_head ).

      " set handler method for button click
      SET HANDLER me->handle_head_button_click FOR me->mo_grid_head.

      CALL METHOD mo_grid_head->set_table_for_first_display
        EXPORTING
          is_variant      = ls_variant
          i_save          = 'A'          " U,X,
          i_default       = 'X'          " Layout can be pre-set
          is_layout       = ls_layout    " Default layout
        CHANGING
          it_outtab       = mt_head
          it_fieldcatalog = lt_fcat.
    ELSE.
*     Keep cursor position by refresh
      ls_stable-row        = 'X'.
      ls_stable-col        = 'X'.

      CALL METHOD mo_grid_head->refresh_table_display
        EXPORTING
          is_stable = ls_stable.
    ENDIF.
  ENDMETHOD.

Method add_grid_buttons just manipulates the STYLE field of our head struture and sets the text of button:

  METHOD add_grid_buttons.
    DATA:
      ls_style TYPE lvc_s_styl.

    LOOP AT me->mt_head ASSIGNING FIELD-SYMBOL().
*     Add style entry to turn the column ACTION into a button
      ls_style-fieldname = 'ACTION'.
      ls_style-style  = cl_gui_alv_grid=>mc_style_button + 
                        cl_gui_alv_grid=>mc_style_enabled.
      INSERT ls_style INTO TABLE -style.

*     Set text for field 'ACTION' in all rows
      -action   = 'BOOKINGS'.
    ENDLOOP.
  ENDMETHOD.

Generic method get_fcat_4_itab which generates field catalog for any givn internal table can be very handy in many cases:

  METHOD get_fcat_4_itab.
    DATA:
      lo_columns      TYPE REF TO cl_salv_columns_table,
      lo_aggregations TYPE REF TO cl_salv_aggregations,
      lo_salv_table   TYPE REF TO cl_salv_table,
      lr_table        TYPE REF TO data.

    FIELD-SYMBOLS:
      <table>         TYPE STANDARD TABLE.

* create unprotected table from import data
    CREATE DATA lr_table LIKE it_table.
    ASSIGN lr_table->* TO <table>.

*...New ALV Instance ...............................................
    TRY.
        cl_salv_table=>factory(
          EXPORTING
            list_display = abap_false
          IMPORTING
            r_salv_table = lo_salv_table
          CHANGING
            t_table      = <table> ).
      CATCH cx_salv_msg.                            "#EC NO_HANDLER
    ENDTRY.
    lo_columns  = lo_salv_table->get_columns( ).
    lo_aggregations = lo_salv_table->get_aggregations( ).
    rt_fcat =
      cl_salv_controller_metadata=>get_lvc_fieldcatalog(
        r_columns             = lo_columns
        r_aggregations        = lo_aggregations ).

    DELETE rt_fcat WHERE rollname = 'MANDT'.
  ENDMETHOD.

Method handle_head_button_click is called every time user clicks on any button in the ALV grid. We handle only the clicks on column ACTION, but generally it’s possible to have more columns where cells are displayed as buttons

  METHOD handle_head_button_click.
    TRY.
        DATA(ls_flight) = me->mt_head[ es_row_no-row_id ].
      CATCH cx_sy_itab_line_not_found.
        " no relevant record found
    ENDTRY.

    CASE es_col_id-fieldname.
      WHEN 'ACTION'.
        me->mt_detail = me->select_item_data(
                          iv_carrid = ls_flight-carrid
                          iv_connid = ls_flight-connid
                          iv_fldate = ls_flight-fldate ).

        IF me->mv_details_visible = abap_false.
          me->mv_details_visible = abap_true.
          me->display_details( ).
        ELSE.
          me->mo_grid_detail->refresh_table_display( ).
        ENDIF.

*      WHEN OTHERS.
*         ...
    ENDCASE.
  ENDMETHOD.

So far we have covered creation and display of the HEAD data.
But In the last described method we will start handling the DETAIL data.
In case user clicked on a button in the ACTIONcolumn, we select the appropriate DETAIL data (bookings for the selected flight) for the row where button was clicked (the key consists of the Carrier, Connection ID and Flight date). Then if the details grid is already displayed, we just refresh data inside, otherwise we need to resize the main grid with HEAD data and create new grid with DETAIL data.

  METHOD select_item_data.
    SELECT *
      INTO TABLE rt_data
      FROM sbook
      WHERE carrid = iv_carrid
        AND connid = iv_connid
        AND fldate = iv_fldate.

  ENDMETHOD.

The display_details method:

  1. Resizes the ALV grid with HEAD data
  2. Creates docking container for the DETAIL grid
  3. Creates new ALV instance to hold the DETAIL data
  4. Registers event handlers where we are able to add (and handle) an extra CLOSE button to the ALV toolbar which will serve to close/hide the detail ALV
  5. Displays the DETAIL grid
  METHOD display_details.
***********************************
*   Reduce extension of main ALV
***********************************
*   get extension of the main ALV with head data
    me->mo_dock_head->get_extension(
      IMPORTING
        extension         = DATA(lv_extension)
    ).

*   reduce extension
    lv_extension = lv_extension - ( lv_extension / 3 ).

*   reduce size of the main ALV by setting new extension
    mo_dock_head->set_extension( lv_extension ).

***********************************
*   Create side bar
***********************************
*   create new container
    CREATE OBJECT me->mo_dock_detail
      EXPORTING
        side      = cl_gui_docking_container=>dock_at_left
        extension = 10000.

*   create alv grid object
    CREATE OBJECT me->mo_grid_detail
      EXPORTING
        i_parent = me->mo_dock_detail.

*   If everything ok, let's display data
    IF me->mo_grid_detail IS NOT INITIAL.
*     set handler method for toolbar
      SET HANDLER me->handle_detail_toolbar FOR me->mo_grid_detail.
*     set handler method for user command
      SET HANDLER me->handle_detail_user_command FOR me->mo_grid_detail.

*     show alv grid
      me->mo_grid_detail->set_table_for_first_display(
        EXPORTING
          i_structure_name     = 'SBOOK'
          is_layout            = VALUE lvc_s_layo( zebra = 'X'
                                                   cwidth_opt = 'X'
                                                   sel_mode   = 'A' )
        CHANGING
          it_outtab            = me->mt_detail ).
    ENDIF.
  ENDMETHOD.

Event handler method handle_detail_toolbar serves to create an extra button in the ALV toolbar:

  METHOD handle_detail_toolbar.
    DATA: ls_button TYPE stb_button.
*   button definition
    ls_button-function  = 'DETAIL_CLOSE'.
    ls_button-icon      = icon_close.
    ls_button-quickinfo = ''.
    ls_button-text      = ''.

*   add button to toolbar
    INSERT ls_button INTO e_object->mt_toolbar INDEX 1.
  ENDMETHOD.

Event handler method handle_detail_user_command handles the pushed CLOSE button and calls a helper method which destroys the objects and do the necessary cleanup:

  METHOD handle_detail_user_command.
    CASE e_ucomm.
      WHEN 'DETAIL_CLOSE'.
        me->close_details( ).
      WHEN OTHERS.
        " others
    ENDCASE.
  ENDMETHOD.

Method close_details resizes the main alv with HEAD data so it’s stretched over the whole screen again and then destroys/cleans the DETAIL-related objects:

  METHOD close_details.
    me->mo_dock_head->set_extension( 10000 ).

    me->mo_grid_detail->free( ).
    me->mo_dock_detail->free( ).

    CLEAR:
      me->mo_grid_detail,
      me->mo_dock_detail,
      me->mv_details_visible,
      me->mt_detail.

    FREE:
      me->mo_grid_detail,
      me->mo_dock_detail.
  ENDMETHOD.

If you managed to read until this point, say horray!
Because that’s all folks.
If you are interested in how it really look like in SAP GUI, here follows some screenshots from the application:

Initial screen (main ALV with HEAD data from SFLIGHT table)

Dynamic split screen - initial screen

Dynamically split screen after a button was pressed:

Split screen with displayed 2nd ALV as sidebar

Please notice the CLOSE button in the right ALV – this is our user-defined toolbar button. After user press it, the DETAIL grid is closed and the HEAD grid is resized so it’s stretched over the whole page again.

2 thoughts on “ABAP – Dynamically show and hide ALV (as sidebar)

    • Hi Ibrahim,
      sadly I don’t understand which refresh are you refering to…if you mean the HEAD grid, then yes, you don’t have to refresh the ALV because the content (data) is loaded only once at start and is not changing/is not reloaded during PBO/PAI. So yes, you can remove this ALV refresh.

Leave a Reply