Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
thomas_szcs
Active Contributor

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.

14 Comments