[ABAP] SALV: Eventhandler für Auswahl einer Zeile (Row) bzw. Spalte (Col)

* Eventhandler
CLASS lcl_events DEFINITION.
  PUBLIC SECTION.

* Platzhalter für Referenz auf SALV-Grid
    CLASS-DATA: o_salv TYPE REF TO cl_salv_table.
* Platzhalter für Referenz auf interne Tabelle
    CLASS-DATA: it_t024 TYPE STANDARD TABLE OF t024 WITH DEFAULT KEY.

* Eventhandler-Methode für Change-Select, Verzögerung ~1.5s
    CLASS-METHODS: on_row_select FOR EVENT delayed_changed_sel_callback OF cl_gui_alv_grid
      IMPORTING sender.
ENDCLASS.

CLASS lcl_events IMPLEMENTATION.
* Event behandelt die Auswahl Zeilen/Spalten im Gitter mit 1,5s Verzögerung
  METHOD on_row_select.
    TRY.
* referenz auf ALV-Grid holen
        DATA(o_alv) = CAST cl_gui_alv_grid( sender ).

* selektierte Zeilen, abh. vom if_salv_c_selection_mode
        o_alv->get_selected_rows( IMPORTING et_row_no     = DATA(it_sel_rows)
                                            et_index_rows = DATA(it_sel_rows_idx) ).

* selektierte Spalten, abh. vom if_salv_c_selection_mode
        o_alv->get_selected_columns( IMPORTING et_index_columns = DATA(it_sel_cols_idx) ).

        LOOP AT it_sel_cols_idx ASSIGNING FIELD-SYMBOL(<col>).
        ENDLOOP.

        LOOP AT it_sel_rows_idx ASSIGNING FIELD-SYMBOL(<row>).
        ENDLOOP.

      CATCH cx_root.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_salv_helper DEFINITION INHERITING FROM cl_salv_model_base FINAL.
  PUBLIC SECTION.

    CLASS-METHODS:
      get_alv_from_salv
        IMPORTING
          io_salv_grid   TYPE REF TO cl_salv_table
        RETURNING
          VALUE(ro_grid) TYPE REF TO cl_gui_alv_grid.
ENDCLASS.

CLASS lcl_salv_helper IMPLEMENTATION.
*---------------------------------------------------------------------*
* Wandelt eine Referenz auf cl_salv_table in ein cl_gui_alv_grid um.
*---------------------------------------------------------------------*
* -> io_model - Referenz auf cl_salv_table
* <- ro_grid  - Referenz auf cl_gui_alv_grid, bei Fehler INITIAL
*---------------------------------------------------------------------*
  METHOD get_alv_from_salv.

    IF io_salv_grid IS BOUND.
      TRY.
          DATA(o_model) = CAST cl_salv_model_base( io_salv_grid->extended_grid_api( ) ).

* Funktioniert nur, wenn SALV-Table schon mit display( ) angezeigt und
* somit schon ein Container dafür erzeugt wurde. Andernfalls ist r_adapter INITIAL.
          IF o_model->r_controller IS BOUND AND o_model->r_controller->r_adapter IS BOUND.

            IF o_model->r_controller->r_adapter IS INSTANCE OF if_salv_table_display_adapter.
              ro_grid = CAST if_salv_table_display_adapter( o_model->r_controller->r_adapter )->get_grid( ).
            ENDIF.

          ENDIF.
        CATCH cx_root.
      ENDTRY.
    ENDIF.

  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

  TRY.
* Beispieldaten (Einkäufergruppen) holen
      SELECT FROM t024
        FIELDS *
        INTO TABLE @lcl_events=>it_t024
        UP TO 10 ROWS.

      IF sy-subrc = 0.
        cl_salv_table=>factory( EXPORTING r_container  = cl_gui_container=>default_screen
                                IMPORTING r_salv_table = lcl_events=>o_salv
                                CHANGING  t_table      = lcl_events=>it_t024 ).

* Standardbuttons der SALV-Table
        lcl_events=>o_salv->get_functions( )->set_all( abap_true ).

* if_salv_c_selection_mode=>single    : es kann eine Zeile im SALV/ALV selektiert werden
* if_salv_c_selection_mode=>row_column: es können mehrere Zeile und Spalten im SALV/ALV selektiert werden
* nach 1,5s wird dann der Event on_delayed_change_select ausgelöst
        lcl_events=>o_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>single ).

* SALV anzeigen
        lcl_events=>o_salv->display( ).

* ALV-Objektreferenz aus dem SALV-Grid holen, funktioniert erst nach o_salv->display( )
        DATA(o_alv) = lcl_salv_helper=>get_alv_from_salv( lcl_events=>o_salv ).
        IF o_alv IS BOUND.
* Event-Handler des ALV
          SET HANDLER lcl_events=>on_row_select FOR o_alv.
* Event registrieren
          o_alv->register_delayed_event( i_event_id = cl_gui_alv_grid=>mc_evt_delayed_change_select ).
        ENDIF.

* Toolbar der Listausgabe unterdrücken
        cl_abap_list_layout=>suppress_toolbar( ).

* Listausgabe erzwingen für Erzeugung von cl_gui_container=>default_screen
        WRITE: space.

      ENDIF.
    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

[ABAP] Editierbares SALV-Grid (IF_SALV_GUI_OM_EXTEND_GRID_API, IF_SALV_GUI_OM_EDIT_RESTRICTED)

* Quelle: https://blogs.sap.com/2022/08/01/editable-cl_salv_table-after-release-756/
* ab SAP Release 756
* Achtung: das Ganze funktioniert nur für kleine Tabellen mit max. 5000 Zellen, siehe:
*
* Methode: CL_SALV_GUI_OM_ADAPTER_TABLE->CAN_RUN_RESTRICTED_EDIT_MODE( )
* Konstante: CV_MAX_CELLS_FOR_EDITABLE (Wert: 5000).

* Eventhandler
CLASS lcl_events DEFINITION.
  PUBLIC SECTION.
* Bezeichner der Buttons
    CONSTANTS: co_btn_edit TYPE string VALUE 'BTN_EDIT'.
    CONSTANTS: co_btn_save TYPE string VALUE 'BTN_SAVE'.

* Platzhalter für Referenz auf SALV-Grid
    CLASS-DATA: o_salv TYPE REF TO cl_salv_table.

* Eventhandler-Methode für Button-Klicks in der Toolbar des SALV-Grids
    CLASS-METHODS : on_toolbar_click FOR EVENT added_function OF cl_salv_events_table
      IMPORTING
        e_salv_function
        sender.
  PRIVATE SECTION.
* Edit-Status des SALV-Grids
    CLASS-DATA: gv_edit TYPE abap_bool VALUE abap_false.
ENDCLASS.

CLASS lcl_events IMPLEMENTATION.
  METHOD on_toolbar_click.
    IF o_salv IS BOUND.

      DATA(o_api) = o_salv->extended_grid_api( ).
      DATA(o_edit) = o_api->editable_restricted( ).

      CASE e_salv_function.

        WHEN co_btn_edit.
* Edit-Modus umschalten
          IF gv_edit = abap_false.
            gv_edit = abap_true.
          ELSE.
            gv_edit = abap_false.
          ENDIF.

          TRY.
* Spalte(n) (nicht) editierbar setzen
              o_edit->set_attributes_for_columnname( EXPORTING columnname              = 'EKGRP'
                                                               all_cells_input_enabled = gv_edit ).

              o_edit->set_attributes_for_columnname( EXPORTING columnname              = 'SMTP_ADDR'
                                                               all_cells_input_enabled = gv_edit ).
            CATCH cx_salv_not_found.
          ENDTRY.

          o_edit->validate_changed_data( ).
          o_salv->refresh( ).

        WHEN co_btn_save.
* Daten auf Validität prüfen
          DATA(lv_data_is_valid) = abap_false.

          TRY.
              o_edit->validate_changed_data( IMPORTING is_input_data_valid = lv_data_is_valid ).
              o_salv->refresh( ).
            CATCH cx_salv_not_found.
          ENDTRY.

          IF lv_data_is_valid = abap_true.
* Daten hier speichern / weiterverarbeiten
            MESSAGE co_btn_save TYPE 'I'.
          ENDIF.
      ENDCASE.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

  TRY.
* Beispieldaten (Einkäufergruppen) holen
      SELECT FROM t024
        FIELDS *
        INTO TABLE @DATA(it_t024).

      IF sy-subrc = 0.
        cl_salv_table=>factory( EXPORTING r_container  = cl_gui_container=>default_screen
                                IMPORTING r_salv_table = lcl_events=>o_salv
                                CHANGING  t_table      = it_t024 ).

* Standardbuttons der SALV-Table ausblenden
        lcl_events=>o_salv->get_functions( )->set_all( abap_false ).

* Eigenen SALV-Button hinzufügen
* das Hinzufügen des Buttons funktioniert nur, wenn die SALV-Table innerhalb eines Containers (z.B. cl_gui_container=>default_screen) eingebettet ist
        lcl_events=>o_salv->get_functions( )->add_function( name = |{ lcl_events=>co_btn_edit }|
                                                            icon = |{ icon_edit_file }|
                                                            text = 'Edit'
                                                            tooltip = 'Daten editieren'
                                                            position = if_salv_c_function_position=>right_of_salv_functions ).

        lcl_events=>o_salv->get_functions( )->add_function( name = |{ lcl_events=>co_btn_save }|
                                                            icon = |{ icon_save_as_template }|
                                                            text = 'Save'
                                                            tooltip = 'Daten speichern'
                                                            position = if_salv_c_function_position=>right_of_salv_functions ).

* Eventhandler für Klicks in die Toolbar des SALV-Grids setzen
        SET HANDLER lcl_events=>on_toolbar_click FOR lcl_events=>o_salv->get_event( ).

* SALV anzeigen
        lcl_events=>o_salv->display( ).

* Toolbar der Listausgabe unterdrücken
        cl_abap_list_layout=>suppress_toolbar( ).

* Listausgabe erzwingen für Erzeugung von cl_gui_container=>default_screen
        WRITE: space.

      ENDIF.
    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

[ABAP] ABAP2XLSX: Excel-Daten (*.xlsx) einlesen und anzeigen

**********************************************************************
* Types
**********************************************************************
TYPES: BEGIN OF ty_xl_line,
         col1 TYPE string,
         col2 TYPE string,
       END OF ty_xl_line.

TYPES: ty_it_xl_lines TYPE STANDARD TABLE OF ty_xl_line WITH DEFAULT KEY.

**********************************************************************
* Selektionsbild
**********************************************************************
PARAMETERS: p_fname TYPE file_table-filename OBLIGATORY.

* wenn die F4-Hilfe für den Dateinamen aufgerufen wird
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_fname.

  DATA: lv_rc TYPE i.
  DATA: it_files TYPE filetable.
  DATA: lv_action TYPE i.

* File-Tabelle leeren, da hier noch alte Einträge von vorherigen Aufrufen drin stehen können
  CLEAR it_files.

* FileOpen-Dialog aufrufen
  TRY.
      cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                    file_filter    = |xlsx (*.xlsx)\|*.xlsx\|{ cl_gui_frontend_services=>filetype_all }|
                                                    multiselection = abap_false
                                                  CHANGING
                                                    file_table  = it_files
                                                    rc          = lv_rc
                                                    user_action = lv_action ).

      IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn Datei ausgewählt wurde
        IF lines( it_files ) > 0.
* ersten Tabelleneintrag lesen
          p_fname = it_files[ 1 ]-filename.
        ENDIF.
      ENDIF.

    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'I'.
  ENDTRY.

START-OF-SELECTION.

  DATA(it_xl) = VALUE ty_it_xl_lines( ).

  TRY.
* Reader-Objekt erzeugen
      DATA(o_reader) = CAST zif_excel_reader( NEW zcl_excel_reader_2007( ) ).
      DATA(o_excel) = o_reader->load_file( p_fname ).

* Worksheet
      DATA(o_worksheet) = o_excel->get_active_worksheet( ).
* max. Zeile und Spalte holen
      DATA(lv_max_col) = o_worksheet->get_highest_column( ).
      DATA(lv_max_row)    = o_worksheet->get_highest_row( ).

      WRITE: / lv_max_col, ',', lv_max_row.

      DATA(lv_row) = 1.

* Worksheet zeilenweise durchlaufen
      WHILE lv_row <= lv_max_row.
* Spaltennummer (1) in Excel-Spalten-Bezeichner (A) umwandeln
        DATA(lv_col_str) = zcl_excel_common=>convert_column2alpha( 1 ).

* Zellinhalt Spalte 1 holen
        o_worksheet->get_cell( EXPORTING
                                 ip_column = lv_col_str
                                 ip_row    = lv_row
                               IMPORTING
                                 ep_value  = DATA(lv_value)
                             ).

* Spaltennummer (2) in Excel-Spalten-Bezeichner (B) umwandeln
        DATA(lv_col_str2) = zcl_excel_common=>convert_column2alpha( 2 ).
* Zellinhalt Spalte 2 holen
        o_worksheet->get_cell( EXPORTING
                                 ip_column = lv_col_str2
                                 ip_row    = lv_row
                               IMPORTING
                                 ep_value  = DATA(lv_value2)
                             ).

* Werte an interne Tabelle anfügen
        APPEND VALUE #(
                        col1 = lv_value
                        col2 = lv_value2
                      ) TO it_xl.

        lv_row = lv_row + 1.
      ENDWHILE.

* Daten in SALV-Grid anzeigen
      TRY.
* SALV-Table
          DATA: o_salv TYPE REF TO cl_salv_table.

          cl_salv_table=>factory( IMPORTING
                                    r_salv_table = o_salv
                                  CHANGING
                                    t_table      = it_xl ).

* Grundeinstellungen
          o_salv->get_functions( )->set_all( abap_true ).
          o_salv->get_columns( )->set_optimize( abap_true ).
          o_salv->get_display_settings( )->set_list_header( |Excel-Werte ({ lines( it_xl ) })| ).
          o_salv->get_display_settings( )->set_striped_pattern( abap_true ).
          o_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).

* Spaltenüberschriften: technischer Name und Beschreibungstexte, Short Text und Medium Text leer lassen für Autosize
          LOOP AT o_salv->get_columns( )->get( ) ASSIGNING FIELD-SYMBOL(<c>).
            DATA(o_col) = <c>-r_column.
            o_col->set_short_text( || ).
            o_col->set_medium_text( || ).
            o_col->set_long_text( |{ o_col->get_columnname( ) } [{ o_col->get_long_text( ) }]| ).
          ENDLOOP.

          o_salv->display( ).
        CATCH cx_root INTO DATA(e_txt).
          WRITE: / e_txt->get_text( ).
      ENDTRY.
    CATCH cx_root INTO DATA(ex).    " Exceptions for ABAP2XLSX
      WRITE: / ex->get_text( ).
      WRITE: / ex->get_longtext( ).
  ENDTRY.

[ABAP] SALV IDA: SAP List Viewer with Integrated Data Access

* Beispielprogramme:
* Paket: SALV_IDA_TEST

CONSTANTS: co_table_name TYPE dbtabl VALUE 'SBOOK'.

DATA: lv_carrid TYPE sbook-carrid.

SELECT-OPTIONS: so_carr FOR lv_carrid.

START-OF-SELECTION.

  TRY.
* SALV Table IDA erzeugen
      DATA(o_salv_ida) = cl_salv_gui_table_ida=>create( iv_table_name = co_table_name ).

* Authority Check (löst evtl. Exception aus, wenn Berechtigungen fehlen)
      o_salv_ida->add_authorization_for_object( iv_authorization_object = 'S_CARRID'
                                                it_activities           = VALUE #( ( auth_field = 'ACTVT' value = '03' ) )
                                                it_field_mapping        = VALUE #( ( auth_field = 'CARRID' view_field = 'CARRID' ) ) ).

* SELECT-Konditionen festlegen
      DATA(o_cond_factory) = o_salv_ida->condition_factory( ).

* RANGE definieren: SELECT-OPTIONS
      DATA(o_ranges) = NEW cl_salv_range_tab_collector( ).
      o_ranges->add_ranges_for_name( iv_name = 'CARRID' it_ranges = so_carr[] ).
      o_ranges->get_collected_ranges( IMPORTING
                                        et_named_ranges = DATA(it_name_range_pairs) ).
* RANGE definieren: manuell
      DATA(it_fixed_ranges) = VALUE if_salv_service_types=>yt_named_ranges( ( name = 'FORCURKEY' sign = 'E' option = 'EQ' low = 'GBP' high = '' ) ).

      APPEND LINES OF it_fixed_ranges TO it_name_range_pairs.

* SELECT-Konditionen und RANGES übergeben
      o_salv_ida->set_select_options( it_ranges    = it_name_range_pairs
                                      io_condition = o_cond_factory->equals( name = 'FLDATE' value = '20120523' )->and(
                                                     o_cond_factory->covers_pattern( name = 'PASSNAME' pattern = '*Anna*')->and(
                                                     o_cond_factory->equals( name = 'FORCURKEY' value = 'EUR' )->or(
                                                     o_cond_factory->equals( name = 'FORCURKEY' value = 'USD' ) ) ) ) ).

* Spaltenüberschrift ändern
      o_salv_ida->field_catalog( )->set_field_header_texts( iv_field_name        = 'PASSNAME'
                                                            iv_header_text       = 'Name des Passagiers'
                                                            iv_tooltip_text      = 'Standard: 40 Zeichen zur Verfügung'
                                                            iv_tooltip_text_long = 'Erweiterung: 100 Zeichen zur Verfügung bei richtiger SAP GUI und SAP Basis Version' ).

* Sortierungen festlegen
      DATA(it_sort_order) = VALUE if_salv_gui_types_ida=>yt_sort_rule( ( field_name = 'PASSNAME'
                                                                         descending = abap_false
                                                                         is_grouped = abap_false ) ).

      o_salv_ida->default_layout( )->set_sort_order( it_sort_order = it_sort_order ).

* manuelle Sortierung für Spalte ausschalten
      o_salv_ida->field_catalog( )->disable_sort( iv_field_name = 'PASSNAME' ).

* Filter ausschalten
      o_salv_ida->field_catalog( )->disable_filter( iv_field_name = 'PASSNAME' ).

* nur bestimmte Spalten darstellen 1
      o_salv_ida->field_catalog( )->get_all_fields( IMPORTING ets_field_names = DATA(it_field_names) ).
      DELETE it_field_names WHERE table_line CP 'SEATS*'.

* nur bestimmte Spalten darstellen 2
*        DATA(it_field_names) = VALUE if_salv_gui_types_ida=>yts_field_name( ( CONV #( 'CARRID' ) )
*                                                                            ( CONV #( 'CONNID' ) )
*                                                                            ( CONV #( 'FLDATE' ) )
*                                                                            ( CONV #( 'FORCURKEY' ) )
*                                                                            ( CONV #( 'CUSTTYPE' ) )
*                                                                            ( CONV #( 'PASSNAME' ) ) ).

      o_salv_ida->field_catalog( )->set_available_fields( its_field_names = it_field_names ).

* passenden Beschreibungstext anhand der Domäne darstellen
      o_salv_ida->field_catalog( )->display_options( )->set_formatting( iv_field_name        = 'CUSTTYPE'
                                                                        iv_presentation_mode = if_salv_gui_types_ida=>cs_presentation_mode-description ).

* Textsuche im Gitter für Spalten 'CARRID' und 'PASSNAME' aktivieren (Button mit Lupe oben rechts)
      o_salv_ida->field_catalog( )->enable_text_search( 'CARRID' ).
      o_salv_ida->field_catalog( )->enable_text_search( 'PASSNAME' ).
      o_salv_ida->standard_functions( )->set_text_search_active( abap_true ).

* Layouts verwalten
      o_salv_ida->layout_persistence( )->set_persistence_options( is_persistence_key           = VALUE #( report_name = sy-repid )
                                                                  i_global_save_allowed        = abap_true
                                                                  i_user_specific_save_allowed = abap_true ).

      o_salv_ida->toolbar( )->enable_listbox_for_layouts( ).

* Titel
      o_salv_ida->display_options( )->set_title( 'Überschrift' ).

* Text für leere Tabelle
      o_salv_ida->display_options( )->set_empty_table_text( 'Keine Daten vorhanden.' ).

* Zebrastreifen
      o_salv_ida->display_options( )->enable_alternating_row_pattern( ).

* Datenbankfähigkeiten abfragen
      IF abap_true = cl_salv_gui_table_ida=>db_capabilities( )->is_text_search_supported( ).
* unscharfe Suche (Fuzzy) -> wird nur von HANA unterstützt
        o_salv_ida->text_search( )->set_field_similarity( '0.8' ).
        o_salv_ida->text_search( )->set_search_term( |Fehler| ).
      ENDIF.

* Wird eine Beschränkung der Anzahl der Datensätze empfohlen?
      IF cl_salv_gui_table_ida=>db_capabilities( )->is_max_rows_recommended( ).
* max. Anzahl der Datensätze beschränken
        o_salv_ida->set_maximum_number_of_rows( iv_number_of_rows = 500 ).
      ENDIF.

      o_salv_ida->fullscreen( )->display( ).

    CATCH cx_root INTO DATA(lo_exc).
      WRITE: / lo_exc->get_text( ).
  ENDTRY.

Links

[ABAP] Einschränkungen bei der Verwendung von SALV-Tables

  • Anzahl der Spalten ist auf 90 begrenzt
  • Ausgabelänge einer Spalte ist auf 128 Zeichen begrenzt
  • Basisfunktionen für das Drucken, komplexe Drucklayouts sind im ALV nicht vorgesehen
  • zur Sortierung und für Zwischensummen stehen max. neun Ebenen bzw. Spalten zur Verfügung
  • bei aggregierbaren Spalten -> interne Länge der Spalte <= Länge Einzelwerte + Länge Ergebnis
  • nur flach strukturierte Tabellen darstellbar, Darstellung verschachtelter Tabellen ist nicht möglich
  • Tabellen sind nicht eingabebereit
  • Tabelle als Grid im Container -> keinen Batch-Modus nutzbar
  • Ergebnisse (Summen und Zwischensummen) können nicht übergeben oder manipuliert werden