ALV Advanced: Layouts, Variants, User Commands, Events, Export
In Blogs 5 and 6 you built a working ALV. Real reports need more: remembered layouts, subtotals, buttons, and clicks that do something. This blog adds all of them to one report. Every snippet here is an optional parameter of REUSE_ALV_GRID_DISPLAY.
1. ALV layout (IS_LAYOUT)
The layout controls how the grid looks and behaves as a whole. It is a structure of type slis_layout_alv.
DATA gs_layout TYPE slis_layout_alv.
gs_layout-zebra = 'X'.
gs_layout-colwidth_optimize = 'X'.
gs_layout-box_fieldname = 'SEL'.
| Field | Effect |
|---|---|
zebra | Alternating row shading for easier reading |
colwidth_optimize | Each column is as wide as its content |
box_fieldname | Adds a selection checkbox column. The named field (a 1-character field of your table) receives 'X' for selected rows. |
no_input | The whole grid is display-only |
cell_merge | Repeated values in a column are shown once |
detail_popup | Double-click or detail icon shows the row in a popup |
Pass it with is_layout = gs_layout. For box_fieldname, add the field to your table type: sel TYPE c LENGTH 1.
2. Variants: saving user layouts
A variant stores a user’s grid settings: column order, hidden columns, widths, sorting and filters. Users save them from the toolbar (Layout > Save) and reload them later.
DATA gs_variant TYPE disvariant.
gs_variant-report = sy-repid. " which program the variant belongs to
gs_variant-handle = 'MAIN'. " optional: separates several grids in one program
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
...
i_save = 'A'
is_variant = gs_variant
...
i_save | Who may save variants |
|---|---|
'A' | User-specific and global (all users) variants |
'U' | Only user-specific variants |
'X' | Only global variants |
' ' | No saving at all |
In the table LTDX (with LTDXT for texts). You do not write to it yourself, ALV does.
Without i_save the Save button is missing. Without is_variant-report, ALV cannot find the variants it saved.
3. Sort and subtotals (IT_SORT)
Presets a sort order when the grid opens. With subtot you get a subtotal every time the value changes.
DATA: gt_sort TYPE slis_t_sortinfo_alv,
gs_sort TYPE slis_sortinfo_alv.
gs_sort-spos = 1. " priority
gs_sort-fieldname = 'VBTYP'.
gs_sort-tabname = 'GT_VBAK'.
gs_sort-up = 'X'. " ascending
gs_sort-subtot = 'X'. " subtotal on every change
APPEND gs_sort TO gt_sort.
CLEAR gs_sort.
gs_sort-spos = 2.
gs_sort-fieldname = 'ERDAT'.
gs_sort-tabname = 'GT_VBAK'.
gs_sort-down = 'X'. " descending
APPEND gs_sort TO gt_sort.
- Pass with
it_sort = gt_sort. - Subtotals need a numeric column with
do_sum = 'X'in the field catalog (Blog 6). - Amounts in different currencies are totalled separately, because the catalog has
cfieldname.
4. Filter (IT_FILTER)
A preset filter hides rows when the grid opens. The user can still remove it in the toolbar.
DATA: gt_filter TYPE slis_t_filter_alv,
gs_filter TYPE slis_filter_alv.
gs_filter-tabname = 'GT_VBAK'.
gs_filter-fieldname = 'VBTYP'.
gs_filter-sign0 = 'I'.
gs_filter-optio = 'EQ'.
gs_filter-valuf_int = 'C'. " show only category C
APPEND gs_filter TO gt_filter.
Pass with it_filter = gt_filter.
Field names of SLIS_FILTER_ALV are worth confirming in SE11. In many cases the better design is to filter in the WHERE clause, so unwanted rows are never read at all.
5. ALV events and callbacks
ALV can call your program when something happens. You register a callback by giving ALV the name of a form in your program.
| Parameter | Form is called when | Typical use |
|---|---|---|
i_callback_top_of_page | The header is drawn | Titles and selection info (Blog 6) |
i_callback_pf_status_set | The toolbar is built | Custom buttons |
i_callback_user_command | The user clicks a button or double-clicks | Actions |
i_callback_html_top_of_page | A rich HTML header is drawn | Logos, formatted headers |
Older programs get a list of all possible events with REUSE_ALV_EVENTS_GET, fill the form name for each, and pass it as it_events. The i_callback_* parameters do the same for the common events with less code. You will meet both.
6. Custom toolbar buttons
Adding your own button takes five steps.
- Open
SE41(Menu Painter). Choose programSAPLKKBL, statusSTANDARD, and click Copy status. - Copy it to your own program with the name
ZSTANDARD. - In your copy, add a button in the application toolbar with function code
ZREFRand text Refresh. Activate. - Set
i_callback_pf_status_set = 'SET_PF_STATUS', with a form that sets your status. - Set
i_callback_user_command = 'HANDLE_USER_COMMAND', with a form that reacts to your function code.
FORM set_pf_status USING rt_extab TYPE slis_t_extab.
SET PF-STATUS 'ZSTANDARD' EXCLUDING rt_extab.
ENDFORM.
FORM handle_user_command USING pv_ucomm TYPE sy-ucomm
ps_selfield TYPE slis_selfield.
CASE pv_ucomm.
WHEN 'ZREFR'.
PERFORM get_data.
ps_selfield-refresh = 'X'. " redraw the grid with new data
WHEN '&IC1'. " double-click on a row
" ps_selfield-tabindex = row, ps_selfield-fieldname = column
ENDCASE.
ENDFORM.
ps_selfield field | Content |
|---|---|
tabindex | Row number in your output table |
fieldname | Column that was clicked |
value | Content of the clicked cell |
refresh | Set to 'X' to redraw the grid after your action |
row_stable / col_stable | Set to 'X' so the grid keeps its scroll position on refresh |
This is the ALV version of the double-click from Blogs 8 and 9, and it needs no offsets and no GET CURSOR.
7. Editable cells
To let users type in a column, set edit in its field catalog row.
ls_fcat-fieldname = 'ERNAM'.
ls_fcat-edit = 'X'. " this column is editable
" In a user command: first pull the typed values into your table
DATA lo_grid TYPE REF TO cl_gui_alv_grid.
CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'
IMPORTING e_grid = lo_grid.
IF lo_grid IS BOUND.
lo_grid->check_changed_data( ).
ENDIF.
- Editing needs the grid display (
REUSE_ALV_GRID_DISPLAY), not the list display. - Without
check_changed_data( ), your table can still hold the old values when a button is pressed. - For validation per cell and rich editing, move to
CL_GUI_ALV_GRID. Editing is the main reason to leave the function module.
8. Export to Excel
The grid toolbar already has Export > Spreadsheet and Local file. Users can export without any code from you.
When the program must create the file itself (for example to attach it to an email), use CL_SALV_TABLE, which can produce the Excel data directly:
" Sketch: Blog 15 introduces SALV in full
cl_salv_table=>factory(
IMPORTING r_salv_table = DATA(lo_salv)
CHANGING t_table = gt_vbak ).
DATA(lv_xlsx) = lo_salv->to_xml( xml_type = if_salv_bs_xml=>c_type_xlsx ).
" lv_xlsx is an xstring: save it with a download or attach it to an email
For a normal report, rely on the built-in export. Write your own only when the file must be created without the user clicking, such as in a background job. Older code uses SAP_CONVERT_TO_XLS_FORMAT, which you can leave alone if it works.
9. GRID vs. LIST
REUSE_ALV_GRID_DISPLAY | REUSE_ALV_LIST_DISPLAY | |
|---|---|---|
| Look | Modern grid | Classical list |
| Editable cells | Yes | No |
| Toolbar, filter, sort | Full | Limited |
| Works in background jobs | No grid, falls back to a list | Yes |
| Use for | Dialog use (default) | Spool and background output |
10. Complete advanced ALV program
This brings together everything in this blog: layout with zebra and selection boxes, a variant, sort with subtotals, a header, a custom Refresh button, a Show selected button, and a double-click handler.
REPORT zalv_advanced.
TYPES: BEGIN OF ty_vbak,
sel TYPE c LENGTH 1, " selection checkbox
vbeln TYPE vbak-vbeln,
erdat TYPE vbak-erdat,
ernam TYPE vbak-ernam,
vbtyp TYPE vbak-vbtyp,
netwr TYPE vbak-netwr,
waerk TYPE vbak-waerk,
END OF ty_vbak.
DATA: gt_vbak TYPE STANDARD TABLE OF ty_vbak WITH DEFAULT KEY,
gt_fcat TYPE slis_t_fieldcat_alv,
gt_sort TYPE slis_t_sortinfo_alv,
gs_layout TYPE slis_layout_alv,
gs_variant TYPE disvariant,
gv_repid TYPE sy-repid,
gv_erdat TYPE vbak-erdat.
SELECT-OPTIONS s_erdat FOR gv_erdat.
PARAMETERS p_max TYPE i DEFAULT 500.
INITIALIZATION.
s_erdat-sign = 'I'.
s_erdat-option = 'BT'.
s_erdat-low = sy-datum - 30.
s_erdat-high = sy-datum.
APPEND s_erdat.
START-OF-SELECTION.
PERFORM get_data.
END-OF-SELECTION.
IF gt_vbak IS NOT INITIAL.
PERFORM build_fieldcat.
PERFORM build_layout.
PERFORM build_sort.
PERFORM display_alv.
ENDIF.
*---------------------------------------------------------------------*
FORM get_data.
SELECT vbeln, erdat, ernam, vbtyp, netwr, waerk
FROM vbak
WHERE erdat IN @s_erdat
ORDER BY erdat DESCENDING, vbeln
INTO CORRESPONDING FIELDS OF TABLE @gt_vbak
UP TO @p_max ROWS.
IF sy-subrc <> 0.
MESSAGE 'No sales orders found for your selection'
TYPE 'S' DISPLAY LIKE 'E'.
ENDIF.
ENDFORM.
*---------------------------------------------------------------------*
FORM add_col USING pv_field TYPE slis_fieldname
pv_text TYPE scrtext_m
pv_len TYPE i.
DATA ls_fcat TYPE slis_fieldcat_alv.
ls_fcat-fieldname = pv_field.
ls_fcat-tabname = 'GT_VBAK'.
ls_fcat-col_pos = lines( gt_fcat ) + 1.
ls_fcat-seltext_s = pv_text.
ls_fcat-seltext_m = pv_text.
ls_fcat-seltext_l = pv_text.
ls_fcat-outputlen = pv_len.
APPEND ls_fcat TO gt_fcat.
ENDFORM.
FORM build_fieldcat.
DATA ls_fcat TYPE slis_fieldcat_alv.
CLEAR gt_fcat.
PERFORM add_col USING 'VBELN' 'Sales Doc.' 12.
PERFORM add_col USING 'ERDAT' 'Created on' 12.
PERFORM add_col USING 'ERNAM' 'Created by' 12.
PERFORM add_col USING 'VBTYP' 'Cat.' 5.
ls_fcat-fieldname = 'NETWR'.
ls_fcat-tabname = 'GT_VBAK'.
ls_fcat-col_pos = lines( gt_fcat ) + 1.
ls_fcat-seltext_s = 'Net value'.
ls_fcat-seltext_m = 'Net value'.
ls_fcat-seltext_l = 'Net value'.
ls_fcat-outputlen = 16.
ls_fcat-cfieldname = 'WAERK'.
ls_fcat-do_sum = 'X'.
APPEND ls_fcat TO gt_fcat.
PERFORM add_col USING 'WAERK' 'Currency' 8.
ENDFORM.
FORM build_layout.
gs_layout-zebra = 'X'.
gs_layout-colwidth_optimize = 'X'.
gs_layout-box_fieldname = 'SEL'.
ENDFORM.
FORM build_sort.
DATA ls_sort TYPE slis_sortinfo_alv.
CLEAR gt_sort.
ls_sort-spos = 1.
ls_sort-fieldname = 'VBTYP'.
ls_sort-tabname = 'GT_VBAK'.
ls_sort-up = 'X'.
ls_sort-subtot = 'X'.
APPEND ls_sort TO gt_sort.
CLEAR ls_sort.
ls_sort-spos = 2.
ls_sort-fieldname = 'ERDAT'.
ls_sort-tabname = 'GT_VBAK'.
ls_sort-down = 'X'.
APPEND ls_sort TO gt_sort.
ENDFORM.
*---------------------------------------------------------------------*
FORM display_alv.
gv_repid = sy-repid.
gs_variant-report = sy-repid.
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
i_callback_program = gv_repid
i_callback_top_of_page = 'ALV_TOP_OF_PAGE'
i_callback_pf_status_set = 'SET_PF_STATUS'
i_callback_user_command = 'HANDLE_USER_COMMAND'
is_layout = gs_layout
it_fieldcat = gt_fcat
it_sort = gt_sort
i_save = 'A'
is_variant = gs_variant
TABLES
t_outtab = gt_vbak
EXCEPTIONS
program_error = 1
OTHERS = 2.
IF sy-subrc <> 0.
MESSAGE 'ALV could not be displayed' TYPE 'S' DISPLAY LIKE 'E'.
ENDIF.
ENDFORM.
*---------------------------------------------------------------------*
* Callbacks
*---------------------------------------------------------------------*
FORM alv_top_of_page.
DATA: lt_header TYPE slis_t_listheader,
ls_header TYPE slis_listheader.
ls_header-typ = 'H'.
ls_header-info = 'Sales Orders: Advanced ALV'.
APPEND ls_header TO lt_header.
CLEAR ls_header.
ls_header-typ = 'S'.
ls_header-key = 'Records:'.
ls_header-info = |{ lines( gt_vbak ) }|.
APPEND ls_header TO lt_header.
CALL FUNCTION 'REUSE_ALV_COMMENTARY_WRITE'
EXPORTING it_list_commentary = lt_header.
ENDFORM.
FORM set_pf_status USING rt_extab TYPE slis_t_extab.
SET PF-STATUS 'ZSTANDARD' EXCLUDING rt_extab.
ENDFORM.
FORM handle_user_command USING pv_ucomm TYPE sy-ucomm
ps_selfield TYPE slis_selfield.
DATA lv_msg TYPE string.
CASE pv_ucomm.
WHEN 'ZREFR'. " custom Refresh button
PERFORM get_data.
ps_selfield-refresh = 'X'.
ps_selfield-row_stable = 'X'.
ps_selfield-col_stable = 'X'.
WHEN 'ZSHOW'. " custom Show selected button
DATA(lv_count) = REDUCE i( INIT n = 0
FOR ls IN gt_vbak WHERE ( sel = 'X' )
NEXT n = n + 1 ).
lv_msg = |{ lv_count } row(s) selected|.
MESSAGE lv_msg TYPE 'I'.
WHEN '&IC1'. " double-click
READ TABLE gt_vbak INTO DATA(ls_row) INDEX ps_selfield-tabindex.
IF sy-subrc = 0.
lv_msg = |Order { ls_row-vbeln } created by { ls_row-ernam }|.
MESSAGE lv_msg TYPE 'I'.
ENDIF.
ENDCASE.
ENDFORM.
Before you run it
- Create status
ZSTANDARDin SE41 by copyingSTANDARDfromSAPLKKBL(section 6). - Add two buttons to its toolbar:
ZREFR(Refresh) andZSHOW(Show selected). Activate the status. - Tick some row boxes, press Show selected, then double-click a row.
- Change the column order, then Layout > Save. Run the report again and load your layout.
11. Try it yourself
- Turn off
zebraand compare readability. - Set
i_save = 'U', save a layout, and load it from another run. - Add a third sort level on
ERNAMand watch the subtotals. - Add a third button,
ZEMAIL, that only shows a message (no real email yet). - Make the column
ERNAMeditable and confirm that the changed value is in the table after pressing Show selected.
12. Common mistakes
i_savenot set: users cannot save variants.is_variant-reportnot set: saved variants cannot be found.- Wrong
tabnamein sort or filter: it is ignored. Use the output table name in capitals. - Forgetting
i_callback_user_command: clicking a button does nothing. refreshnot set: the grid still shows old data after your change.- Status copied from the wrong place: the toolbar loses standard buttons. Copy
STANDARDfromSAPLKKBL. - Callback form missing or misspelled: ALV raises an error or ignores it. The form must be in the same program.
- Editable cells without
check_changed_data( ): typed values are not yet in your table.
Up next: Blog 11
Report Modularization. Your report is getting long. Learn to split it into FORM routines, function modules, includes, and local classes.
WhatsApp us