Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
hardyp180
Active Contributor

The Hunchback of Notre Dame vs CL_GUI_ALV_GRID

Writing a Book for SAP Press – Part Six


Table of Contents


Background


Interfaces


CL_GUI_ALV_GRID


To Be Continued

Background

This is the latest in my series of blogs about writing a book for SAP Press. A book about building monsters for Baron Frankenstein, a situation virtually all ABAP programmers find themselves in at some point in their careers. The last such blog was as follows:-

http://scn.sap.com/community/abap/blog/2015/05/01/there-is-a-monster-in-my-pocket

I have avoided talking about the contents of the book, but rather the thinking behind it. In this series of blogs we have now reached Chapter 10, regarding the ALV interface technology, and the notable point here is that about two thirds of the original chapter I had prepared had to be edited out of the final version for reasons of space.


So this would be an appropriate place to talk about some such subject matter which h never made it to the book, talking more about the philosophy of the usage of various user technologies in general rather than about a specific UI technology such as CL_SALV_TABLE. That said, once the general background has been covered I will use CL_GUI_ALV_GRID as a specific example of a general point.


Interfaces


When I first trying to get my head around object orientated programming one of the areas I struggled with the most was the concept of an “interface”. This appeared to be a class definition containing normal things like methods with their signatures and class attributes and what have you but without any code behind it. Rather like declaring a local class definition, but not the implementation.


What was that all about? I thought to myself. People on the internet were advising me to always use interfaces for the public parts of my classes. Well, why not have a normal class with the public parts public and everything else private and cut out the middleman? I was lost.


I found the answer via lots of experimentation and reading books like “Head First Design Patterns” and articles on the “Dependency Inversion Principle” and so forth, but one of the best definitions I heard was on the SCN by “Fire Fighter”.


You have probably heard phrases like “at work I wear many different hats” or “putting my accountants’ hat on for a second”. Most of us are more than one thing – you could be a computer programmer by trade or an accountant (Or both like me) and a keen sportsperson in your spare time (which I am not) and – in the case of the example – a volunteer fire fighter in your spare time.


You could be looking at a room full of accountants and say “I need a top golfer” and only some of them could step forward and fulfil that need, and most likely very few would be trained in actual firefighting. But they are still all accountants.


So an interface in this case could be a golfing interface with an attribute like “handicap” and methods like “swing club” or “putt”, things of no relevance to accounting. Conversely accountants have might have methods like “do bank reconciliation” or “look forward to month end like it’s Christmas” which have no place on the golf course.


In IT terms you would have a “person” class with methods every person does like “breathe” and attributes like “age”. Some people would implement the accounting interface, some the golfing interface, and some both. The requesting program would only ask for the golfing interface, as that is all it is interested in, it does not care if you are an accountant as well, and it has no interested in the methods every person can do.


Private Investigations


So a class can have one or more interfaces which are the public faces it wears. A requesting class types it’s IMPORTING parameter with reference to an interface rather to a concrete class. When the requesting class gets an instance passed in it neither knows nor cares what the actual type of class is. The requestor knows the class can perform the methods it wants on the grounds that the interface claims that it can. The requestor naturally can only access the interface methods and attributes as it knows nothing else about what the actual class passed in can do.


This is a clear separation of the “what” from the “how”. The interface says “what” the class can do but the actual mechanics of the “how” are as private as can be.


Hale and Pace Layering


Some technologies evolve much faster than others – a concept known as “pace layering”. If the requesting class is the sort of thing that changes very rarely - e.g. the business logic of making concrete, something that has not changed much since the days of the Roman Empire – it can communicate with a technology that changes very frequently – e.g. each week, like user interface technology – by means of an interface. This way the slower evolving class does not need to know how fast (or slow) the other technology is evolving, as it does not care one jot how the job is done, just that it is done.


Fifty Shades of User Interface Technology


One thing I can think of where the technology has changed a great deal over the years and continues to change to this day, yet still does fundamentally the same thing, is the good old ALV grid, so I wanted to devote a chapter of the book to this subject.


You may wonder why in the world I would want to be talking about a technology that uses the SAP GUI when all you tend to hear about at conferences and on the internet is a relentless focus on zero footprint browser based interfaces. I have always thought of the SAP GUI as being like an internal combustion engine car with the various browser based technologies being like electric cars. Electric cars have obvious advantages – cheaper running costs, less parts, saves the poor starving orphan polar bears from drowning – but will only kill conventional cars dead when they can go further on a single charge than a normal car can on a single tank of petrol (gasoline).

In the same way a browser based technology has obvious advantages – it looks nicer and runs on your mobile device – but it will only kill the GUI dead when it can have an equal or faster response time than the SAP GUI for an equivalent business transaction; at that point it will be game over for the GUI. In both cases the new technology has not killed the old one yet – but in both fields a lot of work is going on to try and tilt the balance.

You may think work on the ALV would have stopped dead at SAP but in between starting writing my book in February 2014 and finishing in February 2015 I discovered a new ALV class had been created, especially for dealing with big tables such as you get when using HANA.

Let us have a look at the history of this technology, or to paraphrase Michael Caine “What’s it all about, ALV?”


Why the original ALV was a good thing

The ALV framework – via function modules REUSE ALV DISPLAY LIST and REUSE ALV DISPLAY GRID were brought out round about the year 2000 and you could find them in SAP systems as early as version 4.5.

There is no need to go into detail on how to use those modules in your programs for two reasons:-

·         They have been around (in IT) terms forever so there is an abundance of documentation on the internet (there was not so much around in the year 2000 as I recall)

·         Technically this technique is obsolete, even though I see it still being used an enormous amount and think it will be here for a long while yet. As an example when you upgrade to ECC 6.0 one of the dormant business functions you can switch on is to transform a load of standard SAP reports from WRITE statements to ALV lists.

Instead I am going to focus on the philosophy behind the introduction of the ALV (ALV the Elder), which as we shall see in due course is also mirrored in its SALV younger brother (ALV the Younger), and it’s even younger brother “ALV the glint in HANA the Milkman’s eye”.

The problem ALV was invented to solve

At the time of writing I am 46 so I am too young to ever have used WRITE statements to code a full blown report. I have converted enough WRITE based reports to ALV though to know that it must have been horrible. It reminds me of someone who once told me about when they started programming and they wrote their programs on punch cards and then fed them into the computer one by one and if they dropped the cards and they went out of sequence they were sunk.

The business users have come to expect certain things from a list of data displayed on a screen – to be able to sort it how they want, to filter it by any value they desire, to drill down on a particular field to see the underlying SAP document, to be able to sub-total the values, the list goes on forever.

When the only tool you had was WRITE statements then it was fairly easy to output a static list but if you wanted to give the users any of the fancy features I just mentioned you had to program them yourself in each report program you wrote.

Needless to say every programmer did this in a different fashion, often differently each time they wrote a new program, and implemented between one and all of the fancy features for a given report, so apart from this being an enormous amount of effort for the programmers, you ended up with dozens of reports in your system which all looked and behaved violently differently.

How ALV solved the problem

The primary point of the ALV was to bypass all the “boiler plate” programming of sorting data and drilling down and what have you. Instead you called a function module to display the data and passed into it several structures saying what fancy features you wanted e.g. a field catalogue to say what columns you wanted, a sort catalogue to control the sorting and sub-totalling and you could even control the print formatting to an extent.

So when I converted a WRITE based report into an ALV report the two steps consisted of adding routines to fill up the structures, and then for every line I had added I found myself deleting twenty lines of code which were just not needed any more, for sorting and the like.

The secondary bonus was that after I had converted twenty reports to ALV they then all looked and responded exactly the same as each other which certainly was not the case before. You even got extra features like being able to download to EXCEL.

You could still add extra application specific features like extra buttons at the top of the screen but the point was that having to make the effort of adding those extra features was now the exception rather than the rule.

This brings me to the whole theme of this chapter – the idea behind the ALV was to remove the boiler plate code a programmer had to write, thus leaving them free to concentrate on the code that was specific to the business problem at hand they were trying to solve. This is the OO maxim “separate the things that change from the things that stay the same”.

Why CL_GUI_ALV_GRID was better (and worse)

With the advent of version 4.6 of SAP ERP the ABAP language was renamed “ABAP Objects” and you got a big cave painting telling you just that every time you opened SE80. This was to try and ram home the point it you could now do object orientated programming in ABAP. Even now this still seems to come as a shock to some people.

Assorted classes were now available to do the same sort of thing as the ALV so you were no longer supposed to use the function module REUSE ALV GRID DISPLAY but instead the class CL_GUI_ALV_GRID. It worked on virtually the same principle, passing in input structures.

These classes utilized the “control” technology whereby you could embed controls in areas of DYNPRO screens and then at runtime determine if one or more grids of data (or web pages or pretty pictures or a whole raft of other options) should appear in those areas.

This was part of the hilariously named ‘Enjoy SAP” exercise to try and make the standard transactions more user-friendly. I still find it funny when an end user complains about how difficult it is to use a transaction like ME21N and I get to tell them “this is an ENJOY transaction – you are supposed to be enjoying it”.

Whether the users enjoyed this or not, it did give programmers an enormous amount of new options as to how to display things on the screen. In addition you could now add your own user commands programmatically without having to create a so-called STATUS and then adding your own extra buttons.

However this flexibility comes at a cost – compared to setting up and calling a function module the process of creating a DYNPRO screen and creating and setting up the needed objects can appear quite laborious (though still not as bad as WRITE statements).

For example if all you wanted to do was display a list of data then it would be tempting to just call the function module rather than having to create a screen and then instantiate all the control objects needed for the CL_GUI_ALV_GRID.

To summarize this was better than the ALV function modules because of the vast range of new things you could do, but the downside was that some of the boiler plate code you had thought was safely dead and buried came jumping back out of the grave shouting “did you miss me?”.

Why the SALV reporting framework was the best of both worlds

The SALV reporting framework, in the form of CL_SALV_TABLE and its friends, although coming after the CL_GU_ALV_GRID are in some senses much more of a direct follow on from the ALV function modules.

The most obvious improvement, that made the programming community sing and dance, was that before SALV came on the scene you had your report data in an internal table, and you had to define all the fields of your internal table, and then define those exact same fields again when setting up a field catalogue for an ALV function module or a CL_GUI_ALV_GRID class.

In the SALV reporting framework you just pass the internal table in, and the class dynamically creates the field catalogue for you. In some basic report programs that halves the lines of code in one fowl swoop. Moreover you do not need to create a DYNPRO screen for the report output to live in, the SALV class does that for you as well just like the function module did. You can however, if you so desire, attach your SALV object to a control just like the CL_GUI classes, so you do have the best of both worlds.

This time there is no grey area – if you have a simple report then using CL_SALV_TABLE is always going to take less lines of code and less programming effort than the function module equivalent, at least for saying what columns you want, and many reports have dozens of columns. This was the nail in the ALV function modules coffins.

With ABAP 7.40 we also dispense with another task (declaring the internal table) so we have moved from:-

·         Declaring an internal table by listing all the fields

·         Filling the internal table with data by listing all the fields again in a SELECT statement

·         Creating a field catalogue by listing all the fields again

·         Calling the function module

To:-

·         Filling the internal table by listing all the fields in a SELECT statement, which also dynamically creates the internal table structure

·         Calling the CL_SALV_TABLE class and passing it the internal table


This is all wonderful, even before 7.40 - however what causes consternation amongst programmers the first time they encounter the SALV is that as opposed to having structures as inputs to a function module, a SALV object takes other objects as input, one from sorting, one for subtotalling, one for controlling the fields, in fact quite a large number of possible input objects.

The more features you want in your SALV report the more objects you have to declare, and then create and then link to the main SALV object and then finally feed them the information you want.

This is normal in OO programming but could also be described as a vast horde of boiler plate code statements sweeping out of the graveyard and devouring everything in their path.

So, each of the three technologies discussed here still has its fair share of boiler plate code. How do we go about defeating this enemy?

Susan Boilerplate Code


I dreamed a dream in times gone by, a dream in which I wanted to:-


·         For SALV reports I wanted to avoid having to declare all the helper objects each time, and wanted the code to set up the special features for each report to be as simple (or simpler) as it had been for the original ALV

·         As you still need the CL_ALV_GRID_DISPLAY in certain situations, again I wanted this to be as easy (or easier) as the original ALV

·         As a purely academic exercise to demonstrate the point, I thought why can’t you have the same report with a radio button on the front saying “REUSE_ALV_LIST or CL_GUI_ALV_GRID or CL_SALV_TABLE display” and then have the user choose the report output (rather like the “user settings” options in SE16), but without having the program be three times as long. This is an academic exercise as most users would not know those terms, and you would not actually want that option in real life, this would just be to show you can output the results of the same program using three different technologies and swap between them.

·         To summarise – I wanted to create a template program for an ALV report and then to reduce the code in that report template program to the bare minimum and as a side effect you are then able to switch the technology used to output the report at the drop of a hat.


It’s the same old song, but with a different meaning…..


So the first thing I did was to write three template programs, all reading the same Z table, and all displaying the contents using the ALV. One program used the REUSE_ALV_LIST function module, one used CL_GUI_ALV_GRID and one used CL_SALV_TABLE. Upon completion they all looked totally different - as might be imagined. This is because each one was stuffed full of the specific structures (and objects in the latter two cases) that are specific to that particular version of the UI technology.

However at a very high level all 3 were doing the exact same thing – taking the contents of the same table and displaying it. The aim of the game is to try and see if you can take that process down to a lower level and see if all three are still the same. The lower down you go eventually there is bound to be a difference, and so you have to define your interface one level up from when things start to change depending on what technology you use.

I found by writing the program flow from each of the three programs on a beer mat and drawing circles around blocks of code which did similar things I could break the flow into four main chunks, the same in each case, which would be the level of my interface.

·         Report Agnostic / Technology Specific - Sometimes there was a need to create a “container” (not for the old fashioned one!). The code to do this – though different for each technology -  would not vary between different programs that used that same technology.

·         Report Agnostic / Technology Specific - There was also a need to “initialise” some variables e.g. the variant name off of the selection screen, an instance of the report class when a class is the technology involved. Again the code would be the same for different programs using the same technology, only input values from the selection screen and the like would change.

·         Report Specific / Technology Agnostic - Then there would be what I call “application specific changes” which would always vary between programs e.g. I want to rename this column, enable drill down on this column etc. However this chunk does not care what technology is being used.

·         Report Agnostic / Technology Specific – when you are finished you use your selected technology to display the report to the user. At this point the selected technology takes control and communicates with the calling program by sending back “events” to say what the end user has been doing e.g. double clicking on a field.

You can see from the above that for three of those chunks all the calling report has to say is “do this” and the selected technology will take care of how to carry out the task. For those tasks the amount of code in the template program can be very small indeed.

The third one is the important one – saying what is going to be different about this report in particular.

I don’t care, I don’t care, I don’t care, if a new ALV class comes round here


We want to make sure the calling report program is not too fussed (does not care) what ALV technology is in use, and doesn’t need to get too stressed when it changes. How this is going to work is that you have an interface which describes those four things I have just mentioned. The calling program is going to define a “view” object as TYPE REF TO the interface. Then at the start of the program the view object is going to be created as a specific class that implements that interface e.g.

ZCL_BC_VIEW_RALD - Base View for REUSE_ALV_GRID_DISPLAY

ZCL_BC_VIEW_ALV_GRID - Base View Model for CL_GUI_ALV_GRID

ZCL_BC_VIEW_SALV_TABLE - Base View Model for CL_SALV_TABLE

You could hard code the CREATE OBJECT statement to use the TYPE of the ALV technology you are using at the moment, and then when you want to trade up you just change one line i.e. the class TYPE of the view object you are creating. Or you could read the exact type to be created out of a customising table, maybe have a factory class used by lots of reports that returns a view object. That way the calling program would not have to be changed at all when you wanted to start using a new technology.

For example tomorrow SAP could invent a new ALV class called CL_SALVE_FOR_MY_WOUNDS which is ten times better than anything that came before.

You could then just change the factory class – or an entry in a customising table – and all your reports would suddenly start using the new ALV class. Naturally you would have to create a SALVE specific class that implements the generic interface.

What’s in the book, chook?


In my book I talked about the mechanics of the generic interface and gave a detailed example of a specific implementation using CL_SALV_TABLE. I wanted to give an example using CL_GUI_ALV_GRID as well, but it turned out there was not room. So I will give the CL_GUI_ALV_GRID example here.

This is important because – and I harp on about this like a broken record – the CL_SALV_TABLE is not supposed to be editable, and every single SAP customer needs an ALV grid to be editable, so the “new” technology is missing a vital feature the “old” technology had.

In my book I talk about how to work around this, but really you should not have to work around this, it should be standard. So, if you need an editable grid and you don’t want to break the rules, you need CL_GUI_ALV_GRID.

Moreover I wonder if any of you have been in the boat where you have been asked to write a read-only report, so you chose CL_SALV_TABLE as it was easier, and then at the last second the requirements changes to have “just one column editable”? Then you have to re-write the entire program using CL_GUI_ALV_GRID and that takes ages.

With the interface approach if that happens you just change the object creation line in the calling program to use the CL_GUI_ALV_GRID related Z class as opposed to the CL_SALV_TABLE related Z class.

Example with CL_GUI_ALV_GRID


In the book I talk about a monster report I have I implemented using CL_SALV_TABLE but here I decide I need to use CL_GUI_ALV_GRID instead but leave the monster report totally unchanged with the exception that instead of

CLASS lcl_view DEFINITION INHERITING FROM zcl_bc_view_salv_table.

We have




CLASS lcl_view DEFINITION INHERITING FROM zcl_bc_view_alv_grid.

Since both classes implement our custom interface the calling program cannot tell them apart, though of course the underlying code will be very different.

To start off with the list of the attributes of GUI ALV GRID subclass bear no resemblance to the attributes of the SALV subclass as can be seen below

Custom GUI ALV GRID Subclass Attributes

We will now go through the same functional areas as we did in the last example to see how we achieve each task in the way the CL GUI ALV GRID requires.

Initialisation

Of the three GUI report technologies mentioned earlier, the CL GUI ALV GRID is the only one, where, if you just want a full screen report you have to define a screen which is empty apart from a whacking great “custom control” filling it up. This is because the CL GUI ALV GRID needs a container to sit within.

Defining such a screen for each new report does not take long, but it is another of those boring repetitive tasks I could do without. In such cases I often spend ages coming up with a way to automate such steps so I never have to worry about them again. The solution I describe is naturally not the only way to achieve this, but it works and will give you an idea of the principle.

Sometimes the report will be displayed on part of a screen, in which case we do have to manually create the screen and container area, and then pass the container in, but if we want the screen and associated container to magically create themselves then we split the initialization into two steps – creating such a screen and then the rest of the initialization steps you normally have to do manually in each report.

METHOD create_container_prep_display.

  md_report_name    
= id_report_name.
  ms_variant
-report  = id_report_name.
  ms_variant
-variant = id_variant.
  mt_user_commands[]
= it_user_commands[].

 
CREATE DATA mt_data_table LIKE ct_data_table.
 
GET REFERENCE OF ct_data_table INTO mt_data_table.


 
CALL FUNCTION 'ZSALV_CSQT_CREATE_CONTAINER'
   
EXPORTING
      r_content_manager = me
     
title             = id_title.

ENDMETHOD.

Creating a Container Autaomatically


The first half of the above code just puts the input parameters in global (to the class instance) variables, the second relates to creating a screen and container.

This process of creating a screen automatically is going to seem quite complicated but as noted before this only has to be set up once and all of this complexity is hidden from the calling program. Anyway, I noticed some standard SAP programs seemed to set up their own screens and containers so I investigated how this worked.

I discovered that a class that wants automatic screen creation has to firstly implement interface IF_SALV_CSQT_CONTENT_MANAGER which has the somewhat enigmatic description “manages content”.

There is a standard SAP function SALV_CSQT_CREATE_CONTAINER which I copied and made a small change which was to tick the “without application toolbar” checkbox so the resulting screen does not have an ugly “hole” at the top. The code above makes a call to this function module passing in the calling class so the function knows where to return control to. The function creates a screen and a container and then passes back that container to method FILL CONTAINER CONTENT of the interface that our custom view class has implemented.

METHOD if_salv_csqt_content_manager~fill_container_content.
*---------------------------------------------------------------*
* This gets called from function SALV_CSQT_CREATE_CONTAINER PBO module
* which creates a screen and a container, and passes us that container
*---------------------------------------------------------------*
*Local Variables
  FIELD-SYMBOLS: <lt_data_table> TYPE ANY TABLE.

  ASSIGN mt_data_table->* TO <lt_data_table>.

  prepare_display_data(
    EXPORTING
      id_report_name   = md_report_name " Calling program
      id_variant       = ms_variant-variant
      io_container     = r_container      

      it_user_commands = mt_user_commands
    CHANGING
      ct_data_table    = <lt_data_table> ).  " Data Table


ENDMETHOD.

All the FILL CONTAINER CONTENT does is to pass on the container object to a method which then orchestrates the rest of the initialization steps.

METHOD zif_bc_alv_report_view~prepare_display_data.
* Step One - Generic - Set up the Basic Report
  initialise(
   
EXPORTING
      id_report_name   =  id_report_name
" Calling program
      id_variant       =  id_variant    
" Layout
      io_container     =  io_container  
      it_user_commands =  it_user_commands
" Toolbar Buttons
   
CHANGING
      ct_data_table    = ct_data_table ).

* Step Two - Application Specific
  application_specific_changes( ).

* Step Three - Generic - Actually Display the Report
  display( ).

ENDMETHOD.

METHOD zif_bc_alv_report_view~initialise.
* Local Variables
 
DATA: lo_container TYPE REF TO cl_gui_custom_container.

  ms_variant
-report  = id_report_name.
  ms_variant
-variant = id_variant.
  mt_user_commands[] = it_user_commands[].

 
CREATE DATA mt_data_table LIKE ct_data_table.
 
GET REFERENCE OF ct_data_table INTO mt_data_table.

* Create CL_GUI_ALV_GRID object. Make sure we do not create a container if we are offline
 
IF cl_gui_alv_grid=>offline( ) IS INITIAL.
    mo_custom_container ?= io_container.
 
ENDIF.

* Now we have a container, we place within it an ALV grid
 
CREATE OBJECT mo_alv_grid
   
EXPORTING
      i_appl_events =
'X'
      i_parent      = mo_custom_container.

  display_basic_toolbar( ).

  set_layout( ).

* User Command Processing
  set_handlers( ).

* Turn on the processing of events on <ENTER>
 
IF cl_gui_alv_grid=>offline( ) IS INITIAL.
   
CALL METHOD mo_alv_grid->register_edit_event
     
EXPORTING
        i_event_id = cl_gui_alv_grid
=>mc_evt_enter.
 
ENDIF.

ENDMETHOD.

Custom CL GUI ALV GRID – Initialization Method



Any GUI technology which uses the “controls” framework relies on the processing being done on the “client” i.e. the PC that SAP is talking to, as a lot of the work is done on the client side which is why scrolling is so slow, for example. If the report is running in the background then there is no client and you have to be careful not to cause a short dump. The standard code is usually clever enough to realize that a report is running in the background and switches to a display mode which does not need a client.

You will see in the listing above that we are using a static method of CL GUI ALV GRID to determine if the report is running in the background to make sure we do not pass in unexpected things like a container (which makes no sense when there is no screen output) or preparing for the user pressing enter when there is no user.

Creating the object is simple enough - then we come to setting up the toolbar at the top.

METHOD display_basic_toolbar.
*---------------------------------------------------------------*
* At the top of the standard ALV grid are a big bunch of icons. Some of them
* do nothing, others do harmful things. We want to switch some of them off
*---------------------------------------------------------------*
 
DATA ls_exclude TYPE ui_func.

 
REFRESH mt_exclude.

  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_undo.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_copy_row.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_append_row.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_delete_row.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_paste_new_row.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_paste.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_loc_cut.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_data_save.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_subtot.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_sum.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_refresh.
 
APPEND ls_exclude TO mt_exclude.
  ls_exclude
= cl_gui_alv_grid=>mc_fc_graph.
 
APPEND ls_exclude TO mt_exclude.

ENDMETHOD.

Custom CL GUI ALV GRID Initialization – Toolbar Method


In the SALV object the default behaviour is no buttons at the top of the screen. In CL GUI ALV GRID the reverse is true – a large number of buttons appear by default, the vast majority of which make no sense at all for a static report – buttons for inserting and deleting rows for example. So the code above is concerned with de-activating a large portion of the default buttons.

We set up the display variant right at the start when the object is created and the initialization method is called; now we set up the rest of the report layout options.

METHOD set_layout.

  ms_layout
-stylefname = 'CELLTAB'.
  ms_layout
-zebra      = 'X'.
  ms_layout
-sel_mode   = 'D'.
  ms_layout
-cwidth_opt = 'A'.”Always optimised
  ms_layout
-numc_total = 'X'.

* Set save restriction
* Check authority to change display variants.
 
AUTHORITY-CHECK OBJECT 'Z_VARIANT1' ID 'ACTVT' FIELD '*'.

 
IF sy-subrc = 0.   " does he ride a white horse?
    md_save =
'A'.   " yes,allow user and global display variants
 
ELSE.
    md_save =
'U'.
 
ENDIF.

ENDMETHOD."Set Layout

Using this UI class we don’t really need a separate method to optimize the column widths, as this is done together with the other layout options.

Now we move on to user command processing. This is very similar to the way we set up the event handlers for our custom SALV class; the main difference is that CL GUI ALV GRID has a lot more standard events that it raises.

METHOD set_handlers.

 
SET HANDLER handle_toolbar_set          FOR mo_alv_grid.
 
SET HANDLER handle_context_menu_request FOR mo_alv_grid.
 
SET HANDLER handle_user_command         FOR mo_alv_grid.
 
SET HANDLER handle_double_clicks        FOR mo_alv_grid.
 
SET HANDLER handle_link_click           FOR mo_alv_grid.
 
SET HANDLER handle_on_f4                FOR mo_alv_grid.
 
SET HANDLER handle_button_click         FOR mo_alv_grid.

ENDMETHOD.

Custom CL GUI ALV GRID CLASS – Event handlers


The first two events are not related to anything the user does, they are called when the GUI object creates the toolbar the first time, or the user right clicks on a line and the “context menu” appears.

When our custom view object was being created we had an optional table of extra commands passed in. If this table has any entries then we need to add our new buttons to the toolbar at the top of the screen.

METHOD handle_toolbar_set.
* Local Variables
 
DATA: ls_commands_out TYPE stb_button,
        ls_commands_in 
TYPE zsbc_stb_button.

* Create own Menubuttons and ToolbarButtons
* Append a separator to normal toolbar
 
CLEAR ls_commands_out.
 
MOVE 3 TO ls_commands_out-butn_type.
 
APPEND ls_commands_out TO e_object->mt_toolbar.

* Add the buttons the model would like to add
 
CHECK mt_user_commands[] IS NOT INITIAL.

   
LOOP AT mt_user_commands INTO ls_commands_in.
     
CLEAR ls_commands_out.
     
MOVE-CORRESPONDING ls_commands_in TO ls_commands_out.
     
APPEND ls_commands_out TO e_object->mt_toolbar.
   
ENDLOOP.

 
ENDMETHOD."Handle Toolbar Set

Adding our own user commands to the toolbar


Can be seen in the listing above we first add a separator to make a distinction between the standard toolbar ICONS and our custom ones, and then just loop through our table of extra commands adding them one by one. This is much better than having to create a user defined STATUS and change it each time you need a new command, which was the case with the REUSE_ALV function modules.

METHOD handle_context_menu_request.
* Local Variables
 
DATA: ld_fcode    TYPE ui_func,
        ld_text    
TYPE gui_text,
        ld_icon    
TYPE icon_d,
        ls_commands
TYPE zsbc_stb_button.

 
CHECK mt_user_commands[] IS NOT INITIAL.


 
LOOP AT mt_user_commands INTO ls_commands.

   
MOVE: ls_commands-function  TO ld_fcode,
          ls_commands
-icon      TO ld_icon,
          ls_commands
-quickinfo TO ld_text.

   
CALL METHOD e_object->add_function
     
EXPORTING
        fcode = ld_fcode
       
text  = ld_text
       
icon  = ld_icon.

 
ENDLOOP."Custom User Commands

ENDMETHOD."Handle Context Menu Request

A context menu is a list of user commands that appears in a single column under your cursor when you right click on a cell in the report. We are adding our own commands on at the end in the same way we add them at the top right of toolbar at the top of the screen.

Next we have three events which are identical from our point of view – clicking on a hotspot, double clicking on a cell, and clicking on a cell which is a pushbutton.

METHOD handle_link_click.
* Local Variables
 
DATA: ld_column TYPE salv_de_column,
        ld_row   
TYPE salv_de_row.

* Adapt view specific data to generic values
  ld_row    = es_row_no
-row_id.
  ld_column = e_column_id
-fieldname.

RAISE EVENT user_command_received

EXPORTING ed_user_command = '&IC1'
           ed_row          = ld_row
           ed_column       = ld_column.
ENDMETHOD.”Handle Link Click


This time we need to adapt the data elements CL GUI ALV GRID uses to store the row and column to our more generic data elements we use in the interface.

The events HANDLE_DOUBLE_CLICKS and HANDLE_BUTTON_CLICK are coded identically except for the fact that the names and definitions of the data structure that stores the selected row and column are different each time and need to be similarly adapted.

That just leaves us with HANDLE ON F4 which is triggered when a user places their cursor on a cell on presses F4 to get a list of possible values. This happens automatically for columns defined with reference to a data element, you only need to program this for other columns, which should be a rarity. I have left the implementation empty in the subclass, if a calling program really needs this then the method can be redefined in a local subclass.

We are now finished creating the basic report object, it is now time to define the nature of the report columns.

Formatting the Columns

Sadly CL GUI ALV GRID is not as clever as CL_SALV_TABLE and cannot automatically define the columns based on the internal table that supplies the data. We have to manually add each column to the field catalogue just as we used to do with the REUSE_ALV function modules.

To keep thing as uniform in the calling program as possible, what this means is that for occasions when we use the SALV object we only need to call the SET COLUMN ATTRIBUTES for fields we want to change in some way, for a CL GUI ALV GRID we have to call that method for every single field – in the order we want the columns to appear.

METHOD zif_bc_alv_report_view~set_column_attributes.
* Local Variables
 
DATA: ls_fieldcat LIKE LINE OF mt_fieldcat,
        ld_col_pos 
TYPE sy-tabix,
        ld_name    
TYPE c LENGTH 80.

 
FIELD-SYMBOLS: <ls_fieldcat> LIKE LINE OF mt_fieldcat.

READ TABLE mt_fieldcat ASSIGNING <ls_fieldcat>

WITH KEY fieldname = id_field_name.

 
IF sy-subrc NE 0.

    ld_col_pos =
lines( mt_fieldcat[] ) + 1.

   
APPEND INITIAL LINE TO mt_fieldcat ASSIGNING <ls_fieldcat>.

    <ls_fieldcat>
-col_pos   = ld_col_pos.
    <ls_fieldcat>
-fieldname = id_field_name.
    <ls_fieldcat>
-tabname   = id_table_name.
    <ls_fieldcat>
-ref_table = id_table_name.

 
ENDIF.

  
IF if_is_hotspot = abap_true.
    <ls_fieldcat>
-hotspot = abap_true.
 
ENDIF.

 
IF if_is_visible IS SUPPLIED.
    set_visible(
EXPORTING if_is_visible = if_is_visible
                
CHANGING  cs_fieldcat   = <ls_fieldcat> ).
 
ENDIF.

 
IF if_is_technical = abap_true.
    <ls_fieldcat>
-tech = abap_true.
 
ENDIF.

 
IF if_is_a_button = abap_true.
    <ls_fieldcat>
-style = cl_gui_alv_grid=>mc_style_button.
 
ENDIF.

 
IF if_is_subtotal = abap_true.
    <ls_fieldcat>
-do_sum = abap_true.
 
ENDIF.

 
IF id_long_text IS NOT INITIAL.
    set_long_text(
EXPORTING id_long_text = id_long_text
                  
CHANGING  cs_fieldcat  = <ls_fieldcat> ).
 
ENDIF.

 
IF id_medium_text IS NOT INITIAL.
    set_medium_text(
EXPORTING id_medium_text = id_medium_text
                    
CHANGING  cs_fieldcat    = <ls_fieldcat> ).
 
ENDIF.

 
IF id_short_text IS NOT INITIAL.
   <ls_fieldcat>
-scrtext_s = id_short_text.
 
ENDIF.

 
IF id_tooltip IS NOT INITIAL.
   <ls_fieldcat>
-tooltip = id_tooltip.
 
ENDIF.

ENDMETHOD."Set Column Attributes

Custom CL GUI ALV GRID class – set column attributes


As can be seen the code for setting various column attributes is a lot simpler than the SALV equivalent. This time we don’t need to create helper methods unless they are doing something that requires more than one line of code.

METHOD set_visible.

 
IF if_is_visible = abap_true.
    cs_fieldcat
-no_out = abap_false.
 
ELSE.
    cs_fieldcat
-no_out = abap_true.
 
ENDIF.





ENDMETHOD.


We are keeping the parameters as “is visible” for consistence with the SALV structure, so we just need to reverse the flag and pass it into the CL GUI ALV GRID “no output” flag.

METHOD set_long_text.

  cs_fieldcat
-scrtext_l = id_long_text.

 
IF strlen( id_long_text ) LE 20.
    cs_fieldcat
-scrtext_m = id_long_text.
 
ENDIF.

 
IF strlen( id_long_text ) LE 10.
    cs_fieldcat
-scrtext_s = id_long_text.
 
ENDIF.

ENDMETHOD.

In regard to the column texts, you can see that the same logic is being applied as in the SALV equivalent, to avoid us having to pass the same value in there times for changed descriptions that apply to all three texts.

Sorting


METHOD zif_bc_alv_report_view~add_sort_criteria.
* Local Variables
 
DATA: ld_position TYPE sy-tabix.

 
FIELD-SYMBOLS: <ls_sort> LIKE LINE OF mt_sort.

 
READ TABLE mt_sort ASSIGNING <ls_sort>
 
WITH KEY fieldname = id_columnname
           spos      = id_position.

 
CHECK sy-subrc <> 0.

  ld_position =
lines( mt_sort[] ) + 1.
 
APPEND INITIAL LINE TO mt_sort ASSIGNING <ls_sort>.
  <ls_sort>
-spos      = ld_position.
  <ls_sort>
-fieldname = id_columnname.

 
IF if_descending = abap_true.
    <ls_sort>
-up   = abap_false.
    <ls_sort>
-down = abap_true.
 
ELSE.
    <ls_sort>
-up   = abap_true.
    <ls_sort>
-down = abap_false.
 
ENDIF.

 
IF if_subtotal = abap_true.
    <ls_sort>
-subtot = abap_true.
 
ENDIF.

ENDMETHOD.

The sort method is just a question of adapting the input parameters from the interface method – which are based on the SALV object – to the CL GUI ALV GRID equivalent.

METHOD zif_bc_alv_report_view~add_sort_criteria.
* Local Variables
 
DATA: ld_position TYPE sy-tabix.

 
FIELD-SYMBOLS: <ls_sort> LIKE LINE OF mt_sort.

 
READ TABLE mt_sort ASSIGNING <ls_sort>
 
WITH KEY fieldname = id_columnname
           spos      = id_position.

 
CHECK sy-subrc <> 0.

  ld_position =
lines( mt_sort[] ) + 1.
 
APPEND INITIAL LINE TO mt_sort ASSIGNING <ls_sort>.
  <ls_sort>
-spos      = ld_position.
  <ls_sort>
-fieldname = id_columnname.

 
IF if_descending = abap_true.
    <ls_sort>
-up   = abap_false.
    <ls_sort>
-down = abap_true.
 
ELSE.
    <ls_sort>
-up   = abap_true.
    <ls_sort>
-down = abap_false.
 
ENDIF.

 
IF if_subtotal = abap_true.
    <ls_sort>
-subtot = abap_true.
 
ENDIF.

ENDMETHOD.”Add Sort Criteria

Displaying the Report

There is nothing dramatic here, we are just calling the display method of CL_GUI_ALV_GRID and once again hiding the complexity.

METHOD zif_bc_alv_report_view~display.
* Local Variables
 
FIELD-SYMBOLS: <lt_data> TYPE ANY TABLE.

 
ASSIGN mt_data_table->* TO <lt_data>.

 
CALL METHOD mo_alv_grid->set_table_for_first_display
   
EXPORTING
      it_toolbar_excluding = mt_exclude
      is_layout            = ms_layout
   
CHANGING
      it_fieldcatalog      = mt_fieldcat
      it_sort              = mt_sort
      it_filter            = mt_filter
      it_outtab            = <lt_data>.

ENDMETHOD.

User Commands


We talked about the various events raised by CL GUI ALV GRID earlier. In the event handler methods we just pass these on to an event raised by our custom class, adapting the CL GUI data structures to the SALV equivalent where needed.

METHOD handle_link_click.
* Local Variables
 
DATA: ld_column TYPE salv_de_column,
        ld_row   
TYPE salv_de_row.

* Adapt view specific data to generic values
  ld_row    = es_row_no
-row_id.
  ld_column = e_column_id
-fieldname.

 
RAISE EVENT user_command_received

EXPORTING ed_user_command = '&IC1'
          ed_row          = ld_row
          ed_column       = ld_column.

ENDMETHOD.

METHOD handle_user_command.
* No need for type conversion
 
RAISE EVENT user_command_received

EXPORTING ed_user_command = e_ucomm.
ENDMETHOD.

The calling report will have to have a class which implements a method like this:-

  handle_user_command FOR EVENT user_command_received
                                  OF lcl_view
                                  IMPORTING ed_user_command
                                            ed_row
                                            ed_column.

In the above code listing LCL_VIEW is the local class whch inherits from our abstract custom subclass. The calling report can then decide what to do with the user input, it is no longer the views responsibility. Typically this is picked up by a controller which may or may not respond to the event and then passes the evnt on to the model which also may or may not respond.

Future Proofing

As we have seen by using a common interface we can move a report from one UI technology to another by changing the bare minimum of code.

The logical extension of this is that if and when SAP come up with the successor to the SALV reporting framework, all we will need to do is create a new subclass which implements the generic view interface, create implementations for each of the interface methods to deal with the specific way the new technology will deal with each task, and then we could go around changing existing reports to use the new technology at the drop of a hat, rather than having to rewrite them all.

What are you ON about?


Some of the above example may not have made a lot of sense as it refers to the SALV example of implementing the generic report interface that is in my good old book. Still hopefully you get the idea and of course I am open to answering any questions you may have. In addition if some of the code seems a bit inconsistent, then I am always open to suggestions as to how to improve it. That's what the SCN is all about, after all.....

To Be Continued


To end with the same paragraph as the prior blogs on book writing, in subsequent blogs I will continue to talk about the thought process behind how I chose what topics to include in the book, enriched with some content that was cut from the final version of the book for reasons of space.


Cheersy Cheers


Paul


https://www.sap-press.com/abap-to-the-future_3680/

8 Comments