Search code examples
treeviewabapalv

Changing a node's site in ALV tree


I have an ALV tree and basically what I need to do is to slide (drag) a specific node to the top or bottom or a specific location in the ALV tree.

How could this be accomplished?

For example, here I would like to slide the IF node in place before the AFFECTATIONS node: enter image description here

Expected node's arrangement:

enter image description here


Solution

  • TL;DR: cl_gui_alv_tree cannot do it like you want it to do it.

    Due to the specifics of drag-n-drop implementation in ALV Control Framework, the node in a tree can be either draggable (source) or droppable (target), not both. So dragging a node to another node's place on the same level of hierarchy is not possible, hence it is also not possible to (inter)change nodes with its siblings in runtime.

    However, I wanna propose you a helpful piece of code, which shows how one can achieve smth close to the question by dragging the node onto the parent level of hierarchy.

    Steps for implementing DND functionality in SAP ALV Framework:

    1. Create event class where handlers of ondrag and ondrop events are implemented.
    2. Create DND storage object lcl_dragdropobj, an empty class through the which you pass grid objects (leaves, nodes) in memory.
    3. Create behavior definition for both source and target objects.
    4. With the help of the above behavior object, assign layout to each node/leaf via is_node_layout parameter. That layout would define node behavior: either it's draggable or droppable.

    Here is the sample code, built on spfli/sflight database:

    REPORT  zyyz.
    
    DATA: g_alv_tree         TYPE REF TO cl_gui_alv_tree,
          g_custom_container TYPE REF TO cl_gui_custom_container,
          g_drag_behaviour   TYPE REF TO cl_dragdrop,
          g_drop_behaviour   TYPE REF TO cl_dragdrop.
    DATA: gt_sflight      TYPE TABLE OF sflight INITIAL SIZE 0,      "Output-Table
          gt_fieldcatalog TYPE lvc_t_fcat,
          g_top_key       TYPE lvc_nkey,
          ok_code         LIKE sy-ucomm,
          save_ok         LIKE sy-ucomm,           "OK-Code
          g_max           TYPE i VALUE 255.
    
    CLASS lcl_dragdropobj DEFINITION.
      PUBLIC SECTION.
        DATA: cp_sflight_root     TYPE sflight,
              cp_sflights         TYPE TABLE OF sflight,
              cp_node_text_root   TYPE lvc_value,
              cp_node_texts       TYPE salv_t_value,
              lt_selected_carrids TYPE lvc_t_nkey,
              lt_selected_leafs   TYPE lvc_t_nkey.
    ENDCLASS.
    
    CLASS lcl_event_receiver DEFINITION.
      PUBLIC SECTION.                .
        METHODS:
          on_drag
          FOR EVENT on_drag
                        OF cl_gui_alv_tree
            IMPORTING sender node_key drag_drop_object,
          on_drop
          FOR EVENT on_drop
                        OF cl_gui_alv_tree
            IMPORTING drag_drop_object.
    ENDCLASS.
    *---------------------------------------------------------------------*
    *       CLASS lcl_toolbar_event_receiver IMPLEMENTATION
    *---------------------------------------------------------------------*
    CLASS lcl_event_receiver IMPLEMENTATION.
    
      METHOD on_drag.
        DATA: dataobj           TYPE REF TO lcl_dragdropobj,
              l_sflight         TYPE sflight,
              l_node_text       TYPE lvc_value.
    * create and fill dataobject for event ON_DROP.
        CREATE OBJECT dataobj.
    
    * Get dragged month
        CALL METHOD sender->get_outtab_line
          EXPORTING
            i_node_key    = node_key
          IMPORTING
            e_outtab_line = dataobj->cp_sflight_root
            e_node_text   = dataobj->cp_node_text_root.
    
        CALL METHOD g_alv_tree->get_children
          EXPORTING
            i_node_key  = node_key
          IMPORTING
            et_children = dataobj->lt_selected_carrids.
    
        LOOP AT dataobj->lt_selected_carrids ASSIGNING FIELD-SYMBOL(<carrid>).
          CLEAR: l_sflight, l_node_text.
          CALL METHOD sender->get_outtab_line
            EXPORTING
              i_node_key    = <carrid>
            IMPORTING
              e_outtab_line = l_sflight
              e_node_text   = l_node_text.
          APPEND l_node_text TO dataobj->cp_node_texts.
    
          CALL METHOD g_alv_tree->get_children
            EXPORTING
              i_node_key  = <carrid>
            IMPORTING
              et_children = dataobj->lt_selected_leafs.
    
          LOOP AT dataobj->lt_selected_leafs ASSIGNING FIELD-SYMBOL(<node>).
            CLEAR: l_sflight, l_node_text.
            CALL METHOD sender->get_outtab_line
              EXPORTING
                i_node_key    = <node>
              IMPORTING
                e_outtab_line = l_sflight
                e_node_text   = l_node_text.
            APPEND l_sflight TO dataobj->cp_sflights.
          ENDLOOP.
    
        ENDLOOP.
    
        drag_drop_object->object = dataobj.
    
      ENDMETHOD.
    
      METHOD on_drop.
        DATA: dataobj           TYPE REF TO lcl_dragdropobj,
              l_root_key        TYPE lvc_nkey,
              l_carrid_key      TYPE lvc_nkey,
              l_new_key         TYPE lvc_nkey,
              lt_sflights       TYPE TABLE OF sflight.
    
        CATCH SYSTEM-EXCEPTIONS move_cast_error = 1.
          dataobj ?= drag_drop_object->object.
    
          PERFORM add_month USING dataobj->cp_node_text_root
                                                        g_top_key
                                                        cl_gui_column_tree=>relat_first_child
                                      CHANGING l_root_key.
    *
          LOOP AT dataobj->lt_selected_carrids ASSIGNING FIELD-SYMBOL(<node>).
            READ TABLE dataobj->cp_node_texts ASSIGNING FIELD-SYMBOL(<text>) INDEX sy-tabix.
            DATA(sflight) = VALUE sflight( carrid = <text> ).
            PERFORM add_carrid_line USING sflight
                                                              l_root_key
                                                              cl_gui_column_tree=>relat_last_child
                                  CHANGING l_carrid_key.
            CLEAR lt_sflights.
            INSERT LINES OF dataobj->cp_sflights INTO TABLE lt_sflights.
            DELETE lt_sflights WHERE carrid NE <text>.
            LOOP AT lt_sflights ASSIGNING FIELD-SYMBOL(<flight>).
              PERFORM add_complete_line USING <flight>
                                                                       l_carrid_key
                                                    CHANGING l_new_key.
            ENDLOOP.
          ENDLOOP.
          PERFORM delete_node.
        ENDCATCH.
        IF sy-subrc <> 0.
          CALL METHOD drag_drop_object->abort.
        ENDIF.
      ENDMETHOD.
    ENDCLASS.
    
    START-OF-SELECTION.
    
    END-OF-SELECTION.
      CALL SCREEN 100.
    *&---------------------------------------------------------------------*
    *&      Module  PBO  OUTPUT
    *&---------------------------------------------------------------------*
    MODULE pbo OUTPUT.
      SET PF-STATUS 'MAIN100'.
      SET TITLEBAR 'MAINTITLE'.
      IF g_alv_tree IS INITIAL.
        PERFORM init_tree.
        CALL METHOD cl_gui_cfw=>flush
          EXCEPTIONS
            cntl_system_error = 1
            cntl_error        = 2.
      ENDIF.
    ENDMODULE.                             " PBO  OUTPUT
    *&---------------------------------------------------------------------*
    *&      Module  PAI  INPUT
    *&---------------------------------------------------------------------*
    *       process after input
    *----------------------------------------------------------------------*
    MODULE pai INPUT.
      save_ok = ok_code.
      CLEAR ok_code.
      CASE save_ok.
        WHEN 'EXIT' OR 'BACK' OR 'CANC'.
          PERFORM exit_program.
        WHEN OTHERS.
          CALL METHOD cl_gui_cfw=>dispatch.
      ENDCASE.
      CALL METHOD cl_gui_cfw=>flush.
    ENDMODULE.                             " PAI  INPUT
    *&---------------------------------------------------------------------*
    *&      Form  init_tree
    *&---------------------------------------------------------------------*
    FORM init_tree.
    * create container for alv-tree
      DATA: l_tree_container_name(30) TYPE c.
      l_tree_container_name = 'CCONTAINER1'.
      CREATE OBJECT g_custom_container
        EXPORTING
          container_name              = l_tree_container_name.
      IF sy-subrc <> 0.
        MESSAGE x208(00) WITH 'ERROR'(100).
      ENDIF.
    * create tree control
      CREATE OBJECT g_alv_tree
        EXPORTING
          parent                      = g_custom_container
          node_selection_mode         = cl_gui_column_tree=>node_sel_mode_single
          item_selection              = ' '
          no_html_header              = 'X'
          no_toolbar                  = ''.
      IF sy-subrc <> 0.
        MESSAGE x208(00) WITH 'ERROR'.                          "#EC NOTEXT
      ENDIF.
      DATA l_hierarchy_header TYPE treev_hhdr.
      l_hierarchy_header-width = 35.
      PERFORM build_fieldcatalog.
      CALL METHOD g_alv_tree->set_table_for_first_display
        EXPORTING
          is_hierarchy_header = l_hierarchy_header
        CHANGING
          it_fieldcatalog     = gt_fieldcatalog
          it_outtab           = gt_sflight.
      PERFORM define_dnd_behaviour.
      PERFORM create_hierarchy.
      PERFORM register_events.
      CALL METHOD g_alv_tree->update_calculations.
      CALL METHOD g_alv_tree->frontend_update.
    ENDFORM.                               " init_tree
    
    FORM register_events.
      DATA: lt_events        TYPE cntl_simple_events,
            l_event_receiver TYPE REF TO lcl_event_receiver.
      CALL METHOD g_alv_tree->get_registered_events
        IMPORTING
          events = lt_events.
      CALL METHOD g_alv_tree->set_registered_events
        EXPORTING
          events                    = lt_events.
      IF sy-subrc <> 0.
        MESSAGE x208(00) WITH 'ERROR'.                          "#EC NOTEXT
      ENDIF.
      CREATE OBJECT l_event_receiver.
      SET HANDLER l_event_receiver->on_drop FOR g_alv_tree.
      SET HANDLER l_event_receiver->on_drag FOR g_alv_tree.
    ENDFORM.                               " register_events
    *&---------------------------------------------------------------------*
    *&      Form  exit_program
    *&---------------------------------------------------------------------*
    *       free object and leave program
    *----------------------------------------------------------------------*
    FORM exit_program.
      CALL METHOD g_custom_container->free.
      LEAVE PROGRAM.
    ENDFORM.                               " exit_program
    *--------------------------------------------------------------------
    FORM build_fieldcatalog.
      CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
        EXPORTING
          i_structure_name = 'SFLIGHT'
        CHANGING
          ct_fieldcat      = gt_fieldcatalog.
    ENDFORM.                               " build_fieldcatalog
    *&---------------------------------------------------------------------*
    *&      Form  create_hierarchy
    *&---------------------------------------------------------------------*
    FORM create_hierarchy.
      DATA: ls_sflight       TYPE sflight,
            lt_sflight       TYPE TABLE OF sflight INITIAL SIZE 0,
            l_yyyymm         TYPE c LENGTH 8,            "year and month of sflight-fldate
            l_yyyymm_last(6) TYPE c,
            l_carrid         LIKE sflight-carrid,
            l_carrid_last    LIKE sflight-carrid.
      DATA: l_month_key   TYPE lvc_nkey,
            l_carrid_key  TYPE lvc_nkey,
            l_last_key    TYPE lvc_nkey,
            l_top_key     TYPE lvc_nkey,
            l_layout_node TYPE lvc_s_layn.
    * Select data
      SELECT * FROM sflight INTO TABLE lt_sflight UP TO g_max ROWS.
    * sort table according to conceived hierarchy
      SORT lt_sflight BY fldate+0(6) carrid fldate+6(2).
    
      PERFORM make_drop CHANGING l_layout_node.
    
      CALL METHOD g_alv_tree->add_node
        EXPORTING
          i_relat_node_key = ''
          i_relationship   = cl_gui_column_tree=>relat_last_child
          i_node_text      = text-050
          is_node_layout   = l_layout_node
        IMPORTING
          e_new_node_key   = l_top_key.
    
      g_top_key = l_top_key.
    
      LOOP AT lt_sflight INTO ls_sflight.
        l_yyyymm = ls_sflight-fldate+0(6).
        l_carrid = ls_sflight-carrid.
        IF l_yyyymm <> l_yyyymm_last.      "on change of l_yyyymm
          l_yyyymm_last = l_yyyymm.
    * month nodes
          PERFORM add_month USING l_yyyymm
                                                        l_top_key
                                                        cl_gui_column_tree=>relat_last_child
                                 CHANGING l_month_key.
    * The month changed, thus, there is no predecessor carrier
          CLEAR l_carrid_last.
        ENDIF.
    * Carrier nodes:
        IF l_carrid <> l_carrid_last.      "on change of l_carrid
          l_carrid_last = l_carrid.
          PERFORM add_carrid_line USING    ls_sflight
                                                                 l_month_key
                                                                 cl_gui_column_tree=>relat_last_child
                                  CHANGING l_carrid_key.
        ENDIF.
    * Leaf:
        PERFORM add_complete_line USING  ls_sflight
                                         l_carrid_key
                                CHANGING l_last_key.
      ENDLOOP.
    
      CALL METHOD g_alv_tree->expand_node
        EXPORTING
          i_node_key = l_top_key.
    ENDFORM.                               " create_hierarchy
    *&---------------------------------------------------------------------*
    *&      Form  add_month
    *&---------------------------------------------------------------------*
    FORM add_month  USING     p_yyyymm TYPE clike
                                                 p_relat_key TYPE lvc_nkey
                                                 p_relationship TYPE int4
                             CHANGING  p_node_key TYPE lvc_nkey.
      DATA: l_node_text   TYPE lvc_value,
            ls_sflight    TYPE sflight,
            l_month       TYPE c LENGTH 25,
            l_layout_node TYPE lvc_s_layn.
      IF p_yyyymm CO ' 0123456789'.
        p_yyyymm = p_yyyymm && '01'.
        CALL FUNCTION 'CONVERSION_EXIT_LDATE_OUTPUT'
          EXPORTING
            input  = p_yyyymm
          IMPORTING
            output = l_month.
        REPLACE REGEX `(\d\d\.\s)([[:alpha:]]*)(\s\d{4})` IN l_month WITH '$2'.
        l_node_text = p_yyyymm(4) && `/` && l_month.
      ELSE.
        l_node_text = p_yyyymm.
      ENDIF.
    
      PERFORM make_drag CHANGING l_layout_node.
    * add node
      CALL METHOD g_alv_tree->add_node
        EXPORTING
          i_relat_node_key = p_relat_key
          i_relationship   = p_relationship
          i_node_text      = l_node_text
          is_outtab_line   = ls_sflight
          is_node_layout   = l_layout_node
        IMPORTING
          e_new_node_key   = p_node_key.
    ENDFORM.                               " add_month
    *-----------------------------------------------------------------------
    FORM add_carrid_line USING     ps_sflight TYPE sflight
                                                      p_relat_key TYPE lvc_nkey
                                                      p_relationship TYPE int4
                         CHANGING  p_node_key TYPE lvc_nkey.
      DATA: l_node_text   TYPE lvc_value,
            ls_sflight    TYPE sflight,
            l_layout_node TYPE lvc_s_layn.
    
      l_node_text =  ps_sflight-carrid.
      CALL METHOD g_alv_tree->add_node
        EXPORTING
          i_relat_node_key = p_relat_key
          i_relationship   = p_relationship
          i_node_text      = l_node_text
          is_outtab_line   = ls_sflight
          is_node_layout   = l_layout_node
        IMPORTING
          e_new_node_key   = p_node_key.
    ENDFORM.                               " add_carrid_line
    *&---------------------------------------------------------------------*
    *&      Form  add_complete_line
    *&---------------------------------------------------------------------*
    FORM add_complete_line USING   ps_sflight TYPE sflight
                                   p_relat_key TYPE lvc_nkey
                         CHANGING  p_node_key TYPE lvc_nkey.
      DATA: l_node_text   TYPE lvc_value,
            l_layout_node TYPE lvc_s_layn.
      WRITE ps_sflight-fldate TO l_node_text MM/DD/YYYY.
      CALL METHOD g_alv_tree->add_node
        EXPORTING
          i_relat_node_key = p_relat_key
          i_relationship   = cl_gui_column_tree=>relat_last_child
          is_outtab_line   = ps_sflight
          i_node_text      = l_node_text
          is_node_layout   = l_layout_node
        IMPORTING
          e_new_node_key   = p_node_key.
    ENDFORM.                               " add_complete_line
    
    FORM define_dnd_behaviour.
      DATA: effect TYPE i.
      CREATE OBJECT g_drag_behaviour.
      effect = cl_dragdrop=>move.
      CALL METHOD g_drag_behaviour->add
        EXPORTING
          flavor     = 'default'                  "#EC NOTEXT
          dragsrc    = 'X'
          droptarget = ' '
          effect     = effect.
      CREATE OBJECT g_drop_behaviour.
      effect = cl_dragdrop=>move.
      CALL METHOD g_drop_behaviour->add
        EXPORTING
          flavor     = 'default'                  "#EC NOTEXT
          dragsrc    = ' '
          droptarget = 'X'
          effect     = effect.
    ENDFORM.                    " DEFINE_DND_BEHAVIOUR
    
    FORM delete_node.
      DATA: lt_selected_nodes TYPE lvc_t_nkey,
            l_selected_node   TYPE lvc_nkey.
      CALL METHOD g_alv_tree->get_selected_nodes
        CHANGING
          ct_selected_nodes = lt_selected_nodes.
      CALL METHOD cl_gui_cfw=>flush.
      READ TABLE lt_selected_nodes INTO l_selected_node INDEX 1.
      IF sy-subrc EQ 0.
        CALL METHOD g_alv_tree->delete_subtree
          EXPORTING
            i_node_key = l_selected_node.
        CALL METHOD g_alv_tree->frontend_update.
      ELSE. "sy-subrc EQ 0
        MESSAGE i000(0k) WITH 'Please select a node.'(900).
      ENDIF.
    ENDFORM.
    
    FORM make_drag CHANGING p_layout_node TYPE lvc_s_layn.
      DATA l_handle_line TYPE i.
      CALL METHOD g_drag_behaviour->get_handle
        IMPORTING
          handle = l_handle_line.
      p_layout_node-dragdropid = l_handle_line.
    ENDFORM.
    
    FORM make_drop CHANGING p_layout_node TYPE lvc_s_layn.
      DATA l_handle_line TYPE i.
      CALL METHOD g_drop_behaviour->get_handle
        IMPORTING
          handle = l_handle_line.
      p_layout_node-dragdropid = l_handle_line.
    ENDFORM.
    

    In the above example months are made draggable onto the root Flights node. When they are dragged onto it, the place of the dragged node within hierarchy became first. This is not exactly the behavior you asked, but! Through the set of N draggings you can place any node onto any place within that level of hierarchy.

    More useful DND examples can be found via BCALV_TREE_* pattern.