Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member182680
Active Participant

Popups in Webdynpro for ABAP are annoying

Recently I had the pleasure to show one of these "Are you sure?" popups in the UI. The issue with popups in webdynpro is, due to the web based architecture, the fact that there is no real synchronous program flow as we were used to in SAP GUI.

Instead of stopping the program flow when a popup is shown, the Webdynpro runtime continues in order to fulfill the HTTP web request.

This means, instead of receiving the result of the user's decision right after the popup call, we usually need to implement callback methods which are called when the user presses either "Ok", "No" and so on.

In the sample this would be the view's method ONACTIONAPPLY_CONFIRM.

DATA lo_comp_api TYPE REF TO if_wd_component.
DATA lo_controller_api TYPE REF TO if_wd_controller.
DATA ls_config TYPE wdr_popup_to_confirm.
DATA: lt_text_table TYPE string_table,
          lv_text_table TYPE string,
          lv_title TYPE string.
ls_config-button_cancel-enabled = abap_false.
lo_controller_api = wd_this->wd_get_api( ).
lo_comp_api = wd_comp_controller->wd_get_api( ).
lv_title = 'Are you sure?'.
lv_text_table = 'Really???'.
APPEND lv_text_table TO lt_text_table.
TRY.
          CALL METHOD cl_wd_popup_factory=>popup_to_confirm
          EXPORTING
            component        = lo_comp_api
            text             = lt_text_table
            window_title     = lv_title
            configuration    = ls_config
          RECEIVING
            popup_to_confirm = wd_this->mo_popup.
        wd_this->mo_popup->subscribe_to_events(
          controller = lo_controller_api
          handler_name = 'ONACTIONAPPLY_CONFIRM' ).
*retrieve backend call parameters
DATA lv_p1 TYPE i.
DATA lv_p2 TYPE string.
*...retrieve parameters
*store backend call parameters in the view's attributes
wd_this->mv_p1 = lv_p1.
wd_this->mv_p2 = lv_p2.

Did you recognize line 29 - 34? Actually, what is done here is the preparation of the backend call which is to be executed depending on the user's decision. The method parameters are already stored in some attributes and read for later purpose.

In the callback method, the backend invocation might look like the code below.

METHOD ONACTIONAPPLY_CONFIRM.
  CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  wd_this->mo_some_weird_backend->perform_some_weird_operation( iv_p1 = wd_this->mv_p1 iv_p2 = wd_this->mv_p2 )
ENDMETHOD.

Every parameter that will conditionally passed to the backend will have to be stored somewhere.

You do this kind of work one time, maybe two times or three times and you are going to be annoyed at it.

Couldn't this behaviour be achieved easier and more conventiently?

What is a method invocation queue?

A method invocation queue records method calls for later execution. Instead of storing parameters for later usage, you will store the objects and their method calls including parameters for later usage. As ABAP still lacks function pointers we will need to implement it manually.

Compare the above shown coding, starting at line 29 with this one.

*retrieve backend call parameters and record backend calls
DATA lv_p1 TYPE i.
DATA lv_p2 TYPE string.
*...retrieve parameters
wd_this->mo_method_queue = zcl_method_queue=>new_method_invocation( ).
wd_this->mo_method_queue->with( wd_this->mo_some_weird_backend )->on( 'perform_some_weird_operation' ).
wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p1' iv_value = lv_p1 ).
wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p2' iv_value = lv_p2 ).

Looks more complicated at the beginning. But the big advantage of this concept is, that you will now only have to keep track of the method invocation queue, not method parameters. No further attributes are required.

Actually, using the fluent API, you could write lines 6 to 8 in just one single line.

The popup's callback method now looks like this.

METHOD ONACTIONAPPLY_CONFIRM.
  CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  wd_this->mo_method_queue->flush( ).
ENDMETHOD.

Interested?

So here is the coding. Please be aware, this is just experimental and not meant for productive usage.

This code lacks the support of any other parameter type than IMPORTING parameters as well as proper exception handling. However you may consider to enhance it and write about what you found out 😉

CLASS zcl_method_queue DEFINITION
   PUBLIC
   FINAL
   CREATE PROTECTED .
   PUBLIC SECTION.
     TYPE-POOLS abap .
     CLASS-METHODS new_method_invocation
       RETURNING
         value(ro_method_queue) TYPE REF TO zcl_method_queue .
     METHODS with
       IMPORTING
         !io_caller TYPE REF TO object
       RETURNING
         value(ro_method_queue) TYPE REF TO zcl_method_queue .
     METHODS on
       IMPORTING
         !iv_method TYPE seocpdname
       RETURNING
         value(ro_method_queue) TYPE REF TO zcl_method_queue .
     METHODS add_parameter
       IMPORTING
         !iv_name TYPE abap_parmname
         value(iv_value) TYPE any
       RETURNING
         value(ro_method_queue) TYPE REF TO zcl_method_queue .
     METHODS finalize .
     METHODS flush
       RAISING
         zcx_method_queue .
   PROTECTED SECTION.
     TYPES:
       BEGIN OF ty_s_method_invocation,
         caller TYPE REF TO  object,
         method TYPE  seocpdname,
         parameters TYPE  abap_parmbind_tab,
       END OF ty_s_method_invocation .
     DATA ms_method_invocation TYPE ty_s_method_invocation .
     DATA:
       mt_method_invocations TYPE TABLE OF ty_s_method_invocation .
     METHODS copy_value
       IMPORTING
         !ir_ref TYPE REF TO data
       RETURNING
         value(rr_ref) TYPE REF TO data .
     METHODS constructor .
   PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_METHOD_QUEUE IMPLEMENTATION.
   METHOD add_parameter.
     ro_method_queue = me.
     DATA ls_parameter TYPE abap_parmbind.
     DATA lr_value TYPE REF TO data.
     ls_parameter-name = iv_name.
     TRANSLATE ls_parameter-name TO UPPER CASE.
     ls_parameter-kind = cl_abap_objectdescr=>exporting.
     GET REFERENCE OF iv_value INTO lr_value.
     ls_parameter-value = copy_value( lr_value ).
     INSERT ls_parameter INTO TABLE ms_method_invocation-parameters.
   ENDMETHOD.                    "add_parameter
   METHOD constructor.
   ENDMETHOD.                    "CONSTRUCTOR
   METHOD copy_value.
     FIELD-SYMBOLS <lv_in> TYPE any.
     FIELD-SYMBOLS <lv_out> TYPE any.
     ASSIGN ir_ref->* TO <lv_in>.
     CREATE DATA rr_ref LIKE <lv_in>.
     ASSIGN rr_ref->* TO <lv_out>.
     <lv_out> = <lv_in>.
   ENDMETHOD.
   METHOD finalize.
     CHECK: ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
     APPEND ms_method_invocation TO mt_method_invocations.
     CLEAR: ms_method_invocation.
   ENDMETHOD.
   METHOD flush.
     FIELD-SYMBOLS <ls_call> TYPE ty_s_method_invocation.
     DATA lo_cx_root TYPE REF TO cx_root.
     DATA lo_objectdescr TYPE REF TO cl_abap_classdescr.
     DATA lo_cx_sy_dyn_call_illegal_meth TYPE REF TO cx_sy_dyn_call_illegal_method.
     DATA ls_method TYPE abap_methdescr.
     DATA lv_has_method_been_called TYPE abap_bool.
     DATA lv_count TYPE i.
     DATA lt_method_parts TYPE string_table.
     DATA lv_method_name TYPE string.
     finalize( ).
     LOOP AT mt_method_invocations ASSIGNING <ls_call>.
       TRY.
           CALL METHOD <ls_call>-caller->(<ls_call>-method) PARAMETER-TABLE <ls_call>-parameters.
         CATCH cx_sy_dyn_call_illegal_method INTO lo_cx_sy_dyn_call_illegal_meth.
*         no such method - try to call named method in any interface that the current object might implement
           lv_has_method_been_called = abap_false.
           lo_objectdescr ?= cl_abap_classdescr=>describe_by_object_ref( <ls_call>-caller ).
           LOOP AT lo_objectdescr->methods INTO ls_method.
             TRY.
                 lv_method_name = ls_method-name.
                 SPLIT lv_method_name AT '~' INTO TABLE lt_method_parts.
                 FIND <ls_call>-method IN TABLE lt_method_parts MATCH COUNT lv_count.
                 IF lv_count > 0.
                   CALL METHOD <ls_call>-caller->(ls_method-name) PARAMETER-TABLE <ls_call>-parameters.
                   lv_has_method_been_called = abap_true.
                   EXIT.
                 ENDIF.
               CATCH cx_root INTO lo_cx_root.
                 CONTINUE.
             ENDTRY.
           ENDLOOP.
*         has method been found?
           IF lv_has_method_been_called = abap_false.
             RAISE EXCEPTION TYPE zcx_method_queue
               EXPORTING
                 previous = lo_cx_root
                 method   = <ls_call>-method.
           ENDIF.
         CATCH cx_root INTO lo_cx_root.
           RAISE EXCEPTION TYPE zcx_method_queue
             EXPORTING
               previous = lo_cx_root
               method   = <ls_call>-method.
       ENDTRY.
     ENDLOOP.
   ENDMETHOD.                    "flush
   METHOD new_method_invocation.
     CREATE OBJECT ro_method_queue.
   ENDMETHOD.                    "new_method_invocation
   METHOD on.
     ro_method_queue = me.
     ms_method_invocation-method = iv_method.
     TRANSLATE ms_method_invocation-method TO UPPER CASE.
   ENDMETHOD.                    "on
   METHOD with.
     ro_method_queue = me.
     IF ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
       finalize( ).
     ENDIF.
     ms_method_invocation-caller = io_caller.
   ENDMETHOD.                    "with
ENDCLASS.

Drawbacks of this approach

However, there's no such thing as a free lunch.

If you decide to use such a method invocation queue, you lose the where-used-list support of your backend's methods.

Also, you can only access real backend methods and not component controller methods, as they are accesssible for their views or windows, but not for any other objects, unless these methods are declared public.

So stay careful, be watchful and enjoy...

Labels in this area