2021 Aug 05 11:40 AM
I have a cl_salv_tree tree. I've various operations on it, like activating a leaf or inserting a new leaf. When I do this, I delete all nodes and rebuild the tree. All works fine, so far as that goes.
However, if I've opened a node, it's now closed again. I'd like to leave it open.
My approach would be to create a class that stores the status of the tree, concerning which nodes are expanded, and then can apply that saved status against the new data held by the tree.
But there seems to be no way to query if a node is expanded or not. I suppose another approach would be to update the tree as the structure changes.
Any ideas for the best way forward.
2021 Aug 05 12:11 PM
it seems there is no event for expend node. Only the expend empty.
You could create an "empty" tree, with only the first level created, manage each time someone expand a node .. but if it close it .. you are lost 🙂
2021 Aug 05 2:40 PM
frdric.girod
I've tried the approach of storing the key in the data, of the parent node, rereading the data from the database, redisplaying the tree and then exanding that node. The problem is that the node keys no longer match the table row numbers.
Next approach. Update the internal table, and store the parent node important information, and trigger an update (as sandra.rossi explains here: https://answers.sap.com/questions/12982797/cl-sav-tree-how-to-expand-node.html). But the gui isn't refreshed!
I think I may just have to live with it unless someone else has some bright ideas.
2021 Aug 05 2:44 PM
honestly, I have the same issu with a SIMPLE_TREE class, and I was hopping you find the answer ... 🙂
I will wait too
2021 Aug 05 4:24 PM
CL_SALV_TREE is based on CL_GUI_COLUMN_TREE which has the method GET_EXPANDED_NODES that directly obtains the expanded nodes from the GUI control on the frontend.
One solution to obtain the GUI control is to start from the container in which the ALV tree is displayed.
In this example, double-click any node to show the 2 first expanded nodes:
CLASS lcl_app DEFINITION.
PUBLIC SECTION.
METHODS pbo
RAISING
cx_salv_error.
PRIVATE SECTION.
DATA: salv TYPE REF TO cl_salv_tree,
scarrs TYPE STANDARD TABLE OF scarr.
METHODS on_double_click
FOR EVENT if_salv_events_tree~double_click
OF cl_salv_events_tree.
METHODS get_salv_tree_gui_control
IMPORTING
container TYPE REF TO cl_gui_container OPTIONAL
RETURNING
VALUE(result) TYPE REF TO cl_gui_column_tree.
ENDCLASS.
CLASS lcl_app IMPLEMENTATION.
METHOD pbo.
IF salv IS NOT BOUND.
cl_salv_tree=>factory( EXPORTING r_container = cl_gui_container=>screen0
IMPORTING r_salv_tree = salv
CHANGING t_table = scarrs ).
DATA(lo_settings) = salv->get_tree_settings( ).
lo_settings->set_hierarchy_size( 30 ).
DATA(event) = salv->get_event( ).
SET HANDLER on_double_click FOR event.
SELECT * FROM scarr INTO TABLE @DATA(local_scarrs).
LOOP AT local_scarrs REFERENCE INTO DATA(scarr).
DATA(node) = salv->get_nodes( )->add_node(
related_node = space " (root node)
relationship = cl_gui_column_tree=>relat_last_child
text = |{ scarr->carrid } - { scarr->carrname }|
data_row = scarr->*
folder = abap_true ).
salv->get_nodes( )->add_node(
related_node = node->get_key( )
relationship = cl_gui_column_tree=>relat_last_child
text = |test|
folder = abap_false ).
ENDLOOP.
salv->display( ).
ENDIF.
LOOP AT SCREEN.
screen-active = '0'.
MODIFY SCREEN.
ENDLOOP.
ENDMETHOD.
METHOD on_double_click.
DATA(gui_control) = get_salv_tree_gui_control(
cl_gui_container=>screen0
).
DATA(node_key_table) = VALUE treev_nks( ).
gui_control->get_expanded_nodes( CHANGING node_key_table = node_key_table EXCEPTIONS OTHERS = 4 ).
IF lines( node_key_table ) = 0.
MESSAGE 'Nodes are all collapsed' TYPE 'I'.
ELSEIF lines( node_key_table ) = 1.
MESSAGE |Only this node is expanded: { CAST scarr-carrname( salv->get_nodes( )->get_node( node_key_table[ 1 ]
)->get_item( 'CARRNAME' )->get_value( ) )->* }| TYPE 'I'.
ELSE.
MESSAGE |First 2 nodes to be expanded are: { CAST scarr-carrname( salv->get_nodes( )->get_node( node_key_table[ 1 ]
)->get_item( 'CARRNAME' )->get_value( ) )->* } and { CAST scarr-carrname( salv->get_nodes( )->get_node( node_key_table[ 2 ]
)->get_item( 'CARRNAME' )->get_value( ) )->* }| TYPE 'I'.
ENDIF.
ENDMETHOD.
METHOD get_salv_tree_gui_control.
DATA: splitter TYPE REF TO cl_gui_splitter_container,
simple_container TYPE REF TO cl_gui_simple_container,
splitter_2 TYPE REF TO cl_gui_container,
custom_container TYPE REF TO cl_gui_custom_container.
" all this should be in a TRY-CATCH block because there's a lot of assumption...
IF container IS BOUND.
splitter = CAST #( container->children[ 1 ] ).
splitter_2 = CAST #( splitter->children[ 2 ] ).
result = CAST #( splitter_2->children[ 2 ] ).
ELSE.
custom_container = CAST #( cl_gui_container=>screen0->children[ 1 ] ).
splitter = CAST #( custom_container->children[ 1 ] ).
simple_container = CAST #( splitter->children[ 2 ] ).
result = CAST #( simple_container->children[ 1 ] ).
ENDIF.
ENDMETHOD.
ENDCLASS.
PARAMETERS dummy.
LOAD-OF-PROGRAM.
DATA(app) = NEW lcl_app( ).
AT SELECTION-SCREEN OUTPUT.
TRY.
app->pbo( ).
CATCH cx_root INTO DATA(lx).
MESSAGE lx TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.
2021 Aug 06 11:09 AM
This will be, suitably adapted, my chosen solution. I've written it up here:
https://blogs.sap.com/2021/08/06/refresh-of-cl_salv_tree/
Many thanks.
2021 Aug 06 3:22 PM
Note that the method get_salv_tree_gui_control is made to work in the 2 flavors of CL_SALV_TREE, full screen or container. If it's displayed in Full screen mode (cl_salv_tree=>factory with parameter R_CONTAINER not passed or passed not bound), the method get_salv_tree_gui_control is to be called with container parameter not passed (it's an optional parameter), otherwise the container in which the ALV tree is displayed is to be passed.
2021 Aug 06 7:44 AM
frdric.girod sandra.rossi The below works, but it does a redraw, so not ideal. What would be good is if we could get rid of:
CLEAR tree_records.
tree->get_nodes( )->delete_all( ).
populate_tree( ).
in ON_DOUBLE_CLICK and instead just force an update without a full redraw. I also don't like the way I find the group node. I can't think of another way though, because the repopulation of the tree, creates new node keys.
Working code:
REPORT zsalv_tree.
CLASS lcl_main DEFINITION.
PUBLIC SECTION.
CLASS-METHODS class_constructor.
METHODS: constructor,
go
RAISING
cx_salv_error,
get_tree RETURNING VALUE(r_result) TYPE REF TO cl_salv_tree,
set_tree IMPORTING i_tree TYPE REF TO cl_salv_tree.
PRIVATE SECTION.
TYPES: BEGIN OF ty_tree_record,
group TYPE c LENGTH 10,
count TYPE i,
active TYPE boolean,
active_icon TYPE salv_de_tree_image,
END OF ty_tree_record.
TYPES ty_tree_records TYPE STANDARD TABLE OF ty_tree_record
WITH NON-UNIQUE KEY group count.
CLASS-DATA: BEGIN OF _icons,
active TYPE salv_de_tree_image,
inactive TYPE salv_de_tree_image,
END OF _icons.
DATA tree TYPE REF TO cl_salv_tree.
DATA tree_records TYPE ty_tree_records.
DATA actual_data TYPE ty_tree_records.
METHODS populate_tree
RAISING
cx_salv_error.
METHODS add_group_node
IMPORTING
i_record TYPE ty_tree_record
RETURNING
VALUE(r_result) TYPE lvc_nkey
RAISING
cx_salv_msg.
METHODS add_leaf_node
IMPORTING
i_record TYPE ty_tree_record
i_group_key TYPE lvc_nkey
RAISING
cx_salv_msg.
METHODS set_columns.
METHODS set_events.
METHODS expand_group_node
IMPORTING
i_group TYPE csequence
RAISING
cx_salv_msg.
METHODS on_double_click
FOR EVENT double_click OF cl_salv_events_tree
IMPORTING
node_key
columnname.
CLASS-METHODS get_icon
IMPORTING
i_icon TYPE string
RETURNING
VALUE(r_result) TYPE salv_de_tree_image.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD class_constructor.
_icons-active = get_icon( 'ICON_ACTIVATE' ).
_icons-inactive = get_icon( 'ICON_DEACTIVATE' ).
ENDMETHOD.
METHOD constructor.
actual_data = VALUE #( ( group = 'A' count = 1 active = abap_true )
( group = 'A' count = 2 active = abap_true )
( group = 'B' count = 1 active = abap_true )
( group = 'B' count = 2 active = abap_true ) ).
ENDMETHOD.
METHOD go.
populate_tree( ).
set_columns( ).
set_events( ).
tree->display( ).
ENDMETHOD.
METHOD populate_tree.
IF tree IS NOT BOUND.
cl_salv_tree=>factory( IMPORTING
r_salv_tree = tree
CHANGING
t_table = tree_records ).
ENDIF.
SORT actual_data BY group count.
LOOP AT actual_data INTO DATA(actual_record).
DATA group_key TYPE lvc_nkey.
AT NEW group.
group_key = add_group_node( actual_record ).
ENDAT.
add_leaf_node( i_record = actual_record
i_group_key = group_key ).
ENDLOOP.
ENDMETHOD.
METHOD get_icon.
DATA icon TYPE c LENGTH 255.
CALL FUNCTION 'ICON_CREATE'
EXPORTING
name = i_icon
add_stdinf = space
IMPORTING
result = icon
EXCEPTIONS
icon_not_found = 0
outputfield_too_short = 0
OTHERS = 0.
r_result = icon.
ENDMETHOD.
METHOD add_group_node.
DATA(node) = tree->get_nodes(
)->add_node(
related_node = ''
data_row = VALUE ty_tree_record( group = i_record-group )
relationship = cl_gui_column_tree=>relat_last_child ).
node->set_folder( abap_true ).
r_result = node->get_key( ).
ENDMETHOD.
METHOD add_leaf_node.
tree->get_nodes(
)->add_node( related_node = i_group_key
data_row = i_record
relationship = cl_gui_column_tree=>relat_last_child
)->get_item( 'ACTIVE_ICON'
)->set_icon(
SWITCH #(
i_record-active
WHEN abap_true THEN _icons-active
ELSE _icons-inactive ) ).
ENDMETHOD.
METHOD set_columns.
tree->get_columns( )->set_optimize( ).
LOOP AT tree->get_columns( )->get( ) INTO DATA(column).
CASE column-columnname.
WHEN 'ACTIVE'.
column-r_column->set_technical( ).
WHEN 'ACTIVE_ICON'.
column-r_column->set_short_text( 'Active' ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
METHOD set_events.
DATA(events) = tree->get_event( ).
SET HANDLER on_double_click FOR events.
ENDMETHOD.
METHOD on_double_click.
IF columnname NE 'ACTIVE_ICON'.
RETURN.
ENDIF.
TRY.
DATA(record_ref) = tree->get_nodes( )->get_node( node_key )->get_data_row( ).
FIELD-SYMBOLS <record> TYPE ty_tree_record.
ASSIGN record_ref->* TO <record>.
IF <record>-active = abap_true.
<record>-active = abap_false.
<record>-active_icon = _icons-inactive.
ELSE.
<record>-active = abap_true.
<record>-active_icon = _icons-active.
ENDIF.
READ TABLE actual_data ASSIGNING FIELD-SYMBOL(<actual_line>)
WITH TABLE KEY group = <record>-group
count = <record>-count.
<actual_line>-active = <record>-active.
CLEAR tree_records.
tree->get_nodes( )->delete_all( ).
populate_tree( ).
expand_group_node( <actual_line>-group ).
CATCH cx_salv_msg cx_salv_error INTO DATA(error).
MESSAGE error TYPE 'I' DISPLAY LIKE 'E'.
ENDTRY.
ENDMETHOD.
METHOD expand_group_node.
LOOP AT tree->get_nodes( )->get_all_nodes( ) INTO DATA(node).
DATA(record_ref) = node-node->get_data_row( ).
FIELD-SYMBOLS <record> TYPE ty_tree_record.
ASSIGN record_ref->* TO <record>.
CHECK <record>-group EQ i_group AND <record>-count = 0.
node-node->expand( ).
RETURN.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
end-of-selection.
NEW lcl_main( )->go( ).
2021 Aug 06 7:46 AM
Next, I'll try sandra.rossi's proposal. It looks rather simpler!
2021 Aug 06 8:31 AM
I don't think you can avoid the side effects of rebuilding the whole ALV tree. Instead, just updating the ALV tree should do the trick. With my answer as complement, maybe you can achieve whatever you want.
Concerning the way you "find the group node", maybe you will avoid this part of the program too.
2021 Aug 06 11:10 AM
frdric.girod Blog with full solution available! https://blogs.sap.com/2021/08/06/refresh-of-cl_salv_tree/
2021 Aug 06 11:44 AM
Hey ! realy nice to create a blog for the solution !
thank matthew.billingham !!