Reference a program’s internal table from ALV event handler – 2 Methods
What was the issue?
When using ALVs, in this case using CL_SALV_TABLE, the event handler has to be defined as a method in a class that points to an event in the interface IF_SALV_EVENTS_ACTIONS_TABLE. This is all wonderful in keeping with the object oriented approach to programming.
However, when you try to use or manipulate the data from the displayed ALV grid, the class gets in the way. If you look at the SALV_DEMO_TABLE_EVENTS program, it gets around this issue by having the event handler method calling a Form which is back in the program. While this works, you are still declaring the data table as a global, and not really using the object oriented methodology. Here are two ways to get that data and use object oriented methodology without falling back to a Form in the report.
Method 1 – Reference to the table in the class
The very first thing I want to do is acknowledge that this post borrows heavily from this blog post on the website www.kerum.pl. I looked around the site, and unfortunately I can’t acknowledge the name of the person other than “Kris”. Well thank you Kris! From Kris’ example, I have added some newer ABAP constructs and converted the older style FORM to an object oriented METHOD. The key piece that makes this work is declaring the reference to table in the class…
ref_alv_table TYPE REF TO tt_usr,
and then sending the reference of table used in the ALV to that attribute with this statement.
GET REFERENCE OF lt_usr INTO event_handler->ref_alv_table.
Here is the full refactoring of Kris’s code.
REPORT z_my_test. *----------------------------------------------------------------------* * CLASS lcl_event_handler DEFINITION *----------------------------------------------------------------------* CLASS lcl_event_handler DEFINITION. PUBLIC SECTION. TYPES: * Define a table type, you can use it to store the reference * on the internal table. In this case it is the same as the * table (in which case it is superfluous), but this can be used * to define only a sub-set of the fields. tt_usr TYPE TABLE OF usr02 WITH DEFAULT KEY. * Static attributes to store references on your local variables DATA: * Reference on the internal table with data ref_alv_table TYPE REF TO tt_usr, * Reference on the ALV object ref_alv TYPE REF TO cl_salv_table. METHODS constructor IMPORTING i_alv TYPE REF TO cl_salv_table. METHODS on_link_click FOR EVENT if_salv_events_actions_table~link_click OF cl_salv_events_table IMPORTING row column. ENDCLASS. "lcl_event_handler DEFINITION *----------------------------------------------------------------------* * CLASS lcl_event_handler IMPLEMENTATION *----------------------------------------------------------------------* CLASS lcl_event_handler IMPLEMENTATION. METHOD constructor. ref_alv = i_alv. ENDMETHOD. "constructor METHOD on_link_click. "... find clicked row using the table reference TRY. DATA(usr_record) = ref_alv_table->*[ row ]. cl_abap_browser=>show_html( html = VALUE #( ( usr_record-bname && || ) ( |Created by - | && usr_record-aname ) ) ). CATCH cx_sy_itab_line_not_found. "Error message goes here ENDTRY. " If something changed... DATA(something_changed) = abap_false. IF something_changed = abap_true. "...then refresh. ref_alv->refresh( ). ENDIF. ENDMETHOD. "on_link_click ENDCLASS. "lcl_event_handler IMPLEMENTATION *----------------------------------------------------------------------* * CLASS lcl_routines DEFINITION *----------------------------------------------------------------------* CLASS lcl_routines DEFINITION. PUBLIC SECTION. METHODS display_alv. ENDCLASS. *----------------------------------------------------------------------* * CLASS lcl_routines IMPLEMENTATION *----------------------------------------------------------------------* CLASS lcl_routines IMPLEMENTATION. METHOD display_alv. DATA: lt_usr TYPE lcl_event_handler=>tt_usr. " <- local internal table SELECT * FROM usr02 UP TO 30 ROWS INTO CORRESPONDING FIELDS OF TABLE @lt_usr ORDER BY bname. TRY. CALL METHOD cl_salv_table=>factory IMPORTING r_salv_table = DATA(alv) CHANGING t_table = lt_usr. " Instantiate the event handler passing a reference " To the ALV object which can be used within the event handler DATA(event_handler) = NEW lcl_event_handler( alv ). " Register event handler DATA(lo_events) = alv->get_event( ). SET HANDLER event_handler->on_link_click FOR lo_events. " Get and store the reference on your local internal table GET REFERENCE OF lt_usr INTO event_handler->ref_alv_table. " Also store the reference on the ALV object, it can be useful event_handler->ref_alv = alv. " Set column as hotspot DATA(columns) = alv->get_columns( ). DATA(column) = CAST cl_salv_column_list( columns->get_column( 'BNAME' ) ). column->set_cell_type( if_salv_c_cell_type=>hotspot ). alv->display( ). CATCH cx_salv_msg. " cl_salv_table=>factory() "Ideally raise a message instead of WRITE statements WRITE: / 'cx_salv_msg exception'. CATCH cx_salv_not_found. " cl_salv_columns_table->get_column() WRITE: / 'cx_salv_not_found exception'. ENDTRY. ENDMETHOD. "display_alv ENDCLASS. *----------------------------------------------------------------------* * START-OF-SELECTION *----------------------------------------------------------------------* START-OF-SELECTION. DATA(lcl) = NEW lcl_routines( ). lcl->display_alv( ).
Method 2 – Single class
Now that I have moved the display_alv code into a class… why not move it into the same class as the event handler? This works wonderfully! No more passing references and dereferencing them. The table and the alv object are declared as private attributes. That way any method in the class can access them and you don’t have to pass any references around. I think it is both easier to understand the context of each variable and also reduces the overall code.
Here is the same code refactored again…
REPORT z_my_salv_template. *----------------------------------------------------------------------* * CLASS lcl_routines DEFINITION *----------------------------------------------------------------------* CLASS lcl_routines DEFINITION. PUBLIC SECTION. METHODS display_alv. PRIVATE SECTION. * Attributes to store references on your local variables DATA: * Reference on the internal table with data data_table TYPE STANDARD TABLE OF usr02 WITH DEFAULT KEY, * ALV object alv TYPE REF TO cl_salv_table. METHODS on_link_click FOR EVENT if_salv_events_actions_table~link_click OF cl_salv_events_table IMPORTING row column. ENDCLASS. "lcl_routines DEFINITION *----------------------------------------------------------------------* * CLASS lcl_routines IMPLEMENTATION *----------------------------------------------------------------------* CLASS lcl_routines IMPLEMENTATION. METHOD display_alv. SELECT * FROM usr02 UP TO 30 ROWS INTO CORRESPONDING FIELDS OF TABLE @data_table ORDER BY bname. TRY. CALL METHOD cl_salv_table=>factory IMPORTING r_salv_table = alv CHANGING t_table = data_table. " Register event handler DATA(lo_events) = alv->get_event( ). SET HANDLER me->on_link_click FOR lo_events. " Set column as hotspot DATA(columns) = alv->get_columns( ). DATA(column) = CAST cl_salv_column_list( columns->get_column( 'BNAME' ) ). column->set_cell_type( if_salv_c_cell_type=>hotspot ). alv->display( ). CATCH cx_salv_msg. " cl_salv_table=>factory() "Ideally raise a message instead of WRITE statements WRITE: / 'cx_salv_msg exception'. CATCH cx_salv_not_found. " cl_salv_columns_table->get_column() WRITE: / 'cx_salv_not_found exception'. ENDTRY. ENDMETHOD. "display_alv METHOD on_link_click. "... find clicked row using the table reference TRY. DATA(usr_record) = data_table[ row ]. cl_abap_browser=>show_html( html = VALUE #( ( usr_record-bname && || ) ( |Created by - | && usr_record-aname ) ) ). CATCH cx_sy_itab_line_not_found. "Error message goes here ENDTRY. " If something changed... DATA(something_changed) = abap_false. IF something_changed = abap_true. "...then refresh. alv->refresh( ). ENDIF. ENDMETHOD. "on_link_click ENDCLASS. "lcl_routines IMPLEMENTATION *----------------------------------------------------------------------* * START-OF-SELECTION *----------------------------------------------------------------------* START-OF-SELECTION. DATA(lcl) = NEW lcl_routines( ). lcl->display_alv( ).
Another goodie –
See how I have used CL_ABAP_BROWSER to quickly display a popup window with a couple of lines of data. While I could send it a full HTML table with style sheets and headers and tables, etc… Here I just pass the data separated by a line break .
Just displaying a single piece of data can be written like this…
cl_abap_browser=>show_html( html = VALUE #( ( usr_record-bname ) ). OR cl_abap_browser=>show_html( html = VALUE #( ( 'Here is my static text' ) ).
If anyone has a better and simple way of quickly displaying data, please do share. In the past, I have created screens and added tables using CL_DD_DOCUMENT. This seems to be much simpler, and if you have HTML knowledge, you will probably be able to do a lot more visually.
New NetWeaver Information at SAP.com
Very Helpfull