본문 바로가기
ABAP/소스코드

[Class] SALV Display - SALV 간단 출력 Class - ZCL_SALV_OUTPUT

by name_text 2023. 5. 27.

ABAP 소스 테스트에 유용한 SALV 간단 출력 Class

CL_SALV_TABLE

ZCL_SALV_TEST

ZCL_SALV_OUTPUT

 

# 2023.09.20 수정 : Internal Table뿐만 아니라 Structure도 사용할 수 있도록 기능 개선

# 2023.09.12 수정 : ALV 리스트 더블클릭시 SE16N 처럼 '변환값'/'비변환값'을 보여주는 팝업 기능 추가

# 2025.04.10 수정 : fieldname이 없는 table_line 형식의 Internal Table로 표시되도록 로직 추가

 

로컬 오브젝트로 ABAP 소스 테스트 할때 결과를 확인하기 위해 WRITE 문이나 CL_DEMO_OUTPUT=>DISPLAY( ) 를 많이들 애용합니다.

간단한 내용은 WRITE나 CL_DEMO_OUTPUT을 사용해도 충분하지만, Internal Table의 데이터를 보기에는 ALV만큼 편한게 없죠.

그래서 자주 사용하는 만큼 최대한 사용하기 간단하고 구조도 심플하게 SALV를 이용하여 ALV간단출력 Class를 만들었습니다.

 

참고로, SQL 쿼리문만 테스트를 하는 경우 ZTOAD 사용 추천드립니다!!

New Open SQL에 대해 안되는게 좀 있긴 하지만 그래도 급한게 사용할때 아주 유용하죠

http://quelquepart.biz/article7/ztoad-requeteur-open-sql

 

ZTOAD : Requêteur Open SQL - Quelquepart

 

quelquepart.biz

 

아래 이미지는 SALV 간단출력 Class의 출력 화면 예시입니다.

Internal Table 내용 확인이 주 목적인 만큼 최대한 심플하게 만들었습니다.

 

#1. SALV 간단 출력 Class

열 텍스트는 테스트 편의성을 위해 필드명으로 변경하여 표시됩니다.

class ZCL_SALV_OUTPUT definition
  public
  create public .

public section.

  class-methods DISPLAY
    importing
      value(T_TABLE) type ANY .
  class-methods DISPLAY_NO_CONV
    importing
      value(T_TABLE) type ANY .
  PROTECTED SECTION.
private section.

  class-data MD_TABLE type ref to DATA .

  class-methods SALV_OUTPUT
    importing
      value(T_TABLE) type ANY
      !I_NO_CONV type C .
  class-methods HANDLE_DOUBLE_CLICK
    for event DOUBLE_CLICK of CL_SALV_EVENTS_TABLE
    importing
      !ROW
      !COLUMN .
ENDCLASS.



CLASS ZCL_SALV_OUTPUT IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_SALV_OUTPUT=>DISPLAY
* +-------------------------------------------------------------------------------------------------+
* | [--->] T_TABLE                        TYPE        ANY
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD display.
    salv_output( EXPORTING t_table    = t_table
                           i_no_conv  = space ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_SALV_OUTPUT=>DISPLAY_NO_CONV
* +-------------------------------------------------------------------------------------------------+
* | [--->] T_TABLE                        TYPE        ANY
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD display_no_conv.
    salv_output( EXPORTING t_table    = t_table
                           i_no_conv  = 'X' ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_SALV_OUTPUT=>HANDLE_DOUBLE_CLICK
* +-------------------------------------------------------------------------------------------------+
* | [--->] ROW                            LIKE
* | [--->] COLUMN                         LIKE
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD handle_double_click.
    TYPES: BEGIN OF t_popup,
             position  TYPE tabfdpos,
             fieldname TYPE fieldname,
             fieldtext TYPE as4text,
             value_wc  TYPE string,      "변환값
             value_nc  TYPE string,      "비변환값
             cfield_v  TYPE string,      "통화/단위 값
             convexit  TYPE convexit,
             tabname   TYPE tabname,
             reftable  TYPE reftable,
             reffield  TYPE reffield,
           END OF t_popup.
    DATA: lt_popup TYPE STANDARD TABLE OF t_popup,
          ls_popup LIKE LINE OF lt_popup.

    DATA: lv_field(255) TYPE c,
          lv_funcname   TYPE rs38l_fnam.
    DATA: lv_char(1024) TYPE c,
          lv_datum      TYPE sy-datum,
          lv_uzeit      TYPE sy-uzeit,
          lv_currency   TYPE tcurc-waers,
          lv_unit       TYPE mara-meins.
    DATA lv_rowidx(20) TYPE c.

    FIELD-SYMBOLS: <lft>       TYPE table,
                   <lfv>       TYPE any,
                   <lfv_waers> TYPE any.

    IF row IS INITIAL.
      EXIT.
    ENDIF.

    WRITE row TO lv_rowidx.  CONDENSE lv_rowidx.

    CLEAR lt_popup.

    ASSIGN md_table->* TO <lft>.

    DATA(lt_dfies) = cl_salv_ddic=>get_by_data( <lft> ).

    READ TABLE <lft> ASSIGNING FIELD-SYMBOL(<lfs>) INDEX row.
    IF sy-subrc NE 0.
      EXIT.
    ENDIF.

    DELETE lt_dfies WHERE inttype EQ cl_abap_typedescr=>typekind_table
                       OR inttype EQ cl_abap_typedescr=>typekind_struct1
                       OR inttype EQ cl_abap_typedescr=>typekind_struct2.

    LOOP AT lt_dfies INTO DATA(ls_dfies).
      ls_popup = CORRESPONDING #( ls_dfies ).
      ls_popup-position = sy-tabix.

      lv_field = |<LFS>-{ ls_dfies-fieldname }|.
      UNASSIGN <lfv>.
      ASSIGN (lv_field) TO <lfv>.
      IF <lfv> IS NOT ASSIGNED.
        APPEND ls_popup TO lt_popup.  CLEAR ls_popup.
        CONTINUE.
      ENDIF.

      MOVE <lfv> TO ls_popup-value_wc.
      MOVE <lfv> TO ls_popup-value_nc.

      TRY.
          IF ls_popup-convexit IS NOT INITIAL.
            lv_funcname = |CONVERSION_EXIT_{ ls_popup-convexit }_OUTPUT|.
            TRY.
                CALL FUNCTION lv_funcname
                  EXPORTING
                    input  = <lfv>
                  IMPORTING
                    output = lv_char
                  EXCEPTIONS
                    OTHERS = 1.
                MOVE lv_char TO ls_popup-value_wc.
              CATCH cx_root INTO DATA(lo_cx_alpha1).
            ENDTRY.

          ELSEIF ls_popup-reffield IS NOT INITIAL AND ls_dfies-reftable EQ ls_dfies-precfield.
            lv_field = |<LFS>-{ ls_popup-reffield }|.
            UNASSIGN <lfv_waers>.
            ASSIGN (lv_field) TO <lfv_waers>.
            IF <lfv_waers> IS NOT ASSIGNED.
              APPEND ls_popup TO lt_popup.  CLEAR ls_popup.
              CONTINUE.
            ENDIF.

            MOVE <lfv_waers> TO ls_popup-cfield_v.

            IF ls_dfies-datatype EQ 'CURR'.
              MOVE <lfv_waers> TO lv_currency.
              WRITE <lfv> TO lv_char CURRENCY lv_currency. CONDENSE lv_char.
              MOVE lv_char TO ls_popup-value_wc.
            ELSEIF ls_dfies-datatype EQ 'QUAN'.
              MOVE <lfv_waers> TO lv_unit.
              WRITE <lfv> TO lv_char UNIT lv_unit. CONDENSE lv_char.
              MOVE lv_char TO ls_popup-value_wc.
            ENDIF.

          ELSEIF ls_dfies-inttype EQ cl_abap_typedescr=>typekind_int
              OR ls_dfies-inttype EQ cl_abap_typedescr=>typekind_int1
              OR ls_dfies-inttype EQ cl_abap_typedescr=>typekind_int8
              OR ls_dfies-inttype EQ cl_abap_typedescr=>typekind_int2
              OR ls_dfies-inttype EQ cl_abap_typedescr=>typekind_float
              OR ls_dfies-inttype EQ cl_abap_typedescr=>typekind_packed.
            WRITE <lfv> TO lv_char. CONDENSE lv_char.
            MOVE lv_char TO ls_popup-value_wc.

          ELSEIF ls_dfies-inttype EQ cl_abap_typedescr=>typekind_num.
            SHIFT ls_popup-value_wc LEFT DELETING LEADING '0'.

          ELSEIF ls_dfies-inttype EQ cl_abap_typedescr=>typekind_date.
            MOVE <lfv> TO lv_datum.
            ls_popup-value_wc = |{ lv_datum DATE = USER }|.

          ELSEIF ls_dfies-inttype EQ cl_abap_typedescr=>typekind_time.
            MOVE <lfv> TO lv_uzeit.
            ls_popup-value_wc = |{ lv_uzeit TIME = USER }|.

          ENDIF.
        CATCH cx_root INTO DATA(ls_cx).

      ENDTRY.

      APPEND ls_popup TO lt_popup.  CLEAR ls_popup.
    ENDLOOP.

    IF lt_popup[] IS INITIAL.
      EXIT.
    ENDIF.

    TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = DATA(lr_salv)
        CHANGING
          t_table      = lt_popup ).      "ALV로 출력할 Internal Table
    CATCH CX_SALV_MSG.
      RETURN.
    ENDTRY.

    lr_salv->get_functions( )->set_all( ).        "ALV의 기능키 표시
    lr_salv->get_display_settings( )->set_list_header( CONV #( |Selected row index : { lv_rowidx }| ) ). "TITLE

    DATA: lv_ltext  TYPE scrtext_l,
          lr_column TYPE REF TO cl_salv_column_table.

    DATA(lr_columns) = lr_salv->get_columns( ).
    LOOP AT lr_columns->get( ) INTO DATA(ls_columns).
      CLEAR lv_ltext.
      TRY.
        lr_column ?= lr_columns->get_column( ls_columns-columnname ).
      CATCH CX_SALV_NOT_FOUND.
        CONTINUE.
      ENDTRY.

      CASE ls_columns-columnname.
        WHEN 'FIELDNAME'.
          lr_column->set_key( abap_true ).
        WHEN 'VALUE_WC'.
          lv_ltext = SWITCH #( sy-langu WHEN '3' THEN '값' ELSE 'Value' ).
          lr_column->set_color( VALUE #( col = 5  int = 0 inv = 0 ) ).
        WHEN 'VALUE_NC'.
          lv_ltext = SWITCH #( sy-langu WHEN '3' THEN '비변환값' ELSE 'Value Unconverted' ).
          lr_column->set_color( VALUE #( col = 5  int = 0 inv = 0 ) ).
        WHEN 'CFIELD_V'.
          lv_ltext = SWITCH #( sy-langu WHEN '3' THEN '참조값' ELSE 'Ref. value' ).
          lr_column->set_color( VALUE #( col = 3  int = 0 inv = 0 ) ).
        WHEN 'REFTABLE' OR 'REFFIELD'.
          lr_column->set_color( VALUE #( col = 3  int = 0 inv = 0 ) ).
      ENDCASE.

      IF lv_ltext IS NOT INITIAL.
        lr_column->set_long_text( CONV #( lv_ltext ) ).
        lr_column->set_medium_text( CONV #( lv_ltext ) ).
        lr_column->set_short_text( CONV #( lv_ltext ) ).
      ENDIF.
    ENDLOOP.

    lr_salv->get_columns( )->set_optimize( abap_true ).   "열 너비 최적화
    lr_salv->set_screen_popup(
      start_column = 1
      end_column   = 140
      start_line   = 1
      end_line     = 30 ).
    lr_salv->display( ).
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_SALV_OUTPUT=>SALV_OUTPUT
* +-------------------------------------------------------------------------------------------------+
* | [--->] T_TABLE                        TYPE        ANY
* | [--->] I_NO_CONV                      TYPE        C
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD salv_output.
    FIELD-SYMBOLS: <lft> TYPE STANDARD TABLE.
    DATA: ld_itab  TYPE REF TO data.
    DATA: lo_type      TYPE REF TO cl_abap_typedescr,
          lo_line_type TYPE REF TO cl_abap_typedescr,
          lo_table     TYPE REF TO cl_abap_tabledescr,
          lo_struc     TYPE REF TO cl_abap_structdescr,
          lo_data      TYPE REF TO cl_abap_datadescr.


    lo_type = cl_abap_typedescr=>describe_by_data( t_table ).

    CASE lo_type->kind.
      WHEN cl_abap_typedescr=>kind_table.
        lo_table ?= lo_type.
        lo_line_type = lo_table->get_table_line_type( ).

        IF lo_line_type->kind EQ cl_abap_typedescr=>kind_struct.
          lo_struc ?= lo_line_type.
          lo_table = cl_abap_tabledescr=>create( lo_struc ).

          CREATE DATA ld_itab TYPE HANDLE lo_table.
          ASSIGN ld_itab->* TO <lft>.

          <lft> = CORRESPONDING #( t_table ).
        ELSE.
          lo_data ?= lo_line_type.
          lo_struc =  cl_abap_structdescr=>create( VALUE #( ( name = 'TABLE_LINE_VALUE'
                                                              type = lo_data ) ) ).
          lo_table = cl_abap_tabledescr=>create( lo_struc ).

          CREATE DATA ld_itab TYPE HANDLE lo_table.
          ASSIGN ld_itab->* TO <lft>.

          LOOP AT t_table ASSIGNING FIELD-SYMBOL(<lfs_table>).
            APPEND INITIAL LINE TO <lft> ASSIGNING FIELD-SYMBOL(<lfs>).

            ASSIGN COMPONENT 1 OF STRUCTURE <lfs> TO FIELD-SYMBOL(<lfv>) ELSE UNASSIGN.
            IF <lfv> IS ASSIGNED.
              <lfv> = <lfs_table>.
            ENDIF.
          ENDLOOP.
        ENDIF.

      WHEN cl_abap_typedescr=>kind_struct.
        CREATE DATA ld_itab LIKE TABLE OF t_table.
        ASSIGN ld_itab->* TO <lft>.
        APPEND t_table TO <lft>.

      WHEN OTHERS.
        cl_demo_output=>display( t_table ).
        RETURN.
    ENDCASE.

    GET REFERENCE OF <lft> INTO md_table.

    TRY.
        cl_salv_table=>factory(
          IMPORTING
            r_salv_table = DATA(lr_salv)
          CHANGING
            t_table      = <lft> ).      "ALV로 출력할 Internal Table
      CATCH cx_salv_msg.
        RETURN.
    ENDTRY.

    DATA lv_rowcnt(20) TYPE c.
    WRITE lines( <lft> ) TO lv_rowcnt.  CONDENSE lv_rowcnt.

    lr_salv->get_functions( )->set_all( ).        "ALV의 기능키 표시
    lr_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).   "선택 모드
    lr_salv->get_display_settings( )->set_striped_pattern( cl_salv_display_settings=>true ).  "줄무늬 패턴
    lr_salv->get_display_settings( )->set_list_header( CONV #( |{ sy-cprog } - Result : { lv_rowcnt } rows| ) ). "TITLE

    DATA(lr_columns) = lr_salv->get_columns( ).
    LOOP AT lr_columns->get( ) INTO DATA(ls_columns).     "열 텍스트를 필드명으로 변경
      TRY.
          DATA(lr_column) = lr_columns->get_column( ls_columns-columnname ).
        CATCH cx_salv_not_found.
          CONTINUE.
      ENDTRY.

      DATA(lv_inttype) = lr_column->get_ddic_inttype( ).
      IF lv_inttype EQ cl_abap_typedescr=>typekind_table OR
         lv_inttype EQ cl_abap_typedescr=>typekind_struct1 OR
         lv_inttype EQ cl_abap_typedescr=>typekind_struct2.
        lr_column->set_technical( abap_true ).
      ENDIF.
      lr_column->set_short_text( CONV #( ls_columns-columnname ) ).
      lr_column->set_medium_text( CONV #( ls_columns-columnname ) ).
      lr_column->set_long_text( CONV #( ls_columns-columnname ) ).
      lr_column->set_sign( abap_true ).
      IF i_no_conv EQ 'X'.
        lr_column->set_edit_mask( space ).
        TRY.
            lr_column->set_quantity_column( space ).
          CATCH cx_salv_not_found
                cx_salv_data_error.

        ENDTRY.
        TRY.
            lr_column->set_currency_column( space ).
          CATCH cx_salv_not_found
                cx_salv_data_error.

        ENDTRY.
      ENDIF.
    ENDLOOP.
    lr_salv->get_columns( )->set_optimize( abap_true ).   "열 너비 최적화

    DATA(lr_event_tab) = lr_salv->get_event( ).
    SET HANDLER handle_double_click FOR lr_event_tab.

    lr_salv->display( ).            "ALV 출력
  ENDMETHOD.
ENDCLASS.

 

#2. 사용예시

인터널테이블의 내용을 출력하고자 할때, ZCL_SALV_OUTPUT=>DISPLAY( 인터널테이블 ). 만 있으면 됩니다.

REPORT YTEMP_SANDBOX.
WITH +VBRP AS ( SELECT
                  A~VBELN,
                  A~FKDAT,
                  B~POSNR,
                  B~PAOBJNR,
                  B~NETWR,
                  A~WAERK
                FROM VBRK AS A
                  INNER JOIN VBRP AS B ON A~VBELN EQ B~VBELN
                WHERE A~WAERK EQ 'USD' ),
     +BSEG AS ( SELECT
                  A~AWKEY,
                  B~BUZEI,
                  B~PAOBJNR AS PAOBJNR_FI,
                  B~WRBTR,
                  A~WAERS,
                  B~DMBTR
                FROM BKPF AS A
                  INNER JOIN BSEG AS B ON A~BUKRS EQ B~BUKRS AND A~BELNR EQ B~BELNR AND A~GJAHR EQ B~GJAHR
                WHERE A~AWTYP EQ 'VBRK' )
SELECT
  *
FROM +VBRP AS A
  LEFT OUTER JOIN +BSEG AS B ON A~VBELN EQ B~AWKEY AND A~PAOBJNR EQ B~PAOBJNR_FI
WHERE A~FKDAT BETWEEN '20221201' AND '20221231'
INTO TABLE @DATA(LT_TMP).

"Internal Table의 내용 SALV로 출력
ZCL_SALV_OUTPUT=>DISPLAY( LT_TMP[] ).		"Conversion Routine 적용

ZCL_SALV_OUTPUT=>DISPLAY_NO_CONV( LT_TMP[] ).	"Conversion Routine 미적용

 

댓글