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

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
kesavadas_thekkillath
Active Contributor
0 Kudos
Thanks ... Let me start it from today 🙂
ThomasZloch
Active Contributor
0 Kudos
Thanks for sharing this, I will have to try this out on the next occasion and compare myself to the "old style" that I am still forced to use too often.
Your plug is no shame at all, you two are among those few who really bring ABAP development forward.
Cheers
Thomas
Former Member
0 Kudos
Seems like embedding a report would do a lot of this stuff in a far more repeatable way.

But then I don't even know what an ABAP report is, so maybe I'm overstepping my bounds... 🙂
0 Kudos
A really interesting blog, and I think I agree with all of this - it really would make Dynpro programming more reusable.
I thought it might be interesting to mention the slightly different approach which I take to this.
Firstly, I don't tend to develop any classic dynpro screens any more, any new development is done in WDA. Where I do use the classic dynpro is for reports - and the only reason I use it is because of the logical databases that have pre-existing functionality for report selection. Were there no benefit in using an existing logical database selection screen, I'd build the whole thing in WDA. One of the main things I try to aim for in my development is a separation between model, view and controller.
Logic which I want to use in a report, must be just as easily able to be accessed by WDA, an RFC, an ICF REST "service", a POWL feeder class, BW extractor, etc. The "report" component is just about utilising an existing fron tend for one View implementation. Trying to use a dialog view to extract any kind information from the system (submit or call transaction) should never be necessary if your business logic (model) is sufficiently separated from your view into that model. I'm pretty sure that you'd code with the same thoughts in mind - but as it wasn't explicitly mentioned I thought it might be worth saying, as I think classic reports really do add value here, if you just use them for the existing functionality that they provide.
So for me, using the existing report framework, is just about leveraging a different user "view" to the same logic.
I think if you include your statement "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." and assume I DO like to code cleanly, you'll have a pretty good idea of how most of the reports I code these days work. (and if you can figure out a way to access some of the HR logical databases without _any_ TABLES statements I'd be very happy!).
Thanks for a very interesting blog, I'll certainly emulate this code if I'm ever in a situation where I _have_ to do some classic dynpro coding again. 
UweFetzer_se38
Active Contributor
0 Kudos
Hi Chris, Hi Thorsten,
where's the "Like"-Button? 😉
Uwe (@se38)
former_member182046
Contributor
0 Kudos
Hi Chris,
I agree that Web Dynpro ABAP is the default UI technology and ABAP Dynpro is asked for in rather special cases - it depends on the environment and integration. Your other comment is also correct, any functionality should be developed in the most channel-independent way possible and not tied to any particular UI or other I/O channel. That's why my reports are very short: All the relevant functionality is in channel-independent global classes that expose an easy-to-use API. The web services, BAPIs, reports, ICF handlers, BOR and BOL objects, or Web Dynpro applications I build on top of them just contain those API calls, which ideally makes them read almost like high-level pseudo code.
Cheers,
Thorsten
former_member182046
Contributor
0 Kudos
Hi Jamie,
I'd love to learn more about Business Objects and how it can complement the ABAP-based OLTP solutions we build. It seems that despite the work of BOBJ Mentors like Ingo Hilgefort and you, it's hard for BOBJ to penetrate the stubborn ABAP world. 😉
To respond to your question, a "report" is the ABAP word for an executable program. Many of these are used in batch processing and have nothing to do with actual reporting, business intelligence, analytics, or list processing - but lists and reports were the most frequent use for ABAP executable programs when the language started many years ago, and that's why they were then and still are called ABAP reports. 🙂
Cheers,
Thorsten
former_member182046
Contributor
0 Kudos
Thank you, Thomas, I appreciate your comment and the good feedback on the book. Will forward it to Tobias. 🙂
Cheers,
Thorsten
former_member182046
Contributor
0 Kudos
Uwe, Thank you, I appreciate it. 🙂
Sandra_Rossi
Active Contributor
Thx Thorsten, I love blogs which explain how to make code reusable 🙂
Just one remark : SUBMIT and CALL TRANSACTION do not commit database at all, they just start a new LUW. I must admit that I was confused with that one year ago, but the documentation says it clearly, and you can check it with a few quick tests. Or is it something that vary with some releases or kernel or profile parameters? I'm using basis 7.0)
former_member182046
Contributor
0 Kudos
Hi Sandra,
Thanks for your feedback. Actually, I did check it out on an AS ABAP 7.01 with some simple code along the following lines:

report ztest 1.

tables:
  zsometable.

start-of-selection.
  zsometable-key   = somevalue.
  zsometable-field = somevalue.
  insert zsometable.
  submit ztest2 and return.
* alternatively: call transaction 'ZTEST2'.
  rollback work.

In a separate SAPGui window, I checked the content of the test table. If the documentation says otherwise, I'd be very interested to read it. Do you have a link at hand?
Thank you & cheers,
Thorsten
Sandra_Rossi
Active Contributor
0 Kudos
sure 🙂 http://help.sap.com/abapdocu_70/en/ABENDB_COMMIT.htm
I do the same test as you do (+ ztest2 contains nothing), and the record has been rollbacked
former_member182046
Contributor
Hi Sandra,
Thanks again for pointing this out and for the link. I experimented some more and found out that it depends indeed on what is in the report. So the SUBMIT doesn't always trigger to an implicit DB_COMMIT, but when certain things occur in the called report such as
* selection screen
* list output
* messages (depending on message type)
List display is especially nasty because it does trigger the commit when it occurs in the called report but not in the calling report.
In real life (if you can call work real life), I eliminate the problems of unwanted DB_COMMITS (who ever knows if a called report is going to output a message?) by making my LUW entirely independent of database transactions with the help of update function modules.
1. So all my database updates are encapsulated in update function modules that I call with CALL FUNCTION ... IN UPDATE TASK.
2. In mass-data scenarios I cache the changes somewhere in a cache object that I use both for transparent read access to pending updates and bundling data into tables from which to perform mass updates (INSERT/DELETE/UPDATE ... FROM TABLE ...).
3. The Object Services help because they provide the same functionality out of the box.
Back to your objection - I like being right but I'm glad you corrected me here. 🙂
Cheers,
Thorsten
Sandra_Rossi
Active Contributor
0 Kudos
Thx for the feedback and all these best practices. I like your "who ever knows if a called report is going to output a message?". That could be the beginning of a series of nice blogs (who ever knows what triggers your customer exit (is it from dialog, update task, and so on), who ever knows what SAP could change in the standard, ...) 😄
Former Member
0 Kudos
I just had a code review.  I was asked why I was using standard "classic" statements inside of ABAP Objects.

My answer was that ABAP Objects was standard ABAP.  (And I wanted my code to be re-usable.)  I love to see my ideas reflected by someone else.  In such a great way!  I couldn't have written such a well thought out blog.

Thank you!

Michelle
markteichmann
Product and Topic Expert
Product and Topic Expert
0 Kudos
Most of the new and interesting things that happen in ABAP development are in the field of WebDynpro or Java development. Therefore I am happy that you write about programming for the SAPGui in your blog.
Are our customers the only ones in the world that mostly use SAPGui? Or are people who use 'deprecated' technology the same ones that do not follow the SCN?
I already built some applications following your 'MVC pattern' for classical dynpros and many colleagues asked me what the hell I was doing there 🙂
former_member182046
Contributor
0 Kudos
Hi Mark,
well, of course it is easier to go on SCN with content about new and exciting technologies. It makes you look modern and innovative to write about how to connect River and StreamWork. If, on the other hand, you write about how to integrate SAPscript into ABAP Dynpro, you might do harm to your reputation as a cutting-edge technologist. 😉 I suppose that might be a reason why there is not much content about stuff like ABAP Dynpro, BAPIs, Workflow etc. However, the practical value of good content about these well-adopted tools is very high because many people are using it in real life and all these might potentially benefit from the content - whereas a River/StreamWork is probably more interesting than practically useful for many readers.
So don't feel bad about working with ABAP Dynpro. Everybody does it, but not everybody admits it. 🙂
Cheers,
Thorsten
Former Member
0 Kudos
Thorsten:

In my company we really encourage the use of OO ABAP, so we use it for everything, including Dynpro programming -:) So been there, done that. However your blog is still a very good resource were some ideas can be steal -;)

On the funny side...I haven't been in any project using WebDynpro since I joined the company 9 months ago -:P

Greetings,
Blag.
Former Member
0 Kudos
This is a great approach, and one which we are beginning to follow in our developments.  We are in the process of building a number of brand new screens, each of which is contained within it’s own Function Module, and called via a CALL SCREEN command.  We are taking this approach to create a modular, flexible solution, giving us all of the benefits described above.  Calling each function module, passing in parameters, is proving to work very well.

Do you have any advice on how ‘back’ navigation can be handled?  Our Function Modules (screens) will be called from within one another.  For example:

FM1 (screen 100) calls> FM2 (screen 200) calls> FM3 (screen 300).

This works fine when going forwards, but if the user hits the back button, ideally we want the screens to flow in reverse (300 > 200 > 100) each time the user clicks back.  We are using SET SCREEN 0, LEAVE SCREEN, which seems to be working.  Is this the approach you would recommend?

We have also encountered problems with a TOO_MANY_LPROS runtime error.  The screens can be called from within one another.  FM1 (screen 100) calls> FM2 (screen 200) calls FM1 (screen 100) calls> FM2 (screen 200).  Once 50 screens have been called in this manner (or the same 2 screens called 25 times), SAP gives a runtime error.  We are able to trap this before it occurs and report our own message at screen 49, but have you encountered anything similar?

Regards,

Nathan
Former Member
0 Kudos

Thorsten ,

Nice reading.

Looking forward to such wonderful blogs from you. :smile: It was so informative. Thank you!! got to learn new things from you .....

Thanks ,

Veena.

Former Member
0 Kudos

Beginners in ABAP - must read!!  A healthy foundation for your Knowledge Base!!!

Greetings,

Veena.

Former Member
0 Kudos

Command 'TABLES:'

I still use this and ask people to use it. Is it obsolete?

I use it because it is the only way I can get a list of all tables used in program at one place when I click on "Display Object List" or view program in SE80. It helps a lot when there are multiple custom tables involved in the program.

And I guess the tables don't show up if you don't use them in 'TABLES' syntax and use them directly in select queries to fetch data from. Is there any way to achieve this? - To view all tables used in a big program without using global find?

kesavadas_thekkillath
Active Contributor
0 Kudos

hi,

You can use fm REPOSITORY_ENVIRONMENT_SET_RFC :smile:

Former Member
0 Kudos

Thanks. That is really helpful FM.

bruno_esperanca
Contributor

Fantastic, about to use this myself.

Where would I be if you hadn't shared this? I have no idea... (because I already have an intricate class based "report", and now I was asked to put a selection screen called from a button...)

Thanks!

Kind regards,

Bruno

Koja78
Participant
0 Kudos
I might be ignorant... but how would you read the parameters after displaying the selection-screen?
Thorsten_Franz
Explorer

Hi,

You just code a normal event like AT SELECTION-SCREEN. At this point, everything the user has entered is available as global variables in the program. If the class containing your screen logic is in the same main program (function group), then it has access to these global variables anyway. You can divert the control flow to it by calling an appropriate method such as

AT SELECTION-SCREEN.
myclass=>pai( ).​

For cleaner code (because working with global variables in multiple locations is to be avoided as much as possible) or if the class not local to the function group, you can pass the values explicitly:

AT SELECTION-SCREEN.
myclass=>pai( is_values = VALUE #( sel1 = s_sel1[]
sel2 = s_sel2[] ) ).

Cheers!

Koja78
Participant
0 Kudos
So at the same location as

lcl_sel_scr=>handle_function_codesy-ucomm ).

 

 

 
Koja78
Participant
0 Kudos
The other thing I can't get my head wrapped around is the following:

  • The local application class is in the function group... in order to control the selection-screen in an include in the function group. -> the whole application logic is in the function group <-> program .. as you call the select_data in that same local application class.  Okay you could use a seperate data collection class & visualisation class which you instantiate in that local application class.. but still it doesn't feel as MVC-like as it should be to me.


 
joachimrees1
Active Contributor
0 Kudos
Wow, 11 year old blog, but still relevant I think (I still use reports) - I will keep this as a "read later" (now I just skimmed it...)
michael_calekta
Explorer
0 Kudos

The OO-functionality described above works alright, as long as the Report is a front-end report. When you want to start it as a Batch-job with a given variant the DO./ENDO. for the call of the Selection-Screen loops endlessly and will never stop because the function-code never gets executed.

So in order to achive this I had to do 2 things. First move all funtionality for the normal execution in a seperate method wich I called 'MAIN'.

Secondly I had to alter the START_DIALOG-Method a bit:

METHOD start_dialog.

IF sy-batch IS INITIAL.
DO.
CALL SELECTION-SCREEN 100.
ENDDO.
ELSE.
lcl_report=>main).
ENDIF.

ENDMETHOD.

This way I can now use the report both ways, and still stick to the OO-functionality.

michael_calekta
Explorer
0 Kudos
Okay, now with the addition above it works fine for fore- and background processing, yet there's one thing, that does not work, and that's a simple list-processing. In case of error (wrong stuff entered by the users) I always print a minimal list of errors on the screen with simple WRITE-Commands. That does not work anymore. Any idea how this could be made to work as well?
michael_calekta
Explorer
0 Kudos

Okay, after playing around a little I also found a solution for this problem.

After handling the Function-Code at AT SELECTION-SCREEN you have to add something:

AT SELECTION-SCREEN.

lcl_ui=>handle_fcsy-ucomm ).
IF lcl_report=>mv_write IS NOT INITIAL.
LEAVE TO LIST-PROCESSING AND RETURN TO SCREEN 0.
SET PF-STATUS 'S0100LIST'.
ENDIF.

First I introduced a public variable mv_write which is a flag that says if any listoutput has been produced. In case there is, you have to add the LEAVE TO LIST-PROCESSING-Command and build a little Status for it, where only the Exit-Commands need to be added. If you don't set the PF-Status the program will not react to any entry, so this is rather important. Now it works fine in any way.