Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

cl_salv_tree - restoring the tree expansion/collapse status after data change

matt
Active Contributor
0 Kudos
2,116

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.

11 REPLIES 11

FredericGirod
Active Contributor
1,172

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 🙂

matt
Active Contributor
0 Kudos
1,172

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.

FredericGirod
Active Contributor
0 Kudos
1,172

honestly, I have the same issu with a SIMPLE_TREE class, and I was hopping you find the answer ... 🙂

I will wait too

Sandra_Rossi
Active Contributor
1,172

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.

matt
Active Contributor
0 Kudos
1,172

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.

1,172

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.

matt
Active Contributor
0 Kudos
1,172

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( ).

matt
Active Contributor
0 Kudos
1,172

Next, I'll try sandra.rossi's proposal. It looks rather simpler!

Sandra_Rossi
Active Contributor
1,172

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.

matt
Active Contributor
1,172

frdric.girod Blog with full solution available! https://blogs.sap.com/2021/08/06/refresh-of-cl_salv_tree/

FredericGirod
Active Contributor
0 Kudos
1,172

Hey ! realy nice to create a blog for the solution !

thank matthew.billingham !!