1 2 Previous Next

brian.mckellar

28 Posts
Very often consultants or customers wish to change the look and feel of a BSP application. Within the SAP Enterprise Portal (EP6), there are tools available that allows one turn every possible knob: definitely a designer’s dream. However, for mere mortals, these tools are very complicated to use, and it takes many hours to change from any predefined SAP theme to a new corporate theme. We developed a quick-and-dirty “look and feel service” for the ABAP stack that allows us to have a new colour scheme up and running in _three_ minutes. The new scheme will probably not be a perfect match for anyone responsible for corporate branding. However, for a presentation it is a definite “winner”. The “quick-and-dirty” here reflects in no means on the quality of the programming (I should know, I wrote it), nor on the quality of the algorithm (I should know, the best HTML crack in our group did it). It reflects more on the scope of the solution, and the constraints that are imposed by the route we choose. This work was not done as part of any development plan, but is more the fruits of “having some fun” over a lunch hour or ten. As the tool is deemed to have value for a large group of people, the official decision has been made to ship it. The tool was developed on WebAS, as we had many requirements from consultants that wanted to enhance their presentations at customers, and did not have the space on their laptops for also running an EP6. As they usually have already a mini WebAS running, we decided to reuse this infrastructure. The small print: This tool has a number of constrains. The use of the tool implies also the acceptance of these limitations. Throughout the rest of this weblog, please read these “small print” sections very carefully. Support for the tool will be provided via OSS on queue BC-BSP on a “best effort” basis only. Which means we will do everything possible to keep it running smoothly, but there are no guarantees that we can support this indefinitely. h2. The First Three Minutes One picture should be enough to tell the true story. Allow us two to get it perfectly right. The first picture shows the same BSP application with different predefined themes. These themes we defined to quickly show the effects of applying a new theme to a BSP application. The second picture shows the complete (yes, that is it!) theme editor. Effectively, you define five new colours from which the complete theme is then generated. If the corporate branding colours are already defined, then just plug them into the editor and press the preview button. What the editor does, is on the fly generate new CSS files, as well as hundreds on new GIF images in exactly the right colors. Two interesting ideas flowed into this theme editor. The first is that a few basic colors are sufficient to specify the complete theme. The second is that it is possible even with ABAP to generate all the MIME objects on the fly as they are requested without a noticeable speed hit. We even parse and patch each GIF file that is used within the theme during the usual load process. h2. How Does Customisation Normally Work? The normal process for customizing a theme is to use the tools provided by EP6. These tools provide the complete freedom to change all different aspects of the theme. This might be important for corporate branding purposes. However, to get an initial theme running is already a lot of work. For example, typical steps include that all GIF images (over 300 per theme!) be touched (loaded into a bitmap editor individually!) to set their colours correctly. Even a first version can take hours to get up and running. What the theme editor in EP6 does, is to store all customer settings as metadata, and not directly in the CSS files. Thereafter, in a generation step, the actual required CSS files are created. This is important for handling upgrade situations. When the SAP ships new rendering classes, the information required for the CSS files are also shipped only in metadata form. This is mixed with the old theme settings, and then new CSS files are generated at runtime. Our first constraint was to have a solution that will “survive” a new service pack. The small print: The solution described here generated new themes on the fly. They are never stored in any database, but only cached (both server and browser). After an upgrade, the BSP runtime will load the CSS files with a new version number. This bypasses the cache, causing the CSS files to be loaded (and patched) again. What this implies, is that we do NOT store any generated theme in the MIME repository. The goal of this project was never to replace the theme editor already available in EP6. As such, this approach attempts only a quick approximation of the results, which should be sufficient for showing an application to a customer in the “correct” colour scheme. If fine-tuning of a theme is required, this must still be done with the usual EP6 theme editor. The small print: This work (and the weblog) does NOT show the actual integration into the EP6 theme editor. If there is interest, it can be described in a second weblog. h2. A New Theme from Five Colours The heart of the approach is simplicity. Instead of asking the user to configure and edit all colours, we wanted to have one colour: blue for SAP, etc. Just give us the base colour, and we do the rest. In the end, it turns out that five colours are required and sufficient: one background colour, two branding colours and two selection colours. What we did, was to manually analyse all style classes for all SAP base themes. From this, we grouped relevant classes together, and determined heuristics of how the colours are used in the base themes.  Specifically, we calculate the “distance” of a specific colour from the base colour of the theme. Then, given the five new colours, we apply the same “distance” to the base colours to have the data for the new theme. The other important aspect, is the template theme from which the new theme should be generated. SAP ships today standard five themes. Each theme is different in metrics (padding, font sizes, margins) and also different in the spectrum of colors used. The “distances” between the different colours and the base colour is an important aspect of the theme used as template. For example, typically the high-contrast themes group colours more together at two extremes, achieving the required contrast on screen. Today, SAP sets as default theme “Tradeshow”, and it is recommended to use this as template for most cases. The small print: This theme editor _only_ works with design2003! h2. Integration into WebAS The biggest constrain for us was that the new theme must be upgrade save as much as possible. The first approach we took was to update all CSS and GIF files, and store them in the MIME repository. However, this takes a very long time to generate, and had the side effect that we had to patch resources that might not even be needed. The bigger problem was that we had no hooks to update the mime repository after a new service pack was applied. The route that we then took was to write a new HTTP handler (BSP In-Depth: Writing an HTTP Handler). This handler will intercept all requests to mime objects. Once it detects that a customized object is required, the handler will load the mime object, patch it, and then send it out to the browser. Typically, the time it takes to load a mime object far exceeds the time required to quickly patch the colours in flight. As a last step, the mimes are cached for 7 days in both the server and browser cache. This works, as the BSP runtime was changed in the latest service packs to load all mimes with a version number. Once a service pack is installed, the version number changes, and the mimes will be loaded new. With the caching, the performance of this “patch on the fly” solution is blazing fast. The small print: when using this handler on lower service pack levels, it is recommended to reduce the cache time back to one hour. This might negatively impact overall performance. Only with 620SP54 and 640SP13 will the caching automatically be updated to 7 days. The first question/problem we had, was where to store the new colour information required. Initial approaches always placed thisbasic colour data in the database. However, we set the browser caching 7 days. Once a colour was changed, the browser would not know, nor request the update files until at the end of the seven days. After calculating many days, we realized that 5 colours is only 30 bytes! We added this directly into the URL! For example, for the “Bisque” theme shown above, the theme root was set to: /sap/public/bc/ur/design2002/themes/~alfs~1000202FFE4C43B2D1B7D674EDAA520565656   Here we have encoded inside the URL the template to use (one byte),  the font family to use (one byte with lookup table) and then the 30 bytes for the 5 colours. The big win was that any minor change in the theme resulted in a change of the generated URL, which effectively implied new objects that were not already in the cache. Thus, the complete theme is loaded on the fly again. The other benefit was that it was now possible to have hundreds of themes active in parallel, without writing them into the MIME repository. This was especially important when testing to find the right colour combination. All the different tests with minor colour changes has different URLs, thus keeping the cache content consistent for each test run, allowing one to quickly compare the different themes. The small print: The solution adds about 50 bytes per roundtrip to the complete rendered overhead. We did experiment with using a base64 encoding on the colour values to reduce the overhead with about 20 bytes, but is was not really worth the effort. In slightly more detail: design 2003 mime resources are loaded via the path /sap/public/bc/ur/design2002/themes/sap_tradeshow/.… On this path there is already one HTTP handler installed. What we did, was to write a new HTTP handler that is chained into this path. The new handler is called first. It looks at the incoming URL to search for its signature “~alfs~”. If the signature is found, the correct MIME is loaded, patched (with the colour information from the URL), and the response is written (with caching). All other systems requesting the same resource will be served from the ICM cache, thus making the patch work a once-off process. Should the URL not contain the correct signature, the handler will just signal that it did not handle the incoming HTTP request, and the usual MIME repository handler is then scheduled.

h2. The Source Code: Making It Work See note 850851, ALFS: ABAP (Quick and Dirty) Look and Feel Service, for the complete source. A ZIP file with the source code is attached to the note. This can be manually installed on any system. Officially, the code will ship with 620SP54, 640SP13 and 700SP03. No “Note Assisted” corrections are provided for this solution. Keep in mind that it is very much work developed under the SDN flag, and now just made available. Furthermore, a few manual steps would have been required in anycase. To install the code, create a new class,  and add the interface IF_HTTP_EXTENSION. Paste the following code into the HANDLE_REQUEST method: METHOD if_http_extension~handle_request.  TRY.   server->transactional     = if_http_server=>co_enabled.   if_http_extension~flow_rc = if_http_extension=>co_flow_ok_others_mand.   IF server->request->get_header_field( if_http_header_fields_sap=>path_info ) CS '~alfs~'.    lcl_alfs=>handle_request( server ).    if_http_extension~flow_rc = if_http_extension=>co_flow_ok.   ENDIF.  CATCH cx_root.  ENDTRY. ENDMETHOD.   All that this code does is quickly check for the ALFS signature. Once found, all further processing is done in local classes. Observe the setting of the flow_rc variable to signal whether the HTTP request has been handled or not. In a next step, edit both the CCDEF (button ) and CCIMPL (button ) sections of this class. Paste the complete source from the note into the corresponding sections. Save and activate the class. Keep in mind that the code is slightly different between 620 and 640. As a last step, start transaction SICF. Find the node /sap/public/bc/ur and edit it. Add the new handler class to the list of handler classes.

From time to time the error message GEN_BRANCHOFFSET_LIMIT_REACHED pops up. It is a very complex problem right from the heart of the ABAP VM. Let us look quickly at why it happens and some ideas to work around the problem. No, there is no patch for this problem.

 

Background Information

 

 

Any (most?) computer languages have typical limitations that play a role in code generation. The ABAP VM is not exception in this case. One such an limitation is that one IF-statement can only jump 32KB of byte code. So effectively, the source code between the IF- and ENDIF-statements, when compiled, must not be more than 32KB of byte code.



And this plays a big role when the Layout of a BSP page is compiled. (The same rules apply for BSP views as well.) For each BSP page, an ABAP class is generated (in the temporary package $TMP). The name is very criptic, as it constructed from with a GUID (global unique identifier). For example: CL_O2D7QDB5488MACT6YI2S549GQVB.



In the generated class, for the complete layout, source code isgenerated into one method. The event handlers (OnCreate, OnInitialization, etc) are also placed into separate methods on the same class. In this way, each BSP page is translated into a self contained ABAP class, that is generated and instantiated in one step.



So what the problem effectively states, is that somewhere within a method, there is an IF-statement that has a body that exceeds the jump limitations of the ABAP VM. To understand the problem better, we have to look a little bit at how source code generation is done in BSP.

 

BSP Source Code Generation

 

 

Let us assume a simple BSP page with the following source code:




<!code>  <%@page language="abap"%>
<!code>  <%IF sy-uzeit < '1200'.%>
<!code>  Good morning <%= sy-uname%>,
<!code>  <%ELSE.%>
<!code>  Good afternoon <%= sy-uname%>,
<!code>  <%ENDIF.%>
<!code>  Welcome back to my great BSP application.
<!code>   


This program shows one technique how an IF-statement can appear within the generated source code. The complete source code (in slightly abstract format) will be:




<!code>  METHOD _onlayout.
<!code>  * generated by BSP converter version: 200502181048
<!code>  * generated by BSP compiler version:  1.60
<!code>    DATA: %_O2X TYPE STRING. "#EC *
<!code>    m_out->print_string( value = mhtml offset = 0 length = 2 ).      " CRLF
<!code>    IF sy-uzeit < '1200'.
<!code>      m_out->print_string( value = mhtml offset = 2 length = 15 ).   " Good morning...
<!code>      %_O2X = sy-uname.
<!code>      m_out->print_string( value = %_O2X ).
<!code>    ELSE.
<!code>      m_out->print_string( value = mhtml offset = 20 length = 17 ).  " Good afternoon...
<!code>      %_O2X = sy-uname.
<!code>      m_out->print_string( value = %_O2X ).
<!code>    ENDIF.
<!code>    m_out->print_string( value = mhtml offset = 17 length = 58 ).    " ,CRLF Welcome...
<!code>  ENDMETHOD.
<!code>   


The very first interesting aspect is that all static HTML in the BSP page is stored separately in the database, and dynamically loaded at runtime into one string (_m_html). Any static HTML sequence, independent of length, is translated into print statement.

ABAP code within the BSP page is emitted verbatim. BSP print sequences (<%=...%>) are translated to code that first forces the value to a string (effectively using ABAP assign statement), and then just prints the string.

In this example, the generated source code reflects very much the source code that was written on the BSP page, and there is a relationship between the length of the written layout code and the generated source code.

Let us look at a slightly more complex example, where a few BSP tags are used on a page:

]]>
<!code>  <%@page language="abap"%>
<!code>  <%@extension name="htmlb" prefix="htmlb"%>
<!code>  <htmlb:content design="design2003">
<!code>    <htmlb:page title = "Test Page">
<!code>      <htmlb:form>
<!code>        <htmlb:button text    = "myButton"
<!code>                      onClick = "HitMe!" />
<!code>      </htmlb:form>
<!code>    </htmlb:page>
<!code



This simplest of simple examples, already generate rather complex code. The reason for this is in the specification of the BSP elements themselves. To allow a flexible architecture, BSP elements can dynamically decide a number of aspects. First, let us look at the generated source for the example (extremely stripped!):




<!code>  METHOD _onlayout.
<!code>  * generated by BSP converter version: 200502181048
<!code>  * generated by BSP compiler version:  1.60
<!code>   
<!code>    DATA: %_elem_rc TYPE I.
<!code>   
<!code>  * <htmlb:content>
<!code>    DATA: %_bsp_elem_0 TYPE REF TO CL_HTMLB_CONTENT.
<!code>    CREATE OBJECT %_bsp_elem_0.
<!code>    %_bsp_elem_0->design = 'DESIGN2003'.
<!code>    %_elem_rc = %_bsp_elem_0->DO_AT_BEGINNING( ).
<!code>    IF %_elem_rc = IF_BSP_ELEMENT=>CO_ELEMENT_CONTINUE.
<!code>   
<!code>  *   <htmlb:page>
<!code>      DATA: %_bsp_elem_1 TYPE REF TO CL_HTMLB_PAGE.
<!code>      CREATE OBJECT %_bsp_elem_1.
<!code>      %_bsp_elem_1->title = 'Test Page'.
<!code>      %_elem_rc = %_bsp_elem_1->DO_AT_BEGINNING( ).
<!code>      IF %_elem_rc = IF_BSP_ELEMENT=>CO_ELEMENT_CONTINUE.
<!code>   
<!code>  *     <htmlb:form>
<!code>        DATA: %_bsp_elem_2 TYPE REF TO CL_HTMLB_FORM.
<!code>        CREATE OBJECT %_bsp_elem_2.
<!code>        %_elem_rc = %_bsp_elem_2->DO_AT_BEGINNING( ).
<!code>        IF %_elem_rc = IF_BSP_ELEMENT=>CO_ELEMENT_CONTINUE.
<!code>   
<!code>  *       <htmlb:button/>
<!code>          DATA: %_bsp_elem_3 TYPE REF TO CL_HTMLB_BUTTON.
<!code>          CREATE OBJECT %_bsp_elem_3.
<!code>          %_bsp_elem_3->text = 'myButton'.
<!code>          %_bsp_elem_3->onClick = 'HitMe!'.
<!code>          %_bsp_elem_3->DO_AT_BEGINNING( ).
<!code>          %_bsp_elem_3->DO_AT_END( ).
<!code>   
<!code>  *     </htmlb:form>
<!code>        ENDIF.
<!code>        %_bsp_elem_2->DO_AT_END( ).
<!code>   
<!code>  *   </htmlb:page>
<!code>      ENDIF.
<!code>      %_bsp_elem_1->DO_AT_END( ).
<!code>   
<!code>  * </htmlb:content>
<!code>    ENDIF.
<!code>    %_bsp_elem_0->DO_AT_END( ).
<!code>   
<!code>  ENDMETHOD.
<!code>   


The complexity of this source code comes from the fact that the BSP compiler does not know before hand whether a specific BSP element wishes to process its own body (inner BSP elements), or wish to just over the body. The BSP element can decide this during its DO_AT_BEGINNING( ) method. Therefore the BSP compiler generates the IF-statements to test for this condition.



The use of BSP elements is the second source of IF-statements on a BSP page.



Effectively the problem is that the complete layout of one BSP page is placed into one method in a class. And this method now has in its generated form an IF statement that is too large. Any solution must attempt to remove some code from the generated method (which means directly from the layout) and place it somewhere else. Below are three typical approaches that are recommended.

 

Solution Idea: Use Class Methods to Reduce Layout Code

 

 

Probably the simplest technique would be to strip code from the layout of the BSP page, and to place it into a method of class.

Very important: Long sequences of HTML on a page does not make much of a difference in the generated source of the method. The complete HTML sequence is replaced with out print_string statement to start at an offset and dump the string for a specific length.

For such work, one should look at large blocks of program code (<%...%> sequences), or for large blocks of print code (<%= .. %>). Each of these interrupts the HTML, which causes the print_string statement to be splitted into a large number of print_string statements. See the first code generation example above. Let us examine this example code again:

]]>
<!code>  <%@page language="abap"%>
<!code>  <%IF sy-uzeit < '1200'.%>
<!code>  Good morning <%= sy-uname%>,
<!code>  <%ELSE.%>
<!code>  Good afternoon <%= sy-uname%>,
<!code>  <%ENDIF.%>
<!code>  Welcome back to my great BSP application.
<!code>   


What we see is that the <%IF...%> sequence, and the <%=sy-uname%> causes many more print_string statements to be emitted, which contributes significantly to the generated code in the layout method.

One solution would be to place this critical code sequence in a separate method. Then the layout code would reduce to:

]]>
<!code>  <%@page language="abap"%>
<!code>  <%= cl_my_class=>greetings( name = sy-uname ) %>
<!code>  Welcome back to my great BSP application.
<!code>   


In the above coding, greeting is a method that has a returning parameter of type string. This string is then printed. The method itself would be:




<!code>  METHOD greetings.
<!code>    " name IMPORTING TYPE STRING
<!code>    " html RETURNING TYPE STRING
<!code>    IF sy-uzeit < '1200'.
<!code>      html = `Good morning`.
<!code>    ELSE.
<!code>      html = `Good afternoon`.
<!code>    ENDIF.
<!code>    CONCATENATE html ` ` name `,` INTO html.
<!code>  ENDMETHOD.
</pre>

 

Solution Idea: Use Smaller Views in MVC to Split Layout Code

 

 

The use of methods to contain rendering code works well with HTML code, but comes more complex once BSP extensions are used. Although it is possible process BSP elements inside methods, it is not easy to write and main. Recommended is to split large layouts into separate views. One typical example could be to place the content of each tab in a tabstrip onto a separate view.



Let us start with this example again, with the goal to place the

and its content onto a separate view:



<!code>  <%@page language="abap"%> <!code>  <%@extension name="htmlb" prefix="htmlb"%> <!code>  <htmlb:content design="design2003"> <!code>    <htmlb:page title = "Test Page"> <!code>      <htmlb:form> <!code>        <htmlb:button text    = "myButton" <!code>                      onClick = "HitMe!" /> <!code>      </htmlb:form> <!code>    </htmlb:page> <!code>  </htmlb:content> <!code>   

As a first step we create a new view (called my_view.htm) with the displaced content (cut-and-paste from page to view):




<!code>  <%@page language="abap"%>
<!code>  <%@extension name="htmlb" prefix="htmlb"%>
<!code>      <htmlb:form>
<!code>        <htmlb:button text    = "myButton"
<!code>                      onClick = "HitMe!" />
<!code>      </htmlb:form>
<!code>   


On the page main page (or view!), place now a call to a controller that will embed the view. Keep in mind it is not possible to directly expand views in place. See also that we have now added the BSP extension "BSP".




<!code>  <%@page language="abap"%>
<!code>  <%@extension name="htmlb" prefix="htmlb"%>
<!code>  <%@extension name="bsp" prefix="bsp"%>
<!code>  <htmlb:content design="design2003">
<!code>    <htmlb:page title = "Test Page">
<!code>      <bsp:call url="my_controller.do" comp_id = "view">
<!code>        <bsp:parameter name="view" value="my_view.htm"/>
<!code>      </bsp:call>
<!code>    </htmlb:page>
<!code>  </htmlb:content>
<!code>   


As a last step, we create a new controller (my_controller.do). We add one attribute view TYPE string and implement the do_request method:




<!code>  METHOD do_request.
<!code>    DATA: page TYPE REF TO if_bsp_page.
<!code>    page = create_view( view_name = view ).
<!code>    call_view( page ).
<!code>  ENDMETHOD.
<!code>   


Passing parameters to different views can also easily be done via the controller.



Recommended Reading:



 

Solution Idea: Use BSP Element Composition to Bundle BSP Elements

 

 

 

 

Solution Idea: Replace BSP Element Sequences with Light-Weight Sequences

 

 

Often BSP elements are just wrappers around simple HTML sequences. However, we have seen that such BSP elements can cause a large explosion in the generated code. The use of the BSP elements are practical, as the BSP compiler will help to valid that large sequences are correctly nested. However, in cases where the generated source code is to large, ir might help to replace a few of these sequences with native code.



Let us assume this typical form layout example:




<!code>  <%@page language="abap"%>
<!code>  <%@extension name="htmlb" prefix="htmlb"%>
<!code>  <htmlb:content design="design2003">
<!code>    <htmlb:page title = "Test Page">
<!code>      <htmlb:form>
<!code>        <htmlb:gridLayout rowSize="2" columnSize="2">
<!code>          <htmlb:gridLayoutCell rowIndex="1" columnIndex="1">
<!code>            <htmlb:label      text = "Name:"  for = "Name" />
<!code>          </htmlb:gridLayoutCell>
<!code>          <htmlb:gridLayoutCell rowIndex="1" columnIndex="2">
<!code>            <htmlb:inputField id   = "Name" />
<!code>          </htmlb:gridLayoutCell>
<!code>          <htmlb:gridLayoutCell rowIndex="2" columnIndex="1">
<!code>            <htmlb:label      text = "Email:"  for = "Email" />
<!code>          </htmlb:gridLayoutCell>
<!code>          <htmlb:gridLayoutCell rowIndex="2" columnIndex="2">
<!code>            <htmlb:inputField id   = "Email" />
<!code>          </htmlb:gridLayoutCell>
<!code>        </htmlb:gridLayout>
<!code>      </htmlb:form>
<!code>    </htmlb:page>
<!code>  </htmlb:content>
<!code>   

 

<!code>  <%@page language="abap"%> <!code>  <%@extension name="htmlb" prefix="htmlb"%> <!code>  <htmlb:content design="design2003"> <!code>    <htmlb:page title = "Test Page"> <!code>      <htmlb:form> <!code>       
<!code>         
<!code>            <htmlb:label      text = "Name:"  for  = "Name" /> <!code>         
<!code>            <htmlb:inputField id   = "Name" /> <!code>         
<!code>            <htmlb:label      text = "Email:" for = "Email" /> <!code>         
<!code>            <htmlb:inputField id   = "Email" /> <!code>          </td></tr> <!code>        </table> <!code>      </htmlb:form> <!code>    </htmlb:page> <!code>  </htmlb:content> <!code>   

 

<!code>  <%@page language="abap"%> <!code>  <%@extension name="htmlb"  prefix="htmlb"%> <!code>  <%@extension name="phtmlb" prefix="phtmlb"%> <!code>  <htmlb:content design="design2003"> <!code>    <htmlb:page title = "Test Page"> <!code>      <htmlb:form> <!code>        <phtmlb:matrix> <!code>          <phtmlb:matrixCell row="1" col="1"/> <!code>            <htmlb:label      text = "Name:"   for = "Name" /> <!code>          <phtmlb:matrixCell row="1" col="2"/> <!code>            <htmlb:inputField id   = "Name" /> <!code>          <phtmlb:matrixCell row="2" col="1"/> <!code>            <htmlb:label      text = "Email:"  for = "Email" /> <!code>          <phtmlb:matrixCell row="2" col="2"/> <!code>            <htmlb:inputField id   = "Email" /> <!code>        </phtmlb:matrix> <!code>      </htmlb:form> <!code>    </htmlb:page> <!code>  </htmlb:content> </pre>

In the past few days, a number of people have expressed interest in writing a weblog (I wonder why:), and had some reservations about the complexity in getting the weblog online. Some stopped by, and asked me for a detailed description of everything that I do. Let me take a few minutes to quickly describe how complex this can be! The first step is brewing a cup of coffee. One moment&#133;.hmm!

Step 1: Writing

Some people prefer to write their weblogs using Notepad, and manually adding arcane HTML markup sequences. They can spell perfectly, type blind and know HTML better than the W3C committee. Then there are the meister of writing weblogs: VI or nix.

Mere mortals, like me, prefer to use Microsoft Word! Its good editor supports me with good onscreen formatting, a spell checker and most important, a thesaurus. This allows me to express my pleasure in writing this text as a delight, an enjoyment, state of happiness or even pure satisfaction. Must be the coffee.

The only rule that I follow is to flag all text with a special set of paragraph classes. Later on, the Word document will be converted into an SDN weblog. These classes are: sdn_heading1 for the title text, sdn_heading2 for section headings, sdn_normal for paragraphs of text, sdn_bullet as a clone of the bullet class and sdn_code to list bits of source code (using Courier font and indented). This paragraph is flagged as sdn_normal.

The exact formatting of each class isnot important. Do not pay any attention to things such as fonts, and spacing. All of this is later handled by the style sheets used in SDN. Important is to flag the text correctly, so that later accurate (&#147;correct&#148; improved on by thesaurus) HTML can be generated.

If you wish, you can use my template.

Coffee is finished, so we can start writing. Within Word, the following features are used:

FeatureDescription
TextThis is the fluff to fill the page. All text is flagged with one of the sdn_* paragraph classes.
ImagesImages are screen grabbed using any tool available. Thereafter they are pasted into a bitmap editor ( ) and cropped. All unnecessary detail is deleted, and only the essence is left. This is important to reduce bandwidth. Very important: for SDN the maximum picture size is 600 by 400 pixels. Do not exceed this. Cut away as much clutter as much as possible. Also, do not scale the picture smaller. It really makes it more difficult to enjoy later on. Just cut away not needed detail. Thereafter, the picture is placed into the Word document with cut-and-paste. Do not worry about things such as JPEG or PNG formatting, with separate files, or anything. Just paste the picture into the document and continue.
BulletsUse the paragraph class sdn_bullet.
CodeVery important part of any document. This is a developer network, and the best way for a developer to express himself is with source code. Source code is also pasted into the document and just completely flagged as paragraph class sdn_code.
TablesUse only very simple tables. For this table, I just defined a new table of 2 columns. The first row is the heading row. The text is not flagged in any special way. Just bold it. Nothing more.

Do not worry about the onscreen formatting in Word. For example, for source code (sdn_code) a Courier 9 point font is used with about 1cm indentation. All of this formatting will be stripped later, and in the published weblog will only be the source code in a

sequence.

So the time flies, the page counter jumps, the coffee is already long forgotten and the pleasure is in the creative work. Just use Word and let the fingers fly!

Step 2: Proof Reading

Although not often though about, proof reading is a very important step in the production step. Keep in mind that the hours invested in writing stand in no relationship to the hours that other people spend in reading.

Recommended are two levels of proof reading. For the use of language, ask somebody that speaks native English (the more official language for use in SDN). SDN has an open offer to help with the English aspects. This small step alone on my weblogs has finally convinced everyone that I am English native speaker. This is far from the truth!

At the same time, also ask a colleague to proof read the document for technical content. It helps to catch simple mistakes.

Often during the proof reading steps, changes are made in the Word document, with &#147;Track Changes&#148; active. You can review the changes (and at the same time learn more English!). Thereafter, accept all changes (I usually do) and then switch off this feature again. In addition, delete all comments from the document after reading and following the suggestions. This information should not be published!

Step 3: Publishing

Publishing is the simplest and easiest step.

As a first step, save the document to have the .DOC version of the weblog still available.

Immediately thereafter, save the document a second time in an HTML version. Use the menu sequence &#147;File->Save as Web Page&#148;. A new .HTM version of the document is written to disk, and all the pictures are exported in .JPG, .GIF or .PNG format into a subdirectory.

Most important, immediately close Word afterwards. Otherwise, all new changes will land in the .HTM version of the document.

Now we have on disk both the .DOC and the .HTM versions of the document. In addition, we also have all the exported pictures.

The problem with this .HTM content is that it contains a lot of styles and other markup information that does not work very well with SDN. What we would like to do is strip the file down to an absolute minimum of HTML without any style information.

The first time I did this was by hand. It took a few hours. Thereafter, I developed a small program in a well-chosen language to do this work quickly. (Just so that there are no disappointments among all NetWeaver hackers, ABAP is still alive and in use by a few of us!)

Just download the program, paste it into SE38 and run it quickly. (For SAP internal users, the program is available on our sandbox system B6M under the name SDN_DOC_TO_HTML.) One small warning: this program is the result of only one weekend of tinkering. It works for me, but there are no guarantees. Feel free to adapt it to your requirements. If you should sell it, send my share of the profits to my manager!

The program asks for the SDN weblog user number. This will be used to generate URLs that already point directly into SDN. If the field is left empty, the pictures will be loaded directly from disk.

What the program does is to load the HTM file from disk, strip it down, and write it under a new name. The new name consists of only the camels in the CamelCase name! For example, from the title of this document &#147;The-123-Steps-To-Producing-A-Weblog&#148; the short name &#147;T123STPAW&#148; will be constructed. The new .HTM file is saved. In addition, only the used images are copied over and supplied with new names. This is important, as image names must be unique, and using image001 as the name will cause problems when the second weblog is published. (There will be a second weblog!)

The final step in publishing the Weblog, is just to upload all of this quickly. When you registered as a blogger, you received an URL that is to be used for creating new weblogs.

Just select the Create option. Enter the title of the Weblog, select a category and type in a small description. Finally load the converted text into Notepad (from the short named HTML file) and paste it into the Text editor. Leave the status at draft. Submit the Weblog.

The last step is to quickly upload all the images. Just select the Upload option. Each image is selected and submitted. Only upload the images that the conversion program has changed the names of. For this example, I uploaded all the T123STPAW_nnn.* files.

One small warning: The names of the images are case sensitive! The name format used in the document must match exactly the name format used for uploading. The conversion program will do this correctly.

As the final step, list your draft Weblogs, quickly check that everything is fine, and Mark as Final. Your first Weblog is done!

In Summary

See also the interesting Weblog from Mark: Weblog formatting Tips and Tricks

Even although this step-by-step description is slightly long, it is only because of the detailed description. These few steps always take less than fifteen minutes!

  • Write using Word and a few selected sdn_* paragraph classes. Use images as they add spice. Just crop them to maximum 600x400 pixels.
  • Proof read!
  • Save, Save as Web Page and then Exit!
  • Run conversion program
  • Paste text into SDN and upload pictures.

That is all there is to it. Although this weblog has contained no technical information, I do hope that it has taken away any last reservations about blogging! It is simple and immense fun!

Employee Self Services (ESS) is one of the hottest new developments in the intranet. Most companies attempt to streamline processes, and have employees complete simple administrative processes directly themselves. Typical examples are booking holidays, address changes, or ordering office utilities. These processes are all targeted at occasional use, and must be simple to use. The typical approach is to use a fixed pattern, which all ESS applications follow, so that the casual user will be able to complete the process relatively easily.    For this weblog, we would like to write an ESS application for holiday (vacation) booking. First, we will write a pattern engine that does all of the generic work and handles the overall layout. Thereafter, we will write our small ESS application.     Product remark: The work presented here does not come from any SAP product. The words ESS and pattern engine are used here in a generic way, and do not reflect any product development work that I am aware of. For this weblog, my favorite search engine was used to obtain one or two screen shots of a typical ESS application from a website in the Internet.    For all our ESS applications, we would like to have the same layout. At the top should be the title. A roadmap will be used to give an overview of all the steps to be followed, plus the current active step. Navigation buttons must be at the bottom of the page. All ESS processes will have at a minimum: an introduction page to explain how to complete the process, the actual work pages, a save page giving a summary of the entered data, plus a final confirmation page.       After the weblog that showed how to write composite elements  (BSP Programming: Writing Composite Elements), the first idea was to use a composite element to handle the complete layout, wrapped around the body of the ESS application. However, BSP elements are excellent at handling rendering, but not good for complex logic.In this case, we would have required additional data structures to hold configuration information and extra classes to handle events.     It was then that the plan for using a BSP controller as a pattern engine was hatched. Using a controller has many benefits. The code for the controller is placed in a separate class. Adding the controller into a BSP application is just one data entry. The same controller can be used many times within one BSP application. As a controller is effectively a normal ABAP class, it is possible to place all the type declarations, data and event handling into this class. Furthermore, it is possible to use the same techniques that are used in composite elements, also in controllers. Thus, the controller can contain rendering code, and is quite capable of processing BSP elements.    For our design, we have one controller that will be the pattern engine. The pattern engine is responsible for handling the complete layout, deciding what the current active step is, and displaying it. In addition, the pattern engine will offer a number of events (mapped onto method calls) to help the ESS application, for example: next, previous, confirmed and finished.      The ESS application will consist of a controller that inherits from the pattern engine, a model class, and all the views that are required for rendering each step. Because the ESS application inherits from the pattern engine, it is very easy to complete the configuration data about the pattern and to overwrite any events of interest. Furthermore, the ESS application will contain all the business logic.    The use of a model class is optional. For the ESS application it is used, as the model binding makes it easier to have the data from the incoming request placed automatically back in the model. The model class also handles the conversion between internal and external representation. A typical example is the conversion of a date from ‘YYYYMMDD’ into my preferred version ‘YYYY-MM-DD’ and to ‘MM/DD/YYYY’ for our stateside colleagues.    For each step in the ESS application, one view is written. The view itself will only contain the BSP elements that represent that actual ESS application. Everything else will be done by the pattern engine.  h3. The Final ESS Application   Before looking at the actual code, let us first look at what we want to achieve. This will make the actual code much easier to understand.    Our small ESS application will have five steps. The first is an introduction page, which explains the complete process quickly. The second step will give an overview of the holidays that have been taken this year, and list the available holidays.         The next step will be to enter the data for the next holiday! After the data has been verified as correct, it will be presented in read-only mode in step four, with a confirm button. Up to this step, it's always possible to navigate back to the previous steps, or to cancel the process.      In the last step, a final confirmation shows that the holiday has been booked. Now the only navigation option is to press the finish button. For typical ESS applications, this exit URL will be configured to return to a small “portal” that contained all the different ESS applications.  h3. Writing the ESS Application   Before looking at the more complex pattern engine, let's first look at the work required to develop the ESS application. We create a new BSP application that has one controller and five views.  

 

Extending the Design of the Composite Element

 

 

As the names of all IDs and events used were hard coded, it was not possible to use two pagers on the same HTML page. For example, this could be interesting in scenarios where a split screen showing two logical independent sequences is used, and can be paged separately. Thus, we will add an 'ID attribute'.

 

 

 

In addition, one never knew what the current page was. The pager only handled the previous and next pages. We add a 'current attribute', which is the name of the current page. This will also be rendered (left-aligned on screen).

 

 

 

Last, we are using a control that will have an onX attribute to allow us to configure the event handler that must be called on return. We add the 'onPage attribute'. Note that later we will have both pagePrevious and pageNext events. The onPage is just a string that is the user’s handle for the event. Although in most controls we define an onX per event, it is not required. Using one such onX string for a number of events is perfectly fine.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_EHICE_001.GIF|height=0 width=459 height=327 |width=0 width=459 height=327 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_EHICE_001.GIF|border=0 width=459 height=327 !

 

 

 

For this Weblog, I just defined a new pager2 element, so as to keep the older example for reference. But it is also possible to just change the original code.

 

Using the Composite Element

 

 

Before we start looking under the hood at the code that will be needed to complete the work, let us first “use” the new element. From this, we can get a good feeling for what must be supported. The test program will be similar to that used previously.  We only have to set additional attributes for the element.

 

 

 

For each page, we define the following source code:

 

 

 

 

<!code>  <htmlb:content design="design2003"><htmlb:page><htmlb:form>

<!code>   

<!code>    ...body comes here...

<!code>   

<!code

<!code

<!code

<!code

<!code

<!code>   

<!code>  </htmlb:form></htmlb:page></htmlb:content>

<!code>   

 

 

 

For the onInputProcessing code, we would now like to use code that is similar to that of the HTMLB library:

 

 

 

 

<!code>  DATA: event TYPE REF TO if_htmlb_data.

<!code>  event = cl_htmlb_manager=>get_event_ex( request ).

<!code>  IF event IS NOT INITIAL AND event->event_id = 'myPager'.

<!code>    navigation->goto_page( event->event_defined ).

<!code>  ENDIF.

<!code>   

 

 

 

In addition, the element should support minimal data retrieval, where it is possible to query the previous, current, and next pages. The typical code for the data call is:

 

 

 

 

<!code>  DATA: pager TYPE REF TO CL_BCM_SDN_PAGER2.

<!code>  pager ?= cl_htmlb_manager=>get_data( request = request

<!code>                                       name    = 'bcm_sdn:

pager2'

<!code>                                       id      = 'myPager' ).

<!code>  * use here pager->current, pager->next, pager->prev

<!code>   

 

 

 

Notice that for the get_data call, it is important to also supply the library and element name. The HTMLB manager has no other help available to determine the correct handler class. The library name is not that of the prefix used in the layout, but the original name under which the library was created. This allows the HTMLB manager to again determine the correct handling class.

 

 

 

We see from the above coding that we wish to achieve a new pager element that will work transparently with the HTMLB manager. For the element's user, business is as usual.

 

Use of IDs

 

 

 

<!code>  htmlb_button = cl_htmlb_button=>factory(

<!code>                    id            =  'bcm_sdn_pager_next'

 

<!code>                    ... )

<!code>   

 

<!code>  htmlb_button = cl_htmlb_button=>factory(

<!code>                    id            = me->id

 

<!code>                    id_postfix    = '__Previous'

 

<!code>                    ... ).

<!code>   

<!code>  htmlb_button = cl_htmlb_button=>factory(

<!code>                    id            = me->id

 

<!code>                    id_postfix    = '__Next'

 

<!code>                    ... ).

<!code>   

 

 

 

The factory method will concatenate the id and id_postfix strings together to create the new ID for the specific button.

 

Integrating into the HTMLB Manager

 

 

 

<!code>  METHOD if_htmlb_data~event_initialize.

<!code>   

<!code>  * Initialize event_* parameters

<!code>    me->if_htmlb_data~event_* = ...

<!code>   

<!code>  * Restore all data from the request

<!code>    me->if_htmlb_data~restore_from_request(request = p_request

<!code>                                           id      = me->if_htmlb_data~event_id ).

<!code>   

<!code>    ...now apply event onto restored data...

<!code>   

<!code>  ENDMETHOD.

<!code>   

<!code>  METHOD if_htmlb_data~restore_from_request.

<!code>   

<!code>  * Use event_id as flag to check whether we also have an event. Let it do work.

<!code>    IF me->if_htmlb_data~event_id IS INITIAL AND

<!code>       CL_HTMLB_MANAGER=>CHECK_AND_INITIALISE_EVENT(

<!code>                       instance          = me

<!code>                       request           = request

<!code>                       event_id_expected = id

<!code>                       class_name        = me->m_class_name

<!code>                  ) IS NOT INITIAL.

<!code>      RETURN. " means an event found and restored (recursively called here)

<!code>    ENDIF.

<!code>   

<!code>    ...restore values from request...

<!code>   

<!code>  ENDMETHOD.

<!code>   

 

 

 

Not perfectly elegant, and looking at it now years later…Oh well, let us just cut-and-paste it into our code, no further comments.

 

Data Handling

 

 

We require that the pager is able to restore the values of the previous, current, and next pages. We must keep in mind that any control on the page can trigger an event to the server, and thus it's not always possible to retrieve this information from the event data.

 

 

 

The best technique to store the view state within an HTML page, is to use hidden input fields. This information is not rendered, and will be returned to the server when the form is submitted.

 

 

 

The following code is used within the do_at_beginning method to render the view state into the response, so that it will be returned to the server on the next request:

 

 

 

 

<!code>  DATA: html TYPE STRING.

<!code>  CONCATENATE

<!code>    ``

<!code>    ``

<!code>    ``

<!code>  INTO html.

<!code>  me->print_string( html ).

<!code>   

 

 

 

Notice the use of the ID with sub strings to create new names for each hidden input field. The values are taken from the current element attributes.

 

 

 

To restore the values, the code below is used in the restore_from_request method:

 

 

 

 

<!code>  me->id = id.

<!code>   

<!code>  CONCATENATE me->id `_valPrev` INTO name.

<!code>  me->prev = request->get_form_field( name ).

<!code>   

<!code>  CONCATENATE me->id `_valCurrent` INTO name.

<!code>  me->current = request->get_form_field( name ).

<!code>   

<!code>  CONCATENATE me->id `_valNext` INTO name.

<!code>  me->next = request->get_form_field( name ).

<!code>   

 

 

 

Notice again the use of the ID to compute the actual names of the form fields that hold the data in the incoming HTTP request.

 

Event Handling

 

 

 

<!code>  htmlb:button:click:null

<!code>   

 

 

 

But, it is also possible to add additional handler classes onto this string, using ‘::’ as separator sequences.

 

 

 

 

<!code>  htmlb:button:click:null::

<!code>   

 

 

 

This means that even although a button-click event is received, the newly specified handler class must be called to decode the event. As it is not possible to configure these escape strings when processing another element, the HTMLB manager will also accept these escape sequences when they are attached to the event server name (onX strings).

 

 

 

 

<!code>  DATA: htmlb_button TYPE REF TO cl_htmlb_button.

<!code>  htmlb_button = cl_htmlb_button=>factory(

<!code>                        id            = me->id

<!code>                        id_postfix    = '__pagePrevious'

<!code>                        text          = me->prev

<!code>                        design        = 'PREVIOUS' ).

<!code>  CONCATENATE me->onPage '::cl_bcm_sdn_pager2::' me->prev '.BSP'

 

<!code>         INTO htmlb_button->onclick.

 

<!code>  WHILE m_page_context->element_process(htmlb_button ) = co_element_continue.

<!code>  ENDWHILE.

<!code>   

 

<!code>  METHOD if_htmlb_data~event_initialize .

<!code>   

<!code>  * Copy those parameters which we keep verbatim

<!code>    me->if_htmlb_data~event_id              = p_event_id.

<!code>    me->if_htmlb_data~event_type            = p_event_type.

<!code>    me->if_htmlb_data~event_class           = p_event_class.

<!code>    me->if_htmlb_data~event_name            = p_event_name.

<!code>    me->if_htmlb_data~event_server_name     = p_event_server_name.

<!code>    me->if_htmlb_data~event_defined         = p_event_defined.

<!code>    me->if_htmlb_data~event_intercept_depth = p_event_intercept_depth.

<!code>   

<!code>  * The pager uses two <htmlb:button> elements. Massage the event

<!code>  * to be pager event.

<!code>  * Event name will be 'button', should be our 'pager2'.

<!code>  * Event Id will be __pagePrevious

<!code>  * Event Type will be click from the button. The actual value we want, was

<!code>  * already encoded into the ID before.

<!code>   

<!code>    me->if_htmlb_data~event_name = me->m_name.

 

<!code>    SPLIT me->if_htmlb_data~event_id AT '__'

 

<!code>      INTO me->

if_htmlb_dataevent_id me->if_htmlb_dataevent_type.</b>

<!code>   

<!code>  * Restore view state from the request

<!code>    me->if_htmlb_data~restore_from_request(request = p_request

<!code>                                           id      = me->if_htmlb_data~event_id ).

<!code>   

<!code>  ENDMETHOD.

<!code>   

 

 

 

With the above changes, events are now presented as pager2 events.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_EHICE_002.GIF|height=0 width=331 height=296 |width=0 width=331 height=296 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_EHICE_002.GIF|border=0 width=331 height=296 !</body>

In the past few weeks in SDN, there have been a number of questions on handling HTMLB events. One of the biggest frustrations was with our

 

examples. This is a typical example:

 

 

 

 

<!code>  DATA: evt TYPE REF TO if_htmlb_data.

<!code>  evt = cl_htmlb_manager=>get_event_ex( request ).

<!code>  IF evt IS NOT INITIAL AND evt->event_name = 'button' AND evt->event_type = 'click'.

<!code>    ...

<!code>  ENDIF.

<!code>   

 

 

 

The first questions were always: Where can you obtain a list of events that are available? What are these magic strings? What happens if we write

 

them in upper/lower case?

 

 

 

The second problem was that of event dispatching. For the very old HTMLB library, we supported an interface if_htmlb_event that could be

 

implemented by an event handler class. However, this work was never done for the XHTMLB or PHTMLB libraries. Then a few days ago, we saw an example of

 

this technique being actively used with the newer libraries. So definitely it was time to clean this up.

 

 

 

Last, there was the question in the forum about a technique to build an OO framework for the event handling. If we have a button with onClick =

 

“save”, we would actually like to have the event dispatching done directly onto a method called “save”. After fiddling a little with the code, this

 

suddenly becomes relatively easy.

 

 

 

In the last two weeks, we have reworked the entire event handling code of the HTMLB family of libraries to address these three points. All of these

 

improvements will be shipped in the next service pack (620SP43 & 640SP05) that should be available in September 2004. This weblog will give an

 

overview of the three different techniques on how to handle HTMLB events. It is based on the new work we did. Some parts of this work in older service

 

packs; but especially for the event dispatching, the above service packs are required.

 

Test Program

 

 

is used to show the current index. Only a small part of the layout

 

code is shown here. The

complete source code

is available.

<!code>  <xhtmlb:buttonGroup id = "btngrp" onClick = "TablePager">

<!code>    <xhtmlb:buttonGroupItem key      = "prev_page"

<!code>                            text     = "Previous Page"

<!code>                            design   = "PREVIOUS"

<!code>                            disabled = "<%=vIndex_prev_disabled%>" />

<!code>    <xhtmlb:buttonGroupItem key      = "next_page"

<!code>                            text     = "Next Page"

<!code>                            design   = "NEXT"

<!code>                            disabled = "<%=vIndex_next_disabled%>" />

<!code>  </xhtmlb:buttonGroup>

<!code>   

 

 

 

The output for the complete test program is shown below. In this display, the first visible row is currently one, and thus the button to navigate

 

to the previous page is disabled.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_HHE_001.GIF|height=0 width=545 height=270 |width=0 width=545 height=270 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_HHE_001.GIF|border=0 width=545 height=270 !

 

Approach One: Manually Handling All HTMLB Events

 

 

The first approach to HTMLB event handling is to retrieve the event, and then simply investigate what type of event it is. This type of coding is

 

usually done in the OnInputProcessing handler of BSP pages.

 

 

 

The incoming event is retrieved with the call cl_htmlb_manager=>get_event_ex. In all cases, only this new method must be used. The older

 

get_event method is obsolete and not supported for the XHTMLB and PHTMLB libraries. Note that this method can be called more than once, and will

 

always return the same event.

 

 

 

 

The get_event_ex method will return an event object that implements at least the if_htmlb_data interface. This interface has a number of

 

interesting parameters that can be examined to see what type of event has been received. See also “

BSP In-Depth: Using the HTMLB Event System

” for an in-depth discussion of the different event parameters.

<!code>  DATA: event TYPE REF TO if_htmlb_data.

<!code>  event = cl_htmlb_manager=>get_event_ex( request ).

<!code>   

<!code>  IF  event IS NOT INITIAL

<!code>  AND event->event_name = xhtmlb_events=>buttongroup

<!code>  AND event->event_type = xhtmlb_events=>buttongroup_click.

<!code>    CASE event->event_defined.

<!code>      WHEN 'prev_page'.

<!code>        vindex = vindex - vsize.

<!code>        IF vindex < 1.

<!code>          vindex = 1.

<!code>        ENDIF.

<!code>      WHEN 'next_page'.

<!code>        vindex = vindex + vsize.

<!code>        IF vindex >= LINES( sflight ).

<!code>          vindex = LINES( sflight ) - vsize + 1.

<!code>        ENDIF.

<!code>    ENDCASE.

<!code>  ENDIF.

<!code>   

 

 

 

This approach of event handling is very fast to program, especially on a BSP page. The disadvantage of this approach is that the code quickly

 

explodes once events for many controls must be handled.

 

Approach Two: Dispatching Events via IF_HTMLB_EVENTS Interface

 

 

The HTMLB rendering libraries also contain a technique to dispatch events to a handler class. For this, use the

 

cl_htmlb_manager=>dispatch_event_ex method. One of the parameters is a handler class that will accept the incoming event and process it. Typically,

 

this can be a separate developed class, a controller class, or even the application class.

 

 

 

In this example, we will use a handler class that has been developed separately. On the BSP page, in the OnInputProcessing method, the event

 

handling code now reduces to a few lines. All that is required is an instance of the handler class, and then the dispatcher is called.

 

 

 

 

<!code>  DATA: handler TYPE REF TO CL_SDN_HANDLING_HTMLB_EVENTS.

<!code>  CREATE OBJECT handler.

<!code>  cl_htmlb_manager=>dispatch_event_ex( request       = request

<!code>                                       page_context  = page_context

<!code>                                       event_handler = handler ).

<!code>   

 

 

 

The benefit of this approach is that the event handling code is placed into a separate class, where the full strength of the workbench can be used.

 

It reduces clutter in BSP pages. In addition, if new controls are added, no further plumbing code is required on the BSP page. All events will be

 

dispatched by this one call.

 

 

 

The important question is how will the handler class know what events are available, and what parameters each event handling method must have. For

 

this, an interface if_htmlb_events is defined. This interface contains all the possible events that can be fired by the HTMLB library. Each method

 

contains the correct parameters with which it will be called. Similar interfaces exist: if_xhtmlb_events and if_phtmlb_events for the other two

 

libraries.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_HHE_003.GIF|height=0 width=402 height=238 |width=0 width=402 height=238 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_HHE_003.GIF|border=0 width=402 height=238 !</body>

 

Writing a Test Example

 

 

 

<!code>  <%@extension name="htmlb" prefix="htmlb"%>

<!code>  <%@extension name="phtmlb" prefix="phtmlb"%>

<!code>   

<!code>  <htmlb:content design="design2003">

<!code>   <htmlb:page>

<!code>    <htmlb:form>

<!code>   

<!code>       ...body comes here...

<!code>   

<!code>      <phtmlb:horizontalDivider hasRule          = "TRUE"

<!code>                                separationHeight = "LARGE" />

<!code>   

<!code>      <phtmlb:matrix width = "100%" >

<!code>   

<!code>        <phtmlb:matrixCell hAlign = "RIGHT" />

<!code>   

<!code>          <htmlb:button       text          = "Page In-1"

<!code>                              design        = "PREVIOUS"

<!code>                              onClick       = "pageIn-1.htm" />

<!code>   

<!code>          <htmlb:button       text          = "Page In+1"

<!code>                              design        = "NEXT"

<!code>                              onClick       = "pageIn+1.htm" />

<!code>   

<!code>      </phtmlb:matrix>

<!code>   

<!code>    </htmlb:form>

<!code>   </htmlb:page>

<!code

 

Designing a New Composite Element

 

 

The goal is to replace this entire navigation rendering with one simple element. The expected code on each BSP page would be:

 

 

 

 

<!code>  <%@extension name="htmlb"   prefix="htmlb"%>

<!code>  <%@extension name="bcm_sdn" prefix="sdn"%>

<!code>   

<!code>  <htmlb:content design="design2003">

<!code>   <htmlb:page>

<!code>    <htmlb:form>

<!code>   

<!code>       ...body comes here...

<!code>   

<!code

<!code>    </htmlb:form>

<!code>   </htmlb:page>

<!code>  </htmlb:content>

<!code>   

 

 

 

We want one element that takes a previous and/or next attribute with the text to display. As we are (slightly) lazy, we assume that pages are named

 

exactly the same as the descriptive text, just without spaces, and terminated with our typical ‘.bsp’ extension. (Note that most people use .htm as

 

the page extension. The use of .bsp is a trait mostly used in our small group.)

 

 

 

The definition in the workbench (transaction SE80) of the new BSP element is quickly done. It has only two string attributes. Once this BSP element

 

has been defined and activated, the above example BSP page will actually compile and run. It will just not yet render any output.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_WCE_002.GIF|height=0 width=488 height=268 |width=0 width=488 height=268 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_WCE_002.GIF|border=0 width=488 height=268 !</body>

 

Overview of Event Handling in HTMLB

 

 

HTML/HTTP does not support the concept of server events. At the lowest level, the only building block that is

 

available is forms in HTML, which can be submitted to a server. When a form is submitted, all input fields (which

 

includes hidden input fields) are transported to the server. Therefore, event handling in the browser reduces to

 

setting up specific predefined input fields (these are usually type=”hidden”) with values that reflect the event to

 

be sent to the server, and then submitting the form.

 

 

 

On the server, the event handling system will look at the incoming HTTP request. If it detects form fields with

 

well-known names (all HTMLB event input fields have a prefix ‘htmlbevt_’), it will signal an HTMLB event, and unpack

 

the relevant fields into an event object. Let’s look briefly at all the components that are involved in the event

 

handling system.

 

 

 

 

<!codeRendering Phase

 

<!code>    <htmlb:button id=”myBtn” onClick=”button_clicked”/>

<!code>    ... CL_HTMLB_BUTTON

<!code>        ... event = CL_HTMLB_MANAGER=>RENDER_EVENT_CALL(...).

<!code>        ... render onclick=’htmlbSubmitLib(...)’

<!code>   

<!codeIn Browser

 

<!code>    User clicks on button

<!code>    ... onclick is triggered, calls htmlbSubmitLib(...)

<!code>        ... Sets up a number of input fields with correct values

<!code>        ... calls form.submit();

<!code>   

<!codeOn Server

 

<!code>    event = CL_HTMLB_MANAGER=>GET_EVENT_EX( request )

<!code>        ... examines HTTP request for fields matching htmlbevt_*

<!code>        ... creates event object cl_htmlb_button, unpacks fields

<!code>   

 

 

 

During rendering, each control might require one or more events. This is usually done by wiring the HTML onclick

 

attribute with some JavaScript code that will handle the event. This specific, required JavaScript code is obtained

 

by a call to the method cl_htmlb_manager =>render_event_call. This method will return a sequence of JavaScript

 

code, which consists of one or more calls to the different JavaScript functions that are available for event

 

handling in the browser. The output of this method is internal only, already. In the past this output has been

 

improved a number of times. Do not try to concatenate this JavaScript output together directly, as this will cause

 

problems if the underlying event handling code is modified.

 

 

 

In the above examples, the JavaScript function htmlbSubmitLib is shown. However, the exact call that will be

 

generated depends on a number of factors, for example if a client side event is also involved, and whether the event

 

is listed in a predefined dictionary. Consider the output of the RENDER_EVENT_CALL method as a black box.

 

 

 

In the browser, once a control event is triggered, the JavaScript code in the onclick handler is executed. This

 

code calls the defined JavaScript code, which packs the event relevant information into hidden input fields, and

 

then submits the form.

 

 

 

At the server, the developer calls cl_htmlb_manager =>get_event_ex to see if any event was fired. (Note

 

get_event_ex is the newer replacement of get_event. The libraries XHTMLB and PHTMLB only work with the new

 

get_event_ex method. The HTMLB library has also been updated to work with this new method.) This method sees if any

 

form fields are in the incoming HTTP request that indicates an HTMLB event has been fired. If an event is detected,

 

the specific event handling class is found, instantiated and initialised with the event parameters. For all new

 

libraries, it’s expected that the rendering class is also the event handling class, and that this class implements

 

the additional interface if_htmlb_data.

 

 

 

When developing controls that use the HTMLB event system, there are only two relevant sections of code. The first

 

is the render_event_call for the JavaScript code that must be executed in the browser. The second is the

 

implementation of the if_htmlb_data interface, so that the HTMLB event system can initialize the event object with

 

relevant data.

 

RENDER_EVENT_CALL Method

 

 

Keep in mind that the actual generated JavaScript is placed

 

inside an HTML onclick sequence. In HTML, it is important to keep event bubbling in mind. One typical example is

 

when an anchor is used to render a control. If the onclick does not return false, the documentation  (http://help.sap.com/saphelp_nw04/helpdata/en/f8/7e1d3c55a0f503e10000000a114084/frameset.htm) for detailed instructions on writing BSP extensions and elements.  In the

 

workbench, create a new BSP element, and define a number of attributes. After the element is activated, all the

 

relevant classes are generated.

 

 

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BID_UTHES_001.GIF|height=0 width=570 height=365 |width=0 width=570 height=365 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BID_UTHES_001.GIF|border=0 width=570 height=365 !

 

 

 

The name attribute is used to specify the name of the JavaScript function. Alternatively, if the event_code

 

attribute is specified, the JavaScript code will be returned (placed into this string reference), and no JavaScript

 

function is rendered.

 

 

 

The attributes id, event_type, onClick (mapped onto server_event), event_defined and return_value are strings

 

that will be passed directly to the render_event_call. These strings must be statically known during the rendering,

 

and cannot be determined later in the browser. The attribute onClientClick (mapped onto client_event) can contain

 

any sequence of JavaScript code that will be executed in the browser when the event is triggered.

 

 

 

The attributes p1, p2, and p3 are used to specify the parameters of the rendered JavaScript function, and will

 

then be passed to the event handling code.

 

 

 

As this will be an “empty” control (meaning it can have no body text), only the do_at_beginning method is

 

implemented. The complete method is listed below, and then discussed.

 

 

 

 

<!code>  METHOD if_bsp_element~do_at_beginning.

<!code>   

<!code>    IF me->id IS INITIAL.

<!code>      me->id = 'id'.

<!code>    ENDIF.

<!code>   

<!code>    IF me->event_code IS NOT BOUND.

<!code>      IF me->name IS INITIAL

<!code>      OR me->name    CN '_abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ0123456789'

<!code>      OR me->name(1) CA '0123456789'.

<!code>        me->raise_error( msg = 'Der Parameter "name" ist kein gültiger

 

JavaScript-Name.'(001) ).

<!code>      ENDIF.

<!code>    ENDIF.

<!code>   

<!code>    DATA: param_string TYPE string,

<!code>          param_count  TYPE I VALUE 0.

<!code>    IF me->p1 IS NOT INITIAL.

<!code>      CONCATENATE param_string `,` me->p1 INTO param_string.

<!code>      param_count = param_count + 1.

<!code>    ENDIF.

<!code>    IF me->p2 IS NOT INITIAL.

<!code>      CONCATENATE param_string `,` me->p2 INTO param_string.

<!code>      param_count = param_count + 1.

<!code>    ENDIF.

<!code>    IF me->p3 IS NOT INITIAL.

<!code>      CONCATENATE param_string `,` me->p3 INTO param_string.

<!code>      param_count = param_count + 1.

<!code>    ENDIF.

<!code>   

<!code>    DATA: event_string TYPE string.

<!code>    event_string = CL_HTMLB_MANAGER=>RENDER_EVENT_CALL(

<!code>                      BSP_ELEMENT          = me

<!code>                      EVENT_TYPE           = me->eventType

<!code>                      SERVER_EVENT         = me->onClick

<!code>                      CLIENT_EVENT         = me->onClientClick

<!code>                      CLIENT_EVENT_INLINED = 'X'

<!code>                      EVENT_DEFINED        = me->p0

<!code>                      PARAM_COUNT          = param_count

<!code>                      PARAM_STRING         = param_string

<!code>                      RETURN_VALUE         = js_return ).

<!code>   

<!code>    IF me->event_code IS BOUND.

<!code>      me->event_code->* = event_string.

<!code>    ELSE.

<!code>      IF STRLEN( param_string ) IS NOT INITIAL.

<!code>        SHIFT param_string LEFT.

<!code>      ENDIF.

<!code>      CONCATENATE ``

<!code>                    `function ` name `(` param_string `){ ` event_string `;}`

<!code>                  `</script>`

<!code>             INTO event_string.

<!code>      me->print_string( event_string ).

<!code>    ENDIF.

<!code>   

<!code>    rc = CO_ELEMENT_DONE.

<!code>   

<!code>  endmethod.

<!code>   

 

 

 

The first important step is to do a sanity check on the attribute name, as this string will be used to generate

 

the name of a JavaScript function. This is only done if the event_code is not returned directly.

 

 

 

In the next code block, the optional parameters p1, p2, and p3 are built into one string. This string will be

 

required twice. It is needed to build the interface for the JavaScript function and as a parameter list onto the

 

HTMLB event system call.

 

 

 

Thereafter, the render_event_call is used to retrieve the correct HTMLB event system JavaScript code. Note that

 

the returned code depends very much on the mix of input parameters. The actual string returned is handled as a black

 

box. No assumptions are made about the string or its format.

 

 

 

As the last step, either the event string is returned if so requested, or a JavaScript function is rendered that

 

can be used later to fire the event.

 

Handling Incoming Events

 

 

On incoming HTTP requests, you use the get_event_ex call to determine if an HTMLB event is available. The

 

cl_htmlb_manager will map the event onto the correct class, which is by default the same class that is also used for

 

rendering the control. It instantiates a new copy of this class, and then does a query for the if_htmlb_data

 

interface.

 

 

 

The method event_initialized will be called with all the standard attributes of an HTMLB event. In our case,

 

these values are restored onto the event attributes defined on if_htmlb_data. The last call will be to

 

event_set_parameters with all additional parameters that were available in the incoming HTTP request. These are also

 

restored into the class attributes.

 

 

 

 

<!code>  METHOD if_htmlb_data~event_initialize.

<!code>   

<!code>    me->if_htmlb_data~event_id          = p_event_id.

<!code>    me->if_htmlb_data~event_type        = p_event_type.

<!code>    me->if_htmlb_data~event_class       = p_event_class.

<!code>    me->if_htmlb_data~event_name        = p_event_name.

<!code>    me->if_htmlb_data~event_server_name = p_event_server_name.

<!code>    me->if_htmlb_data~event_defined     = p_event_defined.

<!code>   

<!code>    me->id            = me->if_htmlb_data~event_id.

<!code>    me->event_type    = me->if_htmlb_data~event_type.

<!code>    me->event_defined = me->if_htmlb_data~event_defined.

<!code>    me->onclick       = me->if_htmlb_data~event_server_name.

<!code>   

<!code>  ENDMETHOD.

<!code>   

<!code>  METHOD if_htmlb_data~event_set_parameters.

<!code>   

<!code>    me->p1 = p_param_1.

<!code>    me->p2 = p_param_2.

<!code>    me->p3 = p_param_3.

<!code>   

<!code>  ENDMETHOD.

<!code>   

 

 

 

With the handling of an incoming event, the integration into the HTMLB event handling system is complete, and the

 

control can be used.

 

Using the Event System

 

 

Using the HTMLB event system now reduces to using the control to generate a JavaScript function that can be

 

called from any other HTML or JavaScript code.

 

 

 

 

<!code>  <%@page language="abap"%>

<!code>  <%@extension name="bsp"   prefix="bsp"%>

<!code>  <%@extension name="htmlb" prefix="htmlb"%>

<!code>   

<!code>  <htmlb:content design="design2003">

<!code>  <htmlb:page>

<!code>   <htmlb:form>

<!code>   

<!code>   

<!code>     <bsp:htmlbEvent name="buttonPressed" p1="btnId"/>

<!code>  myButton1

 

<!code>   

<!code>     <% DATA: event_code TYPE STRING. %>

<!code>     <bsp:htmlbEvent event_defined="myBtn2" event_code="<%=event_code%>"/>

<!code>   

<!code

">myButton2]]>

<!code>   

<!code>   </htmlb:form>

<!code>  </htmlb:page>

<!code>  </htmlb:content>

<!code>   

 

 

 

Two different techniques can be used now. In the first, the name attribute is set, and one additional attribute

 

P1 is defined. This will result in a JavaScript function, “buttonPressed(btnId) {...}” been rendered out. In the

 

next step, raw HTML is used, where the onclick handler is now tied to the HTMLB event system. (The sequence

 

is used to generate a button in HTML that can be used to submit a form.)

 

 

 

The alternative technique is to set the event_code attribute. Because this attribute is flagged as a reference

 

attribute, the BSP compiler will pass a reference to the variable into the control. The control will write the

 

generated JavaScript code directly into this variable, so it can be used.

 

 

 

The (heavily edited) output seen in the browser is listed below.

 

 

 

 

<!code

<!code>   

<!code>   

<!code>   

<!code>      function buttonPressed(btnId)

<!code>      {

<!code>        htmlbSubmitLib('htmlb', this, 'bsp:htmlbEvent:EVENT:null', 'htmlb_form_1', 'id', 'buttonPressed',

 

1, btnId);

<!code>        return false;

<!code>      }

<!code>    </script>

<!code>   

<!code>  myButton1

 

<!code>   

<!code>  myButton2

 

<!code>   

<!code>  </form></body></html>

<!code>   

 

 

 

Incoming events are now handled exactly the same way as HTMLB events!

 

 

 

 

<!code>  DATA: event       TYPE REF TO if_htmlb_data,

<!code>        htmlb_event TYPE REF TO cl_bsp_htmlb_event.

<!code>  event = cl_htmlb_manager=>get_event_ex( request ).

<!code>  IF event IS NOT INITIAL AND event->event_class = 'CL_BSP_HTMLB_EVENT'.

<!code>    htmlb_event ?= event.

<!code>    ... use htmlb_event to see p1,p2,p3

<!code>  ENDIF.

<!code>   

 

 

 

The returned interface reference already has enough data to determine nearly all attributes of the event. Only if

 

the parameters p1, p2, or p3 are required, is the actual cast into the class required. The parameters are attributes

 

of the class, and not of the generic HTMLB event system.

 

 

 

If the event data is only displayed on the BSP example page, you have the following output:

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BID_UTHES_002.GIF|height=0 width=283 height=220 |width=0 width=283 height=220 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BID_UTHES_002.GIF|border=0 width=283 height=220 !</body>

 

In a previous weblog (), a simple web crawler was built using the HTML Viewer that is integrated into SAPGUI. In this weblog, it is time to use the web crawler. First, the crawler is extended to a program to specifically fetch the information about all weblogs from SDN. In subsequent steps, the numbers are tortured to tell us the “story of success”. We use BSP Extension “graphics” to visualize the numbers.

 

 

 

The Phases of Crawling SDN

 

 

 

The basic crawler is complete, and was shown previously. Now it is time to build onto this work. Again, only parts of the code will be presented, the full source code is

available

.

 

 

 

 

For the SDN crawler, we will define a number ofphases: LOGON, GET_MONTHS (this was the fastest way to get an overview of all weblogs!) and QUIT. Each phase will have one method to create all the URLs that are required, and one method that will be called with the contents of each URL.

 

 

 

For the LOGON phase, only a POST request is required with the authentication data. This step must be done to gather the different session cookies, and get a SSO2 (single sign on) cookie. The returned content is not further parsed.

 

 

 

The GET_MONTHS phase is the more interesting part. We are interested in a complete list of all weblogs written, plus the basic data about each. However, not all of this information is available via RSS feeds. An alternative source was required. I saw that the monthly archives listed all weblogs since the start of SDN.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_001.GIF|height=0 width=573 height=218 |width=0 width=573 height=218 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_001.GIF|border=0 width=573 height=218 !

 

 

 

Once the URL format for each month’s archive is known, it becomes very easy to build a list of URLs that are required to retrieve the information. With some reverse engineering, you quickly see that the first weblogs were written in May 2003.

 

 

 

 

<!code>    METHOD get_months

.

<!code>      next_phase = 'QUIT'.

<!code>   

<!code>      DATA: url  TYPE string,

<!code>            date TYPE D VALUE '20030501'.

<!code>      WHILE date < sy-datum.

<!code>        CONCATENATE 'get__https://www.sdn.sap.com/irj/sdn/weblogs?blog=/weblogs/date/'

                     date(4) '/' date+4(2)

<!code>               INTO url.

<!code>        APPEND url TO urls.

<!code>        date = date + 31. date+6(2) = '01'.

<!code>      ENDWHILE.

<!code>    ENDMETHOD.

<!code>   

 

 

 

The get_months method is called only once to queue all the URLs that are required for this phase. However, the content will be delivered per page. The input parameter is one string that contains the complete body of the loaded page. Simple ABAP string operations (mostly SPLIT!) are used to extract the relevant weblog information from the HTML string.

 

 

 

 

<!code>    METHOD get_months_content

.

<!code>      WHILE content CS '/pub/wlg/'.

<!code>        ....

<!code>        APPEND INITIAL LINE TO blogs ASSIGNING .

<!code>        ....

<!code>        SPLIT content AT '/pub/wlg/' INTO garbage content.

<!code>        SPLIT content AT 'Permalink' INTO blog    content.

<!code>   

<!code>        SPLIT blog AT '"' INTO -url blog.

<!code>        CONCATENATE 'https://weblogs.sdn.sap.com/pub/wlg/' -url.

<!code>   

<!code>        SPLIT blog AT '-title blog.

<!code>        SPLIT -title.

<!code>   

<!code>        SPLIT blog AT '

' INTO garbage blog.

<!code>        SPLIT blog AT '-abstract blog.

<!code>        ....

<!code>      ENDWHILE.

<!code>    ENDMETHOD.

<!code>   

 

 

 

One interesting aspect is that the HTML received here does not match the actual HTML sent to the page. HTML source (as seen using “View Source” in the browser) is parsed into an HTML DOM (document object model), whichis a tree-like representation of the HTML source. The web crawler uses the outerHTML command to convert the HTML DOM back into a string. One example is the liberal use of “

” sequences, which are rendered as “

” by the outerHTML call.

 

 

 

The QUIT phase just dumps the complete internal table of weblogs into one BSP server side cookie.

 

Writing the SDN Web Crawler

 

 

The basic web crawler already contains all the functionality to handle the request for one URL, and to return the content of the document. Furthermore, it had a number of interesting methods that could be redefined: initialize, next and loaded_content. The SDN crawler is designed so that it will inherit from the simple web crawler. With this, we can just redefine these methods.

 

 

 

 

<!code>  CLASS cl_sdn_crawler DEFINITION INHERITING FROMcl_html_crawler

.

<!code>    PUBLIC SECTION.

<!code>      METHODS initialize     REDEFINITION.

<!code>      METHODS next           REDEFINITION.

<!code>      METHODS loaded_content REDEFINITION.

<!code>      ....

<!code>  ENDCLASS.

<!code>   

 

 

 

The phases have been designed and the basic concept is that each phase will first supply a list of URLs that it is interested in, and will then be called with the content per URL.

 

 

 

Thus the initialize method will just start the first phase, and call the phase method (see the use of a dynamic method call) to fill the URL list.

 

 

 

 

<!code>  METHOD initialize.

<!code>    phase = 'LOGON'.

<!code>    CALL METHOD me->(phase).

<!code>    me->next( ).

<!code>  ENDMETHOD.

<!code>   

 

 

 

The next method is now structured very simply. As long as there are URLs in the list to fetch, remove one from the list, and load it. Once the list is empty, switch to the next phase, and calls the phase method to fill the URL list again.

 

 

 

 

<!code>    METHOD next.

<!code>    IF LINES( urls ) IS INITIAL.

<!code>      phase = next_phase.

<!code>      CALL METHOD me->(phase).

<!code>    ENDIF.

<!code>   

<!code>    IF LINES( urls ) IS INITIAL. RETURN. ENDIF.

<!code>   

<!code>    DATA: url TYPE STRING.

<!code>    READ TABLE urls INDEX 1 INTO url.

<!code>    DELETE urls INDEX 1.

<!code>    me->load_url( url ).

<!code>  ENDMETHOD.

<!code>   

 

 

 

The final method is just to dispatch the incoming content to the correct phase method. Again a dynamic method call is used to call the correct phase handler for the content.

 

 

 

 

<!code>  METHOD loaded_content.

<!code>    DATA: handler TYPE STRING.

<!code>    CONCATENATE phase '_CONTENT' INTO handler.

<!code>    CALL METHOD me->(handler) EXPORTING content = content.

<!code>  ENDMETHOD.

<!code>   

 

 

 

With these few lines of code on the simple web crawler, it’s now possible to crawl through SDN and gather some interesting information.

 

 

 

One important remark: When crawling another site, do not overload the site with too many requests! We already know that the simple web crawler has a large delay of about five seconds for each URL fetched. Furthermore, this complete SDN crawl will only access seventeen URLs. This is a very low load, which any web server should easily be able to handle. (SDN also has the option to cache all the old archives, as these do not change at all. This would definitely reduce their load to a very minimum.) This should be acceptable under any common sense rules. I could not easily recommend more than this.

 

Examining the Catch

 

 

The SDN crawler saved the final output as a BSP server side cookie. As the first test, a simple BSP page is used to see the output from the crawler.

 

 

 

 

<!code>  <%@page language="abap"%>

<!code>  <%@extension name="htmlb" prefix="htmlb"%>

<!code>   

<!code>  <% CL_BSP_SERVER_SIDE_COOKIE=>GET_SERVER_COOKIE( ... ). %>

<!code>   

<!code>  <htmlb:content design="design2003">

<!code>    <htmlb:page>

<!code>      <htmlb:form>

<!code>        <htmlb:tableView id    = "tv1"

<!code>                         table = "<%=blogs%>" />

<!code>      </htmlb:form>

<!code>    </htmlb:page>

<!code>  </htmlb:content>

<!code>   

 

 

 

The output is as expected: a very nice table full of interesting data.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_002.GIF|height=0 width=565 height=110 |width=0 width=565 height=110 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_002.GIF|border=0 width=565 height=110 !

 

 

 

 

It is time to extract one or two interesting bits and pieces. We will limit the statistics to a few examples; otherwise, we just might have to do it every month  ()!</p>

 

Getting Perfect Output

 

 

As a first step, the statistical data was just computed and the tables displayed using the HTMLB tableView control. However, numbers pale in comparison to nice graphs. We definitely required something better!

 

 

 

 

For BSP there are two ways to get to graphics. The first is to use the very old HTMLB chart control, the other is to look at the BSP extension graphics. Unfortunately, in-depth information is not available in the online help system now. Just send a short email to graphics@sap.com  (mailto:graphics@sap.com) and request the full package! (A little bird whistles a song about the SDN download area in future.)

 

 

 

 

For this test application, I quickly looked at the example SBSPEXT_HTMLBchart.bsp, and 15 minutes later, it was complete. The chart control has a very simple interface where the X and Y values are just stored in a table. Thereafter the requested chart type is configured, and the title is set. See the example page for a small example.

 

The Growth of SDN

 

 

 

, wrote the first Weblog on SDN on the 27th May 2003, followed three days later by one from DJ Adams  (). That was a total of two for May 2003! However, after just over a year, we can see 355 weblogs written! So there has been a tremendous growth in the last year.

 

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_003.GIF|height=0 width=503 height=202 |width=0 width=503 height=202 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_003.GIF|border=0 width=503 height=202 !

 

 

 

No doubt, we will see a steady increase in the number of weblogs that are published. This is a growing community. Of course, if all these weblogs flow from the pen of one author, it’s not helping much.

 

 

 

But the data tells us that 76 authors have written for SDN! However, how many did each author write?

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_004.GIF|height=0 width=504 height=193 |width=0 width=504 height=193 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_004.GIF|border=0 width=504 height=193 !

 

 

 

This diagram shows that most authors have only written a few (one to three) weblogs. So SDN has a large number of weblog authors, but at first glance many don’t seem to be very active.

 

 

 

However, you have to remember that this is a growing community. To understand the statistics above better, you have to look at the “age” of the weblog authors. How much time has each author had to write? Let’s look at the start date for each weblog author (date of first publication). In this way, we can easily get a feeling of when people started to write for SDN.

 

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_005.GIF|height=0 width=507 height=196 |width=0 width=507 height=196 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_CSDN_005.GIF|border=0 width=507 height=196 !</body>

 

 

In a previous BSP Programming: RSS = HttpClient + XML + XSLT, ten lines of code using the HttpClient was sufficient to fetch an RSS feed via HTTP. Once I mastered the art of programming the HttpClient, I had the idea to crawl through SDN weblogs to gather some statistics. As the information about all weblogs is not available via RSS feeds, I decided to fetch the HTML pages, and parse them.



Accessing SDN required an HTTPS connection, which has a few fine-points that must be kept in mind. This weblog shows the usual pitfalls into which I also landed, plus the workarounds. However, at one moment I was stuck, and decided to raise the stakes. Instead of using the HttpClient, why not use a real browser (HTML Viewer) integrated into the SAPGUI? This weblog will quickly touch the pitfalls when making HttpClient connections, and then look at an interesting alternative.

 

Making an HTTPS Connection

 

 

Making an HTTP connection with the HttpClient is very easy. Making an HTTPS connection is just as easy, if you first remember to import the certificate of the SSL partner! For SSL connections, the two partners exchange their certificates. The outgoing connection will only be established if the partner certificate can be verified against a copy stored in the database.



SAP ships very few certificate as standard. For other certificates required, you must get them from the partner directly. As an alternative, if the certificate is already available in the web browser, you can export it from there.



 

Once the certificate is available, it can be imported using transaction STRUST. See also the documentation on this topic. With this additional step done, HTTPS connections work exactly the same way as HTTP connections. There is only one small difference: traffic for HTTPS connections are not traced in ICM, due to security reasons (otherwise one could have used HTTP:).</p>

 

Handling Redirects

 

 

The HttpClient will automatically handle all rc=302 (Redirect) requests. However, there is one case where special handling is required. It is possible for the server to set a cookie during the redirect phase, and these additional cookies must be kept in mind when following the redirect. This is not currently being done by the HttpClient (although it’s now under consideration).



For an example, see this trace (strongly edited!):



<!code>  GET http://sdn.sap.com:80/ HTTP/1.1

<!code>  accept: /

<!code>  host: sdn.sap.com:80

<!code>               

<!code>  HTTP/1.1 302 Object moved

<!code>  Date: Wed, 23 Jun 2004 20:44:47 GMT

<!code>  Location: https://www.sdn.sap.com/

<!code>  Content-Type: text/html

<!code

Set-Cookie: ASPSESSIONIDCABABDDR=DJCHEEOALBBIBLPJKAOGDPBM; path=/

<!code>   

<!code>  GET / HTTP/1.1

<!code>  accept: /

<!code>  host: www.sdn.sap.com:443

<!code>   



What we see is that the first GET request is answered by the server with an rc=302 (Redirect) and a “Location” header is supplied. In addition, the server sets a cookie. However, in the default handling of the redirect, the next GET request (to the new location) does not contain the cookie.



Handling the redirects is very easy. Then the traffic reduces to the usual send-receive cycles, and cookies are handled correctly. A small change was made, to flag that redirects should not be followed, and the case of rc=302 was specifically handled in code.



<!code>  http_client->propertytype_redirect = http->co_disabled.

<!code>  ....

<!code>  http_client->receive( ).

<!code>  http_client->response->get_status( IMPORTING code = rc ).

<!code>   

<!code>  IF rc = 302.

<!code>    location = http_client->response->get_header_field( ‘Location’ ).

<!code>    me->GET( url = location ).

<!code>    RETURN.

<!code>  ENDIF.

<!code>   



Note that the GET() method is part of the crawler development, so as to give a higher level interface to the HttpClient. It just packages a number of HttpClient calls into one method.

 

Special Situation: Headers in HTTP Outgoing Requests

 

 

This section can best be described by starting with a small trace of the traffic:



<!code>  POST /SAPPortal/common/CreateNewCookie.asp HTTP/1.1

<!code>  cookie: ASPSESSIONIDCCRABTTC=DGHCFCHCLAKINDJFJAKAGJMO;

<!code>  content-type: application/x-www-form-urlencoded

<!code>  content-length: 46

<!code>  user-agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)

<!code

host: www.sdn.sap.com

<!code>   

<!code>  portalUserName=user&portalPassword=password

<!code>   

<!code>   

<!code>  HTTP/1.1 400 Host Required In Request

<!code>  Date: Thu, 24 Jun 2004 18:04:50 GMT

<!code>  Content-Type: text/html; charset=iso-8859-1

<!code>  Content-Length: 447

<!code>   

<!code

<!code>   

<!code>   

Host Header Required

<!code>   

<!code>    Description: Your browser did not send a "Host" HTTP header field and

<!code>    therefore the virtual host being requested could not be determined. To

<!code>    access this web site correctly, you will need toupgrade to a browser that

<!code>    supports the HTTP "Host" header field.

<!code>   </BODY>

<!code>  </HTML>

<!code>   



 

For performance reasons, the ICM (in the kernel) translates all HTTP headers to lower case. The HTTP headers are also sent out in their lower case form, and not in the usual capitalized form. For the interested reader, I would refer to the HTTP/1.1 spec (RFC 2616 ): “Each header field consists of a name followed by a colon (":") and the field value. <b>Field names are case-insensitive</b>.



The above message seems to be caused by a case-sensitive string processing sequence (where “Host” is not equal to “host”). Unfortunately, it is not clear who is returning the message. It could be any proxy, load balancer, dispatcher, server or even some custom written servlet along the way. This made it difficult to find (and negotiate) a workaround for this problem.

 

Using a Real Browser: The HTML Viewer

 

 

I was always comparing my requests with that of a real browser, so why not use a real browser? There is a complete browser integrated into SAPGUI (under Windows) that can be programmed with ABAP!



 

As the first step, I quickly read the online documentation , and looked at the test programs SAPHTML_DEMO1 and SAPHTML_EVENTS_DEMO. Then it was mostly cut-and-paste work to get the first version up and running.



 

Only small bits and pieces of the basic crawler will be discussed here. For a complete overview of the code, follow this link.



From the beginning, it was clear that any solution would have some JavaScript code in it (at least at the time of writing this text!). The problem is that the cl_gui_html_viewer does not expose its JavaScript APIs directly. These methods are flagged as protected. It was important to define a new class that inherits from the cl_gui_html_viewer class.



<!code>  CLASS cl_html_crawler DEFINITION

INHERITING FROM cl_gui_html_viewer

.

<!code>    PUBLIC SECTION.

<!code>    METHODS: load_url        IMPORTING uri TYPE string.

<!code>    METHODS: on_navigate_complete FOR EVENT navigate_complete OF cl_gui_html_viewer.

<!code>    METHODS: on_sapevent FOR EVENT sapevent OF cl_gui_html_viewer

<!code>                   IMPORTING action postdata.

<!code>    ....

<!code>  ENDCLASS.

<!code>   



The HTML viewer raises a number of events. The first interesting one is the on_navigation_complete event that is fired after the document has been loaded. In addition, it is possible to “talk” from the browser to the SAPGUI (effectively back to ABAP on the server) using special SAP events inside the browser. These will cause the on_sapevent method to be triggered.



Most of the glue code is copied from the demo programs and documentation and is not listed here. We will look at thee interesting aspects: the loading of a document in the HTML viewer, catching the event that the document has been loaded, and extracting the document content.



For the load_url function

, I wanted a very simple interface. However, the interface should still be powerful enough to distinguish between GET and POST methods, also include the URL to load, and form fields for the request. I decided to use a simple string interface, where all the necessary data will be passed as one string, separated by ‘__’ sequences. The format of the string was “GET|POST__url__ff1__ff2…__ffn”. This allowed me to quickly call the load_url method without complex programming.



For example:



<!code>  'GET__http://sdn.sap.com'

<!code>  'POST__https://www.sdn.sap.com/logon.asp__user=me__password=secret'

<!code>   



For the GET sequences, the code was very simple. The HTML viewer already contained a method show_url that effectively handled the GET completely. However, for a POST request, you require a complete HTML document with form that can be posted. For POST requests, the load_url method would build a complete HTML document, load it into the browser, from which it could be posted to the URL.



<!code>  METHOD load_url.

<!code>   

<!code>    DATA:  url      TYPE char255,

<!code>           method   TYPE string,

<!code>           ffs      TYPE string,

<!code>           ff       TYPE string,

<!code>           ff_name  TYPE string,

<!code>           ff_value TYPE string.

<!code>   

<!code>    SPLIT uri AT '__' INTO method url ffs.

<!code>   

<!code>    IF method = 'GET'.

<!code>      me->show_url( url = url ).

<!code>      RETURN.

<!code>    ENDIF.

<!code>   

<!code>    DATA: html TYPE TABLE OF char255,

<!code>          line TYPE char255.

<!code>    APPEND `` TO html.

<!code>    CONCATENATE '

' INTO line.

<!code>    APPEND line TO html.

<!code>    WHILE ffs IS NOT INITIAL.

<!code>      SPLIT ffs AT '__' INTO ff ffs.

<!code>      SPLIT ff  AT '='  INTO ff_name ff_value.

<!code>      CONCATENATE '

' INTO line.

<!code>      APPEND line TO html.

<!code>    ENDWHILE.

<!code>    APPEND '</form></body></html>' TO html.

<!code>   

<!code>    me->load_data( IMPORTING assigned_url = line CHANGING data_table = html ).

<!code>    me->show_url( url = line ).

<!code>   

<!code>  ENDMETHOD.

<!code>   



Given the example POST sequence above, the following HTML document is created:



<!code

<!code

<!code>   

<!code>   

<!code>  </form></body></html>

<!code>   



In this document, the onload is hooked, and once loaded, it will trigger a submit() call on the form. The form itself contains the action (target URL), as all the form fields are stored as hidden input fields in the form.



One line of source code is really worth highlighting:



<!code>    APPEND `` TO html.

<!code>   



ABAP is the only programming language that I know which supports two forms of quotes for creating strings. This allows us to write ABAP, HTML and JavaScript code in one line without any ‘escaping’ required.



Once the URL has been loaded, there is nothing to do but wait for the event signaling complete.



The problem is that there are API function to read the content from the browser. However, it is possible was to fire an event from the browser to the SAPGUI. Each SAP event is implemented as

that is submitted against the very special URL “SAPEVENT:”. The approach we want to follow, is to use a JavaScript function that will place the content of the document into a string inside a form, and submit this form (against the SAPGUI). The pseudo-code would be about:



 

<!code

<!code>   

<!code>  </form>

<!code>   



It is not possible to write this code directly into the loaded document, as that would destroy it. Therefore, I just used JavaScript to create the form dynamically (using createElement call), and to place the content into hidden fields. The content had to be split into a number of short strings, as the SAPGUI will place all input fields into a table of type CHAR255. So the JavaScript function just does the following steps: create new form, create a number of short input fields to hold segments of the content, and then hook this form into the document.



<!code>  METHOD on_navigate_complete.

<!code>   

<!code>    DATA: js   TYPE STANDARD TABLE OF CHAR255,

<!code>          line TYPE STRING..

<!code>   

<!code>    APPEND `function _Dump() {`                                          TO js.

<!code>    APPEND `  var _frm = document.createElement('form');`                TO js.

<!code>    APPEND `  frm.setAttribute( 'id',     'crawler' );`                TO js.

<!code>    APPEND `  frm.setAttribute( 'name',   'crawler' );`                TO js.

<!code>    APPEND `  _frm.setAttribute( 'method', 'POST' );`                    TO js.

<!code>    APPEND `  frm.setAttribute( 'action', 'SAPEVENT:SAVEDOCUMENT' );`  TO js.

<!code>    APPEND `  var _str = document.body.outerHTML;`                       TO js.

<!code>    APPEND `  var _idx = 0;`                                             TO js.

<!code>    APPEND `  while(_idx < _str.length) {`                               TO js.

<!code>    APPEND `  var _if = document.createElement('input');`                TO js.

<!code>    APPEND `  if.setAttribute( 'name',  'content' );`                  TO js.

<!code>    APPEND `  _if.setAttribute( 'type',  'hidden' );`                    TO js.

<!code>    APPEND `  _if.setAttribute( 'value',  str.substr(idx,200) );`      TO js.

<!code>    APPEND `  _frm.appendChild( _if );`                                  TO js.

<!code>    APPEND `  _idx+=200;`                                                TO js.

<!code>    APPEND `  }`                                                         TO js.

<!code>    APPEND `  document.body.appendChild(_frm);`                          TO js.

<!code>    APPEND `  document.all["_crawler"].submit();`                        TO js.

<!code>    APPEND `}`                                                           TO js.

<!code>    APPEND `window.setTimeout("_Dump();",5000);`                         TO js.

<!code>   

<!code>    me->set_script( script = js[] ).

<!code>    me->execute_script( ).

<!code>   

<!code>  ENDMETHOD.

<!code>   



Once the JavaScript function is injected into the browser, it is not immediately executed. From practical experience I saw that sometimes the browser was still busy loading images or executing JavaScript code. So a timer was set to execute the dump function only five seconds later. This also gave a few moments of time to see what was loaded, and verify that the crawler was still on the correct track.



Note that the functions to load the JavaScript code into the browser are protected, and can not be called when using an instance of the class cl_gui_html_viewer. This is the main reason for the inheritance approach, sothat we could actually get access to these two functions.



Five seconds later the form was submitted and the on_sapevent method is called. As input, a table is received that contains a number of rows, each of the sequence “_content=html”. All the lines are concatenated together again into one string. The final string is massaged to remove some of the HTML escaping that was done on the data, and the _content sequences.



<!code>  METHOD on_sapevent.

<!code>   

<!code>    DATA: content TYPE STRING,

<!code>          line    LIKE LINE OF postdata.

<!code>    LOOP AT postdata INTO line.

<!code>      CONCATENATE content line INTO content.

<!code>    ENDLOOP.

<!code>   

<!code>    REPLACE ALL   OCCURRENCES OF '%3D'        IN content WITH '='.

<!code>    REPLACE ALL   OCCURRENCES OF '%3F'        IN content WITH '?'.

<!code>    REPLACE ALL   OCCURRENCES OF '&_content=' IN content WITH ''.

<!code>    REPLACE FIRST OCCURRENCE  OF '_content='  IN content WITH ''.

<!code>   

<!code>    me->loaded_content( content ).

<!code>   

<!code>  ENDMETHOD.




 

The other supporting code is not shown here, as it is mostly plumbing. The complete code can be found here.</p>

 

Final Words

 

 

Using the browser inside the SAPGUI was actually a rather interesting challenge, and we learned a lot about how the browser integration was done, and the possibilities that this enabled. It was now possible to write a web crawler using a true browser with all of its features and idiosyncrasies. In the next Weblog this simple web crawler will be used to build a small SDN crawler, and then extract some statistics from the Weblogs.

 

 

 

In an

article

, DJ Adams really showed the power of RSS. He used a simple BSP page to publish information about cross systems transports as an RSS feed. Then, using a standard RSS browser, he could sit back and browse the information about transports from his system in the same way that news feeds are scanned. The best way to see what is moving!

 

 

 

 

From the beginning, SDN has supported RSS feeds, allowing one to browse different aspects of SDN using an RSS browser. Mark Finnern has written about this topic a few times. Highly recommended reading would be

Fastest Scan of SDN Forum Posts via an RSS -Aggregator

and Newsreaders give Weblog Content extra Speed  ().

 

 

 

 

In his weblog Standardizing Syndication  (),  Marc Andrue Goodner mentions ATOM as a new RSS replacement in the future, and alludes to a cool site that provides ATOM feeds. When we torture these RSS feeds from SDN they give interesting answers. Hmmm…

 

 

 

This weblog is not really about RSS. We are a very small department, and until recently (sniff, sniff) had a few people for a number of hot technologies in our department: XML, XSLT and BSP. This weblog is for lightweight reading. Using RSS as an example, it shows the use of the HTTP client, XML, XSLT and BSP in one example. This is programming purely for the fun factor.

 

 

What is RSS?

 

 

 

For a detailed overview of RSS, please read Mark’s weblogs, and follow the links referenced. For our purposes, an RSS feed is an XML document that can be fetched via HTTP. For this weblog, we will use an SDN feed to play with.

 

 

 

See:

http://weblogs.sdn.sap.com/pub/q/weblog_rss_topic?x-topic=24&x-ver=1.0

 

 

 

 

Just to get a feeling for the RSS format, click on the above link. One will get an XML document displayed in the browser. Reading through the XML, we see that a number of people are starting to write about their BSP experiences, which is definitely better to read than this dry technical stuff. Found it interesting to read the writings of Thomas Jung  (/people/thomas.jung3/blog) and Craig Cmehil  (/people/craig.cmehil3/blog) on their BSP experiences.</p>

 

HTTP Client

 

 

 

Whenever one thinks of HTTP in the context of BSP, one pictures the browser starting the HTTP request, and that the server returns an HTTP response. However, in WebAS it is also possible to play the role of a browser and to effectively make outgoing HTTP calls.

 

 

 

For complete details, please consult the online help for the if_http_client interface  (http://help.sap.com/saphelp_nw04/helpdata/en/e5/4d3514c11411d4ad310000e83539c3/frameset.htm) and an example  (http://help.sap.com/saphelp_nw04/helpdata/en/1f/93163f9959a808e10000000a114084/frameset.htm). Also consider transaction SE38, program RSHTTP01 as a nicetest program.

 

 

 

For this Sunday afternoon fun, we decided to use the minimum number of lines of code. No error handling is done. (If the exceptions are not mapped onto sy-subrc during the method calls, they will just be raised and the BSP program is terminated. This is acceptable for our example. See the documentation for ways to handle errors during communication.)

 

 

 

 

<!code>  DATA: url         TYPE STRING,

<!code>        http_client TYPE REF TO IF_HTTP_CLIENT,

<!code>        return_code TYPE I,

<!code>        content     TYPE STRING.

<!code>   

<!code>  url = 'http://weblogs.sdn.sap.com/pub/q/weblog_rss_topic?x-topic=24&x-ver=1.0'.

<!code>  cl_http_client=>create_by_url( EXPORTING url    = url

<!code>                                 IMPORTING client = http_client ).

<!code>  http_client->send( ).

<!code>  http_client->receive( ).

<!code>  http_client->response->get_status( IMPORTING code = return_code ).

<!code>  content = http_client->response->get_cdata( ).

<!code>  http_client->close( ).

<!code>   

 

 

These few linesare sufficient to pull the RSS data from SDN! The first line creates a new client using a complete URL that already contains the protocol to use (“http:”), plus destination system and port (implicitly port 80) and the requested URL. Once we have the HTTP client instance, we send the request and receive the answer. Ultra important is the close method call, to ensure that the resources held by the HTTP client are released.

 

 

When using the HTTP client, there are a number of interesting additional aspects to consider:

 

 

    • Setting headers such as Accept-Encoding and User-Agent in the request.

 

    • Setting the HTTP protocol and version to use and the method GET or POST.

 

    • Setting up of authentication information for the remote site, possibly also proxy authentication information.

 

    • Looking at the return_code and taking additional action. Interesting values would be rc=200 (OK), rc=302 (Redirect), rc=401 (Authentication Required) and rc=500 (Server Error).

 

 

 

All of these aspects are discussed in the online documentation.

 

 

XML

 

 

 

Once the RSS data has been retrieved, the next step is to parse this into an XML document. Of course, one could consider using normal string operations to extract the interesting data, but it does not have the same elegance.

 

 

 

Reading through the online help  (http://help.sap.com/saphelp_nw04/helpdata/en/47/b5413acdb62f70e10000000a114084/frameset.htm) on XML, we see a very rich API. However, the simple call “document = cl_ixml=>parse( content ).” does not exist. We have to jump through the loops to quickly parse the content. Let’s not complain, and just copy this code directly from the online help.

 

 

 

 

 

<!code>  TYPE-POOLS: ixml.

<!code>  DATA: ixml          TYPE REF TO if_ixml,

<!code>        streamFactory TYPE REF TO if_ixml_stream_factory,

<!code>        istream       TYPE REF TO if_ixml_istream,

<!code>        parser        TYPE REF TO if_ixml_parser,

<!code>        document      TYPE REF TO if_ixml_document.

<!code>   

<!code>  IF content CS '<!DOCTYPE' AND content CS ']>'.

<!code>    DATA dummy type string.

<!code>    SPLIT content AT '<!DOCTYPE' INTO dummy content.

<!code>    SPLIT content AT ']>'        INTO dummy content.

<!code>  ENDIF.

<!code>   

<!code>  ixml     = cl_ixml=>create( ).

<!code>  streamFactory = ixml->create_stream_factory( ).

<!code>  istream  = streamFactory->create_istream_cstring( content ).

<!code>  document = ixml->create_document( ).

<!code>  parser   = ixml->create_parser( stream_factory = streamFactory

<!code>                                 istream         = iStream

<!code>                                 document        = document ).

<!code>  parser->set_normalizing( ).

<!code>  parser->set_validating( mode = if_ixml_parser=>co_no_validation ).

<!code>  parser->parse( ).

<!code>   

 

<!code>  TYPES: BEGIN OF t_blog,

<!code>            title       TYPE string,

<!code>            link        TYPE string,

<!code>            description TYPE string,

<!code>            creator     TYPE string,

<!code>            date        TYPE string,

<!code>         END OF t_blog,

<!code>         t_blogs TYPE TABLE OF t_blog.

<!code>   

<!code>  DATA:          blogs type t_blogs.

<!code>  FIELD-SYMBOLS:  type t_blog.

<!code>   

 

<!code>  DATA:  collection      TYPE REF TO if_ixml_node_collection,

<!code>         node            TYPE REF TO if_ixml_node,

<!code>         element         TYPE REF TO if_ixml_element,

<!code>         index           TYPE i.

<!code>   

<!code>  collection = document->get_elements_by_tag_name( name = 'item' ).

<!code>  WHILE index < collection->get_length( ).

<!code>   

<!code>    APPEND INITIAL LINE TO blogs ASSIGNING .

<!code>    node     = collection->get_item( index ).

<!code>    element ?= node->query_interface( ixml_iid_element ).

<!code>    index    = index + 1.

<!code>   

<!code>    node = element->find_from_name( name = 'title' ).

<!code>    get_value( ).

<!code>   

<!code>    .... repeat above two line sequence for all bits of information required ....

<!code>  ENDWHILE.

<!code>   

 

 

BSP

 

 

 

The final part of the puzzle is to display the output. One technique could be to use an HTMLB tableView. Another could be to transform the table into raw HTML. However, for the fun factor, let’s use a PHTMLB formattedText control. See BSP application SBSPEXT_PHTMLB for an example.

 

 

The formattedText control accepts as input an XML string that contains markup sequences, similar to that of HTML. As a first step, just loop over the internal table with acquired data and generate the XML string.

 

 

 

 

<!code>  DATA: formattedText TYPE string.

<!code>  formattedText = ''.

<!code>  LOOP AT blogs ASSIGNING .

<!code>    CONCATENATE formattedText

<!code>                '

'

<!code>                ''

<!code>                ` by -date `  --  `

<!code>                -description

<!code>                '</P>'

<!code>           INTO formattedText.

<!code>  ENDLOOP.

<!code>  CONCATENATE formattedText '</ROOT>' INTO formattedText.

<!code>   

 

<!code>  <% ....all coding from above.... %>

<!code>   

<!code>  <%@page language="abap"%>

<!code>  <%@extension name="htmlb"  prefix="htmlb"%>

<!code>  <%@extension name="phtmlb" prefix="phtmlb"%>

<!code>  <htmlb:content design="design2003">

<!code>    <htmlb:page>

<!code>      <htmlb:form>

<!code>        <phtmlb:formattedText text = "<%=formattedText%>" />

<!code>      </htmlb:form>

<!code>    </htmlb:page>

<!code>  </htmlb:content>

<!code>   

 

 

The final output is a very nicely formatted list of all current weblogs listed under the BSP topic.

 

 

!https://weblogs.sdn.sap.com/weblogs/images/164/BP_RSSHCXMLXSLT_001.GIF|height=0 width=566 height=156 |width=0 width=566 height=156 |src=https://weblogs.sdn.sap.com/weblogs/images/164/BP_RSSHCXMLXSLT_001.GIF|border=0 width=566 height=156 !</body>

In my previous weblogs in this series - BSP Performance: Measuring Roundtrip Latency and BSP Performance: Statistic Records for Server Latency - we looked at techniques for measuring the actual performance of a BSP application. The next logical question is: can we improve that performance? That question can be answered only if we know what the application in question is doing.

For this last piece of the puzzle, we will use runtime analyses to get a detailed picture of an application&#146;s runtime behaviour. (This weblog does not seek to analyze any specific application; rather, it explores the tools available and looks at expected output.)

Activating Runtime Analysis

The runtime analysis of a BSP application (of any HTTP request, actually) is activated in ICF (see transaction SICF). See also the online help.

After the runtime analyses have been enabled, execute a few test BSP pages.

Looking at the Data

The data collected by the runtime analyses can be seen using transaction SE30. Simply select &#147;Other File&#133;&#148; for the specific user, then double-click on any of the URLs listed.

The gory details are not interesting in the context of this weblog. Instead, let us look briefly at the type of information that can be learned from transaction SE30. The true value of this tool can only be learned from practical (home:) work. Also read the documentation.

The first interesting aspect is the Hit List. This shows all methods and functions called, and their gross and net times. To get an overview of which methods have the longest runtimes, sort on the net times. Also shown is the number of times each method is called. This often helps identify inefficiencies in the way that code is structured.

The Group Hit-List shows top entries grouped into different categories, including method calls, function calls, database access, and program load times.

Database access is usually the more expensive part of an application, and the Database Hit-List provides detailed information about databases that were referenced.

The Call Hierarchy shows the true calling sequence of the application. This can be a daunting display for complex applications, but helps to correlate the dynamic behavior of the application to the more static code one sees in the editor. This is especially interesting to understand paths taken through the code, and for the &#147;who called this method&#148; type of question.

It is always possible to use forward navigation (double-clicking) to get more detailed information about any specific sequence that is displayed. Use the blue &#147;Display Source Code&#148; button at any point to jump directly to the relevant source code.

Summary

This weblog has added the &#147;what is happening&#148; tool to the performance toolbox. This is the most interesting one, as it allows us to find hotspots in the code, and to zoom in the specific areas that can be improved.

In the first weblog BSP Performance: Measuring Roundtrip Latency, we look at a technique to measure the HTTP roundtrip latency from the browser to the server and back again. We saw that in some cases the actual network latency itself can be relatively large (especially when working from home!) and that these numbers do not reflect the actualtime the server is involved.

The actual processing time of the server is important to get a better estimate of the true load that the server can handle. For this weblog, we will use statistic records to get a true feeling of the processing time of one HTTP request on the server.

Activating Statistic Records

Statistic records are by default not enabled for HTTP access to the R/3 system. They can either be enabled with the profile parameter &#147;rdisp/no_statistic&#148; or with the ABAP program RSSTATISTIC (see transaction SE38).

After statistic records have been enabled, execute a few test BSP pages. Important: the BSP pages must be on the application server where the statistic records have been activated. Again execute the tests a number of times to first have a warm-up phase.

Looking at the Data

To see the statistic records, use the ABAP program RSSTAT20 (see transaction SE38). Set time filter around time of test, limit to program SAPMHTTP and user name used in the tests. It is very important to increase the number of records searched.

When the program is started, a list of statistic records according to the filter criteria is shown. Already one can see that the first hit takes much longer than the following hits on the same page. For the first run, the ABAP load for the BSP page has to be fetched from the database and loaded into the program execution buffer. This hit includes a large database overhead. All subsequent hits on the same test page have no database overhead.

Looking at these records, we see that each page hit took 17ms of server time. This is inline with our measurements of HTTP roundtrip latency of 23ms when in the office.

The statistic records reflect the complete ABAP runtime, but do not include the ICM time (part of kernel) for processing the incoming request or the outgoing response (in ICM). This additional time will explain a difference of a few milliseconds between the static records (ABAP runtime) and the HTTP roundtrip latency. The remaining time difference must be attributed to network latency and inaccuracies in the complete process.

Summary

This weblog has added one more tool to the performance toolbox. This is important both in verifying the HTTP roundtrip latency measurements made previously, and understanding true server workload versus network latency.

Again, as a first step one must compute baseline numbers for the BSP runtime and a &#147;Hello World&#148; page for the specific environment (serve configuration and network capabilities). Once these numbers stand, repeat the measurements for the real application.

Performance measurement (How fast does it go?) is the inverse of the question of scaling (How large the box to support N users per time unit?). Which highlights the central theme: measuring the performance of an application.

However, BSP pages are not applications, but merely the enabling technology to support the development of web applications. As such, each BSP application must be measured individually assess its performance characteristics. The first step is to test the overhead of the BSP runtime per se. Any application&#146;s performance will always include this basic overhead. This Weblog will concentrate on measuring the roundtrip latency for the BSP runtime. The roundtrip latency of an application can be measured similarly.

For measuring the BSP overhead, a small &#147;Hello World&#148; BSP page is created, and then fetched from the browser. This simple test measures the latency from browser to WebAS; authentication time; time to create the session; overhead to instantiate all the BSP runtime classes; and the call to the specific BSP page. This is the minimum overhead for each HTTP request onto a BSP page.

The tests are done for BSP pages of 1KB, 2KB and 4KB in size (up to 64KB is usual). These tests verify that there are no memory bandwidth problems within the system (memory copies, et. al.). It is expected that larger pages will take slightly longer, mostly due to larger transmission time on the physical network.

For a second series of tests, GIF images are fetched from the server. Access to the MIME repository is very expensive. However, the images are cached in the ICM cache, and the access time of subsequent does not relate to the first load time. Therefore, we always look only at the performance of the subsequent image loads.

Test Application

For the tests, a BSP application IT03 was used, which consists of a simple &#147;Hello World&#148; page, called &#147;text0kb.htm&#148;. This is the primary page for measuring the BSP runtime overhead. In addition, a number of &#147;textNkb.htm&#148; pages are available for testing. The IT03 application also includes a collection of different imageNkb.gif images.

Factor to Consider: URL Mangling

When doing performance measurements, the goal is to run the same test a number of times. However, most test programs are structured so that each test is run individually, the results recorded, and then the test is started anew. This dictates that specific once-off actions experienced only once by a user are measured continuously by the test.

URL mangling is one such an example. In BSP applications a redirect is done to embed specific information (language, client, etc.) on the first hit for the application. For example, when the user requests the URL &#147;/sap/bc/bsp/it03/page.htm&#148; from the server, a redirect is first done to the new URL &#147;/sap(bD1ZS0PRF1Rw==)/bc/bsp/it03/page.htm&#148;. For performance measurements, only use the mangled URLs, to exclude the once-off mangling from the results.

Factor to Consider: Authentication

A similar problem is that of authentication. When each test run is started, a GET request for an URL is done. However, the WebAS requires authentication from the user, and the HTTP request is returned (reason: authentication required). The browser then pops up a dialog requesting a username and password from the user. This data is encoded into the HTTP header (field &#147;Authorization&#148;), and the URL is requested again. On all subsequent requests to the server, the browser will automatically send the Authorization header with the request.

In this case, two roundtrips for each test are often measured. After the first request is rejected, the test programs will use the stored username and password to test again, and the double roundtrip time is measured for each test run. It is important to ensure that this authentication data is already part of the initial request, to measure the actual request time, and not the additional authentication roundtrip.

Factor to Consider: Program Load Times

At a technical level, a BSP page is a generated ABAP class, for which a load (compiled program) is stored in the database. When an URL is requested the first time, the required program is loaded from the database, and cached in the program buffer. On subsequent requests, the program can be used directly from the buffer.

This first time database load is very expensive, and influences averages times, especially on short runs. Therefore, in all tests, it is highly recommended to add a warm-up phase before the tests. This also ensures that all images required are placed in the ICM cache (which will be the expected case for normal usage).

Factor to Consider: Network Latency

In typical use of intranets, the latency over the network is so small (milliseconds), that it is not really noticed. However, when performance measurements are done in the same range, the network latency plays a large role.

In in-house tests, where multiple hops must be traversed from browser to server, this has added a noticeable additional latency of a few milliseconds. It is recommended that for tests where the absolute numbers are important, both client and server be placed on the same sub-network.

In addition, the packet size of the network plays a role. Ethernet packets can be a maximum size of 1500 bytes. Large data packets are split into smaller IP packets, then sent individually and reassembled on the receiving side by the TCP/IP stack &#150; so although the data is sent all at once, the data in not received in one packet. This has prompted some tools to give both the &#147;time to first byte&#148; (TTFB) and &#147;time to last byte&#148; (TTLB) measurement times. Thedifference between the two values reflects more on network latency than on server processing time.

When testing in WAN environment or via the Internet, the properties of TCP become noticeable. Especially for larger data volumes, the TCP window-size plays a large role. An initial window of data is transmitted, and then transmission is stopped until an acknowledgement is received. This causes the TTFB and TTLB to be disparate.

Factor to Consider: Median Time versus Average Times

Quite often over the duration of a test, a few measurement points are seen which are clearly too large and do not reflect the expected behavior. For example, assume a run of a 1,000 tests, where 999 of the tests completed in exactly 10ms. However, one URL hit took 3 seconds (3,000 ms). Then the average of the 1,000 runs will be 12.99ms, and not the expected 10ms. We see that a few out-risers are capable of affecting the average. For test tests, median times are used. For the above example, the median would have been the expected 10ms.

Tool: Microsoft Web Application Stress Tool

There are many programs available for stress testing web applications. In principle, any program can be used. For this Weblog, we will use the Web Application Stress Tool from Microsoft. It is quick to install, and contains all the necessary features required for simple stress testing.

To download the program, go to Microsoft&#146;s website, and search for &#147;Web Application Stress Tool&#148;. The interesting links currently are: Download the Web Application Stress Tool and Web Application Stress Tutorial.

Running a Test

With the stress tool, it is relatively easy to record a session. For the tests, the BSP test program IT03 is used. Pages and images of 1KB, 2KB, and 4KB are tested. As a first step, either record or manually create the script.

One of the first post-processing steps required is updating the server value. This field is not correctly set during script creation. Also all URLs that we are not interested in are deleted. Test URLs are kept in their mangled form.

Two additional post-processing steps are required. Double click on the first URL, check that the port is correct, and add the Authorization header. Apply this change to all URLs. Unfortunately, if the port is not correct, this must be manually updated for all URLs.

Finally, we configure a few set-up parameters for the test. In all cases, keep the stress level at &#147;1&#148; so that the true end-to-end latency is apparent without having test threads blocking one another. Configure a short warm-up time and the run time. As we have already configured the user information via the Authentication header, we now need to store any additional information.

Looking at the Data

Allow me a short story. Once, a long time ago, someone decided to compare their server platform against the BSP runtime. At the end of the tests, they wrote a small hero email: They could handle thousands of hits per second! The numbers were much better than those for BSP. What the colleagues forgot to check was whether they were also getting back 200 OK answers for each hit. What was actually measured was thousands of 401 Authentication hits! For a server, rejecting a request is much faster than completely processing it. (No, they never did email a retraction.)

Thus, the first check is to ensure that the test did actually run successfully. Look at the summary page, and ensure only OK return codes. The distribution of hits per page should be nearly the same.

Script Settings  Server:                       us4049.wdf.sap.corp  Number of threads:            1  Test length:                  00:03:00  Warmup:                       00:00:30     Result Codes  Code      Description                   Count      200       OK                            381           Page Summary  Page                                                  Hits  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/text1kb.htm     64  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/text2kb.htm     63  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/text4kb.htm     63  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/image1kb.htm    63  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/image2kb.htm    64  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/image4kb.htm    64   

The next step is to look at the results of each page test in detail.

Result Codes  URI:  GET /sap(bD1lbiZjPTAwMA==)/bc/bsp/sap/it03/text1KB.htm  Code      Description                   Count      200       OK                            64            Time to first byte (in milliseconds)  Average:                      295.17  Min:                          181.75  25th Percentile:              190.26  50th Percentile:              192.89  75th Percentile:              299.48  Max:                          3405.89     Time to last byte (in milliseconds)  Average:                      295.31  Min:                          181.86  25th Percentile:              190.37  50th Percentile:              193.01  75th Percentile:              299.60  Max:                          3406.01     Downloaded Content Length (in bytes)  Min:                          1026  25th Percentile:              1026  50th Percentile:              1026  75th Percentile:              1026  Max:                          1026   

Specifically check that the result codes are all 200 OK, and check that the downloaded content length is stable over all tests. These are the first indicators of problems. Specifically if the content length varies over one test run, it can indicate that the page ran into a problem, and is suddenly rendering different output.

Once we are sure that the test run was acceptable, we look at the &#147;Time to First Byte&#148; (TTFB) and/or &#147;Time to Last Byte&#148; (TTLB) values. We know from the WebAS architecture that only complete HTTP responses are transmitted. So once transmission starts, the ABAP part of the processing is complete, and the difference between TTFB and TTLB only reflects network transmission time. TTFB is the more interesting value when looking at server performance (in which case the test machine and server should be close to one another on the same network). TTLB is the value to determine the HTTP roundtrip latency.

The last interesting aspect is the large difference between the average and 50th percentile values. This is due to a few out-risers that can dramatically influence the average value (specifically over a short test time). Because of this, we usually use only the 50th percentile values. (To be quite truthful, the fact that the 50th and 75th percentiles are not close to one another would usually prompt me to repeat the tests over a very long time. But for the educational value of this Weblog, these numbers are sufficient.)

Looking At All Tests Together

Here is a summary of all the tests.

TestTTFB (in ms)TTLB (in ms)
text1KB.htm192.89193.01
text2KB.htm209.73209.94
text4KB.htm219.90285.65
image1KB.gif173.07173.24
image2KB.gif176.08176.28
image4KB.gif193.09266.12

Probably the most important question is whether these numbers are good or bad. Looking at the absolute numbers, they are horrible in terms of what we have measured in optimal lab conditions. However, one must evaluate the numbers in context of the actual tests done. And here, the goal was to measure HTTP roundtrip latency. These tests were run from home, using a DSL connection onto the Internet, with (very expensive!) encrypted VPN tunneling. Furthermore, the tests were done against a development system, running a debug kernel (maybe 30 percent slower). So overall, we are happy with the numbers.

Interesting is that the values for TTFB and TTLB are very close for 1KB and 2KB sizes. However, for 4KB sizes we see a marked difference. This is attributed to the network and TCP properties as discussed before.

Looking at the difference between a1KB page versus an image, we see about 20ms. We know that after the warm-up phase, all images will be stored in the ICM cache, and willbe answered from there. Thus using the TTFB for the image as a rough indicator of the network latency (ICM processing at kernel level is definitely below 1ms!), we get an estimated 20ms processing time for the 1KB page. This is inline of our expectations for a debug kernel. (Estimates confirmed with measurements in the office: 50th percentile was 23ms.)

Summary

This Weblog has not attempted to put absolute perfect numbers on the table. Rather, a more realistic usage scenario was chosen. The first and most important goal was to show that it is relatively easy to make HTTP roundtrip latency measurements. Furthermore, the obtained numbers were slightly interpreted, just to give a feeling for how these numbers should be &#147;read.&#148;

Importance is that these are base line numbers that show the complete end-to-end latency for the specific scenario. This includes the actual transmission time, plus the processing time of the BSP runtime. Using a very small page, we can show that the BSP runtime itself is quite fast up to the point where the application coding is started. As a next step, one must measure a complete application. From there, it can be determined what part of the total time is attributed to the BSP runtime.

Be careful when interpreting single numbers to get a sizing number. It is important that a realistic scenario through the application be used, and that the results of the different URLs are used to calculate the true overhead of the server. Also consider the expected latency a user will experience in a similar network environment and the effects of MIME objects.

Every other day, and sometimes it feels like every other minute, the simple question comes up: &#147;How to do X?&#148;, usually with the qualifiers &#147;Help!&#148;, &#147;Urgent!&#148; or &#147;????&#148;. Most of these questions, when reading between the lines, also contain the additional qualifier &#147;I have not read the documentation&#148;.

In a way, this is understandable. Reading documentation is hard work and boring. However, we have to look at it also from the side of the person answering these questions. It is frustrating to feed people every day with a little spoon. Especially after a hard day&#146;s work.

This Weblog attempts to point to interesting and relevant sources of information. It is not an answer to any question; no, it is the pointer to the answers!

Overview of the More Important Bits and Pieces

Business Server Pages (BSP) is just the tip of the iceberg. It builds on the foundations of many other people&#146;shard work. By the time that the BSP runtime is started, we have already passed the Internet Communication Manager (ICM), the Internet Communication Framework (ICF), and the security group (Men in Black).

ICM is responsible for handling all HTTP communications at a low level. All incoming TCP/IP connections on opened ports are accepted, HTTP requests are read and mapped onto an existing or newly created session. After the HTTP request has been processed, ICM will take the HTTP response, and send it back to the browser. At the level that ICM works, we have only HTTP requests and responses as blobs. See transaction SMICM. A special variation of ICM is the Web Dispatcher. This is a standalone tool for HTTP load balancing.

ICF processes the HTTP request first by using the incoming URL to map onto the correct handler class (servlet in Java terminology!). In addition, ICF does the authentication process, and with the help of the security group, does an authority check (if configured), and some other basic features (activate gzip compression, debugging, etc). See transaction SICF.

BSP is started for all HTTP requests that are mapped onto the BSP handler in the ICF tree. Within BSP, the form fields from the incoming HTTP request are mapped onto data, and then a BSP page (or controller with views) is triggered to render out new HTML into the HTTP response.

Security aspects play an important role in all steps. However, for our daily problems, we seldom involve the security group (those with the dark sunglasses). See transaction STRUST.

The ABAP Language Group (our silent partner) is the last interesting part of the equation. Effectively, each BSP page or view is transformed into one ABAP class. As such, anything that can be used within an ABAP class method, should work on a BSP page. Our experience is that we nearly never involve the language colleagues. Only in cases of problems with a special language construct. Typical example would be a recent question on why tables with header lines do not work within a BSP page (= ABAP class) and what alternatives are available.

The application is actually the most important aspect in this picture. The above is the enabling technology that allows other groups to build complete Internet applications.

Houston, We Have a Problem ==> OSS

Before we start looking at general sources of information, let us first look at problems. Sometimes problems land in a mailbox, or in a newsgroup, where they gather dust. This is not the correct way to handle problems. Problems must be submitted to SAP via OSS.

There are a number of reasons for using OSS, versus sending someone an email, or trying a post on some forum.

  • A total team can read the problem, and immediately start working on the problem.
  • Updates to the problem, including questions from us, and additional information from you, is included in the same message, and available to the total team. If we queue the problem to another component, then we already have all the information contained in the OSS problem ticket.
  • Other people can see the same problem descriptions, and find the same solutions.
  • Your problem is not lost in some mailbox. Your problem even gets attention when one of us takes a break or is on vacation, or is even just stuck in long meetings.

The most important question is what queue to use. Our recommendation is to always consider using the application queue first. These developers know their application and its behavior, and can quickly tell if it is a real problem. They will usually have a list of &#147;known&#148; problems for their application, which could also help in faster processing.

To determine the correct OSS queue for a specific BSP application is relatively simple. Just double click on the application node in navigation tree on the left. Select the properties tab, and from there double click on the package name (also called &#147;Development Class&#148; in prior releases). For the specific package, again look at the Attributes/Properties tab. There the OSS queue (component) is listed.

In addition, below are some OSS queues that can be considered if one is sure about the problem, and where it belongs.

OSS QueueUse For
ICM: BC-CST-ICHTTP not working; problems with HTTP port numbers; HTTP logging and tracing; sizing for HTTP traffic (connections, threads); host naming
ICF: BC-MID-ICFRunning applications anonymously; authority checking; debugging and breakpoints are not working; &#147;Service not active&#148; (rc=403) messages
BSP: BC-BSPErrors with the BSP runtime; rendering errors if the HTMLB libraries are used; JavaScript errors in the browser
Security: BC-SEC  Problems with installing X.509 certificates; problems with Single Sign On (SSO2) cookies; all problems with accepting SSO2 cookies from other sources, ex: Enterprise Portal.
ABAP: BC-ABA-LAAnything that seems to be related to the ABAP language, and can be reproduced without BSP in a normal report (transaction SE38)

SAP Notes

SAP Notes are used for ad-hoc communications between SAP and its customers. It is a very fast way to quickly describe problems and fixes, and get them out the door. However, often one loses the overview with many notes flying around. Therefore, more groups make &#147;collection&#148; notes that contain links to all relevant notes for that group.

Here are some of the more interesting Notes one should keep an eye on.

GroupNote NrTitle
ICM508300ICM Patch Collection (6.20)
ICM698017ICM Patch Collection (6.40)
ICM552286Troubleshooting for SAP Web Dispatcher
ICF517484Inactive Services in the Internet Communication Framework
BSP616900BSP Frequently Asked Questions & Patch Collection per Service Pack
BSP598860Browsers supported by BSP
BSP677118Fully Qualified Domain Names Check
SEC510007Setting up SSL on the Web Application Server (Now tell us that 007 number was just an accident:)

SAP Notes can be read directly on SDN! On the navigation bar on the left, just enter the note number, and change the dropdown listbox to &#147;SAP NetWeaver Notes&#148;.

Unfortunately, this search is not working up to my expectations. Hopefully, the SDN developers will improve the feature to better find and display notes.

Recommended reading: &#147;There are angels on SDN :D&#148;. Here Pankaj shows how to quickly build a URL together that will retrieve SAP Notes from SAP&#146;s Service Market Place.

Documentation

&#147;Read the Fine Manual&#148; (RTFM) has always been the best advice one can give, and the best advice that one can receive! Help can be found at SAP's Help Portal. Navigating the first few steps are slightly confusing, but thereafter the world of knowledge opens! See steps below. It is also possible to search the Help Portal as a selection in SDN.

Some interesting links directly to the documentation of the different groups are listed below.

GroupDirect Help Link
ICMhttp://help.sap.com/saphelp_nw04/helpdata/en/0a/a7903febb15a7be10000000a11405a/frameset.htm  http://help.sap.com/saphelp_nw04/helpdata/en/b8/2a8d65be7eee4eb66067f8a33d1c8b/frameset.htm
ICFhttp://help.sap.com/saphelp_nw04/helpdata/en/69/ac75addb6811d6b2ca00508b5d5c51/frameset.htm
BSPhttp://help.sap.com/saphelp_nw04/helpdata/en/ed/bb153aab4a0c0ee10000000a114084/frameset.htm
SEChttp://help.sap.com/saphelp_nw04/helpdata/en/ed/18cc38e6df4741a264bddcd4f98ae2/frameset.htm
ABAPhttp://help.sap.com/saphelp_nw04/helpdata/en/6f/c7b041548011d192fd0000e829fbc6/frameset.htm

BSP Extensions

BSP Extensions are used extensively for the layout of BSP pages and views. SAP actively ships the HTMLB extension that contains a core set of controls for rendering web pages. In addition, a number of other interesting extensions are available.

BSP ExtensionDescription
HTMLBHTML Business for BSP
XHTMLBExtended HTML Business Library for BSP
PHTMLBPattern HTML Business Library for BSP
BENCHMARKEvaluation of the Runtime Performance

To browse BSP Extensions, just change the selector in transaction SE80. Thereafter enter the name of the specific library. All the BSP elements for the specific extension will be shown in the tree on the left. For each element, one can browse the attributes by just selecting the element.

Online help is available for most libraries. Press the &#147;Documentation&#148; button!

On a BSP page, double clicking on the BSP element will do a forward navigation to the definition of the specific BSP element.

Example Programs

The best way to learn is to follow in the footsteps of other people. A number of interesting BSP applications are available to show how certain things are done.

ProgramComments
IT00Very old test program, from the days before BSP extensions. Contains many interesting examples of how to do HTML-like things in BSP.
ITMVC2, BSP_MODELExample programs for MVC patterns.
HTMLB_SAMPLES, SBSPEXT_HTMLB, SBSPEXT_XHTMLB, SBSPEXT_PHTMLBExamples programs for the BSP extensions HTMLB, XHTMLB and PHTMLB.
SBSPEXT_TABLEMore a test program that we use to test many different aspects of the HTMLB tableView renderer. Shows what can be done with tables.
ITSMExample program to show some ideas for session management in BSP.

SAP Developer Network

SDN is one of the more exiting developments within the last year from SAP. It has really opened up the communication flow among SAP developers world-wide. It is a community for developers from developers.

For BSP, there are two interesting resources in SDN. The first is a series of Weblogs. These are mostly technical articles, written to address a specific topic of interest at a very deep level. Just ignore all the &#147;spam&#148; Weblogs that insist on sitting in every category with their fluff.

The direct link to BSP Weblogs is: http://www.sdn.sap.com/irj/sdn/weblogs?blog=/weblogs/topic/24

The other interesting area on SDN is the forums. There is one forum dedicated to BSP.

Here it is possible to ask questions and get answers. Nevertheless, please keep the rules of engagement in mind. Problems should be in OSS. Simple/stupid questions are OK, but only after reading the documentation. It is good to first read some of the forum, to see if the specific topic you are interested in has not already been discussed. Consider searching through the forum using some keywords. See search area at the top of the forum.

BSP Books

This section lists books on BSP that we are aware of. Should new books be published, just send me a small email to update this list.

Web Programming with the SAP Web Application Server (ISBN 1-59229-013-2)  SAP Web Application Server: Entwicklung von Web-Anwendungen (ISBN 3-89842-213-5)   BSP-Extensions:Komfortables Webreporting mit HTMLB (ISBN 3-89842-943-1)  English version not yet available

Filter Blog

By date: