Change order of table rows in a user-friendly way - abap

Before starting development I'm looking for a standard way of doing something like that: I need to implement user-friendly way for ordering table rows in standard Dynpro ALV grid. I think it should look like a form where filter columns are defined, like it implemented, for example, in standard function module LVC_FILTER.

No, there is no standard functionality for this. However you can do this (manual table sort by drag-and-drop) programmatically by inserting/deleting itab rows in runtime. This can be be implemented using standard ALV events: ondrag, ondrop, and ondropcomplete.
Try these code samples for implementing methods:
method handle_grid_ondrag.
data: data_object type ref to drag_drop_object,
help_row like line of gt_outtab. "#EC NEEDED
read table gt_outtab_2 into help_row index es_row_no-row_id.
create object data_object.
move es_row_no-row_id to data_object->index.
read table gt_outtab_2 into data_object->wa_test index
es_row_no-row_id.
e_dragdropobj->object = data_object.
endmethod.
_
method handle_grid_ondrop.
data: data_object type ref to drag_drop_object,
drop_index type i,
help_row like line of gt_outtab. "#EC NEEDED
delete gt_outtab_2 index data_object->index.
insert data_object->wa_test into gt_outtab_2 index e_row-index
endmethod.
_
method handle_grid_ondropcomplete.
if data_cel = ' '.
call method grid->refresh_table_display.
endif.
endmethod.
Refer to sample program BCALV_TEST_DRAG_DROP_02 if you have difficulties.

Related

How to add old HR INCLUDE into local class?

So I need to use the INCLUDES rpcblo00 and rpcbdt00 to get the type of infotype change (create, update, delete). Beforehand I used a subroutine that had no problem with the includes, but I cannot get them into a class for the life of me.
If I try to put the include into the method as described here (it's even about the same HR include), I get the following error (because of the minus in lo-key):
Syntax error: Names may only consist of the characters "A-Z", "0-9"
and "_". In addition, they may not begin with a number.
minimal reproducible example:
CLASS lcl_infotypaenderungen DEFINITION.
PUBLIC SECTION.
TYPES: tty_aenderungs_operationen TYPE STANDARD TABLE OF pc403.
METHODS:
constructor
IMPORTING is_aenderungs_kopf TYPE pldoc_key,
get_aenderungs_operationen
RETURNING value(rt_aenderungs_operationen) TYPE tty_aenderungs_operationen.
PRIVATE SECTION.
DATA: s_aenderungs_kopf TYPE pldoc_key,
t_aenderungs_operationen TYPE tty_aenderungs_operationen.
METHODS:
select_aenderungs_operationen.
ENDCLASS. "lcl_infotypaenderungen DEFINITION
*----------------------------------------------------------------------*
TYPE-POOLS: abap.
DATA: lo_infotypaenderungen TYPE REF TO lcl_infotypaenderungen,
lv_fehler TYPE sy-subrc,
lt_log_kopf TYPE pldoc_key_tab WITH HEADER LINE,
lt_log_felder TYPE TABLE OF hrinftylog_fields,
lt_infotyp_vorher TYPE prelp_tab,
lt_infotyp_nachher TYPE prelp_tab,
lt_aenderungs_operationen TYPE STANDARD TABLE OF pc403.
FIELD-SYMBOLS: <log_kopfzeile> TYPE pldoc_key.
*----------------------------------------------------------------------*
CALL FUNCTION 'HR_INFOTYPE_LOG_GET_LIST'
EXPORTING
tclas = 'A'
begda = '20190315'
endda = '20190315'
IMPORTING
subrc = lv_fehler
TABLES
infty_logg_key_tab = lt_log_kopf.
CLEAR lv_fehler.
SORT lt_log_kopf DESCENDING BY infty bdate btime pernr.
LOOP AT lt_log_kopf ASSIGNING <log_kopfzeile>.
CALL FUNCTION 'HR_INFOTYPE_LOG_GET_DETAIL'
EXPORTING
logged_infotype = <log_kopfzeile>
IMPORTING
subrc = lv_fehler
TABLES
infty_tab_before = lt_infotyp_vorher
infty_tab_after = lt_infotyp_nachher
fields = lt_log_felder.
CREATE OBJECT lo_infotypaenderungen
EXPORTING
is_aenderungs_kopf = <log_kopfzeile>.
REFRESH lt_aenderungs_operationen.
lt_aenderungs_operationen = lo_infotypaenderungen->get_aenderungs_operationen( ).
ENDLOOP.
*----------------------------------------------------------------------*
CLASS lcl_infotypaenderungen IMPLEMENTATION.
METHOD constructor.
me->s_aenderungs_kopf = is_aenderungs_kopf.
me->select_aenderungs_operationen( ).
ENDMETHOD. "constructor
METHOD select_aenderungs_operationen.
INCLUDE rpcblo00. """ <---
INCLUDE rpcbdt00. """ <---
lo-key-tclas = me->s_aenderungs_kopf-tclas.
lo-key-pernr = me->s_aenderungs_kopf-pernr.
lo-key-infty = me->s_aenderungs_kopf-infty.
lo-key-bdate = me->s_aenderungs_kopf-bdate.
lo-key-btime = me->s_aenderungs_kopf-btime.
lo-key-seqnr = me->s_aenderungs_kopf-seqnr.
IMPORT header TO me->t_aenderungs_operationen FROM DATABASE pcl4(la) ID lo-key.
ENDMETHOD. "select_aenderungs_operationen
METHOD get_aenderungs_operationen.
rt_aenderungs_operationen = me->t_aenderungs_operationen.
ENDMETHOD. "get_aenderungs_operationen
ENDCLASS. "lcl_infotypaenderungen IMPLEMENTATION
Anyone know a good solution? Thanks in advance
Edit: The includes have some declarations and a makro reading from a data cluster. Of course I could just put those directly into the method, but I would like to avoid that (for now I did that).
Alternatively, does someone know of a different way to get the change operation per infotype line?
If you use your class as a local one then the only way to use these includes is to put them at the very beginning of the program. The downside is of course that the variables there become global but unfortunately there is no other way to do that and for sure not if you want to use a global class after all (not sure if your minimal working example is just simplified to use a local class instead of global or not).
REPORT ZZZ.
INCLUDE rpcblo00. """ <---
INCLUDE rpcbdt00. """ <---
CLASS lcl_infotypaenderungen DEFINITION.
" ...
Thanks to Jagger I can make it work with a local class, but in case anyone later wonders how you need to change the include code to be able to use it in a global method, you basically just need to get rid of INCLUDE STRUCTURE declarations and exchange tables with a header line.
So
DATA BEGIN OF LO-KEY.
INCLUDE STRUCTURE PC400.
DATA END OF LO-KEY.
becomes
DATA: lo_key TYPE pc400.
And
DATA BEGIN OF BELEGE_00 OCCURS 100.
DATA:
SPLKZ(01) TYPE X,
FIELD(10) TYPE C,
FTYPE(04) TYPE C,
FLENG(03) TYPE N,
DECIM(02) TYPE N,
OLDDT(50) TYPE C,
NEWDT(50) TYPE C.
DATA END OF BELEGE_00.
becomes
TYPES: BEGIN OF ty_belege,
splkz(01) TYPE x,
field(10) TYPE c,
ftype(04) TYPE c,
fleng(03) TYPE n,
decim(02) TYPE n,
olddt(50) TYPE c,
newdt(50) TYPE c,
END OF ty_belege.
DATA: belege_00 TYPE STANDARD TABLE OF ty_belege.
The macro can stay the same (or I guess you could rewrite it).

Convert dynamic REF TO DATA structure to static

I created structure with three components, one of which is type ref to data and a table type of this structure. The problem is, how do I add data to this table?
It always has three components, but only one of them is discovered during processing, I always know two of them. Thus I always use the entire table type ref to data and then determine the type of this structure and create the table on it.
The issue here is that by doing this, even though I know two of the components, the whole itab will be dynamic, so I must use it in methods exporting/importing a type ref to data, which is inconvenient.
The method below will always return a table type ref to data, which is completely dynamic (type ref to data), but the structure of the table will always be like this:
component 1 -> type pc261.
component 2 -> type pay99_international.
compoment 3 -> well this is always a mistery hehe
methods get_payroll
importing it_rgdir type hrpy_tt_rgdir
returning value(rt_value) type ref to data.
method get_payroll.
field-symbols: <lt_payroll> type standard table.
create data rt_value type standard table of (mv_py_struct_type).
assign rt_value->* to <lt_payroll>.
...
endmethod.
My intention was to have the returning value with another type, a known type, with which I can use the two known components more easily. The idea I had was to create a type with only the unknown field as ref to data, than have a table of it.
This way, I would be able to use it inside methods without having to work so "dynamicaly", which altough works perfectly, is kind of difficult to understand only by reading the code.
types begin of gty_s_generic_payroll.
types evp type pc261.
types inter type pay99_international.
types nat type ref to data.
types end of gty_s_generic_payroll.
types gty_t_generic_payroll type table of gty_s_generic_payroll.
The problem is, how to use an itab of type gty_t_generic_payroll as declared above?
I must somehow create the component 3, but I have no idea how to do it...
At the end, I have a generic field-symbol, that is type table, that has the two known components + the third one that was discovered during processing time.
So how can I pass the content of this field symbol to a table type gty_t_generic_payroll?
data lt_payroll type ref to data.
field-symbols <lt_payroll> type any table.
lt_payroll = mo_payroll->get_payroll( lt_rgdir ). "this will return type ref to data
assign lt_payroll->* to <lt_payroll>.
After executing this code <lt_payroll> has all the values, but it is a dynamic table where I cannot use components <lt_payroll>[1]-inter.
So how to pass to gty_t_generic_payroll-typed variable, so that I can access components without much dynamics?
Given your target structure and table like this:
TYPES:
BEGIN OF payroll_row_type,
known_first_component TYPE something_we_know,
known_second_component TYPE something_else_we_know,
discovered_component TYPE REF TO data,
END OF payroll_row_type.
TYPES payroll_table_type TYPE STANDARD TABLE OF payroll_row WITH EMPTY KEY.
If you now have another table, whose type at runtime is:
TYPES:
BEGIN OF discovered_row_type,
known_first_component TYPE something_we_know,
known_second_component TYPE something_else_we_know,
known_third_component TYPE some_data_type,
END OF discovered_row_type.
TYPES discovered_table_type TYPE STANDARD TABLE OF discovered_row WITH EMPTY KEY.
You can move one to the other with
DATA source TYPE discovered_table_type.
DATA target TYPE payroll_table_type.
DATA resolved_component TYPE REF TO DATA.
DATA(descriptor) =
cl_abap_elemdescr=>describe_by_data( source_row-known_third_component ).
LOOP AT source INTO DATA(source_row).
DATA(target_row) =
VALUE payroll_row_type(
known_first_component = source_row-known_first_component
known_second_component = source_row-known_second_component ).
CREATE DATA target_row-discovered_component TYPE descriptor.
ASSIGN source_row-known_third_component TO FIELD-SYMBOL(<source_component>).
ASSIGN target_row-discovered_component TO FIELD-SYMBOL(<target_component>).
<target_component> = <source_component>.
INSERT target_row INTO TABLE target.
ENDLOOP.
The question and answers may look confusing for future visitors (what is the actual question?), so here is my two cents.
Summary of the question :
You call an external code (1) which gives you an internal table generated dynamically, but you know that all the components are always the same except one which varies but is at the same position, so you'd like to refer to its components statically, except for the one which varies.
(1) so, you can't adapt it.
Your workaround is to define an equivalent internal table statically and the component which varies will be defined as a data reference type (pointer to any data object), then to initialize it by copying the data from the dynamic internal table.
You ask for another better solution because yours consumes extra memory (two internal tables) and decreases the performance (copy process).
Answer :
No better solution
It looks like I was able to pass the values from the fully generic table (type ref to data) to another table that is 1/3 generic (type gty_t_generic_payroll):
methods get_payroll
importing it_rgdir type hrpy_tt_rgdir
returning value(rt_value) type gty_t_generic_payroll.
method get_payroll.
data lt_payroll type gty_t_generic_payroll.
data lt_payroll_aux type ref to data.
field-symbols: <lt_payroll_aux> type standard table.
create data lt_payroll_aux type standard table of (mv_py_struct_type).
assign lt_payroll_aux->* to <lt_payroll_aux> .
call function ' '.
call function ' '
exporting
= mv_relid
= mv_pernr
= xsdbool( gs_parm-use_natio <> abap_true )
tables
= it_rgdir
= <lt_payroll_aux> "table with the values I need
exceptions
= 0.
if sy-subrc <> 0.
return.
endif.
loop at <lt_payroll_aux> assigning field-symbol(<ls_payroll_aux>).
assign component 1 of structure <ls_payroll_aux> to field-symbol(<evp>).
assign component 2 of structure <ls_payroll_aux> to field-symbol(<inter>).
assign component 3 of structure <ls_payroll_aux> to field-symbol(<nat>).
data(ls_value) = value gty_s_generic_payroll(
evp = <evp>
inter = <inter>
).
get reference of <nat> into ls_value-nat.
append ls_value to rt_value. "returning table, with values I need and
"now with 2/3 known types
endloop.
endmethod.
At the end of the day, I acomplished what I needed, but unfortunately I do loose a lot of performance, since I must loop twice in the results now.
to populate the not-so-dynamic-table
to do the actual process of the report (at least it gets pretier lol)
This is the only way because I can't simply use insert lines of dynamic_itab to not_so_dynamic_itab, since the third component is reference .

How to import parameter of type ANY?

How can I put what a method - in this example get_properties - is giving me into a local variable when the type of the parameter is ANY?
"ES_ATTRIBUTES Exporting Type ANY
some_object->get_properties( IMPORTING es_attributes = ????? ).
I tried to put it into this variable, but that didn't work:
FIELD-SYMBOLS:
<ls_attributes> TYPE any.
In ABAP, it means that you may use a data object of any type (the simplest way is to declare it with DATA).
But it may be more restrictive according to the way the developer has coded his method.
Here, I recognize a method of WebUI Components (CRM, SOLMAN, …) so the data object must correspond to the "some_object" you are accessing. Do a debug of GET_PROPERTIES if you are not sure.
Actually as a caller, you should know the type you want to import for this ANY parameter.
You have to know the protocol of GET_PROPERTIES and debug it to know the return type of the parameter. In your method, you create a DATA REFERENCE and have it assigned to a ANY field symbol.
Data:
lr_data type ref to data.
Field-symbols:
<lt_properties> type any.
create data lr_data type TYPE_NAME. 'You should know the type
assign lr_data->* to <lt_properties>.
From my personal view, it is not a very good practice to define a method with EXPORTING parameter type ANY.
You either define a interface with IF_**_PROPERTY and you have a return TABLE of this interface.
or you return a name-value pair table. (From the method signature, it should return a TABLE, GET_PROPERTIES).

Pass table parameter to FM via selection screen

I need to call a function that has a table type as import parameter in a program. I thought about doing this with a selection screen but I can't use deep structures as parameters. When I 'TEST' that function module it shows me a thing where I can add multiple entries and submit everything in the end. Can I get something similar during the execution of a program?
edit: I have to offer a program that calls the function module create_skill_profile.
1
2
3
You can call the function module RS_COMPLEX_OBJECT_EDIT in your report for editing a complex structure. This is the same function module that is used for editing test data in the function module single test.
So, in your report, you could ask for the name of the desired type (if that has to be a dynamic one), and then, in start-of-selection, you can create a data object of this type and pass it to RS_COMPLEX_OBJECT_EDIT to let the user fill it.
A serious limitation of RS_COMPLEX_OBJECT_EDIT is that it can't handle sorted or hashed tables as input. So all the components of your complex structure, if they are of table kind, they have to be standard tables.
What I understand: You want to call a function module that requires a table as import parameter. The table's rows are filled from user input. The number of rows is dynamic.
Approaches:
1) use selection screen with predefined input fields and show/hide them dynamically via PAI (AT SELECTION-SCREEN (on xxx). LOOP AT SCREEN.) then build your table and call your function module on START-OF-SELECTION.
2) show editable ALV grid with table structure. Implement an application toolbar button or use SAVE button to let the user call your function module when he finished inserting his input.
I would defenitely prefer 2), although custom input validation is a bit tricky. But if the required user input is the same as ddic defined table structure the input validation happens automatically.

Method call as a parameter for another method call?

i'm new in abap (OO) but developed before in java and wrote a class abap "cl_caretaker" which should handle the operations on database table and the local copy (intern table) of it.
I want to make the following method call:
caretaker->show_table( caretaker->get_users( ) ) .
with:
caretaker = cl_caretaker=>get_instance( ). "singleton instance
METHODS:
"! get a list of all user which registrated for FCP
"!
"! #parameter rt_users | users which are registrated for FCP
get_users
RETURNING value(rt_users) TYPE itty_users,
"! shows the content of a table
"!
"! #parameter it_table | the table we want to visualize
show_table
IMPORTING
value(it_table) TYPE ANY TABLE.
if I split the call in two and store the result of get_users in a tmp variable it works.
DATA:
gt_tmp_users TYPE caretaker->itty_users.
gt_tmp_users = caretaker->get_users( ).
caretaker->show_table( gt_tmp_users ).
So my questions are:
1) is a call like: caretaker->show_table( caretaker->get_users( ) ).
possible and if how?
2) I also tried to create a generic variable, which stores all kind of tables.
Because i don't want to create for each table kind i use a tmp/help variable.
But i got the information that only (german: Formalparameter) dummy parameters of method definitions are allowed to of generic type (eg. TYPE any TABLE ).
Here some stuff I already tried:
DATA:
* tmpanytable TYPE TABLE OF any.
* tmpAnyTable TYPE any.
tmpanytable TYPE REF TO data.
" needed to store a temporal table
FIELD-SYMBOLS: <tmpanytable> TYPE ANY TABLE.
* ASSIGN caretaker->get_users( ) TO <tmpAnyTable>.
* <tmpAnyTable> = caretaker->get_users( ).
* caretaker->get_users( ).
*caretaker->show_table( <tmpAnyTable> ).
*caretaker->show_table( caretaker->get_users( ) ).
*CALL METHOD: caretaker->show_table( IMPORTING it_table = caretaker->get_users ).
*CALL METHOD: caretaker->show_table( it_table = caretaker->get_users( ) ).
*COMPUTE caretaker->show_table( it_table = caretaker->get_users( ) ).
*ASSIGN caretaker->get_users() ->* to <tmpAnyTable>.
*Caretaker->show_table( <tmpAnyTable> ).
*call METHOD caretaker->show_table
* Exporting It_table = caretaker->get_users( ).
* CREATE DATA tmpanytable TYPE STANDARD TABLE OF (dbtab)
* WITH NON-UNIQUE DEFAULT KEY.
* ASSIGN tmpanytable->* TO <tmpanytable>.
* CREATE DATA tmpanytable TYPE tabkind OF any Table .
* ASSIGN tmpanytable->* TO <tmpanytable>.
*GET REFERENCE OF caretaker->get_users() INTO tmpAnyTable.
*caretaker->show_table( tmpAnyTable ) .
Method chaining is possible, and methods in operand positions are possible as well, but you need at least SAP_ABA 702 for that.
You can use generic types to pass a table around without knowing its type at runtime. However, you can't create a table without knowing its type. Comparing it to OO principles, you can handle references to an abstract superclass and pass them along between components, but you can't instantiate the abstract superclass.
The CREATE DATA statement needs a "concrete data type" to work on, not the "abstract super type STANDARD TABLE". The hard part here is deciding who will know about the type and create the data object.
BTW, you may want to take a look at the built-in Object Services - maybe there's no need to reinvent the database access layer wheel yet again.