Quite 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
- create the docking container which will hold the ALV grid and we use the parameter extension = 10000 to stretch it over the whole screen
- We call method add_grid_buttons which will prepare the necessary structures so cells in one of the ALV columns is displayed as buttons
- We use a generic helper method get_fcat_4_itab which returns a field catalog for any given ITAB
- We register event handler for handling user clicks on a button
- 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:
- Resizes the ALV grid with HEAD data
- Creates docking container for the DETAIL grid
- Creates new ALV instance to hold the DETAIL data
- 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
- 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)
Dynamically split screen after a button was pressed:
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.