BSP Programming: BSP Element Expressions (BEEs)
Rendering libraries are designed from the beginning to solve a set of fixed problems. A typical example is the HTMLB tableView that will render out information directly from the table, no questions asked. One day a colleague complained: he wants an icon in a specific column. OK, we can do that. The next day, the colleague explained again: “No, not a static icon, it must be a traffic light that blinks according to the status of the project. Status completed over 99% is green, and the rest is red.” Thus, we came into the problem of custom rendering.
For the example above, while the HTMLB tableView is rendering, it comes to a cell that requiring filling with one or more controls, which are not previously known. From this was born the concept of BSP Element Expressions, or BEEs.
What is a BEE? The short answer is that it is rendering code that can be used to fill a “hole” or location during rendering. The more complete answer is given below.
The first step is to build a test program. We know (we do!) that there are four supported techniques for using BEEs. So the test program must show four buttons, and must provide an open piece of canvas into which we can render anything at runtime. The complete coding is:
<%@page language="abap"%> <%@extension name="bsp" prefix="bsp"%> <%@extension name="htmlb" prefix="htmlb"%> <%@extension name="xhtmlb" prefix="xhtmlb"%> <%-- general document structure --%> <htmlb:content design="design2002"> <htmlb:page> <htmlb:form> <%-- button group with four test cases --%> <xhtmlb:toolbar id="tbbg1" > <xhtmlb:toolbarItem> <xhtmlb:buttonGroup id = "buttons" onClick = "buttonPressed" > <xhtmlb:buttonGroupItem key = "single" text = "Single" /> <xhtmlb:buttonGroupItem key = "html" text = "HTML" /> <xhtmlb:buttonGroupItem key = "table" text = "Table" /> <xhtmlb:buttonGroupItem key = "xml" text = "XML" /> </xhtmlb:buttonGroup> </xhtmlb:toolbarItem> </xhtmlb:toolbar> <%-- dynamic rendering --%> <bsp:bee bee = "<%= bee %>" /> </htmlb:form> </htmlb:page> </htmlb:content>
font-size:11.5pt;font-family:”Arial monospaced for SAP”; color:black;
First, all the BSP Extension libraries required are defined. For this test program, we are using the HTMLB and XHTMLB libraries for rendering. In addition, we require the BSP library for some utility elements.
The next code block is for a toolbar and button group with four buttons. Finally, we have the “hole”. This is presented by the <bsp:bee> element that can be used to render any BEE in place. Keep in mind that at this stage the exact rendering is not known, and will only be known at runtime.
For the program, three page attributes are defined.
bee TYPE REF TO IF_BSP_BEE text TYPE STRING (auto fill) url TYPE STRING (auto fill)
The bee is the actual instance of rendering code that will be rendered. The text and url fields are used for our small example. For the custom reading, we would like to render two input fields below one another. This first allows us to specify some text for a URL. The URL will be displayed in the second (disabled) input field.
After the first run of the program, the output (the bee is still initial):
Finally, the onInputProcessing event handling code must.
url = 'http://www.sap.com'. DATA: event TYPE REF TO if_htmlb_data, buttongroup TYPE REF TO cl_xhtmlb_buttongroup. event = cl_htmlb_manager=>get_event_ex( runtime->server->request ). IF event IS NOT INITIAL AND event->event_name = cl_xhtmlb_buttongroup=>co_event_name. buttongroup ?= event. CASE buttongroup->buttonclicked. WHEN 'single'. “* TO DO WHEN 'html'. “* TO DO WHEN 'table'. “* TO DO WHEN 'xml'. “* TO DO ENDCASE. ENDIF.
The code just sets the URL (static for our example), and then checks for an event from the HTMLB library. For button group events, the corresponding action is taken.
With this, our test program is finished, and we are ready to get to the hard work.
What is a BEE?
Effectively, a BEE can be described as an interface with one method RENDER( ). The complete interface is (ignoring the trivial second RENDER_TO_STRING method):
interface IF_BSP_BEE. methods RENDER importing PAGE_CONTEXT type ref to IF_BSP_PAGE_CONTEXT . endinterface.
Any class can implement the interface. Once it comes to rendering, the RENDER method receives the page context as an import parameter. The page context contains information about the current BSP page being rendered, the current writer active, and provides methods to handle rendering of BSP elements. The exact definition of the page context is beyond the scope of this article. It fits more into an article about writing BSP elements.
The <bsp:bee> element is very simple. Given a BEE, it is rendered inline at the position that this tag has been placed.
bee->RENDER( page_context = me->m_page_context ).
In summary, a BEE is any class that implements the IF_BSP_BEE interface, and can render itself when requested.
N=1, Using Any BSP Element as BEE
BEEs were initially designed for custom rendering one specific cell in an HTMLB tableView. For this, it was sufficient to specify one BSP element, for example an HTMLB inputField or HTMLB image. It was required that all HTMLB elements must implement the IF_BSP_BEE interface to be usable as BEEs. A lot of work 🙁 .
The simple solution was to implement this interface in the base class for all BSP elements (CL_BSP_ELEMENT). With this small change, it’s suddenly possible to use any BSP element for custom rendering as a BEE.
For this, you must vaguely understand how BSP elements are used inside a BSP page. There are three steps involved:
- Create an instance of element class.
- Set all attributes.
- Process BSP element.
The BSP compiler can look up what class is associated with a specific BSP element. It will then generate roughly the following code:
DATA: tagI TYPE CL_<class_name>. CREATE OBJECT tagI. TagI->A1 = V1. ... TagI->An = Vn. ... process tagI ...
If we wish to use any BSP element as custom renderer at a later stage, we only have to complete the first two steps. Each BSP element has the interface IF_BSP_BEE via the base class, and the render method of this interface knows how to “process” the BSP element at the right time.
The matching class name can easily be looked up via the workbench.
However, using the above technique of setting attributes is still slightly error prone. You have to keep all attributes in mind, their names and types, and especially set all required attributes. For this reason, for each BSP element a factory method is also generated to handle all the hard work. Therefore, the above code would reduce to the following form:
DATA: tagI TYPE CL_<class_name>. TagI = CL_<class_name>=>FACTORY( A1 = V1 ... An = Vn ). ... process tagI ...
The benefit of this approach is that the ABAP compiler is used to check all attributes during the compile phase. If you are uncertain about the available attributes, a simple double click on the “FACTORY” brings you immediately to the definition of the method.
Given our test harness, we have a “hole” in the layout that we wish to fill with one BEE. We know that each BSP element can function as a BEE. So let us use an HTML input field as BEE. Keep in mind that we only must create the BEE. The actual “processing” is done later by the <bsp:bee>.
The code is only a few lines:
WHEN 'single'. DATA: tag_if TYPE REF TO cl_htmlb_inputfield. tag_if = cl_htmlb_inputfield=>factory(id = 'text' value = text ). bee = tag_if.
A new HTMLB input field is declared and then instantiated via its factory method. Thereafter, the HTMLB input field is ready to be rendered. For this, we assign the instance to the bee page variable. (This works because each BSP element implements the IF_BSP_BEE interface via the base class.)
For the purists among us, the code can be reduced to a one-liner:
WHEN 'single'. bee = cl_htmlb_inputfield=>factory( id = 'text' value = text ).
With the first button now completed, we run the BSP page again, and look at the output:
Works as designed! Even with the BSP element declared in one place, the rendering at a later stage during the layout phase works perfectly. (And with this, nearly 80% of the work is done for HTMLB tableView custom rendering. But that is the topic of the next article!)
Using BSP elements to fill “holes” is interesting, but sometimes a man must roll his own. For this, there is nothing better than HTML. For raw HTML, a second BEE is supported. This class, CL_BSP_BEE_HTML, also implements the IF_BSP_BEE interface (and can therefore be rendered later). Its primary goal is to store HTML sequences and render them out later.
Keep in mind that we actually wanted to have two input fields, one for the text and the second to display the URL. Here is the code:
WHEN 'html'. DATA: bee_html TYPE REF TO cl_bsp_bee_html. CREATE OBJECT bee_html. bee_html->add( html1 = `<input name="text" id="text" ` html2 = `title="Inputfield for text" ` html3 = `value="` html4 = text html5 = `">` html6 = `<BR>` ). bee_html->add( html1 = `<input name="url" id="url" ` html2 = `title="Inputfield for url Disabled" ` html3 = `value="` html4 = url html5 = `" ` html6 = `readonly style="background-color:#ABABAB">` ). bee = bee_html.
In the first step, an instance is allocated of the HTML BEE. The add method is called twice, once for each input field. The add method has the nice feature that the HTML string can be supplied in snippets, and they’re internally concatenated together. In the last step, the BEE is assigned to be rendered later.
We run the test page again, and press the HTML button:
The HTML is rendered in the “hole”, as expected.
WARNING: Using raw HTML is not recommended for the faint of heart. In principle, HTML coding is easy to understand and use. However, once written, you must also accept responsibility that the code will work in other supported browsers, for example Netscape. Even more important, you must handle all accessibility aspects (at a minimum setting the title attribute correctly).
The N=1 case was powerful, but did not go very far, and HTML is very flexible but comes at a cost. You require a set of BSP elements that can be rendered together. Effectively, an expression!
For our given “problem”, we could have written the following code directly in the layout of the BSP page:
<htmlb:gridLayout columnSize = "1" rowSize = "2"> <htmlb:gridLayoutCell columnIndex = "1" rowIndex = "1"> <htmlb:inputField id = "text" value = "<%=text%>" /> </htmlb:gridLayoutCell> <htmlb:gridLayoutCell columnIndex = "1" rowIndex = "2"> <htmlb:inputField id = "url" value = "<%=url%>" disabled = "TRUE" /> </htmlb:gridLayoutCell> </htmlb:gridLayout>
However, we wish to process something similar dynamically. We already know how to create an instance of any BSP element via its factory method. You need a technique to put all these BSP elements into one expression, and still keep the relationship among them. For this, the table BEE was designed.
WHEN 'table'. DATA: tag_if_text TYPE REF TO cl_htmlb_inputfield, tag_if_url TYPE REF TO cl_htmlb_inputfield. tag_if_text = cl_htmlb_inputfield=>factory( id = 'text' value = text ). tag_if_url = cl_htmlb_inputfield=>factory( id = 'url' value = url disabled = 'TRUE' ). DATA: tag_gl TYPE REF TO cl_htmlb_gridlayout, tag_glc_text TYPE REF TO cl_htmlb_gridlayoutcell, tag_glc_url TYPE REF TO cl_htmlb_gridlayoutcell. tag_gl = cl_htmlb_gridlayout=>factory( columnSize = '1' rowSize = '2' ). tag_glc_text = cl_htmlb_gridlayoutcell=>factory( columnIndex = '1' rowIndex = '1' ). tag_glc_url = cl_htmlb_gridlayoutcell=>factory( columnIndex = '1' rowIndex = '2' ). DATA: bee_table TYPE REF TO cl_bsp_bee_table. CREATE OBJECT bee_table. bee_table->add( level = 1 element = tag_gl ). bee_table->add( level = 2 element = tag_glc_text ). bee_table->add( level = 3 element = tag_if_text ). bee_table->add( level = 2 element = tag_glc_url ). bee_table->add( level = 3 element = tag_if_url ). bee = bee_table.
The first part of the code just uses the factory methods of the different BSP elements to create all the BSP element instances required. In the next step, the table BEE is created, and then each BSP element is added in the sequence that it will be processed. Very special care is taken with the level attribute. This is an integer number used later during processing to determine if the next BSP element in sequence is a child of the current BSP element, or must be processed at the same level. The RENDER method of the table BEE knows how to render such a complex expression.
The final output is:
With this, we now achieve complete dynamic rendering of a “hole” witha sequence of BSP elements (the expression!).
Of course, it didn’t take long until another colleague stormed into our office with a “special” version of the problem. “Writing all this code by hand is too complex, and besides, I want to load the complete sequence from a string (stored on the database).” The reason was simple: a string is easy to write, can easily be understood and can be changed by customers (to dynamically define their own layout for a specific part of the screen).
Problem specification: take an XML string containing a BSP element expression and render it into the “hole”. We aim to please :). The final source is (in the scope of our test harness):
WHEN 'xml'. DATA: xml_string TYPE STRING. CONCATENATE '<bee:root> ' ' <htmlb:gridLayout columnSize ="1" rowSize ="2"> ' ' <htmlb:gridLayoutCell columnIndex="1" rowIndex="1"> ' ' <htmlb:inputField id ="text" value ="<%=text%>" /> ' ' </htmlb:gridLayoutCell> ' ' <htmlb:gridLayoutCell columnIndex="1" rowIndex="2"> ' ' <htmlb:inputField id ="url" value ="<%=url%>" disabled = "TRUE"/>' ' </htmlb:gridLayoutCell> ' ' </htmlb:gridLayout> ' '</bee:root> ' INTO xml_string SEPARATED BY CL_ABAP_CHAR_UTILITIES=>CR_LF. DATA: xml_parms TYPE TABLE_BSP_BEE_PARMS. FIELD-SYMBOLS: <xml_parm> TYPE BSP_BEE_PARM. INSERT INITIAL LINE INTO TABLE xml_parms ASSIGNING <xml_parm>. <xml_parm>-name = 'text'. <xml_parm>-value = text. INSERT INITIAL LINE INTO TABLE xml_parms ASSIGNING <xml_parm>. <xml_parm>-name = 'url'. <xml_parm>-value = url. DATA: bee_xml TYPE REF TO cl_bsp_bee_xml. CREATE OBJECT bee_xml. bee_xml->set( EXPORTING xml = xml_string parms = xml_parms IMPORTING xml_errors = xml_errors ). bee = bee_xml.
The first block of code just “writes” the BSP page code dynamically into a string. With a few small exceptions, this is written exactly as in the layout part of any BSP page. The next block of code handles the dynamic attributes. These attributes are stored in a table, from which the values can be looked up when the BEE is processed. The last block creates a new XML BEE, and sets both the XML string and dynamic parameters.
The final output, with a slight anti-climax, looks exactly like that of the table BEE (exactly the same BSP elements are executed!):
Although the table and XML BEEs achieve the same goal, they still have slightly different dynamics. For the table BEE, some form of code must be known beforehand, and written, to fill the table. The XML BEE is completely dynamic. It also has the benefit that configuration-like layout sequences can be read from the database.
Warning: This flexibility comes at a (high) price. The XML BEE was rewritten three times by one of the best programmers in the ABAP Language Group. (Come to think of it, there are only good programmers in that group!) However, this still doesn’t help with the fact that XML is parsed, interpreted, and code executed dynamically. This technique will always be slow and only recommended for very special cases.
What can be used in the XML string?
- Most important, the <bsp:root> node must be used. This is just a pseudo-node added to enable a valid XML DOM for parse.
- Another pseudo-node that is supported is <bee:html>. It can be used to pack HTML text into the string. Although the inner HTML text does not conform XML rules, this is a nice user-friendly way of writing. The HTML block is internally placed into a CDATA section before the DOM is parsed.
- All other XML nodes in the DOM are considered to be BSP extension elements. No dynamic prefixes are supported; the prefixes used must match directly the BSP extension IDs (not to be confused with the recommended default IDs.)
- BSP elements with “element dependent” bodies are not supported. Within all HTMLB rendering libraries there are no such elements, so this should not be a problem. For such BSP elements, the complete body must be placed in a CDATA section.
- Dynamic expressions from BSP pages (<%…%>) are not supported.
- Any CDATA section is printed verbatim onto the output stream.
Errors and Error Handling: Of course, this small program was completed quickly and worked (nearly!) flawlessly. The XML BEE failed! No output was rendered. An hour was spent to chase this error down. (For the gory details of one small XML bug revealing a BEE bug, see OSS/CSN note 674230).
The lesson (again!) is to not ignore the return codes of method calls. The “bee_xml->set( )” returns a list of XML errors. These where just ignored. Had we considered this table immediately, the hour would have been saved!
The final solution, to show the power of the BEEs, was (note xml_errors is defined as a page attribute of type TIXMLTERR to keep reference alive even after onInputProcessing has been processed):
IF LINES( xml_errors ) > 0. DATA: table_ref TYPE REF TO DATA. GET REFERENCE OF xml_errors INTO table_ref. bee = CL_HTMLB_TABLEVIEW=>FACTORY( id = 'tview' table = table_ref ). ENDIF.
If an error occurred, the HTMLB tableView is used as BEE to render out the error table. The output we saw (and that was immediately fixed!), was:
This shows the importance of error handling, and the strength of the custom rendering provided by the BEEs.
A Final Note
BEEs are powerful concepts. However, like all good hammers, they must be used with care. For rendering small parts of the layout, they are excellent and highly recommended. The use of BEEs to custom render HTMLB tableView cells will be described in the next article.