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.
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.
*&---------------------------------------------------------------------*
*& 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.
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.