ALV table edit with copy row, check against duplicate keys, protocol and delta update

Here sample program which shows, how to realize table edit through ALV (based on class CL_GUI_ALV_GRID).
The program has some additional features:
  • extends the standard check with check against duplicate keys using event DATA_CHANGED
  • has a protocol and shows errors using class CL_ALV_CHANGED_DATA_PROTOCOL
  • collects the changes for further delta updates of the database table
  • disables the key fields for edit in existing rows and enable in inserted rows
  • provides functionality of copy row, which you probably missing in standard sample program BCALV_EDIT_04
If you want to start the program in your system, besides copying the source code, you have to create screen, GUI status, etc. See the comment at the beginning of the program for the detailed instruction.
*&---------------------------------------------------------------------*
*& Program  ZKMALVED
*&
*& Sample program for table edit through ALV
*& with check against duplicate keys, protocol and delta update
*&---------------------------------------------------------------------*

* Steps to create this program in your system:
*
* 1. SE38: create executable program (type 1)
*
* 2. SE80: create screen 0100
*
* 3. Screen Painter: on the screen 0100 create 'Custom control'
*    CONT1_0100
*
* 4. SE80: for screen 0100 set the variable OK_CODE for the element OK
*
* 5. SE80: create calls to PBO and PAI in the flow logic of screen 0100:
*
*    PROCESS BEFORE OUTPUT.
*      MODULE pbo_0100.
*    PROCESS AFTER INPUT.
*      MODULE pai_0100.
*
* 6. SE80: create GUI Status STAT_0100, assign functions EXIT and SAVE
*    to standard icons, eventually create your own toolbar button

PROGRAM zkmalved.

TYPES: BEGIN OF ts_data.
INCLUDE  STRUCTURE spfli.
TYPES:   mod_type TYPE string.      " type of modification (for protocol)
TYPES:   verified TYPE c.           " flag for verification
TYPES:   celltab  TYPE lvc_t_styl.
TYPES: END OF ts_data.

TYPES:
  tt_data    TYPE STANDARD TABLE OF ts_data WITH NON-UNIQUE DEFAULT KEY.

CLASS cl_event_handler DEFINITION DEFERRED.

DATA:
  gt_data          TYPE tt_data,
  go_grid          TYPE REF TO cl_gui_alv_grid,
  go_cont          TYPE REF TO cl_gui_custom_container,
  gt_fcat          TYPE lvc_t_fcat,
  ok_code          LIKE sy-ucomm,
  go_event_handler TYPE REF TO cl_event_handler.

*----------------------------------------------------------------------*
*       CLASS cl_event_handler DEFINITION
*----------------------------------------------------------------------*
CLASS cl_event_handler DEFINITION.

  PUBLIC SECTION.

    CLASS-DATA:
      data_changed_error  TYPE i,        " error flag
      protocol_tab        TYPE tt_data.  " protocol of changes since last save

    METHODS handle_toolbar
      FOR EVENT toolbar OF cl_gui_alv_grid
        IMPORTING
          e_object
          e_interactive.

    METHODS handle_user_command
      FOR EVENT user_command OF cl_gui_alv_grid
        IMPORTING
          e_ucomm.

    METHODS handle_data_changed
      FOR EVENT data_changed OF cl_gui_alv_grid
        IMPORTING
          er_data_changed.

    METHODS protocol_add
      IMPORTING
        pi_tabix        TYPE i
        er_data_changed TYPE REF TO cl_alv_changed_data_protocol.

ENDCLASS.                    "cl_event_handler DEFINITION

*----------------------------------------------------------------------*
*       CLASS cl_event_handler IMPLEMENTATION
*----------------------------------------------------------------------*
CLASS cl_event_handler IMPLEMENTATION.

*&---------------------------------------------------------------------*
*&      METHOD handle_toolbar
*&---------------------------------------------------------------------*
  METHOD handle_toolbar.

    FIELD-SYMBOLS:
      <fs_button> TYPE stb_button.

*   replace the standard function 'check' with own one
    READ TABLE e_object->mt_toolbar ASSIGNING <fs_button>
      WITH KEY function = cl_gui_alv_grid=>mc_fc_check. " '&CHECK'.
    <fs_button>-function = 'CHECK'.

**  deactivate the 'copy row' function
**  (not necessary in the logic of this program)
*
*   READ TABLE e_object->mt_toolbar ASSIGNING <fs_button>
*     WITH KEY function = cl_gui_alv_grid=>mc_fc_loc_copy_row. " '&LOCAL&COPY_ROW'.
*   <fs_button>-disabled = 'X'.
  ENDMETHOD.                    "handle_toolbar

*&---------------------------------------------------------------------*
*&      METHOD handle_user_command
*&---------------------------------------------------------------------*
  METHOD handle_user_command.

*   force PAI processing with user function as ok_code
    cl_gui_cfw=>set_new_ok_code( e_ucomm ).

  ENDMETHOD.                    "handle_user_command

*&---------------------------------------------------------------------*
*&      METHOD handle_data_changed
*&---------------------------------------------------------------------*
*&      validate changes against duplicate keys and collect the protocol
*&      of the changes for further db update or transport of changes
*&---------------------------------------------------------------------*
  METHOD handle_data_changed.

    DATA:
      ls_row          TYPE lvc_s_moce,
      ls_cell         TYPE lvc_s_modi,
      lt_data         TYPE tt_data,
      ls_data         TYPE ts_data,
      ls_data2        TYPE ts_data,
      lv_tabix        TYPE i,
      lv_tabix2       TYPE i,
      lv_new          TYPE i.

    FIELD-SYMBOLS:
      <ft_data>       TYPE tt_data,
      <fs_cell>       TYPE lvc_s_modi.

    ASSIGN er_data_changed->mp_mod_rows->* TO <ft_data>.

*   consider present error messages from standard data checks
    IF LINES( er_data_changed->mt_protocol ) = 0.
      data_changed_error = 0.
    ELSE.
      data_changed_error = 1.
    ENDIF.

*   1. collect the deleted rows (no checks needed)
    LOOP AT er_data_changed->mt_deleted_rows INTO ls_row.

      READ TABLE gt_data INTO ls_data INDEX ls_row-row_id.
      ls_data-mod_type = 'DELETE'.
      APPEND ls_data TO protocol_tab.
    ENDLOOP.

*   2. collect the 'old' changed rows (no changes on key fields)
    LOOP AT <ft_data> INTO ls_data
      WHERE verified = 'X'.

      ls_data-mod_type = 'UPDATE'.
      APPEND ls_data TO protocol_tab.
    ENDLOOP.

*   3. check the new rows against the old, verified rows
    LOOP AT <ft_data> INTO ls_data
      WHERE verified = ''.

      lv_tabix = sy-tabix.
      ADD 1 TO lv_new.

      READ TABLE gt_data TRANSPORTING NO FIELDS
        WITH KEY carrid = ls_data-carrid
                 connid = ls_data-connid.

      CHECK sy-subrc = 0.

      data_changed_error = 1.

      CALL METHOD protocol_add
        EXPORTING
          pi_tabix        = lv_tabix
          er_data_changed = er_data_changed.

    ENDLOOP.

*   4. check the new rows among themselves
    IF lv_new > 1.
      LOOP AT <ft_data> INTO ls_data
        WHERE verified = ''.

        lv_tabix = sy-tabix.

        LOOP AT <ft_data> INTO ls_data2
          WHERE verified = ''.

          lv_tabix2 = sy-tabix.
          CHECK lv_tabix2 <> lv_tabix.

          CHECK ls_data2-carrid = ls_data-carrid
            AND ls_data2-connid = ls_data-connid.

          data_changed_error = 1.

          CALL METHOD protocol_add
            EXPORTING
              pi_tabix        = lv_tabix
              er_data_changed = er_data_changed.

          CALL METHOD protocol_add
            EXPORTING
              pi_tabix        = lv_tabix2
              er_data_changed = er_data_changed.

        ENDLOOP.
      ENDLOOP.
    ENDIF.

*   5. last aktivities in event DATA_CHANGED
    LOOP AT <ft_data> INTO ls_data
      WHERE verified = ''.

      lv_tabix = sy-tabix.

      READ TABLE er_data_changed->mt_mod_cells INTO ls_cell WITH KEY tabix = sy-tabix.

      READ TABLE er_data_changed->mt_protocol TRANSPORTING NO FIELDS WITH KEY row_id = ls_cell-row_id.

*     if check against duplicate key was ok
      CHECK sy-subrc <> 0.

*     collect new rows
      ls_data-mod_type = 'INSERT'.
      APPEND ls_data TO protocol_tab.

*     disable key fields
      LOOP AT er_data_changed->mt_good_cells ASSIGNING <fs_cell>
        WHERE tabix = lv_tabix.

        READ TABLE gt_fcat TRANSPORTING NO FIELDS WITH KEY fieldname = <fs_cell>-fieldname
                                                           key       = 'X'.

        CHECK sy-subrc = 0.
        <fs_cell>-style = cl_gui_alv_grid=>mc_style_disabled.
      ENDLOOP.
    ENDLOOP.

**   6. activate the copied rows
**   (not necessary in the logic of this program)
*
*    DATA:
*      ls_cell TYPE lvc_s_modi.
*
*    LOOP AT er_data_changed->mt_inserted_rows INTO ls_row.
*
*      LOOP AT er_data_changed->mt_mod_cells INTO ls_cell
*        WHERE row_id = ls_row-row_id.
*
*        CALL METHOD er_data_changed->modify_style
*          EXPORTING
*            i_row_id    = ls_cell-row_id
*            i_fieldname = ls_cell-fieldname
*            i_style     = cl_gui_alv_grid=>mc_style_enabled.
*      ENDLOOP.
*    ENDLOOP.
  ENDMETHOD.                    "handle_data_changed

*&---------------------------------------------------------------------*
*&      METHOD protocol_add
*&---------------------------------------------------------------------*
  METHOD protocol_add.

    DATA:
      ls_cell       TYPE lvc_s_modi,
      ls_fcat       TYPE lvc_s_fcat,
      lv_string     TYPE string,
      lv_msgv1      TYPE symsgv,
      lv_msgv2      TYPE symsgv,
      ls_roid_front TYPE lvc_s_roid,
      lv_row_id     TYPE i.

    LOOP AT er_data_changed->mt_mod_cells INTO ls_cell
      WHERE tabix = pi_tabix.

*     report only key fields
      READ TABLE gt_fcat INTO ls_fcat WITH KEY fieldname = ls_cell-fieldname.
      CHECK ls_fcat-key = 'X'.

*     report only once
      READ TABLE er_data_changed->mt_protocol TRANSPORTING NO FIELDS
        WITH KEY fieldname = ls_cell-fieldname
                 row_id    = ls_cell-row_id.

      CHECK sy-subrc <> 0.

*     map the row id between frontend and internal tables for the message
      READ TABLE er_data_changed->mt_roid_front INTO ls_roid_front
        WITH KEY row_id = ls_cell-row_id.

      lv_row_id = sy-tabix.

      lv_msgv1 = 'Row &1:'.
      lv_string = lv_row_id.
      REPLACE SUBSTRING '&1' IN lv_msgv1 WITH lv_string.

      lv_msgv2 = 'Duplicate key with value ''&1'''.
      lv_string = ls_cell-value.
      REPLACE SUBSTRING '&1' IN lv_msgv2 WITH lv_string.

      CALL METHOD er_data_changed->add_protocol_entry
        EXPORTING
          i_msgid     = '0K'
          i_msgno     = '000'
          i_msgty     = 'E'
          i_msgv1     = lv_msgv1
          i_msgv2     = lv_msgv2
          i_fieldname = ls_cell-fieldname
          i_row_id    = ls_cell-row_id.    " pass the row id without mapping here
    ENDLOOP.
  ENDMETHOD.                    "protocol_add
ENDCLASS.                    "cl_event_handler IMPLEMENTATION

*&---------------------------------------------------------------------*
*&      START-OF-SELECTION
*&---------------------------------------------------------------------*
START-OF-SELECTION.

  PERFORM data_load CHANGING gt_data.

* initially check all rows as verified
  PERFORM data_verified_set CHANGING gt_data.

  PERFORM fieldcat_create CHANGING gt_fcat.

  PERFORM data_key_readonly_set USING gt_fcat CHANGING gt_data.

  CALL SCREEN 0100.

*&---------------------------------------------------------------------*
*&      Form  data_load
*&---------------------------------------------------------------------*
FORM data_load CHANGING pt_data TYPE tt_data.

  SELECT *
    INTO CORRESPONDING FIELDS OF TABLE pt_data
    FROM spfli.

ENDFORM.                    "data_load

*&---------------------------------------------------------------------*
*&      Form  fieldcat_create
*&---------------------------------------------------------------------*
FORM fieldcat_create CHANGING pt_fcat TYPE lvc_t_fcat.

  FIELD-SYMBOLS:
    <fs_fcat> TYPE lvc_s_fcat.

  CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
    EXPORTING
      i_structure_name = 'SPFLI'
    CHANGING
      ct_fieldcat      = gt_fcat.

  LOOP AT gt_fcat ASSIGNING <fs_fcat>.
    <fs_fcat>-edit = 'X'.

*   key fields
    IF   <fs_fcat>-fieldname = 'CARRID'
      OR <fs_fcat>-fieldname = 'CONNID'.

      <fs_fcat>-key = 'X'.
    ELSE.
      <fs_fcat>-key = ''.
    ENDIF.
  ENDLOOP.
ENDFORM.                    "fieldcat_create

*&---------------------------------------------------------------------*
*&      Form  data_key_readonly_set
*&---------------------------------------------------------------------*
FORM data_key_readonly_set USING    pt_fcat TYPE lvc_t_fcat
                           CHANGING pt_data TYPE tt_data.

  DATA:
    lt_style TYPE lvc_t_styl,
    ls_style TYPE lvc_s_styl,
    ls_fcat  TYPE lvc_s_fcat.

  FIELD-SYMBOLS:
    <fs_data> TYPE ts_data.

* the following construct works well for function 'copy row' (MC_FC_LOC_COPY_ROW)
* (the style of the whole row is not copied and the key fields are open for change)

* first deactivate complete row for edit
  ls_style-style = cl_gui_alv_grid=>mc_style_disabled.
  INSERT ls_style INTO TABLE lt_style.

  LOOP AT pt_fcat INTO ls_fcat
    WHERE NOT key = 'X'.

*   then activate non-key fields
    ls_style-fieldname = ls_fcat-fieldname.
    ls_style-style = cl_gui_alv_grid=>mc_style_enabled.
    INSERT ls_style INTO TABLE lt_style.
  ENDLOOP.

  LOOP AT pt_data ASSIGNING <fs_data>.
    <fs_data>-celltab = lt_style.
  ENDLOOP.
ENDFORM.                    "data_key_readonly_set

*&---------------------------------------------------------------------*
*&      Form  data_verified_set
*&---------------------------------------------------------------------*
FORM data_verified_set CHANGING pt_data TYPE tt_data.

  FIELD-SYMBOLS:
    <fs_data> TYPE ts_data.

  LOOP AT pt_data ASSIGNING <fs_data>
    WHERE verified = ''.

    <fs_data>-verified = 'X'.

  ENDLOOP.
ENDFORM.                    "data_verified_set

*&---------------------------------------------------------------------*
*&      Form  data_change_post_processing
*&---------------------------------------------------------------------*
FORM data_change_post_processing CHANGING pt_data TYPE tt_data.

* check all rows as verified
  PERFORM data_verified_set CHANGING pt_data.

* deactivate edit mode of key fields
  PERFORM data_key_readonly_set USING    gt_fcat
                                CHANGING pt_data.

* refresh ALV grid from internal table
  CALL METHOD go_grid->refresh_table_display.

ENDFORM.                    "data_change_post_processing

*&---------------------------------------------------------------------*
*&      Form  data_save
*&---------------------------------------------------------------------*
FORM data_save.

  DATA:
    ls_data    TYPE ts_data,
    ls_db_data TYPE spfli.

  LOOP AT cl_event_handler=>protocol_tab INTO ls_data.

    MOVE-CORRESPONDING ls_data TO ls_db_data.

    CASE ls_data-mod_type.

      WHEN 'INSERT'.
        INSERT spfli FROM ls_db_data.

      WHEN 'UPDATE'.
        UPDATE spfli FROM ls_db_data.

      WHEN 'DELETE'.
        DELETE spfli FROM ls_db_data.
    ENDCASE.
  ENDLOOP.

  COMMIT WORK.

  MESSAGE S999(B1) WITH 'Data saved'.
ENDFORM.                    "data_save

*&---------------------------------------------------------------------*
*&      Form  alv_init
*&---------------------------------------------------------------------*
FORM alv_init.

  DATA:
    ls_layout TYPE lvc_s_layo.

  IF go_cont IS INITIAL.

    SET PF-STATUS 'STAT_0100'.

    CREATE OBJECT go_cont
      EXPORTING
        container_name = 'CONT1_0100'.

    CREATE OBJECT go_grid
      EXPORTING
        i_parent = go_cont.

    CREATE OBJECT go_event_handler.
    SET HANDLER go_event_handler->handle_toolbar      FOR go_grid.
    SET HANDLER go_event_handler->handle_user_command FOR go_grid.
    SET HANDLER go_event_handler->handle_data_changed FOR go_grid.

    ls_layout-stylefname = 'CELLTAB'.

    CALL METHOD go_grid->set_table_for_first_display
      EXPORTING
        is_layout       = ls_layout
      CHANGING
        it_fieldcatalog = gt_fcat
        it_outtab       = gt_data.

**   if you need event DATA_CHANGED after function 'copy row'
**   (not necessary in the logic of this program)
*
*    CALL METHOD go_grid->register_edit_event
*      EXPORTING
*        i_event_id = cl_gui_alv_grid=>mc_evt_modified.
  ENDIF.
ENDFORM.                    "alv_init

*&---------------------------------------------------------------------*
*&      Form  user_command
*&---------------------------------------------------------------------*
FORM user_command.

  CALL METHOD cl_gui_cfw=>dispatch.

  CASE ok_code.

    WHEN 'EXIT'.
      LEAVE PROGRAM.

    WHEN 'CHECK'.
*     1. synchronize the internal table and check against duplicate keys
      CALL METHOD go_grid->check_changed_data.

*     2. if there are no duplicated keys
      CHECK cl_event_handler=>data_changed_error = 0.
*     post processing after successful synchronization
      PERFORM data_change_post_processing CHANGING gt_data.

    WHEN 'SAVE'.
*     1. synchronize the internal table
      CALL METHOD go_grid->check_changed_data.

*     2. if there are no duplicated keys
      CHECK cl_event_handler=>data_changed_error = 0.
*     post processing after successful synchronization
      PERFORM data_change_post_processing CHANGING gt_data.

*     3. if the data was changed
      CHECK NOT cl_event_handler=>protocol_tab IS INITIAL.
*     save the delta changes in database
      PERFORM data_save.
*     clear the protocol
      CLEAR cl_event_handler=>protocol_tab.
  ENDCASE.

  CLEAR ok_code.
ENDFORM.                    "user_command

*----------------------------------------------------------------------*
*  MODULE pbo_0100 OUTPUT
*----------------------------------------------------------------------*
MODULE pbo_0100 OUTPUT.

  PERFORM alv_init.
ENDMODULE.                    "pbo_0100 OUTPUT

*----------------------------------------------------------------------*
*  MODULE pai_0100 INPUT
*----------------------------------------------------------------------*
MODULE pai_0100 INPUT.

  PERFORM user_command.
ENDMODULE.                    "pai_0100 INPUT
See other related notes in my infodepot:
Events in the object oriented ALV based on class CL_GUI_ALV_GRID
Full list of examples in my infodepot

If you have a question, have found an error or just want to contact me, please use this form.

Copyright (C) 2012 http://www.kerum.pl/infodepot/

Disclaimer: I am not affiliated or related to any division or subsidiary of SAP AG.
Trademarks or registered trademarks of any products or companies referred to on this site belong to those companies.
Anyone using the given solutions, is doing it under his/her own responsibility and at own risk.