In this blog post, I would like to share a few thoughts on how to write better code in SAP Abap by applying MVC to code structure.

I work as an Expert Abap consultant and sometimes fellow developers are asking advices on writing better code. There are many patterns, rules in coding world to help you to write better code. MVC is one of them, and I will try to explain where MVC fits in Abap development.

Previous to Abap, I worked with many different programming languages and my favorite languages are the object oriented ones, such as Java, C# and of course Abap. I like object oriented languages, because, you can create a model of the required algorithm in small modules, you can re-use code and encapsulate complexities. I believe, it is important to write lego like, modular, reusable code. That makes coding more fun and less painfull.

Below, we will see basics of MVC and then I will try to explain, the need for MVC with a basic scenario. And finally, we will see be how it can be applied in Abap with a mvc applied report code sample.

Let’s start with MVC word itself; MVC stands for Model View Controller pattern. It is used to separate layers from each other and also to make sure right code is place in right layer, in that way your application code is more robust, easier to extend, test and maintain.

And, the layers of MVC are, from top to bottom;

View -> In view layer you present/display your data. Reports, RFCs, Apis, Smartforms, ITS Dialog Screens can be part of this layer.

Controller -> In controller layer, requests are directed to related model method. Controller is the connection between view and model.  Controller can be a form procedure, method, function, just directs requests from view to Model. Should not contain any business logic in it.

Model -> Code where business logic is operated. Entry point of model can be a class method or a function. I prefer to use methods.

MVC Layers

For the sample scenario, let’s imagine we are requested to create an alv report to list the pending purchase order approvals. We need to display, PO number, Person to Approve, Date Sent for Approval, Number of days since PO sent for approval.

You got the related tables and bapis, you wrote the code under report include. Great! Quickly a solution is created. You could write code in a function or in a method of a class. But, there was no time, or that was a small development or just a simple piece of code, so code is just left under report.

Now, the requirement is, if number of days of waiting is more than 3 days, you need to send a reminder email to approver. Simple, just copy the code from program and paste it to another program and create a background job to send emails in given email format. With that development, now you have 2 programs.

And after sometime, someone asked you to create pdf output to send managers of approvers. Now you copy the code again, this time place it under code block of adobe pdf interface, done! Solution created.

And after sometime again, you are requested to implement a logic to check number of days from a Z table. instead of constant duration, 3 days, days are variable according material group and safety stock quantities.  There is a saying, “When a programmer first creates his code, only he and God know how it works, a few months down the line and only God knows“. You remember the last development maybe, or you may not be the developer of other programs and previous developer is gone. So You found out, there is a PDF form and a report. You have changed the code in those 2 areas. But background job is forgotten! First inconsistency.

This scenario can be extended with new requirements, new web apis, fiori apps, or for custom mobile apps. And each copy of the code is actually a base for inconsistency and extra work.

So what we have done as a fast solution, which took short time, can be very expensive at the end. Not just time while we are changing copies of code, but also in a critical scenario, inconsistencies may cause bigger problems than time. Therefore we need write code in a way, that we can re-use if we need, interface must not contain business logic ,but should be able to call function or better the method to retrieve required data and just display it.

In example above, report, email, pdf out are just interfaces. If the biz logic was implemented in a class. All interfaces could call class and get required data. And when you as a developer need to do a change, you would change that class, test that class. That is much easier, much more robust and definitely less time consuming, more extendible way of coding.

What I suggest is, try to implement MVC when you are coding, even in a small development. Once you change the way you think how to structure your programs with MVC, you will get better at that.

When you are trying to implement MVC, you may ask a question to yourself, in which layer should I put that piece of code now? Shall I keep it in interface or shall I move it to model class. Answer is simple, ask yourself, will I require that code if interface was different. If yes, place the code in lower layer, in model. In that way, you do not need to write same code for different interfaces.  If the answer is no, than just keep that code in interface.

And lets see the code samples for different types of developments, how MVC can be implemented.

 

Sample Report template;

Below template acts as two different reports/views. One of them shows data and user selects lines and creates wm transfer orders for selected ones. Or report can be set as background job with a variant and when job runs creates transfer orders for all records. Please check the code to see code reuse. One model class but two different views. That is because, business logic is not placed under report includes. If required a new program, web api, smartform or adobe form or any kind of interface can be created and can call the same model class.

I will place full code of report and related parts of class code, so that can remain here as reference.

Short info about report/view and class/model interaction ->  Report calls get_data method of class. get_data method acts as controller and forwards request to model, model object fills gt_recs table and that table is the source of ALV display.

If user_command button is clicked on screen with &createto function code, user command procedure forwards that request to model, acts as a controller.

*&---------------------------------------------------------------------*
*& Include ZBM_P_MVC_TEMPLATE- Report ZBM_P_MVC_TEMPLATE
*& Sample MVC implementation temaplate by BM
*& This report creates interface for users and also for background job
*& Calls bizobj methods to read data and displays data directly from
*& bizobj's public datas.
*&---------------------------------------------------------------------*

INCLUDE ZBM_P_MVC_TEMPLATE_TOP                          .    " Global Data
INCLUDE ZBM_P_MVC_TEMPLATE_F01                          .    " Controller

END-OF-SELECTION.

  PERFORM get_data.

  IF bizobj->gt_recs[] IS NOT INITIAL.
    IF p_backgr EQ abap_true.
      PERFORM background_process.
    ELSE.
      PERFORM display_data.
    ENDIF.

  ENDIF.     .  " FORM-Routines

 

*&---------------------------------------------------------------------*
*& Include ZBM_P_MVC_TEMPLATE_TOP                   - Report ZBM_P_MVC_TEMPLATE
*& Sample MVC implementation temaplate by BM
*& This include contains global data and selection screen definitions
*&---------------------------------------------------------------------*

REPORT zbm_p_mvc_template.

"Global data
TABLES: zwm_s_lt10_alv, lqua.
DATA: gv_grid TYPE REF TO cl_gui_alv_grid,
      bizobj  TYPE REF TO zbm_cl_mvc_template.

FIELD-SYMBOLS: <gs_selected_row> TYPE zwm_s_lt10_alv.

SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01.

  SELECT-OPTIONS: s_lgnum FOR lqua-lgnum OBLIGATORY,
                  s_lgtyp FOR lqua-lgtyp OBLIGATORY,
                  s_lgpla FOR lqua-lgpla,
                  s_matnr FOR lqua-matnr.

  PARAMETERS:
    p_bwlvs  TYPE ltak-bwlvs DEFAULT 999 OBLIGATORY,
    p_inglck TYPE xfeld DEFAULT abap_true.

SELECTION-SCREEN END OF BLOCK b1.

SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-t02.

  PARAMETERS:
    p_backgr TYPE xfeld. "Background processing request flag

SELECTION-SCREEN END OF BLOCK b2.


INITIALIZATION.
  bizobj = NEW zbm_cl_mvc_template( ).

 

*&---------------------------------------------------------------------*
*& Include          ZBM_P_MVC_TEMPLATE_F01
*& Sample MVC implementation temaplate by BM
*& This include contains Report / ALV related procedures
*& Also acts as controller of MVC implementation
*& by calling related methods of model class.
*&---------------------------------------------------------------------*


******************* Report Related Procedures - Begin **********************

TYPE-POOLS: slis.

"Field catalog, sütunlar için
DATA: gt_fieldcat TYPE slis_t_fieldcat_alv,
      gs_fieldcat TYPE LINE OF slis_t_fieldcat_alv,
      gt_layout   TYPE slis_layout_alv,
      g_variant   LIKE disvariant.


FORM set_grid_var.
  IF gv_grid IS INITIAL.
    "Getting the reference to teh ALV grid
    CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'
      IMPORTING
        e_grid = gv_grid.
  ENDIF.
ENDFORM.


FORM prepare_field_catalog.
  "Prepare fields catalog
  IF gt_fieldcat IS INITIAL.

    CALL FUNCTION 'REUSE_ALV_FIELDCATALOG_MERGE'
      EXPORTING
        i_program_name   = sy-repid
        i_structure_name = 'zwm_s_lt10_alv'
        i_inclname       = sy-repid
      CHANGING
        ct_fieldcat      = gt_fieldcat[].

    LOOP AT gt_fieldcat ASSIGNING FIELD-SYMBOL(<wa>)
      WHERE fieldname EQ 'IS_SELECTED'.
      <wa>-checkbox = abap_true.
      <wa>-no_out = abap_true.
    ENDLOOP.

  ENDIF.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  prepare_layout_alv
*&---------------------------------------------------------------------*
FORM prepare_layout_alv .
  gt_layout-colwidth_optimize = 'X'.
  gt_layout-zebra = 'X'.
  gt_layout-no_input = 'X'.
  gt_layout-box_fieldname = 'IS_SELECTED'.

ENDFORM.


FORM set_pf_status USING rt_extab TYPE slis_t_extab.
  "menüleri ve butonları standart olarak çıkarmayı sağlıyor.
  SET PF-STATUS 'STANDARD'.
  SET TITLEBAR 'TITLE1' WITH sy-uname.
ENDFORM.

FORM refresh_grid_data.
  "Refreshes the grid data
  PERFORM set_grid_var.
  CALL METHOD gv_grid->refresh_table_display.
ENDFORM.


FORM reload_data.
  "Verileri tekrar yükle
  PERFORM get_data.
  PERFORM refresh_grid_data.
ENDFORM.



"Display data
FORM display_data.
  PERFORM prepare_field_catalog.
  PERFORM prepare_layout_alv.

  IF g_variant IS INITIAL.
    g_variant-report   = sy-repid.
    g_variant-username = sy-uname.
  ENDIF.

  "Dipslay data
  CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
    EXPORTING
      i_callback_program       = sy-repid
      i_callback_pf_status_set = 'SET_PF_STATUS'
      is_layout                = gt_layout
      it_fieldcat              = gt_fieldcat[]
      i_default                = 'X'
      i_save                   = 'A'
      is_variant               = g_variant
      i_callback_user_command  = 'USER_COMMAND'
    TABLES
      t_outtab                 = bizobj->gt_recs[].

  "Set grid var
  PERFORM set_grid_var.
ENDFORM.
******************* Report Related Procedures - End **********************



****************************** MVC Controller Part - Begin **********************

"ALV Komutları
FORM user_command USING r_ucomm
      LIKE sy-ucomm rs_selfield TYPE slis_selfield.

  CASE r_ucomm.

    WHEN '&CREATETO'.
      PERFORM create_to.

  ENDCASE.

ENDFORM. "USER_COMMAND

"Gets report data
FORM get_data.
  "Calls model class to retrieve report data
  "That is contoller method, simply passes view parameters to model
  "And retrieves data

  DATA: r_lgnum TYPE zwm_cl_lt10=>ty_r_lgnum,
        r_lgtyp TYPE zwm_cl_lt10=>ty_r_lgtyp,
        r_lgpla TYPE zwm_cl_lt10=>ty_r_lgpla,
        r_matnr TYPE zwm_cl_lt10=>ty_r_matnr.

  MOVE-CORRESPONDING s_lgnum[] TO r_lgnum[].
  MOVE-CORRESPONDING s_lgtyp[] TO r_lgtyp[].
  MOVE-CORRESPONDING s_lgpla[] TO r_lgpla[].
  MOVE-CORRESPONDING s_matnr[] TO r_matnr[].


  "Load excel data and display on ALV
  DATA(ls_status) = bizobj->get_data(
                      i_r_lgnum       = r_lgnum[]
                      i_r_lgtyp       = r_lgtyp[]
                      i_r_lgpla       = r_lgpla[]
                      i_r_matnr       = r_matnr[]
                      i_ignore_locked = p_inglck
                    ).
  IF ls_Status-status EQ zwm_cl_defs=>c_stat_success.
    DATA: lv_cnt TYPE i.
    lv_cnt = lines( bizobj->gt_recs[] ).
  ENDIF.

  MESSAGE ls_status-status_text TYPE 'S' DISPLAY LIKE ls_status-status.

ENDFORM.


FORM create_to.
  "Calls model class to Create transfer order in this example
  "That is contoller method, simply passes view parameters to model

  DATA: lv_ans2 TYPE char1.
  CLEAR lv_ans2.
  CALL FUNCTION 'POPUP_TO_CONFIRM'
    EXPORTING
      titlebar              = TEXT-tc1
      text_question         = TEXT-tc2
      text_button_1         = TEXT-tc3
      icon_button_1         = 'ICON_CHECKED'
      text_button_2         = TEXT-tc4
      icon_button_2         = 'ICON_CANCEL'
      display_cancel_button = ' '
      popup_type            = 'ICON_MESSAGE_ERROR'
    IMPORTING
      answer                = lv_ans2
    EXCEPTIONS
      text_not_found        = 1
      OTHERS                = 2.
  IF sy-subrc <> 0. ENDIF.

  IF lv_ans2 = '2'.
    RETURN.
  ENDIF.


  DATA(ls_state) = bizobj->create_transfer_orders(
                       i_bwlvs = p_bwlvs
                   ).

  PERFORM refresh_grid_data.

  MESSAGE ls_state-status_text TYPE 'S' DISPLAY LIKE ls_state-status.
ENDFORM.

FORM background_process.
  "Calls model class to Create transfer order in this example
  "That is contoller method, simply passes view parameters to model

  DATA(ls_state) = bizobj->create_transfer_orders_backg(
                       i_bwlvs = p_bwlvs
                   ).

  MESSAGE ls_state-status_text TYPE 'S' DISPLAY LIKE ls_state-status.
ENDFORM.

****************************** MVC Controller Part - End **********************

 

And model class code is below. Some part of the code is removed.

CLASS zbm_cl_mvc_template DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    "Types
    TYPES: BEGIN OF ty_status,
             status      TYPE zwm_e_status,
             status_text TYPE zwm_e_status_text,
           END OF ty_status.

    "Range Types
    TYPES: ty_R_lgnum TYPE RANGE OF lqua-lgnum,
           ty_R_lgtyp TYPE RANGE OF lqua-lgtyp,
           ty_R_lgpla TYPE RANGE OF lqua-lgpla,
           ty_R_matnr TYPE RANGE OF lqua-matnr.

    "Operation return status
    CONSTANTS c_stat_success TYPE zwm_e_status VALUE 'S' ##NO_TEXT.
    CONSTANTS c_stat_warning TYPE zwm_e_status VALUE 'W' ##NO_TEXT.
    CONSTANTS c_stat_error TYPE zwm_e_status VALUE 'E' ##NO_TEXT.
    CONSTANTS c_stat_info TYPE zwm_e_status VALUE 'I' ##NO_TEXT.


    DATA: gt_recs TYPE zwm_ty_s_lt10_alv.

    METHODS get_data
      IMPORTING
                VALUE(i_r_lgnum)       TYPE ty_r_lgnum
                VALUE(i_r_lgtyp)       TYPE ty_r_lgtyp
                VALUE(i_r_lgpla)       TYPE ty_r_lgpla
                VALUE(i_r_matnr)       TYPE ty_r_matnr
                VALUE(i_ignore_locked) TYPE xfeld OPTIONAL
      RETURNING VALUE(e_status)        TYPE ty_status.

    METHODS create_transfer_orders
      IMPORTING
                i_bwlvs         TYPE ltak-bwlvs OPTIONAL
      RETURNING VALUE(e_status) TYPE ty_status.

    METHODS create_transfer_orders_backg
      IMPORTING
                i_bwlvs         TYPE ltak-bwlvs OPTIONAL
      RETURNING VALUE(e_status) TYPE ty_status.

  PROTECTED SECTION.
  PRIVATE SECTION.

    METHODS set_status
      RETURNING VALUE(e_status) TYPE ty_Status.

    METHODS create_transfer_order
      IMPORTING
                i_bwlvs         TYPE ltak-bwlvs OPTIONAL
      CHANGING  c_rec           TYPE zwm_s_lt10_alv
      RETURNING VALUE(e_status) TYPE ty_Status.


ENDCLASS.



CLASS ZBM_CL_MVC_TEMPLATE IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZBM_CL_MVC_TEMPLATE->CREATE_TRANSFER_ORDER
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_BWLVS                        TYPE        LTAK-BWLVS(optional)
* | [<-->] C_REC                          TYPE        ZWM_S_LT10_ALV
* | [<-()] E_STATUS                       TYPE        TY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD create_transfer_order.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        DATA: lt_return TYPE TABLE OF bapiret2.
        CALL FUNCTION 'L_TO_CREATE_SINGLE'
          EXPORTING
            i_bwlvs     .....

          IF c_rec-status_text IS INITIAL.
            MESSAGE s024(zwm) WITH c_rec-lgnum c_rec-tanum INTO c_rec-status_text.
          ENDIF.

        ENDIF.
      CATCH cx_root INTO exref.
        c_rec-status = zwm_cl_defs=>c_stat_error.
        c_rec-status_text = exref->get_text( ).
    ENDTRY.
  ENDMETHOD.                    "create_transfer_order


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZBM_CL_MVC_TEMPLATE->CREATE_TRANSFER_ORDERS
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_BWLVS                        TYPE        LTAK-BWLVS(optional)
* | [<-()] E_STATUS                       TYPE        TY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD create_transfer_orders.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        DATA lv_errored_recs TYPE i.
        LOOP AT gt_recs ASSIGNING FIELD-SYMBOL(<fs_rec>)
          WHERE is_selected EQ abap_true
            AND is_locked IS INITIAL.

          create_transfer_order(
            EXPORTING
              i_bwlvs  = i_bwlvs
            CHANGING
              c_rec    = <fs_rec>
          ).

          "Check all controls
          IF <fs_rec>-status NE c_stat_success.
            Lv_errored_recs = Lv_errored_recs + 1.
          ENDIF.

        ENDLOOP.
        IF sy-subrc IS INITIAL.
          IF lv_errored_recs EQ 0.
            e_status-status = zwm_cl_defs=>c_stat_success.
            MESSAGE s029(zwm) INTO e_status-status_text.
          ELSE.
            e_status-status = zwm_cl_defs=>c_stat_warning.
            MESSAGE s014(zwm) WITH lv_errored_recs INTO e_status-status_text.
          ENDIF.
        ELSE.
          e_status-status = zwm_cl_defs=>c_stat_warning.
          MESSAGE s026(zwm) INTO e_status-status_text.
        ENDIF.

      CATCH cx_root INTO exref.
        e_status-status = c_stat_error.
        e_status-status_text = exref->get_text( ).
    ENDTRY.

  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZBM_CL_MVC_TEMPLATE->CREATE_TRANSFER_ORDERS_BACKG
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_BWLVS                        TYPE        LTAK-BWLVS(optional)
* | [<-()] E_STATUS                       TYPE        TY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD create_transfer_orders_backg.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        LOOP AT gt_recs ASSIGNING FIELD-SYMBOL(<wa>).
          <wa>-is_selected = abap_true.
        ENDLOOP.

        create_transfer_orders(
            i_bwlvs = i_bwlvs
        ).

      CATCH cx_root INTO exref.
        e_status-status = c_stat_error.
        e_status-status_text = exref->get_text( ).
    ENDTRY.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZBM_CL_MVC_TEMPLATE->GET_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_R_LGNUM                      TYPE        TY_R_LGNUM
* | [--->] I_R_LGTYP                      TYPE        TY_R_LGTYP
* | [--->] I_R_LGPLA                      TYPE        TY_R_LGPLA
* | [--->] I_R_MATNR                      TYPE        TY_R_MATNR
* | [--->] I_IGNORE_LOCKED                TYPE        XFELD(optional)
* | [<-()] E_STATUS                       TYPE        TY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD get_data.
    DATA: exref TYPE REF TO cx_root.
    TRY.
        "Get pending records of LT10
        SELECT *
          INTO CORRESPONDING FIELDS OF TABLE gt_recs
          FROM lqua ....

        IF gt_recs IS NOT INITIAL.
          e_status-status = c_stat_success.
          DATA: lv_cnt TYPE i.
          lv_cnt = lines( gt_recs ).
          MESSAGE s027(zwm) WITH lv_cnt INTO e_status-status_text.
        ELSE.
          e_status-status = c_stat_warning.
          MESSAGE s028(zwm) INTO e_status-status_text.
        ENDIF.

      CATCH cx_root INTO exref.
        e_status-status = c_stat_error.
        e_status-status_text = exref->get_text( ).
    ENDTRY.

  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZBM_CL_MVC_TEMPLATE->SET_STATUS
* +-------------------------------------------------------------------------------------------------+
* | [<-()] E_STATUS                       TYPE        TY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD set_status.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        IF gt_recs IS NOT INITIAL.

          SELECT .......
        ENDIF.

        e_status-status = c_stat_success.

      CATCH cx_root INTO exref.
        e_status-status = c_stat_error.
        e_status-status_text = exref->get_text( ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

 

Sample ITS Dialog Screen program;

Here is a program with three dialog screens. I will share screens 1001’s and 1002’s codes and layouts. At the 1001 screen user enters a transfer order and all the data is processed in model class or calls 1003 screen to pick a transfer order from list.

 

1001%20Screen%20Layout%20and%20Attributes

1001 Screen Layout and Attributes

*&---------------------------------------------------------------------*
*& Report ZSD_P_PRNT_SHP_LBL
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*

INCLUDE zsd_p_prnt_shp_lbl_top.   " Global Data
INCLUDE zsd_p_prnt_shp_lbl_f1o01. "Screen 1001 codes
INCLUDE zsd_p_prnt_shp_lbl_f1o02. "Screen 1002 codes
INCLUDE zsd_p_prnt_shp_lbl_f1o03. "Screen 1003 codes

 

As you can see, there is no global data except object itself. All the screen related fields are stored in obj.

*&---------------------------------------------------------------------*
*& Include ZSD_P_PRNT_SHP_LBL_TOP                   - Report ZSD_P_PRNT_SHP_LBL
*&---------------------------------------------------------------------*
REPORT ZSD_P_PRNT_SHP_LBL.

"obj's public data is used directly on screen fields too
DATA: obj TYPE REF TO zsd_cl_p_prnt_shp_lbl_bo. 

 

1001 Screens’s code.

*----------------------------------------------------------------------*
***INCLUDE ZSD_P_PRNT_SHP_LBL_F1O01.
*----------------------------------------------------------------------*
*&---------------------------------------------------------------------*
*& Module STATUS_1001 OUTPUT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
MODULE status_1001 OUTPUT.
  PERFORM status_1001.
ENDMODULE.
*&---------------------------------------------------------------------*
*&      Module  USER_COMMAND_1001  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE user_command_1001 INPUT.
  PERFORM 1001_command.
ENDMODULE.

FORM status_1001.
  SET PF-STATUS 'ST_NOMENU'.
  SET TITLEBAR 'T_1001'.

  IF obj IS INITIAL.
    obj = NEW zsd_cl_p_prnt_shp_lbl_bo( ).
  ENDIF.

  "Set Cursor Field
  IF obj->scr_1001-tanum IS INITIAL.
    SET CURSOR FIELD 'obj->scr_1001-tanum'.
  ENDIF.

  PERFORM process_call_back_1001.

ENDFORM.

FORM 1001_command.
  "1001 ekranı komutları

  DATA: lv_ucomm TYPE sy-ucomm.
  lv_ucomm = sy-ucomm.
  CLEAR sy-ucomm.

  CASE lv_ucomm.
    WHEN 'NEXT' OR ''.
      PERFORM next_1001.

    WHEN 'CLR'.
      PERFORM clear_1001.

    WHEN 'BACK'.
      LEAVE TO SCREEN 0.

    WHEN 'F4ORD'.
      PERFORM call_1003.

  ENDCASE.

ENDFORM.

FORM next_1001.
  DATA(res) = obj->process_next_1001( ).

  IF res-status EQ obj->c_status_success.
    PERFORM call_1002.

  ELSEIF res-status EQ obj->c_status_confirm.
    PERFORM confirm_1001 USING res-status_text.

  ELSE.
    PERFORM clear_1001.

    CALL FUNCTION 'ZWM_FM_CALL_MSG_SCR_TEXT'
      EXPORTING
        i_msg = res-status_text.
  ENDIF.
ENDFORM.

FORM clear_1001.
  obj->clear_1001( ).
ENDFORM.

FORM confirm_1001 USING stat_text.
  DATA answer TYPE char1.
  CALL FUNCTION 'ZWM_FM_CALL_CONF_SCR_TEXT'
    EXPORTING
      i_msg  = stat_text
    IMPORTING
      answer = answer.

  IF answer NE 'X'.
    PERFORM clear_1001.
  ELSE.
    PERFORM call_1002.
  ENDIF.
ENDFORM.

FORM call_1002.

  DATA(res) = obj->before_calling_1002( ).
  IF res-status NE obj->c_status_success.
    CALL FUNCTION 'ZWM_FM_CALL_MSG_SCR_TEXT'
      EXPORTING
        i_msg = res-status_text.
  ENDIF.

  LEAVE TO SCREEN '1002'.

ENDFORM.


FORM call_1003.

  DATA(res) = obj->before_calling_1003( ).
  IF res-status EQ obj->c_status_success.
    obj->scr_1003-callbackscr = sy-dynnr.
    LEAVE TO SCREEN '1003'.
  ELSE.
    CALL FUNCTION 'ZWM_FM_CALL_MSG_SCR_TEXT'
      EXPORTING
        i_msg = res-status_text.
  ENDIF.



ENDFORM.

FORM process_call_back_1001.

  IF obj->scr_1003-callbackscr EQ sy-dynnr AND obj->scr_1003-sel_rec-tanum IS NOT INITIAL.
    obj->scr_1001-tanum = obj->scr_1003-sel_rec-tanum.
    CLEAR obj->scr_1003-callbackscr.
    PERFORM next_1001.
  ENDIF.

ENDFORM.

 

1003 Screen and Code

1003 screen is called by 1001 screen when “Search Transfer Order” button is clicked.

1003%20Screen%20Layout%20and%20Elements

1003 Screen Layout and Elements

Code of 1003

*&---------------------------------------------------------------------*
*& Include          ZWM_P_MOBILE_SCREEN_1003
*&---------------------------------------------------------------------*
*&---------------------------------------------------------------------*
*& Module STATUS_1003 OUTPUT
*&---------------------------------------------------------------------*
*& That is the detail screen of 1003, please call 1001 first
*&---------------------------------------------------------------------*
MODULE status_1003 OUTPUT.
  PERFORM status_1003.
ENDMODULE.
*&---------------------------------------------------------------------*
*&      Module  USER_COMMAND_1003  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE user_command_1003 INPUT.
  PERFORM 1003_command.
ENDMODULE.

FORM status_1003.
  SET PF-STATUS 'ST_NOMENU'.
  SET TITLEBAR 'T_1003'.

  IF obj IS INITIAL.
    LEAVE TO SCREEN '1001'.
  ENDIF.

ENDFORM.

FORM 1003_command.

  DATA: lv_ucomm TYPE sy-ucomm.
  lv_ucomm = sy-ucomm.
  CLEAR sy-ucomm.

  IF lv_ucomm EQ 'BACK'.
    CLEAR obj->scr_1003-sel_rec.
    LEAVE TO SCREEN obj->scr_1003-callbackscr.

  ELSEIF lv_ucomm EQ 'NEXT'.
    LEAVE TO SCREEN obj->scr_1003-callbackscr.


  ELSEIF lv_ucomm EQ 'FORW'.
    PERFORM list_next_1003.

  ELSEIF lv_ucomm EQ 'PREV'.
    PERFORM list_prev_1003.

  ENDIF.

ENDFORM.

FORM list_next_1003.
  DATA: lv_lines TYPE i.
  DESCRIBE TABLE obj->scr_1003-transfer_orders LINES obj->scr_1003-lines.

  lv_lines = lv_lines - 1.
  IF obj->scr_1003-line LT obj->scr_1003-lines .
    obj->scr_1003-line = obj->scr_1003-line + 1.
  ENDIF.

ENDFORM.

FORM list_prev_1003.

  IF obj->scr_1003-line NE 0.
    obj->scr_1003-line = obj->scr_1003-line - 1.
  ENDIF.

ENDFORM.

FORM back_1003.
  "obj->clear_1003( ).
  LEAVE TO SCREEN '1003'.
ENDFORM.

*&---------------------------------------------------------------------*
*& Module FILL_1003_TAB OUTPUT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
MODULE fill_1003_tab OUTPUT.
  PERFORM fill_tab_line_1003.
ENDMODULE.
*&---------------------------------------------------------------------*
*&      Module  READ_1003_TAB  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE read_1003_tab INPUT.
  PERFORM read_tab_1003.
ENDMODULE.


FORM read_tab_1003.
  "Set line range and load screen table
  obj->scr_1003-lines = sy-loopc.
  obj->scr_1003-idx = sy-stepl + obj->scr_1003-line.

  IF obj->scr_1003-line_data-checked EQ abap_true.
    obj->scr_1003-sel_rec = obj->scr_1003-line_data.
  ENDIF.

ENDFORM.

FORM fill_tab_line_1003.
  obj->scr_1003-idx = sy-stepl + obj->scr_1003-line.
  READ TABLE obj->scr_1003-transfer_orders INTO obj->scr_1003-line_data
    INDEX obj->scr_1003-idx.
ENDFORM.

 

 

And class code, specific to this dialog screens’ program.

CLASS zsd_cl_p_prnt_shp_lbl_bo DEFINITION
  PUBLIC
  INHERITING FROM zsd_cl_p_prnt_shp_lbl_base
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    TYPES:
      BEGIN OF gty_scr_1001,
        tanum TYPE ltak-tanum,
      END OF gty_scr_1001 .

    TYPES:
      BEGIN OF gty_scr_1003,
        callbackscr     TYPE sy-dynnr,
        transfer_orders TYPE zwm_ty_s_to_with_delv,
        sel_rec         TYPE LINE OF zwm_ty_s_to_with_delv,

        "Screen table indexes
        idx             TYPE syst_stepl,
        line            TYPE int4,
        lines           TYPE int4,

        line_data       TYPE LINE OF zwm_ty_s_to_with_delv,

      END OF gty_scr_1003 .

    DATA lbldata TYPE gty_lbldata .
    DATA print_settigs TYPE gty_print_settings .
    DATA scr_1001 TYPE gty_scr_1001 . "Stores all data related to screen 1001
    DATA scr_1003 TYPE gty_scr_1003 . "Stores all data related to screen 1003

    METHODS process_next_1001
      RETURNING
        VALUE(result) TYPE gty_status .

    METHODS clear_1001 .

    METHODS before_calling_1002
      RETURNING
        VALUE(result) TYPE gty_status .

    METHODS print_1002
      RETURNING
        VALUE(result) TYPE gty_status .

    METHODS before_calling_1003
      RETURNING
        VALUE(result) TYPE gty_status .

  PROTECTED SECTION.

  PRIVATE SECTION.

    CONSTANTS: c_doc_type_delv    TYPE vbfa-vbtyp_v VALUE 'J',
               c_delv_stat_picked TYPE likp-kostk VALUE 'C',
               c_to_confirmed     TYPE ltak-kquit VALUE 'X'.

    METHODS fill_label_data_via_to
      IMPORTING
        VALUE(uname)  TYPE sy-uname OPTIONAL
        VALUE(tanum)  TYPE ltak-tanum
      RETURNING
        VALUE(result) TYPE gty_status .

    METHODS fill_print_settings_data
      RETURNING
        VALUE(result) TYPE gty_status .
......

ENDCLASS.



CLASS ZSD_CL_P_PRNT_SHP_LBL_BO IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZSD_CL_P_PRNT_SHP_LBL_BO->BEFORE_CALLING_1002
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RESULT                         TYPE        GTY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD before_calling_1002.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        result = get_cust_info( ).
        IF result-status EQ c_status_success.
          result = fill_print_settings_data( ).
        ENDIF.

      CATCH cx_root INTO exref.
        result-status = c_status_error.
        result-status_text = exref->get_text( ).
    ENDTRY.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZSD_CL_P_PRNT_SHP_LBL_BO->CLEAR_1001
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD clear_1001.
    CLEAR: lbldata ,scr_1001.
  ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZSD_CL_P_PRNT_SHP_LBL_BO->FILL_LABEL_DATA_VIA_TO
* +-------------------------------------------------------------------------------------------------+
* | [--->] UNAME                          TYPE        SY-UNAME(optional)
* | [--->] TANUM                          TYPE        LTAK-TANUM
* | [<-()] RESULT                         TYPE        GTY_STATUS
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD fill_label_data_via_to.
    DATA: exref TYPE REF TO cx_root.
    TRY.

        CLEAR lbldata.
        lbldata-uname = uname.
        IF lbldata-uname IS INITIAL.
          lbldata-uname = sy-uname.
        ENDIF.

        lbldata-to-tanum = tanum.

        result = get_to_and_validate( ).
        IF result-status EQ c_status_success.
          result = get_delv_and_validate( ).
        ENDIF.

      CATCH cx_root INTO exref.
        result-status = c_status_error.
        result-status_text = exref->get_text( ).
    ENDTRY.
  ENDMETHOD.

***************

ENDCLASS.

 

As you can see in that dialog screen example, you can create many layers as you need. As long as you do not directly write your code under view object itself, you increase modularity and code reusability. Testing becomes easier. You can test class methods directly. Provides many benefits.

So these were basic examples of ,how to write an abap report by applying MVC pattern. MVC enables us to split view and business logic code. We can re-use model class for different types of interfaces. Within Fiori projects, frontend and backend development can be split and MVC enables us to share tasks. One can write backend and other can write front end.

Depending on interest, I can add more mvc templates. The logic is same, you can apply mvc to any code.

And if you like the idea of lego like ,reusable and robust coding, you must check solid rules too ,which is very important.  Here is a link for solid. And a link for clean abap.

I hope that gives some of you an idea about MVC layering in Abap. Please feel free to add your own examples in comments.

Thanks for reading.

 

Edit(27.10.2022): A sample on how to apply mvc on Adobe/Smartforms -> Blog Post.

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x