Skip to Content
Technical Articles
Author's profile photo Anton Sikidin

Zmacros – my way to write code as quickly as using macros, without using macros

Macros is a powerful tool that increase boilerplate writing speed. Unfortunately, exploitation of  macros is forbidden in many projects because macros cannot be debugged.

Zmacros is my development to generate a boilerplate in few clicks.

Source: https://github.com/AntonSikidin/zmacros

Clone with abapGit

Transaction – Zmacros

  • F8 – rerun program
  • Refresh – reread template and create variables
  • List – load template from db
  • ListAll – show template with versions
  • Save – save template to db
  • Run – populate template with values
  • Range gen – generate number range

 

FIRST CASE – GENERATION OF SCREENS.

If you want to get something like that:

*----------------------------
*-- top screen 0100
      , ok_code      TYPE sy-ucomm
      , gr_alv_0100            TYPE REF TO cl_gui_alv_grid
      , gr_cont_0100           TYPE REF TO cl_gui_custom_container
      , gt_fieldcat_0100       TYPE lvc_t_fcat
      , gt_sort_0100           TYPE lvc_t_sort
      , gs_layout_0100         TYPE lvc_s_layo
      , gs_vari_0100           TYPE disvariant



*----------------------------
*screen logic

PROCESS BEFORE OUTPUT.
 MODULE STATUS_0100.
*
PROCESS AFTER INPUT.
 MODULE USER_COMMAND_0100.


*----------------------------
*modules
MODULE status_0100 OUTPUT.
  SET PF-STATUS 'STATUS0100'.
  SET TITLEBAR 'TITLEBAR0100'.


  IF gr_alv_0100 IS NOT BOUND.
    CREATE OBJECT gr_cont_0100
      EXPORTING
        container_name = 'CONT_0100'.

    CREATE OBJECT gr_alv_0100
      EXPORTING
        i_parent = gr_cont_0100.

    gs_layout_0100-no_rowmove = 'X'.
    gs_layout_0100-cwidth_opt = 'X'.

    gs_layout_0100-zebra = 'X'.
    gs_layout_0100-no_rowmark = ''.
    gs_layout_0100-sel_mode = 'A'.

    gs_vari_0100-report = sy-repid.
    gs_vari_0100-handle = '0100'.


    CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
      EXPORTING
        I_STRUCTURE_NAME = 'but100'
      CHANGING
        ct_fieldcat      = gt_fieldcat_0100.


    CALL METHOD gr_alv_0100->set_table_for_first_display
      EXPORTING
        i_save          = 'A'
        is_layout       = gs_layout_0100
        is_variant      = gs_vari_0100
      CHANGING
        it_sort         = gt_sort_0100
        it_outtab       = gt_0100
        it_fieldcatalog = gt_fieldcat_0100.

  ELSE.

    CALL METHOD gr_alv_0100->get_frontend_layout
      IMPORTING
        es_layout = gs_layout_0100.

    gs_layout_0100-cwidth_opt = 'X'.

    CALL METHOD gr_alv_0100->set_frontend_layout
      EXPORTING
        is_layout = gs_layout_0100.


    gr_alv_0100->refresh_table_display( ).
  ENDIF.

ENDMODULE.




MODULE user_command_0100 INPUT.

  CASE  ok_code .
    WHEN 'BACK'.
      SET SCREEN 0.
*   when 'XXXXXXX'.

  ENDCASE.
  clear ok_code.

ENDMODULE.

 

Use this template:

*----------------------------
*-- top screen \screen_number/
      , ok_code      TYPE sy-ucomm
      , gr_alv_\screen_number/            TYPE REF TO cl_gui_alv_grid
      , gr_cont_\screen_number/           TYPE REF TO cl_gui_custom_container
      , gt_fieldcat_\screen_number/       TYPE lvc_t_fcat
      , gt_sort_\screen_number/           TYPE lvc_t_sort
      , gs_layout_\screen_number/         TYPE lvc_s_layo
      , gs_vari_\screen_number/           TYPE disvariant



*----------------------------
*screen logic

PROCESS BEFORE OUTPUT.
 MODULE STATUS_\screen_number/.
*
PROCESS AFTER INPUT.
 MODULE USER_COMMAND_\screen_number/.


*----------------------------
*modules
MODULE status_\screen_number/ OUTPUT.
  SET PF-STATUS 'STATUS\screen_number/'.
  SET TITLEBAR 'TITLEBAR\screen_number/'.


  IF gr_alv_\screen_number/ IS NOT BOUND.
    CREATE OBJECT gr_cont_\screen_number/
      EXPORTING
        container_name = 'CONT_\screen_number/'.

    CREATE OBJECT gr_alv_\screen_number/
      EXPORTING
        i_parent = gr_cont_\screen_number/.

    gs_layout_\screen_number/-no_rowmove = 'X'.
    gs_layout_\screen_number/-cwidth_opt = 'X'.

    gs_layout_\screen_number/-zebra = 'X'.
    gs_layout_\screen_number/-no_rowmark = ''.
    gs_layout_\screen_number/-sel_mode = 'A'.

    gs_vari_\screen_number/-report = sy-repid.
    gs_vari_\screen_number/-handle = '\screen_number/'.


    CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
      EXPORTING
        I_STRUCTURE_NAME = '\struct_name/'
      CHANGING
        ct_fieldcat      = gt_fieldcat_\screen_number/.


    CALL METHOD gr_alv_\screen_number/->set_table_for_first_display
      EXPORTING
        i_save          = 'A'
        is_layout       = gs_layout_\screen_number/
        is_variant      = gs_vari_\screen_number/
      CHANGING
        it_sort         = gt_sort_\screen_number/
        it_outtab       = gt_\screen_number/
        it_fieldcatalog = gt_fieldcat_\screen_number/.

  ELSE.

    CALL METHOD gr_alv_\screen_number/->get_frontend_layout
      IMPORTING
        es_layout = gs_layout_\screen_number/.

    gs_layout_\screen_number/-cwidth_opt = 'X'.

    CALL METHOD gr_alv_\screen_number/->set_frontend_layout
      EXPORTING
        is_layout = gs_layout_\screen_number/.


    gr_alv_\screen_number/->refresh_table_display( ).
  ENDIF.

ENDMODULE.

MODULE user_command_\screen_number/ INPUT.

  CASE  ok_code .
    WHEN 'BACK'.
      SET SCREEN 0.
*   when 'XXXXXXX'.

  ENDCASE.
  clear ok_code.

ENDMODULE.

 

Copy, past and press “Refresh”.

Fill \screen_number/ and \struct_name/ and press “Run”.

Result:

*----------------------------
*-- top screen 0100
      , ok_code      TYPE sy-ucomm
      , gr_alv_0100            TYPE REF TO cl_gui_alv_grid
      , gr_cont_0100           TYPE REF TO cl_gui_custom_container
      , gt_fieldcat_0100       TYPE lvc_t_fcat
      , gt_sort_0100           TYPE lvc_t_sort
      , gs_layout_0100         TYPE lvc_s_layo
      , gs_vari_0100           TYPE disvariant



*----------------------------
*screen logic

PROCESS BEFORE OUTPUT.
 MODULE STATUS_0100.
*
PROCESS AFTER INPUT.
 MODULE USER_COMMAND_0100.


*----------------------------
*modules
MODULE status_0100 OUTPUT.
  SET PF-STATUS 'STATUS0100'.
  SET TITLEBAR 'TITLEBAR0100'.


  IF gr_alv_0100 IS NOT BOUND.
    CREATE OBJECT gr_cont_0100
      EXPORTING
        container_name = 'CONT_0100'.

    CREATE OBJECT gr_alv_0100
      EXPORTING
        i_parent = gr_cont_0100.

    gs_layout_0100-no_rowmove = 'X'.
    gs_layout_0100-cwidth_opt = 'X'.

    gs_layout_0100-zebra = 'X'.
    gs_layout_0100-no_rowmark = ''.
    gs_layout_0100-sel_mode = 'A'.

    gs_vari_0100-report = sy-repid.
    gs_vari_0100-handle = '0100'.


    CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
      EXPORTING
        I_STRUCTURE_NAME = 'pa0298'
      CHANGING
        ct_fieldcat      = gt_fieldcat_0100.


    CALL METHOD gr_alv_0100->set_table_for_first_display
      EXPORTING
        i_save          = 'A'
        is_layout       = gs_layout_0100
        is_variant      = gs_vari_0100
      CHANGING
        it_sort         = gt_sort_0100
        it_outtab       = gt_0100
        it_fieldcatalog = gt_fieldcat_0100.

  ELSE.

    CALL METHOD gr_alv_0100->get_frontend_layout
      IMPORTING
        es_layout = gs_layout_0100.

    gs_layout_0100-cwidth_opt = 'X'.

    CALL METHOD gr_alv_0100->set_frontend_layout
      EXPORTING
        is_layout = gs_layout_0100.


    gr_alv_0100->refresh_table_display( ).
  ENDIF.

ENDMODULE.




MODULE user_command_0100 INPUT.

  CASE  ok_code .
    WHEN 'BACK'.
      SET SCREEN 0.
*   when 'XXXXXXX'.

  ENDCASE.
  clear ok_code.

ENDMODULE.

Replace 0100 with 0200 and you get boilerplate for screen 0200.

As you can see ‘\’ and ‘/’ start and stop symbol define variable in template.

You can save template for future use by press “Save”.

A dialog will appear:

This dialog shows templates to update.

If it’s a new template – just press “Escape” and enter new name.

  • List” – load a last version of template
  • List all” – show all versions of all templates

 

SECOND CASE – TYPE DEFINITION.

If you want to get something like that:

    TYPES:
      BEGIN OF  t_data_row,
        LINE  TYPE CIFCOUNT,
        T_TYPE  TYPE CLASSTTYPE,
        TABNAME  TYPE TABNAME,
        JJOIN  TYPE TABNAME,
        LEFT1  TYPE TABNAME,
        RIGHT1  TYPE TABNAME,
        LV_KEY_SELECT  TYPE TABNAME,
        LEFT_61  TYPE ECP_FIELDNAME,
        RIGHT_61  TYPE ECP_FIELDNAME,
        LV_KEY_JOIN  TYPE ECP_FIELDNAME,
        SORTORDER  TYPE ANZST,
        CRITERIAFOR  TYPE ADDIFCTCOD,
        DISABLE  TYPE CLASSTTYPE,
        OR1  TYPE AKB_NOTE,
        OR2  TYPE AKB_NOTE,
        OR3  TYPE AKB_NOTE,
        OR4  TYPE AKB_NOTE,
      END OF t_data_row .

 

Use this template:

    TYPES:
      BEGIN OF  t_\type/,
        $1-1#  TYPE $1-2#,
      END OF t_\type/ .

Copy, past and press “Refresh”.

You can see table “Main” with variable \type/ and table1 with two fields.

Go to transaction se11 and copy field name and field type.

Next, copy desired fields.

Past in table1.

Press “Run” and you will get this:

As you can see there is a new kind of variable ‘$1-2#’.

  • $ – start symbol
  • # – end symbol
  • 1 – table 1
  • 2 – column 2

You can define any number of tables with any number of columns.

THIRD CASE – MOVING STRUCTURE FROM OLD TO NEW.

If you want to get something like that:

ls_new-TABNAME = ls_old-TAB .
ls_new-JJOIN = ls_old-JJOIN_old .
ls_new-LEFT1 = ls_old-LEF .
ls_new-RIGHT1 = ls_old-RIG .
ls_new-LV_KEY_SELECT = ls_old-LV_KEY_old .

Template:

ls_new-$1-1# = ls_old-$1-2# .

Copy, past and press “Refresh”.

Result:

FOURTH CASE USING ANY TYPE OF “CASE-WHEN”.

If you want to get something like that:

LOOP AT mt_alv_fieldcat INTO ls_alv_fieldcat WHERE tech EQ space
 AND no_out EQ space.
    CASE ls_alv_fieldcat-fieldname.
      WHEN 'A38'.
        lv_string = ls_totals-a38.
         set_shift.
      WHEN 'A39'.
        lv_string = ls_totals-a39.
         set_shift.
      WHEN 'A40'.
        lv_string = ls_totals-a40.
         set_shift.
      WHEN 'A41'.
        lv_string = ls_totals-a41.
         set_shift.
      WHEN 'A42'.
        lv_string = ls_totals-a42.
         set_shift.
      WHEN 'A43'.
        lv_string = ls_totals-a43.
         set_shift.
      WHEN 'A44'.
        lv_string = ls_totals-a44.
         set_shift.
      WHEN 'A45'.
        lv_string = ls_totals-a45.
         set_shift.
    ENDCASE.
    ADD ls_alv_fieldcat-outputlen TO lv_len.
    ADD 1 TO lv_len.
  ENDLOOP.

Template:

  LOOP AT mt_alv_fieldcat INTO ls_alv_fieldcat WHERE tech EQ space
 AND no_out EQ space.
    CASE ls_alv_fieldcat-fieldname.
>1
      WHEN 'A$1-1#'.
        lv_string = ls_totals-a$1-1#.
         set_shift.
<1
    ENDCASE.
    ADD ls_alv_fieldcat-outputlen TO lv_len.
    ADD 1 TO lv_len.
  ENDLOOP.

Copy, past and press “Refresh”.

As you can see there is new punctuation – ‘>1’ and ‘<1’.

  • >1 – start of template that will be copied n-times, where n is number of lines in table1
  • <1 – end of template that will be copied n-times, where n is number of lines in table1

As in HTML all start and stop tag must be nested.

This is good:

>1
 >2
  $1-1# $2-1#
 <2
<1

This is bad:

>1
  >2
    $1-1# $2-1#
<1
 <2

and you will get error:

Very often you need a sequence of increasing or decreasing numbers is required.
In this case, you haveRange gen”.

Generate range from 2 to 15 and copy to clipboard.

Generate range from 15 to 2 and copy to clipboard.

Generate range from 02 to 15 with leading zeros and copy to clipboard.

Generate range from 015 to 002 with leading zeros and copy to clipboard.

All you need is past values in table.

Press “Run” and you will get this:

Go to more complex example.

With template:

$1-1#  $2-1# $3-1#

We can get something like this:

FIFTH CASE – THIS TOOL IS BEST FOR GENERATING ALL POSSIBLE VARIANTS FOR UNIT TESTING.

With this template you can test logic function.

>1
  >2
     >3
       >4

 perform logic_$1-1# using '$2-1#' '$3-1#'  '$4-1#' changing lv_result.
 write :/  ' $1-1# on  v1=$2-1# v2=$3-1#  v3=$4-1# ' , lv_result .
     <4
  <3
 <2


<1 

Result:

 perform logic_or using 'X' 'X'  'X' changing lv_result.
 write :/  ' or on  v1=X v2=X  v3=X ' , lv_result .

 perform logic_or using 'X' 'X'  '' changing lv_result.
 write :/  ' or on  v1=X v2=X  v3= ' , lv_result .

 perform logic_or using 'X' ''  'X' changing lv_result.
 write :/  ' or on  v1=X v2=  v3=X ' , lv_result .

 perform logic_or using 'X' ''  '' changing lv_result.
 write :/  ' or on  v1=X v2=  v3= ' , lv_result .

 perform logic_or using '' 'X'  'X' changing lv_result.
 write :/  ' or on  v1= v2=X  v3=X ' , lv_result .

 perform logic_or using '' 'X'  '' changing lv_result.
 write :/  ' or on  v1= v2=X  v3= ' , lv_result .

 perform logic_or using '' ''  'X' changing lv_result.
 write :/  ' or on  v1= v2=  v3=X ' , lv_result .

 perform logic_or using '' ''  '' changing lv_result.
 write :/  ' or on  v1= v2=  v3= ' , lv_result .



 perform logic_and using 'X' 'X'  'X' changing lv_result.
 write :/  ' and on  v1=X v2=X  v3=X ' , lv_result .

 perform logic_and using 'X' 'X'  '' changing lv_result.
 write :/  ' and on  v1=X v2=X  v3= ' , lv_result .

 perform logic_and using 'X' ''  'X' changing lv_result.
 write :/  ' and on  v1=X v2=  v3=X ' , lv_result .

 perform logic_and using 'X' ''  '' changing lv_result.
 write :/  ' and on  v1=X v2=  v3= ' , lv_result .

 perform logic_and using '' 'X'  'X' changing lv_result.
 write :/  ' and on  v1= v2=X  v3=X ' , lv_result .

 perform logic_and using '' 'X'  '' changing lv_result.
 write :/  ' and on  v1= v2=X  v3= ' , lv_result .

 perform logic_and using '' ''  'X' changing lv_result.
 write :/  ' and on  v1= v2=  v3=X ' , lv_result .

 perform logic_and using '' ''  '' changing lv_result.
 write :/  ' and on  v1= v2=  v3= ' , lv_result .



 perform logic_xor using 'X' 'X'  'X' changing lv_result.
 write :/  ' xor on  v1=X v2=X  v3=X ' , lv_result .

 perform logic_xor using 'X' 'X'  '' changing lv_result.
 write :/  ' xor on  v1=X v2=X  v3= ' , lv_result .

 perform logic_xor using 'X' ''  'X' changing lv_result.
 write :/  ' xor on  v1=X v2=  v3=X ' , lv_result .

 perform logic_xor using 'X' ''  '' changing lv_result.
 write :/  ' xor on  v1=X v2=  v3= ' , lv_result .

 perform logic_xor using '' 'X'  'X' changing lv_result.
 write :/  ' xor on  v1= v2=X  v3=X ' , lv_result .

 perform logic_xor using '' 'X'  '' changing lv_result.
 write :/  ' xor on  v1= v2=X  v3= ' , lv_result .

 perform logic_xor using '' ''  'X' changing lv_result.
 write :/  ' xor on  v1= v2=  v3=X ' , lv_result .

 perform logic_xor using '' ''  '' changing lv_result.
 write :/  ' xor on  v1= v2=  v3= ' , lv_result .



 perform logic_magic using 'X' 'X'  'X' changing lv_result.
 write :/  ' magic on  v1=X v2=X  v3=X ' , lv_result .

 perform logic_magic using 'X' 'X'  '' changing lv_result.
 write :/  ' magic on  v1=X v2=X  v3= ' , lv_result .

 perform logic_magic using 'X' ''  'X' changing lv_result.
 write :/  ' magic on  v1=X v2=  v3=X ' , lv_result .

 perform logic_magic using 'X' ''  '' changing lv_result.
 write :/  ' magic on  v1=X v2=  v3= ' , lv_result .

 perform logic_magic using '' 'X'  'X' changing lv_result.
 write :/  ' magic on  v1= v2=X  v3=X ' , lv_result .

 perform logic_magic using '' 'X'  '' changing lv_result.
 write :/  ' magic on  v1= v2=X  v3= ' , lv_result .

 perform logic_magic using '' ''  'X' changing lv_result.
 write :/  ' magic on  v1= v2=  v3=X ' , lv_result .

 perform logic_magic using '' ''  '' changing lv_result.
 write :/  ' magic on  v1= v2=  v3= ' , lv_result .

Conclusion:

All my code is about increase your speed writing boilerplate and save your time. Hope it will be helpful.

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Shai Sinai
      Shai Sinai

      Interesting.

      My 2 cents:

      Since at the end of the day you would you like to include the generated code in your code, it would be nicer to include it in one of the implicit editor tools (instead of copy paste).

      1. For simple/static code, you may utilize the code templates, both in SAP GUI editor and in ADT.
      2. For dynamic code (wizard style), you may implement a custom pattern for the SAP GUI editor.
        (I’m not sure if a similar solution is available in ADT).
      Author's profile photo Anton Sikidin
      Anton Sikidin
      Blog Post Author

      Hello Shai Sinai,

      I know about this 2 options.

      Did you try to implement my example in the way you propose?

      We talking about large part of code >10 lines of code, how you fill template in way you propose with 3 tables of variable?

       

      Author's profile photo Shai Sinai
      Shai Sinai

      This can be achieved with the second option (custom pattern with dynamic pattern / FM implementation).

      This is basically a freestyle ABAP code in which you can write whatever you wish (e.g. a popup ALV).

      Author's profile photo Anton Sikidin
      Anton Sikidin
      Blog Post Author

      Hello Shai Sinai,

      I assume that you have more experience on the subject. And your solution is more optimal.

      Teach us!

      Please share your knowledge and show your solution for my examples. If we reduce the development time in this way, I'll be very grateful.

      Author's profile photo Shai Sinai
      Shai Sinai

      Hi,

      To be honest, I don't have "my" solution.

      This is a simplified implementation of a pattern FM:

      FUNCTION ztest_editor_exit.
      *"----------------------------------------------------------------------
      *"*"Local Interface:
      *"  TABLES
      *"      BUFFER TYPE  RSWSOURCET
      *"  EXCEPTIONS
      *"      CANCELLED
      *"----------------------------------------------------------------------
      
        DATA lt_text TYPE STANDARD TABLE OF tdline.
      
        CALL FUNCTION 'ZTEST_TEXT_EDITOR'
          IMPORTING
            et_new_text = lt_text.
      
        APPEND LINES OF lt_text TO buffer.
      
      ENDFUNCTION.

      What can be done, for example, without many modifications, is calling your report and setting the results back into buffer (or converting your report into a modal dialog).

      Author's profile photo Anton Sikidin
      Anton Sikidin
      Blog Post Author

      ok, this is better.

      But write custom enhancement for any case for 1 use is more complex then use my solution.

      Integrete my solution in ide also not good idea because in case of typo you need wipe 119 line of code and repeat.

      Best practise fast modify – fast get result. In case code is what you want they can be copypasted to main program.

      Did we come to conclusion that may program is the best solution to generate boilerplate?

      Author's profile photo Shai Sinai
      Shai Sinai

      I guess it depends on the complexity,

      but I agree that you suggestion does make sense in some use cases.

      Author's profile photo Fuqiang Li
      Fuqiang Li

      hi Anton Sikidin,

      good job~,i want use the tools in my sap sys, but our abap version only stay in 701, so i can't import your project code with abapGit. can you let me konw which way is do that use your code in my sys.

      thx a lot of u!

      Author's profile photo Anton Sikidin
      Anton Sikidin
      Blog Post Author

      Yes, i will update this repo in week, i am at vakation.

      Author's profile photo Anton Sikidin
      Anton Sikidin
      Blog Post Author

      what error do you see when trying import?
      i haven't access to system with sap 701 version, i cannot predict what can go wrong.