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: 

Programming riddle: Sort rows via drag and drop

ennowulff
Active Contributor

Hi there!

today I doubt that I do programming for so many years...

The task: Provide an easy sorting mechanism in ALV grid.

The user should be able to mark any lines (one, a block of lines or arbitrary lines) and move them via drag and drop to another place.

I want to have a solution that can be used instead of the cl_gui_alv_grid. Therefore I inherited the class and implemented drag and drop feature.

The task seems to be quite easy: renumber the marked rows, find the new position in the data table and renumber the old entries.

May be I do think to complex or it really is not that trivial...

I would like to invite you to a challenge and try to find a working solution.

I myself had three approaches which either do not work for every selection or which use too much memory and performance.

The following code provides a demo report that

  • displays table T005T (countries) in alv grid
  • has a derived cl_gui_alv_grid class
  • has a dragdrop-data-object class
  • is prepared for dragging and dropping lines (or a single cell)

Just copy and paste the following code and try to find a simple solution that just sets the new order-id in the given field.

I am excited to see your solutions! 🙂

Enno

EXAMPLE

this is one of the most complex example: marking several lines that are not in one block and dragging them to another line:

Lines 4,6,7 and 9 are marked and are moved to line 2.

After dropping the lines the renumbering will be done and shown in field NATIO:

HINTS

It matters if you move the lines up or down.

Calculating the new order number should be preferred. I have a solution where I copy the selected lines to a temporary created identical table, deleting the marked lines, do the numbering and insertion at new position. This might be a problem in huge tables...

CODE

REPORT .

CLASS lcl_dragdrop_sort DEFINITION.
  PUBLIC SECTION.
    DATA at_rows TYPE lvc_t_row.
    DATA ao_data TYPE REF TO data.
ENDCLASS.

CLASS lcl_gui_alv_grid_sort DEFINITION
  INHERITING FROM cl_gui_alv_grid.
  PUBLIC SECTION.
    METHODS set_sort_field
      IMPORTING
        fieldname TYPE clike.
  PROTECTED SECTION.
    DATA mv_sort_field TYPE char30 .
    CONSTANTS mc_sort_flavor TYPE c LENGTH 10 VALUE 'SortMe'.
    METHODS on_drag            FOR EVENT ondrag       OF cl_gui_alv_grid
      IMPORTING e_column e_row e_dragdropobj es_row_no sender.
    METHODS on_drop            FOR EVENT ondrop       OF cl_gui_alv_grid
      IMPORTING e_column e_row e_dragdropobj es_row_no sender.
ENDCLASS.

CLASS lcl_gui_alv_grid_sort IMPLEMENTATION.
  METHOD on_drag.
    DATA lo_dragdrop_sort TYPE REF TO lcl_dragdrop_sort.
    lo_dragdrop_sort = NEW #( ).
    sender->get_selected_rows( IMPORTING et_index_rows = lo_dragdrop_sort->at_rows ).

    IF lo_dragdrop_sort->at_rows IS INITIAL.
     "No lines selected: get current cell
      sender->get_current_cell(
        IMPORTING
          e_row = DATA(lv_row) ).
      IF NOT lv_row IS INITIAL.
        APPEND VALUE #( index = lv_row ) TO lo_dragdrop_sort->at_rows.
      ENDIF.
    ENDIF.

    lo_dragdrop_sort->ao_data = mt_outtab.
    e_dragdropobj->object ?= lo_dragdrop_sort.

  ENDMETHOD.

  METHOD on_drop.
    DATA lo_dragdrop_sort    TYPE REF TO lcl_dragdrop_sort.
    FIELD-SYMBOLS <lft_data> TYPE table.

    CHECK e_dragdropobj->flavor = mc_sort_flavor.

    TRY.
        lo_dragdrop_sort ?= e_dragdropobj->object.
        CHECK lo_dragdrop_sort->at_rows  IS NOT INITIAL.
      CATCH cx_sy_move_cast_error.
        RETURN.
    ENDTRY.

    ASSIGN lo_dragdrop_sort->ao_data->* TO <lft_data>.

    LOOP AT <lft_data> ASSIGNING FIELD-SYMBOL(<lfs_data>).
      ASSIGN COMPONENT mv_sort_field OF STRUCTURE <lfs_data> 
          TO FIELD-SYMBOL(<lfv_sortn>).
      CHECK sy-subrc = 0.
      <lfv_sortn> = CONV numc10( sy-tabix ).
      "------->
      "PLACE YOUR SOLUTION HERE
      "AND HERE
      "...
      "....AND
      "------> HERE
      "---------------------------------------------> SOLUTION!! <------"
    ENDLOOP.
    
    "Set sort field
    me->get_sort_criteria( IMPORTING et_sort = DATA(lt_sort) ).
    IF NOT line_exists( lt_sort[ fieldname = mv_sort_field ] ).
      INSERT VALUE #( fieldname = mv_sort_field spos = 1 up = abap_true ) 
        INTO lt_sort INDEX 1.
    ENDIF.

   "Set sorting
    me->set_sort_criteria( lt_sort ).

    "Show changes
    refresh_table_display( ).

  ENDMETHOD.

  METHOD set_sort_field.

    mv_sort_field = fieldname.

    SET HANDLER on_drop    FOR me.
    SET HANDLER on_drag    FOR me.

    DATA(lo_dd) = NEW cl_dragdrop( ).
    lo_dd->add( dragsrc    = abap_true
                droptarget = abap_true
                flavor     = mc_sort_flavor
                effect     = cl_dragdrop=>copy
                effect_in_ctrl = cl_dragdrop=>move ).

    DATA(lv_dd_handle) = 0.
    lo_dd->get_handle( IMPORTING handle = lv_dd_handle ).

    "Set drag-drop-handle
    get_frontend_layout( IMPORTING es_layout = DATA(ls_layout) ).
    ls_layout-s_dragdrop-row_ddid = lv_dd_handle.
    set_frontend_layout( ls_layout ).
    refresh_table_display( ).

  ENDMETHOD.
ENDCLASS.

CLASS main DEFINITION.
  PUBLIC SECTION.
    TYPES ty_data       TYPE t005t.
    TYPES ty_data_t     TYPE STANDARD TABLE OF ty_data
                             WITH DEFAULT KEY.
    DATA ms_data        TYPE ty_data.
    DATA mt_data        TYPE ty_data_t.

    DATA mr_grid        TYPE REF TO lcl_gui_alv_grid_sort.
    METHODS start.
  PROTECTED SECTION.
    METHODS selection.
    METHODS display.

ENDCLASS.

CLASS main IMPLEMENTATION.
  METHOD selection.
    SELECT * FROM t005t INTO TABLE mt_data UP TO 10 ROWS 
     WHERE spras = sy-langu.
  ENDMETHOD.

  METHOD start.
    selection( ).
    display( ).
  ENDMETHOD.

  METHOD display.

   WRITE 'DUMMY'.
    
   CREATE OBJECT mr_grid
      EXPORTING
        i_parent      = cl_gui_container=>screen0
        i_appl_events = space.

    DATA lv_structure_name    TYPE dd02l-tabname VALUE 'T005T'.
    DATA ls_variant           TYPE disvariant.
    DATA lv_save              TYPE char01 VALUE 'U'.
    DATA lv_default           TYPE char01 VALUE abap_true.
    DATA ls_layout            TYPE lvc_s_layo.

    ls_layout-sel_mode       = 'A'.
    ls_layout-grid_title     = 'Countries'.

    mr_grid->set_table_for_first_display(
      EXPORTING
        i_structure_name              = lv_structure_name
        is_variant                    = ls_variant
        i_save                        = lv_save
        i_default                     = lv_default
        is_layout                     = ls_layout
      CHANGING
        it_outtab                     = mt_data ).

    mr_grid->set_sort_field( 'NATIO' ).

  ENDMETHOD.
ENDCLASS.


START-OF-SELECTION.
  NEW main( )->start( ).
7 REPLIES 7

horst_keller
Product and Topic Expert
Product and Topic Expert
0 Kudos

Just a hunch without delving into your problem, can you employ virtual sorting somehow?

https://help.sap.com/http.svc/rc/abapdocu_752_index_htm/7.52/en-US/index.htm?file=abenvirtual_sort_a...

(I know that this was invented by some demand of the ALV framework)

0 Kudos

Thanks for the hint! The sorting itself is not the problem. Knowing or calculating the new sort order is the challenge.

ennowulff
Active Contributor
0 Kudos

Just found out the following:

If ALV grid is in edit-mode then you can move the lines by default even between two lines:

but:

1. the sorting is wrong when selecting multiple "blocks" of lines

2. How can I access the event "dragging complete"? Event DATA_CHANGED does not work for dragging and dropping.

Former Member

If the number of lines can be limit to 100/1000/10000, which are Drap&Drop at once:

  1. [main->selection] Enumerate the data from beginning in steps of 100/1000/10000 like 1100, 1200, 1300
  2. [lcl_gui_alv_grid_sort->on_drop] Re-Enumerate dropped lines beginning at insert line like 1301, 1302, 1303
  3. [lcl_gui_alv_grid_sort->on_dropcomplete] Use event ondropcomplete to sort and re-enumerate like in step 1

I put line comment > " new < on changed/new lines.

REPORT .
CLASS lcl_dragdrop_sort DEFINITION.
  PUBLIC SECTION.
    DATA at_rows TYPE lvc_t_row.
    DATA ao_data TYPE REF TO data.
ENDCLASS.
CLASS lcl_gui_alv_grid_sort DEFINITION
  INHERITING FROM cl_gui_alv_grid.
  PUBLIC SECTION.
    METHODS set_sort_field
      IMPORTING
        fieldname TYPE clike.
  PROTECTED SECTION.
    DATA mv_sort_field TYPE char30 .
    CONSTANTS mc_sort_flavor TYPE c LENGTH 10 VALUE 'SortMe'.
    METHODS on_drag            FOR EVENT ondrag       OF cl_gui_alv_grid
      IMPORTING e_column e_row e_dragdropobj es_row_no sender.
    METHODS on_drop            FOR EVENT ondrop       OF cl_gui_alv_grid
      IMPORTING e_column e_row e_dragdropobj es_row_no sender.
    METHODS on_dropcomplete    FOR EVENT ondropcomplete OF cl_gui_alv_grid " new
      IMPORTING e_row e_column es_row_no e_dragdropobj.                    " new
ENDCLASS.
CLASS lcl_gui_alv_grid_sort IMPLEMENTATION.
  METHOD on_drag.
    DATA lo_dragdrop_sort TYPE REF TO lcl_dragdrop_sort.
    lo_dragdrop_sort = NEW #( ).
    sender->get_selected_rows( IMPORTING et_index_rows = lo_dragdrop_sort->at_rows ).
    IF lo_dragdrop_sort->at_rows IS INITIAL.
      "No lines selected: get current cell
      sender->get_current_cell(
        IMPORTING
          e_row = DATA(lv_row) ).
      IF NOT lv_row IS INITIAL.
        APPEND VALUE #( index = lv_row ) TO lo_dragdrop_sort->at_rows.
      ENDIF.
    ENDIF.
    lo_dragdrop_sort->ao_data = mt_outtab.
    e_dragdropobj->object ?= lo_dragdrop_sort.
  ENDMETHOD.
  METHOD on_drop.
    DATA lo_dragdrop_sort    TYPE REF TO lcl_dragdrop_sort.
    FIELD-SYMBOLS <lft_data> TYPE table.
    DATA newnatio TYPE i.
    CHECK e_dragdropobj->flavor = mc_sort_flavor.
    TRY.
        lo_dragdrop_sort ?= e_dragdropobj->object.
        CHECK lo_dragdrop_sort->at_rows  IS NOT INITIAL.
        ASSIGN lo_dragdrop_sort->ao_data->* TO <lft_data>.
        READ TABLE <lft_data> ASSIGNING FIELD-SYMBOL(<lfs_data>) INDEX e_row-index.
        CHECK sy-subrc EQ 0.
        ASSIGN COMPONENT 'NATIO' OF STRUCTURE <lfs_data> TO FIELD-SYMBOL(<natio>).
        newnatio = <natio>.
        LOOP AT lo_dragdrop_sort->at_rows ASSIGNING FIELD-SYMBOL(<at_rows>).
          READ TABLE <lft_data> ASSIGNING <lfs_data> INDEX <at_rows>-index.
          CHECK sy-subrc EQ 0.
          ADD 1 TO newnatio.
          ASSIGN COMPONENT 'NATIO' OF STRUCTURE <lfs_data> TO <natio>.
          <natio> = CONV numc10( newnatio ).
        ENDLOOP.
      CATCH cx_sy_move_cast_error.
        RETURN.
    ENDTRY.
*    ASSIGN lo_dragdrop_sort->ao_data->* TO <lft_data>.                       " new >>>
*
*    LOOP AT <lft_data> ASSIGNING FIELD-SYMBOL(<lfs_data>).
*      ASSIGN COMPONENT mv_sort_field OF STRUCTURE <lfs_data>
*          TO FIELD-SYMBOL(<lfv_sortn>).
*      CHECK sy-subrc = 0.
*      <lfv_sortn> = CONV numc10( sy-tabix ).
*      "------->
*      "PLACE YOUR SOLUTION HERE
*      "AND HERE
*      "...
*      "....AND
*      "------> HERE
*      "---------------------------------------------> SOLUTION!! <------"
*    ENDLOOP.
*
*    "Set sort field
*    me->get_sort_criteria( IMPORTING et_sort = DATA(lt_sort) ).
*    IF NOT line_exists( lt_sort[ fieldname = mv_sort_field ] ).
*      INSERT VALUE #( fieldname = mv_sort_field spos = 1 up = abap_true )
*        INTO lt_sort INDEX 1.
*    ENDIF.
*    "Set sorting
*    me->set_sort_criteria( lt_sort ).
*
*    "Show changes
*    refresh_table_display( ).                                              " new <<<
  ENDMETHOD.
  METHOD on_dropcomplete.                                                   " new >>>
    DATA lo_dragdrop_sort    TYPE REF TO lcl_dragdrop_sort.
    FIELD-SYMBOLS <lft_data> TYPE table.
    CHECK e_dragdropobj->flavor = mc_sort_flavor.
    TRY.
        lo_dragdrop_sort ?= e_dragdropobj->object.
        CHECK lo_dragdrop_sort->at_rows  IS NOT INITIAL.
      CATCH cx_sy_move_cast_error.
        RETURN.
    ENDTRY.
    ASSIGN lo_dragdrop_sort->ao_data->* TO <lft_data>.
    SORT <lft_data> BY ('NATIO').
    LOOP AT <lft_data> ASSIGNING FIELD-SYMBOL(<lfs_data>).
      ASSIGN COMPONENT mv_sort_field OF STRUCTURE <lfs_data>
          TO FIELD-SYMBOL(<lfv_sortn>).
      CHECK sy-subrc = 0.
      <lfv_sortn> = CONV numc10( 1000 + ( sy-tabix * 100 ) ).
      "------->
      "PLACE YOUR SOLUTION HERE
      "AND HERE
      "...
      "....AND
      "------> HERE
      "---------------------------------------------> SOLUTION!! <------"
    ENDLOOP.
    "Set sort field
    me->get_sort_criteria( IMPORTING et_sort = DATA(lt_sort) ).
    IF NOT line_exists( lt_sort[ fieldname = mv_sort_field ] ).
      INSERT VALUE #( fieldname = mv_sort_field spos = 1 up = abap_true )
        INTO lt_sort INDEX 1.
    ENDIF.
    "Set sorting
    me->set_sort_criteria( lt_sort ).
    "Show changes
    refresh_table_display( ).
  ENDMETHOD.                                                               " new <<<
  METHOD set_sort_field.
    mv_sort_field = fieldname.
    SET HANDLER on_drop    FOR me.
    SET HANDLER on_drag    FOR me.
    SET HANDLER on_dropcomplete FOR me.
    DATA(lo_dd) = NEW cl_dragdrop( ).
    lo_dd->add( dragsrc    = abap_true
                droptarget = abap_true
                flavor     = mc_sort_flavor
                effect     = cl_dragdrop=>copy
                effect_in_ctrl = cl_dragdrop=>move ).
    DATA(lv_dd_handle) = 0.
    lo_dd->get_handle( IMPORTING handle = lv_dd_handle ).
    "Set drag-drop-handle
    get_frontend_layout( IMPORTING es_layout = DATA(ls_layout) ).
    ls_layout-s_dragdrop-row_ddid = lv_dd_handle.
    set_frontend_layout( ls_layout ).
    refresh_table_display( ).
  ENDMETHOD.
ENDCLASS.
CLASS main DEFINITION.
  PUBLIC SECTION.
    TYPES ty_data       TYPE t005t.
    TYPES ty_data_t     TYPE STANDARD TABLE OF ty_data
                             WITH DEFAULT KEY.
    DATA ms_data        TYPE ty_data.
    DATA mt_data        TYPE ty_data_t.
    DATA mr_grid        TYPE REF TO lcl_gui_alv_grid_sort.
    METHODS start.
  PROTECTED SECTION.
    METHODS selection.
    METHODS display.
ENDCLASS.
CLASS main IMPLEMENTATION.
  METHOD selection.
    SELECT * FROM t005t INTO TABLE mt_data UP TO 10 ROWS
     WHERE spras = sy-langu.
    LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<mt_data>).            " new
      <mt_data>-natio = CONV numc10( 1000 + ( sy-tabix * 100 ) ). " new
    ENDLOOP.                                                      " new
  ENDMETHOD.
  METHOD start.
    selection( ).
    display( ).
  ENDMETHOD.
  METHOD display.
    WRITE 'DUMMY'.
    CREATE OBJECT mr_grid
      EXPORTING
        i_parent      = cl_gui_container=>screen0
        i_appl_events = space.
    DATA lv_structure_name    TYPE dd02l-tabname VALUE 'T005T'.
    DATA ls_variant           TYPE disvariant.
    DATA lv_save              TYPE char01 VALUE 'U'.
    DATA lv_default           TYPE char01 VALUE abap_true.
    DATA ls_layout            TYPE lvc_s_layo.
    ls_layout-sel_mode       = 'A'.
    ls_layout-grid_title     = 'Countries'.
    mr_grid->set_table_for_first_display(
      EXPORTING
        i_structure_name              = lv_structure_name
        is_variant                    = ls_variant
        i_save                        = lv_save
        i_default                     = lv_default
        is_layout                     = ls_layout
      CHANGING
        it_outtab                     = mt_data ).
    mr_grid->set_sort_field( 'NATIO' ).
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  NEW main( )->start( ).

0 Kudos

Thanks for this solution Tibor!

This is a simple solution. I remember I also thought a second about this but rejected the solution because in my special case the sort field was just 4 digits long...

And as I began to calculate the new positions I thought that it must be possible to exactly calculate it. Maybe it's possible but not that simple...

Why did you put your last code in OnDropComplete? In this case it should also work w/o OnDropComplete...?

Regards

Enno

Former Member

Sorry, could not post it as reply to comment above. Tried it several times.

Enno wrote:
Why did you put your last code in OnDropComplete? In this case it should also work w/o OnDropComplete...?

I used OnDropComplete, because you mentioned it in your additional comment ("2. How can I access the event "dragging complete"?)

And it is inspired from paper algorithm.


Starting with initialized numbering:

On_Drap move selected lines and number them:

On_Drop move lines to new position and insert them:

On_DropComplete initialize numbering for next roundtrip:

haha, great! Love your paper work! 😉

The additional question especially referred to the drag-and-drop IN EDIT MODE. In this case the "normal" drag-and-drop events are not triggered.