Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member182046
Contributor
23,906

ABAP report programming has its advantages. It’s quick and simple, but it comes with some limitations. If you strive for sophistication and flexible reuse, you should consider some of the advanced programming techniques I discuss in this weblog.

Suppose you have a simple report that consist of a selection screen and an ALV grid (hopefully programmed with the new SALV service classes). It will probably look something like this:

REPORT zalv.

TABLES:
  mytable.

DATA:
  gt_mydata TYPE STANDARD TABLE OF mytable.

SELECT-OPTIONS:
  s_this   FOR mytable-this,
  s_that   FOR mytable-that.

START-OF-SELECTION.
  PERFORM select_data.
  PERFORM display_alv.

FORM select_data.
  …
ENDFORM.

FORM display_alv.
  …
ENDFORM.

Depending on how cleanly you like to program, you might eliminate the obsolete TABLES statement, pass the select-options and table as parameters (to avoid accessing global data in form routines), and use a local application class with (mostly private) methods instead of form routines.

Once your report is fully functional and neatly polished, you still have serious limitations left:

  • Your report can be started as a standard report or you can create a transaction code to embed it into a user role or an area menu.
  • However, you cannot easily integrate it into a seamless dialog sequence.

Problems with SUBMIT … AND RETURN and CALL TRANSACTION

The only way to programmatically call your report or transaction, say, from a different screen or function module, would would be the SUBMIT … AND RETURN or CALL TRANSACTION statements. 

But these two come with serious drawbacks: Both SUBMIT … AND RETURN and CALL TRANSACTION

  • allow you to skip the first screen and pass parameters but don’t check (statically or at runtime) if the caller program is using the correct parameter names,
  • create a new internal mode in which the program is executed and
  • may cause an implicit DB_COMMIT.

Implications of creating a new internal mode

Causing the creation of a new internal mode means that the states of every function group, class, and object instance you have touched and changed during the course of your main program is reset. If you have, say, initialized the Business Application Log function group and opened a log to which you want to add entries, you’re out of luck – the log lives in the internal mode of the caller but not the called program.
Same with any ABAP classes that you may have touched: All of their class constructors will have to be executed again in the new internal mode, different instances will be created for singleton, and the state of the internal mode will generally behave like a remotely called system (CALL FUNCTION … DESTINATION), except that you won’t be able to return to it with repeated function calls and that no call-back (as in CALL FUNCTION … DESTINATION ‘BACK’) is possible.

Implications of a DB_COMMIT

Calling a program with SUBMIT or CALL TRANSACTION may cause a database commit that affects your transaction if any of the triggers listed here   in the SAP Library is processed (e.g. a selection screen, messages  of certain types, RFC calls, list output). This means that all pending database transactions will be closed, irrevocably persisting database changes made with the OpenSQL commands INSERT, UPDATE, or MODIFY. Calling ROLLBACK WORK afterwards will have no effect.

(By the way, a database commit is not the same as the end of an LUW as triggered with the COMMIT WORK statement: COMMIT WORK will not only cause a database commit, but also execute any routine registered with PERFORM … ON COMMIT and any function module invoked with CALL FUNCTION … IN UPDATE TASK. It does a lot of highly interesting and well-documented stuff and is very much worth an in-depth exploration.

Thanks to Sandra Rossi for  pointing out that the DB_COMMIT occurs not always but under certain circumstances.)

So what are those sophisticated alternatives?

When I create ABAP Dynpros, I usually don’t place them in reports but in function groups. This allows me to create function modules that can

  • receive parameters from the caller and store them in the state (memory) of the function group as a way of passing them to the screen,
  • invoke the actual dynpro with the CALL SCREEN command,
  • and return result parameters (retrieved from the global memory of the function group, where the dynpro has deposited them) to the caller.

Being able to conveniently pass parameters into a screen and receive result parameters is very useful when you want to reuse your screen in many meaningful scenarios.

You can:

  • tell the screen which object instance (e.g. a particular business document, business partner, etc.) to load
  • parametrize the screen state, e.g. call the dynpro in display mode, edit mode, or create mode
  • pass values for the screen’s input fields
  • parametrize the appearance of the screen, e.g. hide certain screen areas such as a locator bar,
  • set a particular navigational state, e.g. activate the third tab and place the cursor into a certain field.

Needless to say, being able to call a screen as a function module is also helpful because it allows for dynamic calls across programs, packages, and software components, and is a vital programming technique for creating frameworks.

Using function modules and classes

By the way, I would like to be able to embed my dynpros into classes, but ABAP doesn’t support that. I’m forced to place my dynpro in a function group and create a function module in which to put the CALL SCREEN command (it has to be in a modularization unit of the same main program), but I try to put as much of the application code as possible into classes. If I see no potential for reuse, these may be local classes in the same function group. If I do see some reuse potential for the future, e.g. creating subclasses with specialized or enhanced functionality, I create global classes which have better visibility and can be reused outside the function group.

This is especially useful because it allows me to expose the class, and not the function module, as the interface for usage by anyone who wants to use my screen. This permits better type safety than function calls while allowing for sophisticated features like polymorphism at screen level, which is a basic feature in modern environments like Web Dynpro but typically not available in ABAP Dynpro.

Selection screens are a special case

Moving normal dynpros from a report into a function group is one thing – but the selection screen of a report is a different thing. Why would we want to create a selection screen (as opposed to a normal dynpro)?

The answer:

  • SELECT-OPTIONS are very useful but hard to create with the dynpro painter
  • It’s very easy
  • Ability to use report variants

However, there are a number of differences:

  • While dynpros are designed and programmed with the Graphical Screen Painter, a WYSIWYG editor for user interfaces, selection screens are programmed, using statements such as PARAMETERS, SELECT-OPTIONS, and so on.
  • In report programming, usually no dynpro number is explicitly declared, so the default dynpro 1000 is generated.
  • Unlike in normal dynpro programming, you don’t code PBO and PAI modules, but events such as AT SELECTION-SCREEN OUTPUT, AT SELECTION-SCREEN, AT SELECTION-SCREEN ON EXIT-COMMAND. (When a report is generated, the system creates PBO and PAI modules out of the coding for these events that should not be edited.)
  • Selection screens should not be called with CALL SCREEN but with the special command CALL SELECTION-SCREEN. This is necessary to ensure that the GUI status (PF-STATUS) with its menus and function codes, the GUI title, report variant handling, etc. will work correctly.

So in order to create a selection screen in your function module, you have to do the following. In your global data include or a new include in the direct vincinity, define the selection screen as follows:

TABLES:
  mytable.

SELECTION-SCREEN BEGIN OF SCREEN 0100 WITH TITLE TEXT-100.
SELECT-OPTIONS:
  s_this   FOR mytable-this,
  s_that   FOR mytable-that.
SELECTION-SCREEN END OF SCREEN 0100.

Call the selection screen from somewhere in the same function group. I usually create a method in the local application class inside that function group:

METHOD start_dialog.
  CALL SELECTION-SCREEN 0100.
ENDMETHOD.

When working with a global class, you cannot use CALL SELECTION-SCREEN or CALL SCREEN in its methods. You have to implement it somewhere inside your function group and have the global class delegate the call there, most easily by means of a function module.

Now you need to handle user commands. Create a method in your application class to handle all function codes:

CLASS lcl_application DEFINITION.
  PUBLIC-SECTION.
    CLASS-METHODS:
      handle_function_code IMPORTING VALUE(iv_fcode) TYPE sy-ucomm.
  …
ENDCLASS.

CLASS lcl_application IMPLEMENTATION.
  METHOD handle_function_code.
    CASE iv_fcode.
      WHEN 'CRET'. " execute
        select_data( ).
        display_alv( ).
      WHEN 'CBAC' or 'CEND' or 'CCAN'.
        LEAVE PROGRAM.
    ENDCASE.
  ENDMETHOD.
  …
ENDCLASS.

(Whatever code was previously located in the START-OF-SELECTION processing block should be moved to the branch for the CRET function code.)

In order for the handle_function_code( ) method to be called, we need to implement the corresponding selection screen events right below the definition of the selection screen:

AT SELECTION-SCREEN.
  lcl_application=>handle_function_code( sy-ucomm ).

AT SELECTION-SCREEN ON EXIT-COMMAND.
  lcl_application=>handle_function_code( sy-ucomm ).

Now only one problem remains: After the execution of our former START-OF-SELECTION processing block, we will not return to the selection screen but to the position immediately after the CALL SELECTION-SCREEN command, which typically brings us right back to the caller.

To modify this behaviour so that our application behaves more like a standard report, you can change the start_dialog( ) method as follows:

METHOD start_dialog.
  DO.
    CALL SELECTION-SCREEN 0100.
  ENDDO.
ENDMETHOD.

The only way out of this look is through the LEAVE PROGRAM command in our handle_function_code( ) method. But of course you can implement other ways and more explicit checks and flow controls to navigate back or elsewhere.

A glimpse at the BUS_SCREEN framework

If want to learn more about object-oriented ABAP dynpro programming, you might want to explore the BUS_SCREEN framework in package BUS_TOOLS, package interface BUS_SCREEN. It provides abstract base classes for main screens, subscreens, tabstrips, and tabs – and if you feel very explorative, you can do what I did and find out how to use it by analyzing the BUS_LOCATOR framework (which is the foundation of the Business Partner search bar in transaction BP).

While I don’t want to turn this blog into a shameless plug, it feels appropriate to mention that you can also read up on it in the SAP Press book on ABAP programming I wrote with my esteemed colleague and fellow SAP Mentor Tobias Trapp, "ABAP Objects: Application Development from Scratch" (German edition: "Anwendungsentwicklung mit ABAP Objects").

Summary

By taking normal dynpros and selection-screens out of standard reports and encapsulating them with function groups and global classes, we gain many advantages:

  • seamless integration into dialog flows
  • ability to share the same internal mode and database transaction
  • ability to pass parameters back and forth
  • sophisticated modularization
  • flexible reuse
  • features of object-orientation: encapsulation, inheritance, and polymorphism
  • better interfaces and type-safety

It’s a bit more design-intensive and requires more lines of code but especially the increased reusability is worth the effort in many cases.

 

P.S.: If you're interested in ABAP Dynpro programming, you might want to read my recent blog post Kiss of Life for ABAP Dynpro – It’s going to stay, so let’s improve the integration.

33 Comments