Skip to Content

After giving a brief introduction to dynamic programming in Web Dynpro ABAP and taking a look at a few important concepts regarding ViewElements Dynamic Programming in Web Dynpro ABAP – Introduction and Part I: Understanding UI Elements, we will actually do some dynamic programming now. We will look at accessing a ViewElement and changing its properties. Later on we will use the theory introduced previously to create a Button (a UIElement), as well as a Group (a UIElementContainer). Last but not least, this blog will cover binding events to actions and attributes to context entities – of course all of it done at runtime.</p>


Accessing an Existing ViewElement


In order to change an existing ViewElement, one obviously needs to have a pointer to it. Such a pointer can easily be obtained by calling the method GET_ELEMENT of interface IF_WD_VIEW. Such a typed pointer is available within method wdDoModifyView (avialable within any ViewController), as a parameter called VIEW. The following source code shows how one can obtain a pointer to the RootUIElementContainer.


data:
lr_container type ref to cl_wd_transparent_container.

lr_container ?= view->get_element( `ROOTUIELEMENTCONTAINER` ).


Creating a ViewElement


method wddomodifyview.


  data:
    lr_container type ref to cl_wd_uielement_container,
    lr_button    type ref to cl_wd_button.


  • get a pointer to the RootUIElementContainer

  lr_container ?= view->get_element( ‘ROOTUIELEMENTCONTAINER’ ).


  • create the button and add it to the container

  lr_button = cl_wd_button=>new_button( ).
  cl_wd_flow_data=>new_flow_data( element = lr_button ).
  lr_container->add_child( lr_button ).


endmethod.

After retrieving the surrounding container, the next two lines create the button and the FlowLayoutData. Since we don’t need to further access the LayoutData, the pointer is not saved in a local variable. After that the button is added to the surrounding container.

In case the to be created UIElement is a container, the sequence of steps needs to be extended by adding a Layout. It looks like this:

  • Create the container
  • Create the LayoutData that fits to the Layout of the surrounding container
  • Create the Layout that is to be used by the container
  • Insert the new container into the surrounding container

Let’s make an example where we create a Group that gets a MatrixLayout and resides within a container that has a FlowLayout



method wddomodifyview.


  data:
    lr_container type ref to cl_wd_uielement_container,
    lr_group     type ref to cl_wd_group.


  1. get a pointer to the RootUIElementContainer

  lr_container ?= view->get_element( ‘ROOTUIELEMENTCONTAINER’ ).


  1. create the group and add it to the container

  lr_group     = cl_wd_group=>new_group( ).
  cl_wd_flow_data=>new_flow_data( element = lr_group ).
  cl_wd_matrix_layout=>new_matrix_layout( container = lr_group ).
  lr_container->add_child( lr_group ).


endmethod.
</pre>


Assigning Values to Attributes


After implementing and executing the example given in the last chapter, you will notice that the Button has no caption. We are going to change that now. In order to fix this problem, we simply provide a string, which contains the text, during creation of this ViewElement. The coding changes as follows:


lr_button = cl_wd_button=>new_button( text = `Confirm` ).

Although the coding above works like a charm, it has a serious flaw: The text provided won’t be translated into other languages, since it is just a string somewhere in the source code. In order to overcome this limitation, we need to use a text from a text repository or some other place from which we know, that it will be translated. One way to achieve this, could be to employ the new assistant class concept and to get the text from there. But since this would sidetrack us too much into first explaining the concept behind this special class, I will simply use the OTR. Web Dynpro offers support for OTR texts. Class CL_WD_UTILITIES provides a method to read such a text.

The coding to create the button now changes to:



method wddomodifyview.


  data:
    lr_container type ref to cl_wd_uielement_container,
    lr_button    type ref to cl_wd_button,
    text         type string.


  1. get a pointer to the RootUIElementContainer

  lr_container ?= view->get_element( ‘ROOTUIELEMENTCONTAINER’ ).


  1. get the text from the OTR

  text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/CONFIRM’ ).


  1. create the button and add it to the container

  lr_button = cl_wd_button=>new_button( text = text ).
  cl_wd_flow_data=>new_flow_data( element = lr_button ).
  lr_container->add_child( lr_button ).


endmethod.

Besides supplying attributes with values while creating a ViewElement, there is also the possibility to change them later. In order to support this, each ViewElement provides a SET_ method for each of its attributes. A correspnding GET_ method exists as well. The following coding shows how the text of the button is changed from “Confirm” to “Copy”.



  1. get the new text from the OTR

  new_text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/COPY’ ).


  1. set the new text

  lr_button->set_text( new_text ).

Obviously, not all attributes present a text that is to be displayed somewhere within the ViewElement. Another type of attributes are those that contain a value of a certain enumeration. The ViewDesigner in the Workbench visualizes them as a dropdown listbox.


image


The type of such an attribute is a special data element – a different one for each type of enumeration. The general rule is that these data elements start with the prefix WDUI_. So if you are looking for the enumeration that is behind the attribute VISIBLE, you would take WDUI_VISIBILITY. The values of those enumerations are provided directly at the ViewElement itself. Let’s make an example. For a button such an enumeration attribute is “design”. The following coding changes the design of a button to “emphasized”:


lr_button->set_design( cl_wd_button=>e_design-emphasized ).

Simple enough. Do you recognize the scheme behind accessing such an enum value? For each attribute that is of a enum type, just add e_ in front of the name of the attribute and add the nane of the enum value after a – behind it. The data element is only used when you need to bind such an attribute to the context. The context attribute has the type of the data element then.


Binding Events to Actions


The button from the example above has a caption and a nice design, but one thing is still missing: No one can click on it. The reason is that we haven’t specified what should happen if someone did that. Such a description is stored in an Action as normal source code. The assignment of an Action to an event of a ViewElement is called “event binding”.

For a button, the only available event is “onAction”. The runtime raises it, whenever a user clicks the button. By the way: it was not named “onClick”, because this event can also be triggered by using the keyboard or via source code.

In the following example, this event will either be bound to the action CHANGE_TEXT or RESET_TEXT. The coding within those two actions is then supposed to change the text of the button whenever the onAction event was raised. This will be achieved by switching the event binding between those two actions. The source code of CHANGE_TEXT changes the text of the button to “Copy” whereas RESET_TEXT resets it to the initial “Confirm”.

An event can already be bound to an action during creation of a ViewElement. The corresponding parameter of the NEW_ method has the same name as the event itself. In our case, this is ON_ACTION. The action specified has to exist within the current view to be executed. The coding to create a button and to assign action CHANGE_TEXT to its event looks as follows:


lr_button = cl_wd_button=>new_button( on_action = `CHANGE_TEXT` ).

Furthermore there is the possibility to specify/change event binding later on. For this reason, there exists a SET_ method. In our case, it is called SET_ON_ACTION. A corresponding GET_ method exists as well. Therefore, the coding to change the event binding to RESET_TEXT looks as follows:


lr_button->set_on_action( `RESET_TEXT` ).

All these method calls can only be done in method wdDoModifyView of a view controller since a pointer to the view is not supplied in the other hook methods. So if you would like to change a view element, a two-step process has to be followed: First, some controller-global variable is filled in the action that contains a marker to be evaluated later on in wdDoModifyView. Secondly, within wdDoModifyView the marker is checked and the appropriate operation is executed.

The whole coding to change the event binding and thus the caption of the button looks like follows:



method wddomodifyview.


  data:
    lr_container type ref to cl_wd_uielement_container,
    lr_button    type ref to cl_wd_button,
    text         type string,
    new_text     type string.


  constants:
    lc_my_button type string value ‘MY_BUTTON’.


  1. act depending on if we are supposed to initialize everything or change the
  2. screen according to some action handler

  if first_time = abap_true.

  1.   get the text of the button from OTR

    text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/CONFIRM’ ).


  1.   create the button

    lr_container ?= view->get_element( ‘ROOTUIELEMENTCONTAINER’ ).
    lr_button = cl_wd_button=>new_button( id = lc_my_button text = text on_action = ‘CHANGE_TEXT’ ).
    cl_wd_flow_data=>new_flow_data( element = lr_button ).
    lr_container->add_child( lr_button ).
  else.

  1.   not the first time
  2.   could be changing the text

    if wd_this->m_change_text = abap_true.
      lr_button ?= view->get_element( lc_my_button ).
      text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/COPY’ ).
      lr_button->set_text( text ).
      lr_button->set_on_action( `RESET_TEXT` ).
      wd_this->m_change_text = abap_false.
    endif.


  1.   could be resetting the text

    if wd_this->m_reset_text = abap_true.
      lr_button ?= view->get_element( lc_my_button ).
      text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/CONFIRM’ ).
      lr_button->set_text( text ).
      lr_button->set_on_action( `CHANGE_TEXT` ).
      wd_this->m_reset_text = abap_false.
    endif.
  endif.


endmethod.


method onactionchange_text.


  wd_this->m_change_text = abap_true.


endmethod.


method onactionreset_text.


  wd_this->m_reset_text = abap_true.


endmethod.

The two marker attributes at the ViewController are as follows:


m_change_text type wdy_boolean.
m_reset_text type wdy_boolean.

Honestly, the example mainly has some academic value, because in real life an event bindin won’t usually be changed at runtime. The described problem could have been solved alot easier by using a single action. If the text attribute of the button had been bound to a context attribute, no flags would have to be set and no additional coding that goes beyond creating the button would have been necessary within wdDoModifyView. The caption could have been changed with the action handler itself. This will be done in the next chapter.


Binding Attributes to the Context


Changing the caption of the button by using the SET_TEXT method of the view element has one major disadvantage: It is totally intransparent and creates so called spaghetti-coding where marked flags set somewhere trigger something else in wdDoModifyView. Doing dynamic programming this way, will sooner or later lead to highly fragmented coding and thus bad maintainability. For complex applications it would be very difficult to determine which view element uses which data, because everything would be solely expressed by using source code. For large applications wdDoModifyView would perhaps call many different methods – one for each operation – worsening the problem even further.

In order to separate the data from its representation within a ViewElement, every controller features a context for storing all kinds of data. Attributes of a ViewElement can access this data by binding to an entity of such a context. Entities of the context are nodes, node elements and attributes. They all come in different flavours, but this is not important in this scope, because all kinds of nodes and attributes behave the same way towards the attributes of the ViewElements that bind to it.

So let’s extend our button example by dynamically binding the text attribute to a context attribute of type string that will supply us with the text. Later on, we will then change the text by clicking on the button. For the user, the result will be the same as in the previous event binding example.

But before we will jump into actually coding this part, some theory regarding the binding of attributes of ViewElements has be covered as well.

An attribute of a ViewElement is either “bindable”, “bindable mandatory” or “not bindable”. This is specified in the meta model of this ViewElement. In case an attribute can be bound, the model further distinguishes if it should bind to a node or to an attribute. If it binds to an attribute, it is specified if the cardinality of context node or one of the parent nodes have to be multiple or not. Multiple means that such a node is allowed to have more than one node element. All this has to be taken into account when context binding is done dynamically. The online documentation that is generated from the meta model shows the binding type of each attribute. Specifically, for button you can find the information here
method wddoinit.


  1. get the two texts from OTR

  wd_this->m_confirm_text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/CONFIRM’ ).
  wd_this->m_copy_text    = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/COPY’ ).


endmethod.


method wddomodifyview.


  data:
    lr_container type ref to cl_wd_uielement_container,
    lr_button    type ref to cl_wd_button,
    text         type string.


  constants:
    lc_description type string value ‘DESCRIPTION’.


  1. let’s do it only once

  check first_time = abap_true.


  1. get a pointer to the RootUIElementContainer

  lr_container ?= view->get_element( ‘ROOTUIELEMENTCONTAINER’ ).


  1. create the button and add it to the container

  lr_button = cl_wd_button=>new_button( bind_text = lc_description on_action = ‘ON_ACTION’ ).
  cl_wd_flow_data=>new_flow_data( element = lr_button ).
  lr_container->add_child( lr_button ).


  1. fill the context attribute with the OTR text

  text = cl_wd_utilities=>get_otr_text_by_alias( ‘SOTR_VOCABULARY_BASIC/CONFIRM’ ).
  wd_context->set_attribute( name = lc_description value = text ).


endmethod.


method onactionon_action.


  data:
    text type string.


  constants:
    lc_description type string value ‘DESCRIPTION’.


  1. get the current caption text

  wd_context->get_attribute( exporting name = lc_description importing value = text ).


  1. change the text

  if text = wd_this->m_copy_text.
    wd_context->set_attribute( name = lc_description value = wd_this->m_confirm_text ).
  else.
    wd_context->set_attribute( name = lc_description value = wd_this->m_copy_text ).
  endif.


endmethod.

Additionally, there is a context attribute DESCRIPTION of type string as well as the following two controller attributes:


m_confirm_text type string
m_copy_text type string

As you can see, most of the drawbacks of the event-binding example are gone. Method wdDoModifyView now does only what is supposed to do: Creating the button. With the guarding statement at the beginning it is only executed a single time now during initial creation of the view. Furthermore, there is only a single action handler. The coding that handles the event is now fully inside of the action. No more fragmentation over multiple methods with marker attributes to keep track of what was changed. We could even use the context log and have the runtime keep track of all changes. Very convinient and the associated costs for maintaining this solution are much lower compared to the event binding example.


Conclusion


We have concluded with the first part of the basic operations concerning dynamic programming of ViewElements. The next next part of this blog will mainly deal with aggregations – a huge topic by itself. We already touched it a bit as we were creating a UIElement, because we had to add it to a container – namely to its CHILDREN aggregation. Besides aggregations, another advanced topic will be presented in the next part, which will be creating a DDIC binding of a property at runtime.

To report this post you need to login first.

14 Comments

You must be Logged on to comment or reply to a post.

  1. Hi Thomas,

    This article was very helpful to play with the dynamic programming.

    I tried few examples and need your comments on the following

    1. When is WDDOMODIFYVIEW triggered? Is it for every user action on the UI elements in the view?

    2. How to disable an Input Field on the click of a Button in a view, as IF_WD_VIEW is not available to access the pointer to the UI element(Input Field) within the defined action method.

    Thanks & Regards
    Kamal

    (0) 
    1. Thomas Szücs Post author
      Hi Kamal,

      Thanks that you liked the web log. Let me quickly answer your two questions:

      1. Whenever an action handler was called before in the same roundtrip (so not while scrolling in a table without a bound onSort action or while expanding a tree node).
      2. Best way would be to bind the enabled attribute to a context attribute and then change it. This is possible in an action handler. Another not recommended way is to aquire a pointer to the inputfield in question in wdDoModifyView and to store it as a controller attribute.

      Best regards,
      Thomas

      P.S.: The next part covering aggregations is almost finished. I made a few changes to the original text and created two additional examples. Should be published somewhen beginning of next week.

      (0) 
  2. Ian Stubbings
    Hi Thomas

    I have read all three of your blogs on the this subject and have one question for you.

    Can the view->get_element reference only be directed to the root element?

    Whenever I try to insert an element elsewhere i.e where I want to! I get a runtime error.

    I have a requirement that would be hugely simplified by utilising dynamic ui element creation but I can’t seem to make any headway at the moment due to this apparent limitation.

    Thanks in advance
    Ian

    (0) 
    1. Thomas Szücs Post author
      Hi Ian,

      Using view->get_element you can access any view element for which you know its id. In order to insert a new view element you need to access the parent and add the new element to the proper aggregation using the corresponding add_* or set_* method.

      Best regards,
      Thomas

      (0) 
      1. Ian Stubbings
        Hi Thomas

        This I have been doing but without success.  I wish to add transparent containers to a tab and therefore I am using the set_content method of the tab.  I presume the setting of the layout type is mandatory i.e. flow/matrix etc.  This is what I have been using:

        DATA:   lr_tab type REF TO CL_WD_tab,
                lr_tco_tab type REF TO cl_wd_transparent_container.

        lr_tab ?= view->get_element( ‘TAB3’ ).
        lr_tco_tab = cl_wd_transparent_container=>new_transparent_container( ).
        cl_wd_flow_data=>new_flow_data( element = lr_tco_tab ).
        lr_tab->set_content( lr_tco_tab ).

        On debug all the references are instantiated.  There are no errors.

        I have tried having the tab typed to various ‘view elements’ but it will only compile when I type it to the cl_wd_tab ref.

        Every time I get the RABAX_STATE Error which in ST22 translates as “CL_WDR_VIEW_ELEMENT_ADAPTER===CP” “OBJECTS_OBJREF_NOT_ASSIGNED”

        Termination occurred in the ABAP program “CL_WDR_VIEW_ELEMENT_ADAPTER===CP” – 
        in “CREATE_BY_VIEW_ELEMENT”.                                                 

        I am working on the trial version of 2004s service pack 5.

        Any ideas? 

        Cheers
        Ian

        (0) 
        1. Thomas Szücs Post author
          Hi Ian,

          For containers it is mandatory to specify a layout. The layout data is only needed if a ui element was placed inside of another container. A tab of a tabstrip is not a container (although it appears to be one as one can add other ui elements). I suppose the error will be solved by writing the following:

          lr_tab ?= view->get_element( ‘TAB3’ ).
          lr_tco_tab = cl_wd_transparent_container=>new_transparent_container( ).
          lr_flow_layout = cl_wd_flow_layout=>new_flow_layout( ).
          lr_tco_tab->set_layout( lr_flow_layout ).
          lr_tab->set_content( lr_tco_tab ).

          In case there is still an error, I was wondering, if you could post the coding lines at which the error occurs.

          Best regards,
          Thomas

          (0) 
  3. David Halitsky
    … but I’m very glad I didn’t read them before I wrote my two “experiential” tutorials on the SAP-delivered WDA component “WDR_TEST_UI_ELEMENTS”.  It was much more fun learning everything in your two posts just by going thru the methods of that one component and its two supporting classes CL_WDR_ALL_IN_ONE_UIELEM and CL_WDR_ALL_IN_ONE_UTILS.

    By learning this stuff the way I chose to, I quickly got a sense of what I’d have to “steal” in order to do my own dynamic progreamming.

    But by going thru your two posts, on the other hand, I understand much better what I was stealing and why.

    So I think people should probably consider learning WDA in both ways = “experientially” and “passively”.

    By the way, please pass on my best regards to the SAP developers who wrote WDR_TEST_UI_ELEMENTS, CL_WDR_ALL_IN_ONE_UIELEM, and CL_WDR_ALL_IN_ONE_UTILS.  The “create/aggregate/create” recursion in CL_WDR_ALL_IN_ONE_UIELEM is exquisite, as is the way in which this recursion is executed via the handler class CL_WDR_ALL_IN_ONE_UTILS.

    What I’m doing now is going thru the WDJ tutorials so I understand the critical similarities and differences between how things are done in WDA and how they’re done in WDJ (given the basic difference in underlying classes that Armin Reichert pointed out to me a little while back.)

    (0) 
  4. Hi there,
    I want to create the FileUpload UI event dynamically and dont want the UI Element to be visible on the layout.

    As in the UIElemnt for the fileupload we have a browse button which lets me select a file path.
    How can this file name be dynamically passed and the contents stored in an xstring variable?

    (0) 
  5. Kishore Balakrishnan
    Many thanks for this post

    I have created multiple dynamic buttons.

    I want to handle every button just based on the text of the button with 1 action/method

    Basically, the number of buttons or the text of the buttons are determined in run-time

    Do I need to create 1 action per button at runtime dynamically? I want to avoid that..

    Any hints most appreciated

    (0) 
    1. Thomas Szücs Post author
      Hello Kishore,

      Glad, that you liked this blog.

      You can fulfill your requirements by using the same action for all buttons. Inside of the action, add parameter “ID type STRING” to the method. When the action handler is called, the parameter will contain the ID of the button on which the user clicked.

      Best regards,
      Thomas

      (0) 
  6. Barbara Fiedler
    Hello Thomas,

    thank you very much for this blog and the time you invested in writing. Your blog helped me understand how to create a checkbox during runtime.

    Sinceresly,
    Barbara

    (0) 

Leave a Reply