본문 바로가기

ABAP 프로그래밍 개념/ABAP Dynamic Programming(동적 프로그래밍)

2.ABAP 동적 프로그래밍 : 필드 심볼(Field Symbol) 1

Field Symbol( 이하 필드심볼) 은 Pointer(존재하는 Data Object를 가르키는)와 비슷한 개념입니다, 필드 심볼은 data object는 아니기 때문에, 그 자신이 소유한 어떠한 메모리도 잡고 있지 않습니다. 대신에, 데이타가 필드심볼에 할당(Assign) 된다면, 할당된 data object의 메모리 위치를 가르킵니다(points to the memory location).

필드 심볼은 Data Object 자체에 접근할 수 있는 것처럼 접근할 수 있는 할당된 Data Object의 라벨 역할을 합니다. 예를 들어, Field1, Field2, Field3라는 세 개의 필드를 가지고 있는 인터널 테이블이 있다고 생각을 해보고, 인터널 테이블의 세번째 Row의 값을 바꾸고 싶다고 해봅니다. 그렇다면 인터널 테이블과 워크 에어리어를 사용한다면 아래와 같은 구문을 사용할 것입니다.

DATA wa Like Line OF itab.
wa-field1 = '123'.
wa-field2 = 'ABC'.
wa-field3 = '가나다'.

MODIFY itab FROM wa INDEX 3.

위 와 같은 경우에는, wa를 사용하여 인터널 테이블을 바꾸어졌는데, wa와 itab은 두 개의 분리된 메모리 위치를 가지고 있고, MODIFY 구문을  통해서 wa의 내용을 itab으로 복사해준 것입니다.

FIELD-SYMBOLS : <wa> LIKE LINE OF itab.
READ TABLE itab ASSIGNING <wa> INDEX 3.

IF <wa> IS ASSIGNED.
<wa>-field1 = '123'.
<wa>-field2 = 'ABC'.
<wa>-field3 = '가나다'.
ENDIF.

위 구문에서은 필드심볼 <wa>를 itab의 라인 형태로 정의하였습니다. READ 구문을 통해 itab의 세번 째 row를 필드심볼 <wa>에 할당하였습니다. 이러한 관점에서, 필드심볼 <wa>는 인터널 테이블의 세 번째 row를 바라보고 있습니다. 우리가 필드 심볼을 통해 필드 값을 바꾸었을때, 세번 째 row의 필드는 즉시 바뀌는데, 필드 심볼이 세 번째 row를 직접 가르키고 있기 때문입니다.

data object가 필드 심볼에 할당된 후에는, 우리는 필드심볼을 data object와 같이 사용할 수 있고 data object로 수행되었던 작업들을 수행할 수 있습니다. 거의 대부분의 data object는 필드 심볼에 할당될 수 있스빈다.

위 코드에서 볼수 있듯이, 필드 심볼을 선언하는 방법과, data object를 할당하는 방법을 볼 수 있는데, 필드 심볼에 접근(상용)하기 전에는 필드 심볼이 잘 할당되었는지 확인해야합니다. 


필드 심볼을 사용하여 동적 프로그램 만들기


예시를 통해 간단하게, 자재에 대한 기본 정보를 보여주는 레포트를 만든다고 합니다. 이 레포트에서 SAP List Viewer(ALV) 객체 모델을 사용하여 결과를 보여줄 것 입니다. 유저에게 더블클릭을 가능하게 하는 기능을 제공하여 하단부에 새 컨테이너에 셀 값을 통해 필터된 기록을 보여주고자 합니다.

이 레포트의 대부분의 구성요소들은 우리가 미리 선언한 정적인 데이터일 것입니다. 그러나, 사용자의 행동(더블클릭)에 따른 필터링은 동적일 것입니다. 아래 코드를 통해 정적인 방법과 동적인 방법으로 구성하는 방법을 알려드리겠습니다.

REPORT z_field_symbol.

CLASS lcl_event_handler DEFINITION DEFERRED.

DATA: custom_container TYPE REF TO cl_gui_custom_container,
          splitter TYPE REF TO cl_gui_splitter_container,
          container_1 TYPE REF TO cl_gui_container,
          so_alv TYPE REF TO cl_salv_table,
          mo_alv TYPE REF TO cl_salv_table,
          gr_event_handler TYPE REF TO lcl_event_handler,
          gr_event TYPE REF TO cl_salv_events_table.

TYPES: BEGIN OF ty_mara,
              matnr TYPE mara-matnr,
              ersda TYPE ersda,
              ernam TYPE ernam,
              laeda TYPE laeda,
              mtart TYPE mtart,
             END OF ty_mara.

DATA: it_mara TYPE STANDARD TABLE OF ty_mara,
          gv_matnr TYPE matnr.

SELECT-OPTIONS: s_matnr FOR gv_matnr.
*&--------------------------------------------------------------*
*& Class LCL_EVENT_HANDLER
*&--------------------------------------------------------------*
CLASS lcl_event_handler DEFINITION.
  PUBLIC SECTION.
    METHODS : on_double_click FOR EVENT double_click OF cl_salv_events_table IMPORTING row column.
ENDCLASS. "LCL_EVENT_HANDLER

*---------------------------------------------------------------*
* CLASS lcl_event_handler IMPLEMENTATION
*---------------------------------------------------------------*
CLASS lcl_event_handler IMPLEMENTATION.
  METHOD on_double_click.
    DATA : lo_sel TYPE REF TO cl_salv_selections,
               ls_cell TYPE salv_s_cell,
               container_2 TYPE REF TO cl_gui_container,
               lt_mara TYPE STANDARD TABLE OF ty_mara,
               lw_mara TYPE ty_mara.

    lo_sel = mo_alv->get_selections( ).
    ls_cell = lo_sel->get_current_cell( ).

    IF ls_cell-columnname EQ 'MATNR'.
      LOOP AT it_mara INTO lw_mara WHERE matnr EQ ls_cell-value.
        APPEND lw_mara TO lt_mara.
      ENDLOOP.
    ELSEIF ls_cell-columnname EQ 'ERSDA'.
      LOOP AT it_mara INTO lw_mara WHERE ersda EQ ls_cell-value.
        APPEND lw_mara TO lt_mara.
      ENDLOOP.
    ELSEIF ls_cell-columnname EQ 'ERNAM'.
      LOOP AT it_mara INTO lw_mara WHERE ernam EQ ls_cell-value.
        APPEND lw_mara TO lt_mara.
      ENDLOOP.
    ELSEIF ls_cell-columnname EQ 'LAEDA'.
      LOOP AT it_mara INTO lw_mara WHERE laeda EQ ls_cell-value.
        APPEND lw_mara TO lt_mara.
      ENDLOOP.
    ELSEIF ls_cell-columnname EQ 'MTART'.
      LOOP AT it_mara INTO lw_mara WHERE mtart EQ ls_cell-value.
        APPEND lw_mara TO lt_mara.
      ENDLOOP.
    ENDIF.

    CALL METHOD splitter->get_container
        EXPORTING
            row = 2
            column = 1
        RECEIVING
            container = container_2.

    TRY.
      IF so_alv IS BOUND.
        so_alv->set_data( CHANGING t_table = lt_mara ).
      ELSE.
      CALL METHOD cl_salv_table=>factory
        EXPORTING
            list_display = if_salv_c_bool_sap=>false
            r_container = container_2
        IMPORTING
            r_salv_table = so_alv
        CHANGING
            t_table = lt_mara.
      ENDIF.

    CATCH cx_salv_no_new_data_allowed.
    CATCH cx_salv_msg .
    ENDTRY.

    so_alv->display( ).

  ENDMETHOD. "on_double_click

ENDCLASS. "lcl_event_handler

*--------------------------------------------------------------------*
*START-OF-SELECTION
*--------------------------------------------------------------------*

START-OF-SELECTION.

  PERFORM fill_table.
  CALL SCREEN 100.
*&--------------------------------------------------------------*
*& Module STATUS_100 OUTPUT
*&--------------------------------------------------------------*
MODULE status_100 OUTPUT.
* Create container object
  CREATE OBJECT custom_container
    EXPORTING
      container_name = 'CONTAINER'.

  CREATE OBJECT splitter
    EXPORTING
      parent = custom_container
      rows = 2
      columns = 1.

  CALL METHOD splitter->get_container
    EXPORTING
        row = 1
      column = 1
    RECEIVING
      container = container_1.


  TRY.
  CALL METHOD cl_salv_table=>factory
    EXPORTING
      list_display = if_salv_c_bool_sap=>false
      r_container = container_1
    IMPORTING
      r_salv_table = mo_alv
    CHANGING
      t_table = it_mara.
  CATCH cx_salv_msg .
  ENDTRY.


  gr_event = mo_alv->get_event( ).

  CREATE OBJECT gr_event_handler.
  SET HANDLER gr_event_handler->on_double_click FOR gr_event.

  mo_alv->display( ).
ENDMODULE. " STATUS_100 OUTPUT

*&--------------------------------------------------------------*
*& Form FILL_TABLE
*&--------------------------------------------------------------*
FORM fill_table .
  SELECT matnr ersda ernam laeda mtart FROM mara INTO TABLE it_mara WHERE matnr IN s_matnr.
ENDFORM. " FILL_TABLE

위 코드를 보면, MARA 테이블로부터 특정 데이터를 Select하고, 결과를 보여줍니다. 여기서 ALV Object Model을 사용하여 ALV 결과를 생성합니다.

커스텀 큰트롤을 위한 100번 스크린을 프로그램을 위해 만들었는데, 추가로 같은 화면에 필터된 데이터들을 보여주기 원했기 때문에, CL_GUI_SPLITTER_CONTAINER 클래스를 통해 화면을 가로로 나누었스빈다.

상단 화면에는 선택된 모든 데이터들이 나타나고 하단 화면에는 필터된 데이터들이 나타납니다. 예시는 아래와 같습니다. 

위 화면은 최초 데이터가 조회된 결과이고 하단 화면은 상단 화면에서 더블클릭시 해당 자재 유형에 맞는 데이터를 필터하여 나타낸 데이터 입니다.

구문을 보면, 유저가 더블클릭을 할 때 LCL_EVENT_HANDLER의 ON_DOUBLE_CLICK 메소드가 호출됩니다. 이 메소드의 코드를 보면, IF...ELSEIF...ENDIF BLOCK을 사용하여 명시적으로 필드이름을 적고 데이터를 필터한 것을 알 수 있습니다.

왜냐하면, 어떤 칼럼을 유저가 더블클릭할 지 정적으로 알 수 없기 때문에, IF...ELSEIF BLOCK을 구성하여 인터널 테이블의 모든 필드를 포함시킨 것입니다. 이러한 구문은 필드가 적으면 가능할 수도 있겠으나 많으면 어렵습니다. 또한 필드가 많은 것 뿐만 아니라 미래에 유지보수 과정에서 증가가 된다면 어떻게 될까요? 추가적인 필드를 로직에 더하는 것 말고는 다른 선택권이 없을 것입니다. 이렇게 모든 필드를 명시하거나 IF...ELSEIF Block을 사용하는 것은 최대한 피하는 것이 좋습니다.

이러한 문제를 다루기 위한 좋은 방법은 필드 심볼을 사용하는 것입니다. 필드심볼을 사용하면, 위에서 사용한 더블클릭 코드를 아래와 같이 바꿀 수 있습니다.

METHOD on_double_click.

FIELD-SYMBOLS: <field> TYPE any.
DATA : lo_sel TYPE REF TO cl_salv_selections,
	   ls_cell TYPE salv_s_cell,
       container_2 TYPE REF TO cl_gui_container,
       lt_mara TYPE STANDARD TABLE OF ty_mara,
       lw_mara TYPE ty_mara.

lo_sel = mo_alv->get_selections( ).
ls_cell = lo_sel->get_current_cell( ).

LOOP AT it_mara INTO lw_mara.
 ASSIGN COMPONENT ls_cell-columnname OF STRUCTURE lw_mara TO <field>.
 IF <field> IS ASSIGNED.
  IF <field> EQ ls_cell-value.
   APPEND lw_mara TO lt_mara.
  ENDIF.
 ENDIF.
ENDLOOP.
    
CALL METHOD splitter->get_container
 EXPORTING
  row = 2
  column = 1
 RECEIVING
  container = container_2.
  
TRY.
 IF so_alv IS BOUND.
  so_alv->set_data( CHANGING t_table = lt_mara ).
 ELSE.
  CALL METHOD cl_salv_table=>factory
   EXPORTING
    list_display = if_salv_c_bool_sap=>false
    r_container = container_2
   IMPORTING
    r_salv_table = so_alv
   CHANGING
    t_table = lt_mara.
 ENDIF.
CATCH cx_salv_msg .
ENDTRY.

so_alv->display( ).

ENDMETHOD. "on_double_click
위 구문에서, <FIELD>generic type으로 정의하여, 루프안에서, 테이블의 구성요소를 필드 심볼로 동적으로 할당하였습니다. 유저가 더블 클릭한 칼럼에 근거하여, LS_CELL_COLUMNNAME 필드는 필드 심볼을 가지게 될 것입니다. ASSIGN COMPONENT OF 구문은 Structure의 필드 명에 해당되는 필드를 <FIELD>할당하는 것입니다.
이렇게 필터링 로직을 동적으로 사용자가 어떠한 필드를 더블 클릭해도 필터가 되도록 진행할 수 있습니다. 이러한 코드는 새 필드가 Structure에 미래에 추가되더라도 효과적입니다. 이러한 예시로 언제 필드 심볼이 사용될 수 있는 지 이해하고 앞으로 글들을 통해서 정의하는 것에 대해서 알아보도록 합니다.

 

반응형