Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
raghug
Active Contributor
12,813

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 && |</br>| )
( |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 && |</br>| )
( |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 </br>.

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.
9 Comments
Labels in this area