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` ).
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:
Let's make an example where we create a Group that gets a MatrixLayout and resides within a container that has a FlowLayout
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:
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".
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.
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.
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:
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.
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.
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.
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.