Skip to Content
Author's profile photo Enno Wulff

Variations Of Selecting Multiple Options From A List

If you want to provide a user the possibility to select multiple values from a list then you normally have – in other programming languages – a control containing items an the possibiity to mark or unmark them. It’s called a listbox in Visual Basic for example. You eather choose items by holding CTRL key pressed or clicking the check box of the desired list items.

In ABAP you do not have such a simple control; you must build your own.

Using SALV

The most obvious way probably would be to use an SALV as a multi selection box which could look something like this:

The cl_gui_selection_box (no, it does not exist. yet…) can be easily enhanced by adding colors:

By clicking the checkbox the color of the line changes to green.

You could also use Icons instead of the checkbox:

If you would like to invest a little more time you can simulate a modern UI (kind of…):

You can also use the standard row selection of the SALV by marking the lines at the most left side of the SALV table. The user will then have to hold CTRL key to select more than one line. This is a bit problematic because if the user clicks somewehre else in the table the selection will get lost.

Using The Toolbar

A different way to achieve the goal of mutliple selections is using the toolbar-control CL_GUI_TOOLBAR. Here you can define a button as checked which indicates that this button or option is set or active. Using Icons the result might look something like this:

Disadvantages

All the above solutions – which really look quite cool, don’t they? – have one major disadvantage: The cost quite a lot amount of space. If selecting some options from a list is not part of a process step or if there is simply not enought place for showing this control than you use a dropdown menu.

The main advantage of these lists is that you always can see all selected and not selected options.

Using CL_CTMENU

The menu is quite a good option for displaying selected or unselected values because every menu item can be checked or not. If checked it will have a small circle in front of the option:

The disadvantage is that you cannot see all the selected options. You will have to click the menu to see which options are marked.

This dropdown menu has another disadvantage: After hitting an option the menu disappears and you will have to click again.

And this actually is the main reason for posting this: I had the idea to raise the dropdown-menu-clicked-event again after having selected an entry and rebuild the menu. The effect is that you can select an option, the menu entry will be changed to checked and right after the click the menu will be displayed again.

The effect can be set by using the DISPATCH method.

Ennotations & Ennohancements

🙂

Modularization

This code is a very good example to show how to build a general function that could be used in several different programs. You will have to add some methods so that a programmer can work with it (setting options, getting th results, placing the control in a given container, setting Icons and so) but I think it’s easy to imagine that such a tool can be very good re-used.

Customizing

With the given coding you can make the class customizable so that the programmer can set icons to be used, displaying titles etc. You could also use this mutliple selection control as a radio button alternative. just make sure that after selecting an option the oter options are unselected.

Multiple States

I could also imagine multiple states which iteratively will be set after clicking: Not only ON and OFF but GREEN, YELLOW, RED or MUST, SHOULD, CAN’T.

Dynamic programming

Dealing with tables where the structure is unknown could also be a possible option. Just pass the table to the class and tell the class which field is the one to use for marking and which one is the text field to display. Using ASSIGN COMPONENT (fieldname_for_text) OF STRUCTURE … you can provide an easy way for selecting mutliple lines of any table.

Code now on github

via abapGit

https://github.com/tricktresor/multiple_selections

Code For Dropdown Menu

REPORT.


CLASS lcl_main DEFINITION.
  PUBLIC SECTION.
    METHODS display.

  PROTECTED SECTION.
    TYPES: BEGIN OF ty_option,
             value   TYPE char10,
             text    TYPE string,
             checked TYPE boolean_flg,
           END OF ty_option.
    DATA: mytoolbar    TYPE REF TO cl_gui_toolbar,
          menupos_x    TYPE i,
          menupos_y    TYPE i,
          options      TYPE STANDARD TABLE OF ty_option,
          menu_dynamic TYPE REF TO cl_ctmenu.
    METHODS build_menu.
    METHODS on_function_selected FOR EVENT function_selected OF cl_gui_toolbar
      IMPORTING fcode sender.
    METHODS on_dropdown_clicked  FOR EVENT dropdown_clicked OF cl_gui_toolbar
      IMPORTING fcode posx posy sender.
ENDCLASS.                    "lcl_my_event_handler DEFINITION



CLASS lcl_main IMPLEMENTATION.

  METHOD build_menu.

    IF menu_dynamic IS INITIAL.
      "Create menu
      CREATE OBJECT menu_dynamic.
    ELSE.
      "Clear all entries before rebuild
      menu_dynamic->clear( ).
    ENDIF.

    LOOP AT options ASSIGNING FIELD-SYMBOL(<option>).
      "add menu entry with current status
      menu_dynamic->add_function( fcode   = CONV #( <option>-value )
                                  checked = <option>-checked
                                  text    = CONV #( <option>-text ) ).

    ENDLOOP.

  ENDMETHOD.

  METHOD display.

    "Create docker on Top of the screen
    DATA(docker) = NEW cl_gui_docking_container( side = cl_gui_docking_container=>dock_at_top extension = 30 ).

    "create toolbar object
    mytoolbar = NEW #( parent = docker ).

    "register events
    mytoolbar->set_registered_events( VALUE #( ( eventid = cl_gui_toolbar=>m_id_function_selected )
                                               ( eventid = cl_gui_toolbar=>m_id_dropdown_clicked ) ) ).

    "Set handler
    SET HANDLER on_function_selected FOR mytoolbar.
    SET HANDLER on_dropdown_clicked  FOR mytoolbar.

    "set initial values
    options = VALUE #( ( value = 'ONE'   text = 'Option One' )
                       ( value = 'TWO'   text = 'Option Two' )
                       ( value = 'THREE' text = 'Option Three' )
                       ( value = 'FOUR'  text = 'Option Four' ) ).
    "Build menu
    build_menu( ).

    "Add button for selecting options
    mytoolbar->add_button( EXPORTING
                             icon             = 'ICON_TOOL'
                             fcode            = 'CHOOSE'
                             butn_type        = '1'
                             text             = 'Select options'
                             quickinfo        = 'Select some options...'
                           EXCEPTIONS
                             cntb_error_fcode = 1 ).

  ENDMETHOD.

  METHOD on_function_selected.

    "switch option entry
    LOOP AT options ASSIGNING FIELD-SYMBOL(<option>).
      IF <option>-value = fcode.
        IF <option>-checked = abap_true.
          <option>-checked = abap_false.
        ELSE.
          <option>-checked = abap_true.
        ENDIF.
      ENDIF.
    ENDLOOP.

    "rebuild menu
    build_menu( ).

    "raise event dropdown clicked again
    sender->dispatch( cargo = 'mytoolbar' eventid = cl_gui_toolbar=>m_id_dropdown_clicked is_shellevent = abap_false ).

    "Set coordinates of menu
    sender->track_context_menu(
         context_menu = menu_dynamic
         posx         = menupos_x
         posy         = menupos_y ).

  ENDMETHOD.                    "lcl_my_event_handler

  METHOD on_dropdown_clicked.

    IF fcode = 'CHOOSE'.
      "call of dropdown: remember current position for displaying menu
      menupos_x = posx.
      menupos_y = posy.
    ENDIF.

    "Set coordinates
    mytoolbar->track_context_menu(
        context_menu = menu_dynamic
        posx         = posx
        posy         = posy ).

  ENDMETHOD.                    "lcl_my_event_handler

ENDCLASS.                    "lcl_my_event_handler IMPLEMENTATION


INITIALIZATION.

  new lcl_main( )->display( ).


  PARAMETERS p_test.

Code For SALV List

REPORT .

CLASS lcl_main DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_option,
             mark  TYPE boolean_flg,
             icon  type icon_d,
             key   TYPE c LENGTH 10,
             text  TYPE c LENGTH 100,
             _col_ TYPE lvc_t_scol,
           END OF ty_option,
           ty_options TYPE STANDARD TABLE OF ty_option with DEFAULT KEY.
    METHODS set IMPORTING options TYPE ty_options.
    METHODS get RETURNING VALUE(options) TYPE ty_options.
    METHODS display.
  PROTECTED SECTION.
    DATA mt_options TYPE ty_options.
    DATA mo_salv TYPE REF TO cl_salv_table.
    METHODS on_click FOR EVENT link_click OF cl_salv_events_table IMPORTING row column sender.
    METHODS set_colors.
ENDCLASS.

CLASS lcl_main IMPLEMENTATION.
  METHOD set.
    mt_options = options.
    set_colors( ).
  ENDMETHOD.

  METHOD get.
    options = mt_options.
  ENDMETHOD.

  METHOD display.

    DATA(docker) = NEW cl_gui_docking_container( extension = 200 side = cl_gui_docking_container=>dock_at_left ).

    DATA o_column TYPE REF TO cl_salv_column_table.

    cl_salv_table=>factory( EXPORTING r_container = docker
                            IMPORTING r_salv_table = mo_salv
                            CHANGING t_table = mt_options ).

    DATA(layout)  = mo_salv->get_display_settings( ).
    layout->set_list_header( 'Select option' ).



    DATA(columns) = mo_salv->get_columns( ).
    columns->set_color_column( '_COL_' ).
    columns->set_headers_visible( abap_false ).

    o_column ?= columns->get_column( 'MARK' ).
*    o_column->set_cell_type( if_salv_c_cell_type=>checkbox_hotspot ).
    o_column->set_technical( abap_true ).

    o_column ?= columns->get_column( 'ICON' ).
    o_column->set_cell_type( if_salv_c_cell_type=>hotspot ).
    o_column->set_icon( abap_true ).

    o_column ?= columns->get_column( 'KEY' ).
    o_column->set_technical( abap_true ).

    o_column ?= columns->get_column( 'TEXT' ).

    mo_salv->display( ).

    DATA(handler) = mo_salv->get_event( ).
    SET HANDLER on_click FOR handler.


  ENDMETHOD.
  METHOD on_click.

    READ TABLE mt_options ASSIGNING FIELD-SYMBOL(<option>) INDEX row.
    IF sy-subrc = 0.
      IF <option>-mark = abap_true.
        <option>-mark = abap_false.
         <option>-icon = icon_led_red.
        CLEAR <option>-_col_.
        APPEND INITIAL LINE TO <option>-_col_ ASSIGNING FIELD-SYMBOL(<col>).
*        <col>-color-col = col_background.
      ELSE.
        <option>-mark  = abap_true.
         <option>-icon = icon_led_green.
        CLEAR <option>-_col_.
        APPEND INITIAL LINE TO <option>-_col_ ASSIGNING <col>.
*        <col>-color-col = col_positive.
      ENDIF.
    ENDIF.

    mo_salv->refresh( ).
    DATA(selections) = mo_salv->get_selections( ).
    selections->set_selected_cells( VALUE #( ) ).


  ENDMETHOD.

  METHOD set_colors.

    LOOP AT mt_options ASSIGNING FIELD-SYMBOL(<option>).
      CLEAR <option>-_col_.
      APPEND INITIAL LINE TO <option>-_col_ ASSIGNING FIELD-SYMBOL(<col>).
      IF <option>-mark = abap_false.
*        <col>-color-col = col_background.
        <option>-icon = icon_led_red.
      ELSE.
*        <col>-color-col = col_positive.
        <option>-icon = icon_led_green.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.
ENDCLASS.
PARAMETERS p_dummy.

INITIALIZATION.

  DATA(main) = NEW lcl_main( ).
  main->set( VALUE #( ( text = `One`   key = '1' )
                      ( text = `Two`   key = '2' )
                      ( text = `Three` key = '3'  )
                      ( text = `Four`  key = '4'  )
                     ) ).
  main->display( ).

AT SELECTION-SCREEN.
  cl_demo_output=>display_data( main->get( ) ).

Code For Menu Buttons

REPORT.

CLASS lcl_main DEFINITION.
  PUBLIC SECTION.
    METHODS display.

  PROTECTED SECTION.
    TYPES: BEGIN OF ty_option,
             value   TYPE char10,
             text    TYPE string,
             checked TYPE boolean_flg,
           END OF ty_option.
    DATA: mytoolbar TYPE REF TO cl_gui_toolbar,
          options   TYPE STANDARD TABLE OF ty_option.
    METHODS build_menu.
    METHODS on_function_selected FOR EVENT function_selected OF cl_gui_toolbar
      IMPORTING fcode sender.

ENDCLASS.                    "lcl_my_event_handler DEFINITION


CLASS lcl_main IMPLEMENTATION.

  METHOD build_menu.

    DATA lv_icon TYPE icon_text.

    IF mytoolbar IS INITIAL.
      "Create docker on Top of the screen
      DATA(docker) = NEW cl_gui_docking_container( side = cl_gui_docking_container=>dock_at_left extension = 130 ).


      "create toolbar object
      mytoolbar = NEW #( parent       = docker
                         display_mode = cl_gui_toolbar=>m_mode_vertical ).

      "register events
      mytoolbar->set_registered_events( VALUE #( ( eventid = cl_gui_toolbar=>m_id_function_selected ) ) ).

      "Set handler
      SET HANDLER on_function_selected FOR mytoolbar.
    ELSE.
      mytoolbar->delete_all_buttons( ).
    ENDIF.

    LOOP AT options ASSIGNING FIELD-SYMBOL(<option>).
      IF <option>-checked = abap_false.
*        lv_icon = icon_led_red.
*        lv_icon = icon_space.
        lv_icon = icon_unlocked.
      ELSE.
*        lv_icon = icon_led_green.
*        lv_icon = icon_alarm.
        lv_icon = icon_locked.
      ENDIF.
      "add menu entry with current status
      mytoolbar->add_button( fcode      = CONV #( <option>-value )
                             icon       = lv_icon
                             is_checked = <option>-checked
                             butn_type  = cntb_btype_button
                             text       = CONV #( <option>-text ) ).

    ENDLOOP.

  ENDMETHOD.

  METHOD display.


    "set initial values
    options = VALUE #( ( value = 'ONE'   text = 'Option One' )
                       ( value = 'TWO'   text = 'Option Two' )
                       ( value = 'THREE' text = 'Option Three' )
                       ( value = 'FOUR'  text = 'Option Four' ) ).
    "Build menu
    build_menu( ).


  ENDMETHOD.

  METHOD on_function_selected.

    ASSIGN options[ value = fcode ] TO FIELD-SYMBOL(<option>).
    IF sy-subrc = 0.
      IF <option>-checked = abap_true.
        <option>-checked = abap_false.
      ELSE.
        <option>-checked = abap_true.
      ENDIF.
    ENDIF.

    build_menu( ).

  ENDMETHOD.                    "lcl_my_event_handler



ENDCLASS.                    "lcl_my_event_handler IMPLEMENTATION


INITIALIZATION.

  NEW lcl_main( )->display( ).


  PARAMETERS p_test.

Assigned Tags

      14 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michelle Crapo
      Michelle Crapo

      Love this!

      Thank you,

      Michelle

      Author's profile photo Christian Guenter
      Christian Guenter

      Hi Enno, nice work, but you forgot the link to the abapGit repository 😉

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      It's localGit... 😉

      I will promise to catch up on this!

      Easy way to fill my repository... :]

      Author's profile photo Florian Henninger
      Florian Henninger

      Christian Guenter already mentioned it?

      Nice work and always good to see that others also have to solve such problems.

      Only question left. Why local classes? I do not see any advantage of using it....

      No matter if just used in one application or not.

      ~Florian

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      Local classes because I can write and test it very easily in one report. CTRL-A + CTRL-C in the web and CTRL-V in your editor and you can test once in a second in your system.

      simpleGit. 😉

      Nearly every article on tricktresor.de works like that...

       

      Author's profile photo Florian Henninger
      Florian Henninger

      ADT global classes also work like a charm:-)

      Heyhey, I'm with you and can understand that for testing and publishing purpose a local class is a good isntrument.

      Author's profile photo Timo John
      Timo John

      Hello Enno,

      thanks great blog to #currentWorldsChallenges we face right now.

      Will try these option in the next classic Report.

       

      Florian Henninger For these kind of demos I really see an advantage in portability to do everything local. If I recall it correctrly there are tolls to migrate from local to global by SAP and the other by Former Member  https://github.com/larshp/local-to-global but I did not test it ...

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      #currentWorldsChallenges 😉

      I stopped the world exploding just in time... 🙂

      Author's profile photo Joachim Rees
      Joachim Rees

      Well, no extra tools needed, SE24 an do it out of the box:

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      abapGit repository added: https://github.com/tricktresor/multiple_selections

       

      Author's profile photo Christian Guenter
      Christian Guenter

      You got your first pull request 😉

      Author's profile photo Enno Wulff
      Enno Wulff
      Blog Post Author

      And kinda proud of it... :]

      Author's profile photo Former Member
      Former Member

      Good

      Author's profile photo Javier Andrés Cáceres Moreno
      Javier Andrés Cáceres Moreno

      Good coding