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

SALV and Pepper – Edit it Real Good


Editing Specific Columns in CL_SALV_TABLE


One Sentence Summary


How to write a report using CL_SALV_TABLE and have specific columns open for editing.


Back Story


SCN Blogger Naimesh Patel has written several articles about custom workarounds to make CL_SALV_TABLE editable, for example the following:-


http://scn.sap.com/community/abap/blog/2015/06/25/salv-editable-with-single-custom-method

The development department at SAP are horrified that developers around the world are trying to make the SALV editable, because it is naughty and against the rules. Nonetheless it is a requirement every developer in every company has and lots of such developers just don’t care that it’s naughty.


I have declared February the 8th “International Editable SALV Day” on the grounds that was when people first started begging SAP to add this functionality – in 2008.


http://scn.sap.com/community/abap/blog/2015/02/08/international-editable-salv-day-2015

I imagine I will be writing another blog on the same day in 2016 to celebrate the 8th anniversary of nothing changing.

The irony is at the moment there is a push for companies to adopt the non-on-premise version of S/4 HANA (though none have to date) on the grounds that although there will be far fewer user exits SAP will quickly add missing functionality when you ask for it.

Can anyone see the gap between that promise and the SALV situation?


Column the Barbarian


Anyway, time to stop moaning and move on to the problem at hand. The original solution by Naimesh made the CL_SALV_TABLE editable, but you had to open it in read only mode and then press a button to change to editable mode.


So I asked him if there was a way that you could open up the SALV directly in editable mode, like you can with CL_GUI_ALV_GRID. Every less button pressed by the users is a good thing. He came back with a solution almost at once – it is described in the blog accessed by the link at the top of this blog.


So far so good, but the whole grid was editable – and 99 times out of a hundred you only want one or two columns editable. So I thought it was my turn to contribute something in this area, so I worked out a way to open the SALV in editable mode with only one or two columns changeable.


This may not be the best solution in the world, but it works. In fact what I am going to write about here is about the fourth iteration already, but it suddenly occurred to me I could spend the rest of my life fiddling around trying to get this “perfect” and then this blog would never get written.


MVC Potter and the Philosophers Corner


One area I spent ages agonising over was that given an MVC design what class should do the work of altering the data table so that some columns were editable? It is the models job to know what data a user is allowed to change, and yet the actual mechanism by which you change the data table is very much UI technology specific, and thus the province of the view.


Many people have told me I should not be mixing up philosophical talk – matters of OO design and the like – with the actual instructions on how to do something, but to me they are two sides of the same coin. Right at the moment I am reading one of the “Head First” books about software design and they are presenting how a bad design can really stuff you up down the track even if the program works technically.


One area I still have not finished is to isolate all the code that would be exactly the same in every report into dedicated classes. I am fanatic about this matter – the idea is that if you find yourself with the same task you had six months ago, say creating a pop up box showing a data table, then if you find yourself cut and pasting large chunks of code from the last program and then only changing 5% of it, then something is rotten in the state of Denmark. This is especially true of the SALV class where you might find yourself making twenty lines of data declarations for all the helper objects each time.


My other aim is to have a report framework which is not dependant on the actual UI technology i.e. CL_SALV_TABLE. As an experiment I want to be able to switch between CL_SALV_TABLE and CL_GUI_ALV_GRID by changing just one line in the calling program – or use a configuration table and thus have to change no lines. That way if a successor to the SALV comes along and I think it is much better than I do not have to change half a million report programs.


To summarise – a “perfect” design would be one where the vast bulk of the code is 100% specific to the report at hand and the small proportion remaining consisting of calls to various generic helper classes – thus separating the “things that change from the things that stay the same”. I have not quite got there yet, but as they say, an aim in life is the only treasure worth finding.


Nelson’s Editable Column


In the code that follows you will see that model is shouting out what columns are editable or need to be renamed or allow a drill down etc. and then the view can obey those instructions using its specific UI technology, which in this case is going to be the SALV.


How I am going to do this is by stepping through the program flow so you can see what happens in the same way you would if you filled the custom methods with break points and executed the report.


At the end of the blog will be a good old SAPLINK file so you can install the objects in your own system and play with them.


Bogus Syntax Error


First off I want to mention an annoying problem I have and then people can tell me how to get around it – it is either something obvious, or I am heading down the right path and just need to make things more generic.


The “monster monitor” program is a type one executable report with a selection screen. As we know the selection parameters are global variables (Oh no!). If I have my local class definitions and implementations in the same program as the selection screen then such classes have access to those selection criteria and can use them for database access for example.


However if the class definitions are in one INCLUDE and the class implementations are in another INCLUDE then although the classes still have access to the selection screen parameters and the program will still run, if you do a syntax check whilst in the INCLUDE with the implementations then you get a false syntax error saying something like “variable P_WHATEVER is unknown”.


This makes the syntax check useless and since I do one every ten seconds on average I had to get around this somehow. One way would be not to use INCLUDES though I thought the consensus was that INCLUDES for use with a single program were OK.


What I did was to create a local class for the sole purpose of storing the selection screen parameters, and the local classes that needed any of those values would never access the global parameter variables directly, thus stopping the bogus error message.


START-OF-SELECTION.
 
"This nonsense is the only way I can avoid getting bogus syntax
 
"errors when doing a syntax check on the local class implementations
 
CREATE OBJECT go_selections
   
EXPORTING
      is_numbr
= s_numbr[]
      is_name 
= s_name[]
      ip_vari 
= p_vari
      ip_edit 
= p_edit
      ip_macro
= p_macro
      ip_send 
= p_send
      ip_email
= p_email.


All the selections class does is to take in the values the user entered on the screen and place them in identically named public attributes so they can be used by other local classes. This is of course a right royal pain. If there is not an obvious solution to the bogus error message I imagine the next step would be some sort of generic class where you could add each selection option to a sort of COSEL table and each parameters to a list of value pairs. There might even be such a class available in SAP standard.


Anyway there is one local class called LCL_APPLICATION with a single MAIN static method which is called directly after the selection options have been stored. So there are only two commands after the START-OF_SELECTIONS, creating the selection options and then LCL_APPLICATION=>MAIN( ).


Lion’s MAIN


This is a method that is crying out to be made generic, as the vast bulk of the code would be exactly the same in every report.


CLASS lcl_application IMPLEMENTATION.

 
METHOD main.
* Local Variables
   
DATA: ld_report_name TYPE string,
          ld_repid     
TYPE sy-repid.

   
CONCATENATE sy-tcode sy-title INTO ld_report_name
   
SEPARATED BY ' : '.

   
CREATE OBJECT mo_model.
   
CREATE OBJECT mo_view TYPE zcl_bc_view_salv_table.
   
CREATE OBJECT mo_controller
     
EXPORTING
        io_model
= mo_model
        io_view 
= mo_view.

    mo_model
->data_retrieval( ).
    mo_model
->prepare_data_for_ouput( ).

   
"It is bad news to pass system variables as parameters
    ld_repid
= sy-repid.

In the above code the only thing I might want to vary is the TYPE of the view object being created, so I could pass this in as a parameter. Next, if we are online we want to automatically create a screen with a container so we can do funky tricks like adding our own commands programmatically or enabling editing mode, neither of which you can do in full screen mode.


Naturally if we are running a batch job there is no user to invoke the custom commands or edit the data.


In the below code the model never has direct contact with the view but the things that the model wants to say e.g. which fields are editable get passed on regardless. In a simple program like this, the controller and the application class are pretty much the same thing, I only split them out in case you want to go bananas and have the program call lots of different views for some reason.


  IF sy-batch IS INITIAL.
*--------------------------------------------------------------------*
* Calling a SALV report whilst creating a container
* automatically
*--------------------------------------------------------------------*
* Program flow is as follows:-
* ZCL_BC_VIEW_SALV_TABLE->CREATE_CONTAINER_PREPARE_DATA
* Function ZSALV_CSQT_CREATE_CONTAINER
* ZSALV_CSQT_CREATE_CONTAINER->FILL_CONTAINER_CONTENT
* ZCL_BC_VIEW_SALV_TABLE->PREPARE_DISPLAY_DATA
* --> INITIALISE (Generic)
* --> Application Specific Changes (in this program)
* --> Display (Generic)

      mo_view->create_container_prep_display(
   
EXPORTING
      id_title             
= ld_report_name
      id_report_name       
= ld_repid      " Calling program
      id_variant           
= go_selections->p_vari
      if_start_in_edit_mode
= go_selections->p_edit
      id_edit_control_field
= mo_model->md_edit_control_field
      it_editable_fields   
= mo_model->mt_editable_fields
      it_technicals       
= mo_model->mt_technicals
      it_hidden           
= mo_model->mt_hidden
      it_hotspots         
= mo_model->mt_hotspots
      it_subtotal_fields   
= mo_model->mt_subtotal_fields
      it_field_texts       
= mo_model->mt_field_texts
      it_user_commands     
= mo_model->mt_user_commands
   
CHANGING
      ct_data_table       
= mo_model->mt_output_data ).

   
ELSE.
* If this is running in the background there is no way
* in the world we want/need a container, as there is no
* chance for the user to press any user command buttons or
* edit the data, as there is no user, and no screen for the
* container to live on for that matter
      mo_view
->prepare_display_data(
       
EXPORTING
          id_report_name   
= ld_repid
          id_variant       
= go_selections->p_vari
          it_technicals     
= mo_model->mt_technicals
          it_hidden         
= mo_model->mt_hidden
          it_subtotal_fields
= mo_model->mt_subtotal_fields
          it_field_texts   
= mo_model->mt_field_texts
          it_user_commands 
= mo_model->mt_user_commands
       
CHANGING
          ct_data_table     
= mo_model->mt_output_data ).
   
ENDIF."Are we running in the background?

   
IF go_selections->p_email IS NOT INITIAL.
      mo_controller
->send_email( ).
   
ENDIF.

 
ENDMETHOD.                                              "main

ENDCLASS.                    "lcl_application IMPLEMENTATION

In the above code the program flow was detailed very precisely. I am using the same technique SAP itself uses to avoid having to create a screen explicitly and I have talked about this in blogs as well as the book.


In this case, of course we are going to be editing the data so we need a container. A full screen SALV grid has limited functionality e.g. you cannot add commands programmatically to the toolbar, so we virtually always want a container, but having to create the screen and paint a container on it for each report is mundane work which we want to automate.


Going back to the method calls to the “create container” method all those IMPORTING parameter tables like editable fields and hotspots and the like are all optional as not every report will want to take advantage of all of these features, though most will want one or two and of course as time goes by the users will ask for extra things.


The next bunch of code moves all the provided parameters to the equivalent instance variables in the view class, and then creates a screen by the only means available to a method in a class i.e. it calls a function module. There is a standard function module that SAP uses in its own programs for this purpose, but for reasons best known to themselves the created screen has a big hole in it, so I created a Z copy minus the hole.


METHOD zif_bc_alv_report_view~create_container_prep_display.
*--------------------------------------------------------------------*
* Creating a Container Automatically
*--------------------------------------------------------------------*
* The below function creates a screen and a container, and then does
* a callback to method FILL CONTAINER CONTENT of interface
* IF_SALV_CSQT_CONTENT_MANAGER so that the calling class must
* implement method FILL_CONTAINER_CONTENT
* This way for CL_SALV_TABLE we can add our own functions without having
* to create a PF-STATUS
*--------------------------------------------------------------------*
  md_report_name       
= id_report_name.
  md_edit_control_field
= id_edit_control_field.
  mf_start_in_edit_mode
= if_start_in_edit_mode.
  ms_variant
-report    = id_report_name.
  ms_variant
-variant    = id_variant.
  mt_editable_fields[] 
= it_editable_fields[].
  mt_technicals[]     
= it_technicals[].
  mt_hidden[]         
= it_hidden[].
  mt_hotspots[]       
= it_hotspots[].
  mt_subtotal_fields[] 
= it_subtotal_fields[].
  mt_field_texts[]     
= it_field_texts[].
  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.


I just can’t Container myself


For 15 years SAP has been pushing people to use OO programming, but in all that time they have not found a replacement for the CALL SCREEN method for bringing up a user interface, something you cannot do from within the OO framework.


So the recommendation is to use function modules for UI processing as you can do a CALL SCREEN from within a function module. The function module in the code above contains a screen definition, just a screen filled with a big container. The function module creates the container and calls up the screen. In the PBO processing of that screen control is passed back to the calling program.


The calling class (the view class) implements an interface which has the method “fill container content” a method which expects a container object to be supplied. The code below is a copy of the standard SAP code in the PBO section of the function modules screen. When a call is made to the “fill container content” control is returned to our view class.


As a PBO is called every time the user interacts with the screen the code has to make sure the container is created only once.


FORM pbo.

 
SET PF-STATUS 'D0100'.

 
IF gr_container IS INITIAL.
   
IF cl_salv_table=>is_offline( ) EQ if_salv_c_bool_sap=>false.
     
CREATE OBJECT gr_container
       
EXPORTING
          container_name
= 'CONTAINER'.
   
ENDIF.

   
SET TITLEBAR 'STANDARD' WITH g_title.

    gr_content_manager
->fill_container_content(
        r_container
= gr_container ).
 
ENDIF.

ENDFORM.                    "pbo

Once the screen is running the CL_SALV_TABLE is going to handle all the user interaction, the screen is just looping through PBO/PAI until such time as the user presses a CANCEL or BACK or EXIT button to shut down the screen.

Next we will back in the generic section of ZCL_BC_VIEW_SALV_TABLE i.e. in a method which will be exactly the same for every single report and thus does not need to be redefined.

All the code below does is make a call to PREPARE_DISPLAY_DATA. You will have seen in the code above that when we are in the background the PREPARE_DISPLAY_DATA method gets called directly, without having to fluff about creating a screen and container, as there is no user to see the screen.

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
* in the form of importing parameter R_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          " Layout
        if_start_in_edit_mode
= mf_start_in_edit_mode
        id_edit_control_field
= md_edit_control_field
        it_editable_fields   
= mt_editable_fields
        it_technicals       
= mt_technicals
        it_hidden           
= mt_hidden
        it_hotspots         
= mt_hotspots
        it_subtotal_fields   
= mt_subtotal_fields
        it_field_texts       
= mt_field_texts
        io_container         
= r_container 
        it_user_commands     
= mt_user_commands " Toolbar Buttons
     
CHANGING
        ct_data_table       
= <lt_data_table> )." Data Table

 
ENDMETHOD."if_salv_csqt_content_manager~fill_container_content

This might all seem ludicrously complicated, but bear in mind that the code is only written once, and then you never have to bother with it every again, it just gets re-used in every subsequent report. Anyway, the next method that gets executed is in three parts – a generic part which sets up the basic settings for CL_SALV_TABLE, a call to an application specific method which alters the columns in the SALV grid based on the instructions the model class issued, and lastly a simple call to a method to call up the SALV grid.

METHOD zif_bc_alv_report_view~prepare_display_data.
* Step One - Set up the Basic Report
    initialise
(
     
EXPORTING
        id_report_name       
= id_report_name
        id_variant           
= id_variant                      " Layout
        if_start_in_edit_mode
= if_start_in_edit_mode
        id_edit_control_field
= id_edit_control_field
        it_editable_fields   
= it_editable_fields
        io_container         
= io_container 
        it_user_commands     
= it_user_commands
     
CHANGING
        ct_data_table       
= ct_data_table ).

* Step Two – makes changes based on tables sent in by the model
    application_specific_changes
(
      it_technicals 
= it_technicals
      it_hidden     
= it_hidden
      it_hotspots   
= it_hotspots
      it_subtotals 
= it_subtotal_fields
      it_field_texts
= it_field_texts ).

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

 
ENDMETHOD."zif_bc_alv_report_view~prepare_display_data

In the next method called we first of all use the factory method to get an instance of CL_SALV_TABLE linked to the container on the screen our lovely function module called up.

We then add the basic toolbar, followed by any custom report specific commands our model class has said it can respond to. This is a good time to set up the MO_COLUMNS object as alter on we will need this to change attributes of various report columns, like making them hotspots or whatever.

We set some handlers for when a user double clicks on a cell in the grid, or presses a n icon in the toolbar. Both actions will cause the CL_SALV_TABLE to raise an event which our custom class needs to handle (in fact all our class does is raise a corresponding event of it’s own for the controller to respond to).

  METHOD zif_bc_alv_report_view~initialise.

   
TRY.
*--------------------------------------------------------------------*
* If we have a container, then we can add our own user defined
* commands programtaically
*--------------------------------------------------------------------          cl_salv_table=>factory(
           
EXPORTING
              r_container 
= io_container
           
IMPORTING
              r_salv_table
= mo_alv_grid
           
CHANGING
              t_table     
= ct_data_table[] ).

          display_basic_toolbar
( ).
         
IF it_user_commands[] IS NOT INITIAL.
            add_commands_to_toolbar
( it_user_commands ).
         
ENDIF.

        mo_columns
= mo_alv_grid->get_columns( ).
        set_layout
( id_variant ).
        set_handlers
( ).

*--------------------------------------------------------------------*
At long last, this is where things start getting interesting i.e. we are going to set things up so that certain columns are editable i.e. the purported purpose of this entire blog.

In the blogs I linked to at the start the subject of creating a custom class ZCL_SALV_MODEL was discussed, this class has one purpose in life, and that is to get hold of the underlying CL_GUI_ALV_GRID instance which lives hidden like Rapunzel in the tower that is CL_SALV_TABLE. This is a subclass of CL_SALV_MODEL_LIST. As we shall see later it has a method for climbing up the tower and rescuing the princess from the evil Rumpelstiltskin who is head of ABAP development at SAP. Or getting the underlying grid object – it’s one or the other; I can’t remember which offhand.

There is another method to the class called “set editable”. This was the miracle solution discovered by Naimesh whereby our custom code can respond to the event raised when the SALV object is ready to burst onto our screen, so we can hold it up for a second and make it editable.

We will get into that code in a minute, and discuss the slight additions I have made to it, and afterwards discuss the other method call in the code below, which changes the data table such that certain columns can be edited. To make a column editable in the SALV (or indeed in CL_GUI_ALV_GRID) both the data in the table and the field catalogue have to be fiddled with.

*--------------------------------------------------------------------*
       
DATA: lo_salv_model TYPE REF TO cl_salv_model.

       
"Narrow casting
       
"CL_SALV_MODEL is a superclass of CL_SALV_TABLE
       
"Target = LO_SALV_MODEL = CL_SALV_MODEL
       
"Source = MO_ALV_GRID  = CL_SALV_TABLE
        lo_salv_model ?= mo_alv_grid
.

       
"Object to access underlying CL_GUI_ALV_GRID
       
CREATE OBJECT mo_salv_model
         
EXPORTING
            io_model
= lo_salv_model.

       
IF if_start_in_edit_mode = abap_true.
         
"Prepare the Field Catalogue to be Editable
          mo_salv_model
->set_editable(

          io_salv              = mo_alv_grid
          id_edit_control_field
= id_edit_control_field
          it_editable_fields   
= it_editable_fields ).
         
"Prepare the Data Table to be Editable
          make_column_editable
(
           
EXPORTING id_edit_control_field = id_edit_control_field
                      it_editable_fields   
= it_editable_fields
           
CHANGING  ct_data_table        = ct_data_table ).
       
ENDIF.

     
CATCH cx_salv_msg.
       
MESSAGE 'Report in Trouble' TYPE 'E'.
   
ENDTRY.

 
ENDMETHOD.                    "zif_bc_alv_report_view~initialise

It is time to introduce yet another custom class ZCL_BC_SALV_EVENT_HANDLER. This class has the job of responding to the event raised when the SALV has finished building itself and is ready to be displayed (the REFRESH event). The code below I copied from the prior blog, all I added was some lines to store a table of what fields (columns) we want to make editable, and what the name of the control field in the data table is. A control field is one that has the structure LVC_T_STYL. I usually call such a field CELLTAB but I could call it FRUIT_LOOPS if I wanted, so I don’t want to hard code the field name, instead the model declares what such a field is named. The table of editable fields and the control field will be used later when the “refresh” event of the SALV is called.

METHOD set_editable.
* Local Variables
 
DATA: lo_event_handler TYPE REF TO zcl_bc_salv_event_handler.

 
"Ensure one, and only one, static event handler exists
 
IF zcl_salv_model=>mo_event_handler IS NOT BOUND.
   
CREATE OBJECT zcl_salv_model=>mo_event_handler
     
TYPE zcl_bc_salv_event_handler.
 
ENDIF.

  lo_event_handler ?= zcl_salv_model
=>mo_event_handler.

  lo_event_handler
->md_edit_control_field = id_edit_control_field.
  lo_event_handler
->mt_editable_fields    = it_editable_fields.

 
APPEND io_salv TO lo_event_handler->mt_salv.

 
"At such time as any SALV object is displayed, call the
 
"after refresh event to make the grid editable
 
SET HANDLER lo_event_handler->on_after_refresh
   
FOR ALL INSTANCES
    ACTIVATION
'X'.

 
"Sometimes the icons needed for an editable grid do not
 
"display, so we have to force the issue
 
IF io_salv->get_display_object( ) = 3.
   
SET HANDLER lo_event_handler->on_toolbar
     
FOR ALL INSTANCES
      ACTIVATION
'X'.
 
ENDIF.

ENDMETHOD.

Now we come to a method in ZCL_BC_VIEW_SALV_TABLE called “make column editable”. This takes in the data table from the model and changes the control field in each row of the data table so that our desired columns are made ready to be editable. You will see I have not quite finished it yet as there are comments saying what I still need to do. As mentioned at the start of the blog I was going to wait till I was 100% ready, but that would mean this blog never be written, as I am never 100% satisfied with the code I write.

METHOD make_column_editable.
* Local Variables
 
DATA :ls_celltab      TYPE lvc_s_styl,
        lt_celltab     
TYPE lvc_t_styl,
        ld_index       
TYPE sy-tabix,
        ldo_table_line 
TYPE REF TO data,
        ld_editable_field
LIKE LINE OF it_editable_fields.

 
FIELD-SYMBOLS: <ls_data_table> TYPE any,
                <ls_celltab>   
TYPE lvc_s_styl,
                <lt_celltab>   
TYPE lvc_t_styl.

* Dynamically create work area for looping through the table
* that was passed in
 
CREATE DATA ldo_table_line LIKE LINE OF ct_data_table.

 
ASSIGN ldo_table_line->TO <ls_data_table>.

 
LOOP AT ct_data_table ASSIGNING <ls_data_table>.

   
"Need a TRY/CATCH block here – “if the control field is not of type LVC_T_STYL
   
"then a system generated exception will be thrown
   
ASSIGN COMPONENT id_edit_control_field OF STRUCTURE <ls_data_table>

    TO <lt_celltab>.

   
IF sy-subrc <> 0.
     
"We cannot go on, the control field is not in the structure
     
"Need a fatal error here, violated pre-condition
     
RETURN.
   
ENDIF.

   
LOOP AT it_editable_fields INTO ld_editable_field.

     
READ TABLE <lt_celltab> ASSIGNING <ls_celltab>

      WITH KEY fieldname = ld_editable_field.

     
IF sy-subrc <> 0.
        ld_index           
= sy-tabix.
        ls_celltab
-fieldname = ld_editable_field.
       
INSERT ls_celltab INTO <lt_celltab> INDEX ld_index.
       
READ TABLE <lt_celltab> ASSIGNING <ls_celltab>

        WITH KEY fieldname = ld_editable_field.
     
ENDIF.

     
IF <ls_celltab>-style EQ cl_gui_alv_grid=>mc_style_enabled.
        <ls_celltab>
-style = cl_gui_alv_grid=>mc_style_disabled."Read Only
     
ELSE.
        <ls_celltab>
-style = cl_gui_alv_grid=>mc_style_enabled."Editable
     
ENDIF.

   
ENDLOOP."List of Editable Fields
 
ENDLOOP."Lines of the Data Table

ENDMETHOD.”Make Column Editable

Originally I had the “application specific changes” method redefined in every calling report, but I have changed this design to instead have the model class saying what fields it wants changed e.g. length changed, turned into a hotspot, description changed etc.

A certain German someone will say my model class is the devil incarnate and that I certainly shouldn’t be using it to say what the column headings should be. Well you know what? As Chas and Dave would say “I don’t care. I don’t care, I don’t care, I don’t care if he comes round here, I’ve got my model class on the sideboard here, let your mother sort it out if he comes round here.”

METHOD zif_bc_alv_report_view~application_specific_changes.
**********************************************************************
* The job of the model is to say waht fields can be drilled into, and what
* alternative names they have etc...
* The job of the view is to realise this technically
* Since this is CL_SALV_TABLE we cannot make fields editable here, but
* we can do all the other adjustments needed
**********************************************************************
* Local Variables
 
DATA: lo_error          TYPE REF TO cx_salv_msg,
        lo_data_error   
TYPE REF TO cx_salv_data_error,
        lo_not_found     
TYPE REF TO cx_salv_not_found,
        ls_error         
TYPE bal_s_msg,
        lf_error_occurred
TYPE abap_bool,
        ld_field_name   
TYPE lvc_fname,
        ls_alv_texts     
TYPE zsbc_alv_texts.

 
TRY.
     
IF if_optimise_column_widths = abap_true.
        optimise_column_width
( ).
     
ENDIF.

* Technical Fields
     
LOOP AT it_technicals INTO ld_field_name.
        set_column_attributes
( id_field_name  = ld_field_name
                              if_is_technical
= abap_true ).
     
ENDLOOP.
* Hidden Fields
     
LOOP AT it_hidden INTO ld_field_name.
        set_column_attributes
( id_field_name  = ld_field_name
                              if_is_visible 
= abap_false ).
     
ENDLOOP.
* Hotspots
     
LOOP AT it_hotspots INTO ld_field_name.
        set_column_attributes
( id_field_name  = ld_field_name
                              if_is_hotspot 
= abap_true ).
     
ENDLOOP.
* Renamed Fields / Tooltips
     
LOOP AT it_field_texts INTO ls_alv_texts.
       
IF ls_alv_texts-tooltip IS NOT INITIAL.
          set_column_attributes
( id_field_name = ls_alv_texts-field_name
                                id_tooltip   
= ls_alv_texts-tooltip ).
       
ENDIF.
       
IF ls_alv_texts-long_text IS NOT INITIAL.
          set_column_attributes
( id_field_name = ls_alv_texts-field_name
                                id_long_text 
= ls_alv_texts-long_text ).
       
ENDIF.
       
IF ls_alv_texts-medium_text IS NOT INITIAL.
          set_column_attributes
( id_field_name  = ls_alv_texts-field_name
                                id_medium_text
= ls_alv_texts-medium_text ).
       
ENDIF.
       
IF ls_alv_texts-short_text IS NOT INITIAL.
          set_column_attributes
( id_field_name = ls_alv_texts-field_name
                                id_short_text
= ls_alv_texts-short_text ).
       
ENDIF.
     
ENDLOOP.
* Subtotals
     
LOOP AT it_subtotals INTO ld_field_name.
        set_column_attributes
( id_field_name  = ld_field_name
                              if_is_subtotal
= abap_true ).
     
ENDLOOP.

   
CATCH cx_salv_not_found INTO lo_not_found.
      lf_error_occurred
= abap_true.
     
"Object = Column
     
"Key    = Field Name e.g. VBELN
      zcl_dbc
=>require( that            = |{ lo_not_found->object } { lo_not_found->key } must exist|
                        which_is_true_if
= boolc( lf_error_occurred = abap_false ) ).
   
CATCH cx_salv_data_error INTO lo_data_error.
      ls_error
= lo_data_error->get_message( ).
     
MESSAGE ID ls_error-msgid TYPE 'E' NUMBER ls_error-msgno
             
WITH ls_error-msgv1 ls_error-msgv2
                  ls_error
-msgv3 ls_error-msgv4.
   
CATCH cx_salv_msg INTO lo_error.
      ls_error
= lo_error->get_message( ).
     
MESSAGE ID ls_error-msgid TYPE 'E' NUMBER ls_error-msgno
             
WITH ls_error-msgv1 ls_error-msgv2
                  ls_error
-msgv3 ls_error-msgv4.
 
ENDTRY.

ENDMETHOD.”Application Specific Changes

You will notice a nice lot of error handling at the end, errors raised here indicate a serious bug in the calling program –i.e. trying to manipulate a field which does not exist - and so should stop processing dead until the bug is corrected.

I am using the “design by contract” class here…

http://scn.sap.com/community/abap/blog/2012/09/08/design-by-contract-in-abap

… to make it crystal clear that the calling program should not be trying to change a field that is not in the output structure. If it does then the program will not run until such time as the bug is fixed.

The “display” method in ZCL_BC_VIEW_SALV_TABLE just calls the “display” method of CL_SALV_TABLE. If we were using a different UI technology maybe the “display” method would have to be more complicated. In any event the time has come, the Walrus said, to actually display the SALV grid on the screen.

At some point in the bowels of the CL_SALV_TABLE “display” method the event “on after refresh” is raised (this is an event of the CL_GUI_ALV_GRID that lives inside the SALV) and our custom event handler class has been set up to intercept that event.

The On After Refresh Prince of Bel Air

Let us have a sticky beak at the two methods in the ZCL_BC_SALV_EVENT_HANDLER class. First off we shall look at the ON_AFTER_REFRESH method. During creation the event handler class was (optionally) passed a list of editable fields and the name of the edit control field. If that information was not passed in at the time of creation, the code below will just make the whole grid editable.

If a list of editable fields was passed in the field catalogue is modified to make the desired fields editable. We could not do this earlier as the CL_GUI_ALV_GRID hidden inside CL_SALV_TABLE is not instantiated till the DISPLAY method is called.

The instance of the SALV lives inside an internal table of SALV instances called MT_SALV. The import parameter “sender” in the code below refers to the CL_GUI_ALV_GRID instance that sent the “on after refresh” method. Thus you could have a screen with more than one SALV on it, and the correct grid would be processed.

METHOD on_after_refresh.
*--------------------------------------------------------------------*
* What we are doing here is enabling the SALV GRID to open in editable
* mode
*--------------------------------------------------------------------*
* Local Variables
 
DATA: lo_grid            TYPE REF TO cl_gui_alv_grid.
 
DATA: ls_layout          TYPE lvc_s_layo,
        lt_fcat           
TYPE lvc_t_fcat,
        ls_editable_fields
LIKE LINE OF mt_editable_fields.
 
DATA: lo_salv            TYPE REF TO cl_salv_table.
 
DATA: lo_salv_model      TYPE REF TO cl_salv_model,
        lo_sneaky_model   
TYPE REF TO zcl_salv_model.

 
FIELD-SYMBOLS: <ls_fcat> LIKE LINE OF lt_fcat.

 
TRY .
     
LOOP AT mt_salv INTO lo_salv.
       
"Narrow casting
       
"CL_SALV_MODEL is a superclass of CL_SALV_TABLE
       
"Target = LO_SALV_MODEL = CL_SALV_MODEL
       
"Source = MO_ALV_GRID  = CL_SALV_TABLE
        lo_salv_model ?= lo_salv
.

       
"Object to access underlying CL_GUI_ALV_GRID
       
CREATE OBJECT lo_sneaky_model
         
EXPORTING
            io_model
= lo_salv_model.

        lo_grid
= lo_sneaky_model->get_alv_grid( ).
       
CHECK lo_grid EQ sender.

       
"Deregister the event handler
       
"i.e. we do not want to keep calling this every time
       
"the user refreshes the display.
       
"Once the report is running the user can control whether
       
"the grid is editable by using the icons at the top of the screen
       
SET HANDLER me->on_after_refresh
         
FOR ALL INSTANCES
          ACTIVATION space
.

       
"Set editable
       
IF md_edit_control_field IS NOT INITIAL.
         
"Make certain fields editable based on FIELDCAT
          ls_layout
-stylefname = md_edit_control_field.
lo_grid
->get_frontend_fieldcatalog( IMPORTING et_fieldcatalog = lt_fcat ).
         
LOOP AT mt_editable_fields INTO ls_editable_fields.
           
READ TABLE lt_fcat ASSIGNING <ls_fcat>

            WITH KEY fieldname = ls_editable_fields.
           
IF sy-subrc = 0.
              <ls_fcat>
-edit = abap_true.
           
ENDIF.
         
ENDLOOP.
          lo_grid
->set_frontend_fieldcatalog( lt_fcat ).
       
ELSE.
         
"Make everything editable
          ls_layout
-edit = 'X'.
       
ENDIF.
        lo_grid
->set_frontend_layout( ls_layout ).
        lo_grid
->set_ready_for_input( 1 ).
     
ENDLOOP.
   
CATCH cx_salv_error.

 
ENDTRY.

ENDMETHOD.”On after refresh method of ZCL_BC_SALV_EVENT_HANDLER

Toolbar Bar Bar, Bar Barbara Ann

The next problem – as solved in the blog by Naimesh - is that the SALV does not expect the grid to be editable, so some ICONS in the toolbar you would normally expect for editable grids are missing. This is really only applicable when all the fields are editable, as that is the only situation where copying a row makes sense, as then you would change a key field. If only one field is editable you would not want the user to be able to delete a row either.

The most important thing for me is to get the separators working! There is no point in my regurgitating the code here – you can see it in the other blog, and I have not changed anything, though I am going to add something to only add the extra buttons if we do not have a specific list of editable fields.

Lord Editable Column-Ostomy Bag

Now we see the final result! The grid opens with several columns editable.


List of Ingredients

Here are the custom objects I had to create to get this working. In lots of ways it doesn’t matter how many were needed as apart from the calling report they are all totally generic and can get re-used again and again.

·        Calling Report

·        Interface ZIF_BC_ALV_REPORT_VIEW (UI Technology Agnostic)

·        Class ZCL_BC_VIEW_SALV_TABLE (SALV Specific)

·        Class ZCL_SALV_MODEL (to access the CL_GUI_ALV_GRID)

·        Class ZCL_BC_SALV_EVENT_HANDLER (for opening in edit mode)

·        Function Module ZSALV_CSQT_CREATE_CONTAINER (to create screen)

I will whip up a SAPLINK file and attach it to this blog at some point in the near future.

Going Forward

As I keep stressing this is a work in progress, I am going to have concentrate on my presentation for the SAP Australian Users Group next week (SAUG, pronounced “Sausage”), and then on my presentation for SAP TECHED in Las Vegas in October (boast boast) and then I will be able to work on the next iteration of this.

In the interim I am happy to take questions, or suggestions for improvements.

Cheersy Cheers

Paul

10/11/2015 - I have attached the SAPLINK nugget (as a text file, as the SAP community network does not allow SAP specific formats like nuggets) - please see my comments below for some manual steps you will unfortunately have to make after importing the nugget to your system.

46 Comments