Customer Relationship Management Blogs by Members
Find insights on SAP CRM and other customer relationship management products and topics in blog posts from community members and share your own perspective.
Showing results for 
Search instead for 
Did you mean: 
Former Member

1 Requirement

In WebUI scenarios specific requests and queries often take a long time until the data is available. In particular, RFC calls to external systems like the ERP can slow down performance. Due to the WebUI framework where visualization and rendering is done after server side processes are finished the user requires dedicated parallel processing. Table Views in WebUI include usually a large amount of different data and therefore different request origins. The ambition is to separate slow requests from the other request in order to fasten the entire visualization.


1.1 Preview

Take a look at the asynchronous rendering in action.



2 Proposal

Using a combination of server implementation and client side scripting Table Views can be rendered asynchronously where required. At first rendering the data is not received but a loading information is displayed instead.

After an AJAX-Callback is started, the data is received and the response is sent back to the client in order to display the Table View entries at the moment they’re getting available.

3 Implementation Steps

Both server side and client side implementation needs to be done in order to achieve this functionality.

3.1 Server Side

The implementation class that handles the callback needs to be the same instance as the one the table view is rendered. Therefore, a modification in the method IF_HTTP_EXTENSION~HANDLE_REQUEST of class CL_CRM_WEB_UTILITY needs to be done. Similar to SAP Note 1968050 where the instance is not created again.

3.1.1 Modification in Web Utility Class

The modification is not critical from my perspective. It might be usefull to integrate a customizing there if more than one class needs to be included for different Table Views.

* -> create handler class
lv_handler_class ?= lv_target_controller.
CREATE OBJECT lv_handler_class
* -> call back application handler
ir_server = server
ir_controller = lv_target_controller ).
CATCH cx_root.

3.1.2 HTML

The rendering of the Table View itself must be done manually without using BSP tags to save the created BEE in the respective implementation class. Otherwise, the logic would be called after every roundtrip.

DATA: lr_config_table TYPE REF TO cl_chtmlb_config_cellerator.

IF controller->gr_config_table IS INITIAL.

CALL METHOD cl_chtmlb_config_cellerator=>factory
id = 'Table'
table = controller->typed_context->btqropp->table
_table = '//BTQROPP/Table'
xml = controller->configuration_descr->get_config_data( )
width = '100%'
selectionmode = cl_thtmlb_cellerator=>gc_selection_mode_none
personalizable = 'FALSE'
element = lr_config_table.

controller->gr_config_table ?= lr_config_table.
thtmlbAJAXCall.callBackend("<%= controller->create_ajax_url( ) %>",thtmlbCCelleratorManager.asynchronousRenderingCallback);
lr_config_table->emptytabletext = cl_wd_utilities=>get_otr_text_by_alias( 'CRM_BSP_UI_FRAME_RECOBJ/LOADING' ).
lr_config_table ?= controller->gr_config_table.
lr_config_table->if_bsp_bee~render( _m_page_context ).

3.1.3 Clear on new focus

Depending on the usage it may be required to handle the logic whenever the focus is changed. For instance, in an assignment block that is included in the BP_HEAD. If the business partner is changed the search must be done again. I guess, there can be done some additional investigation to handle the partner change itself and in those cases just take the data out of the BOL. So the performance can be improved even more. Method: IF_BSP_MODEL~INIT

Get the instance of the controller implementation class.

CALL METHOD super->if_bsp_model~init
id = id
owner = owner.

gv_owner ?= owner. Method: ON_NEW_FOCUS

Clear the BSP bee.

CLEAR gv_owner->gr_config_table.

3.1.4 AJAX-URL

The URL which calls the backend via JavaScript can be created in the implementation class directly.

CALL METHOD cl_crm_web_utility=>create_service_url
iv_handler_class_name = 'ZL_ZWEBUI_B_ZACCOUNTOPPOR_IMPL'
iv_controller_id = me->component_id
ev_url = ev_url.

3.1.5 Callback

The implementation class needs the Interface IF_CRM_WEB_CALLBACK in order to receive the previously callback. Method: IF_CRM_WEB_CALLBACK~HANDLE_REQUEST

The method handles the callback, retrieves the data and creates the HTML code for the response.


*|-- References
DATA: lr_result TYPE REF TO if_bol_bo_col,
lr_query_service TYPE REF TO cl_crm_bol_dquery_service.
*|-- Variables
DATA: lv_partner TYPE string,
lv_html TYPE string.

*|-- BP number
lv_partner = me->typed_context->builheader->collection_wrapper->get_current( )->get_property_as_string( iv_attr_name = 'BP_NUMBER' ).

*|-- Query Service for Opportunities
lr_query_service = cl_crm_bol_dquery_service=>get_instance( 'BTQOpp' ).

*|-- Query-Parameter
CALL METHOD lr_query_service->add_selection_param
iv_attr_name = 'PROSPECT'
iv_sign = 'I'
iv_option = 'EQ'
iv_low = lv_partner.

*|-- Result
lr_result = lr_query_service->get_query_result( ).

CATCH cx_crm_genil_general_error.

*|-- Sorting
IF lr_result IS BOUND.
iv_attr_name = 'EXP_REVENUE'
iv_sort_order = if_bol_bo_col=>sort_descending ).

*|-- Set Result to Context & build table
me->typed_context->btqropp->collection_wrapper->set_collection( lr_result ).
me->typed_context->btqropp->build_table( ).

*|-- Clear text
CLEAR me->gr_config_table->emptytabletext.

*|-- Create HTML
CALL METHOD create_table_view_html
ir_server = ir_server
ir_controller = ir_controller
iv_binding_string = '//BTQROPP/TABLE'
iv_table_id = me->get_id( 'Table' )
ev_html = lv_html.

*|-- Set Response
ir_server->response->set_cdata( lv_html ).
ir_server->response->set_header_field( name = 'content-type'
value = 'text/xml' ).

*|-- Invalidate ´content
CALL METHOD cl_ajax_utility=>invalidate_area_content
ir_controller = ir_controller. Method: CREATE_TABLE_VIEW_HTML

*|-- Constants
DATA: lc_separator TYPE string VALUE '__'.
*|-- Variables
DATA: lv_attribute_path TYPE string,
lv_model_name TYPE string,
lv_lines TYPE i,
lv_string_lines TYPE string,
lv_count TYPE i VALUE 0,
lv_row_id TYPE string,
lv_html TYPE string,
lv_template_row_tr_id TYPE string,
lv_new_row_tr_id TYPE string,
lv_rows TYPE string,
lv_row_ids TYPE string,
lv_fixed_left_rows TYPE string,
lv_fixed_right_rows TYPE string,
lv_marked_rows TYPE string.
*|-- Strucures
DATA: ls_area_content TYPE crms_tajax_area_content.
*|-- References
DATA: lo_page TYPE REF TO cl_bsp_ctrl_adapter,
lo_view_manager TYPE REF TO cl_bsp_wd_view_manager,
lo_view_controller TYPE REF TO cl_bsp_wd_view_controller,
lo_model TYPE REF TO if_bsp_model_binding,
lo_context_node TYPE REF TO cl_bsp_wd_context_node,
lo_context_node_tv TYPE REF TO cl_bsp_wd_context_node_tv.
*|-- Field Symbols
FIELD-SYMBOLS: <fs_page> TYPE bsprtip.

*|-- Create page instance
READ TABLE cl_bsp_context=>c_page_instances
WITH KEY page_name = cl_bsp_wd_appl_controller=>appl_controller_name
ASSIGNING <fs_page>.

*|-- Rendering
IF sy-subrc IS INITIAL AND <fs_page>-instance IS BOUND.
lo_page ?= <fs_page>-instance.
lo_view_manager ?= lo_page->m_adaptee.
lo_view_controller ?= ir_controller.
lo_view_manager->render( iv_root_view = lo_view_controller ).

*|-- Get model
CALL METHOD cl_bsp_model=>if_bsp_model_util~split_binding_expression
binding_expression = iv_binding_string
attribute_path = lv_attribute_path
model_name = lv_model_name.
lo_model ?= ir_controller->get_model( lv_model_name ).
lo_context_node ?= lo_model.
lo_context_node_tv ?= lo_model.
lv_lines = lo_context_node->collection_wrapper->size( ).
CATCH: cx_root.

WHILE lv_count < lv_lines.
"Create AJAX content
lv_count = lv_count + 1.
lv_string_lines = lv_count.
CONDENSE lv_string_lines NO-GAPS.
CONCATENATE iv_table_id '__' lv_string_lines '__1' INTO lv_row_id.
CALL METHOD lo_view_controller->retrieve_ajax_area_content
iv_area_id = lv_row_id
iv_page_id = ir_controller->component_id
es_content_info = ls_area_content
er_used_controller = lo_view_controller.
"Covert HTML
IF ls_area_content-area_content IS NOT INITIAL.
lv_html = cl_thtmlb_util=>escape_xss_javascript( ls_area_content-area_content ).
CLEAR ls_area_content.
"Build table
lo_context_node_tv->build_table( ).
"Create Response
IF lv_rows IS INITIAL.
CONCATENATE `'` lv_html `'` INTO lv_rows.
CONCATENATE `'` '' `'` INTO lv_fixed_left_rows.
CONCATENATE `'` '' `'` INTO lv_fixed_right_rows.
CONCATENATE `'` lv_row_id `'` INTO lv_row_ids.
CONCATENATE `'` '' `'` INTO lv_marked_rows.
CONCATENATE lv_rows `,'` lv_html `'` INTO lv_rows.
CONCATENATE lv_fixed_left_rows `,'` '' `'` INTO lv_fixed_left_rows.
CONCATENATE lv_fixed_right_rows `,'` '' `'` INTO lv_fixed_right_rows.
CONCATENATE lv_row_ids `,'` lv_row_id `'` INTO lv_row_ids.
CONCATENATE lv_marked_rows `,'` '' `'` INTO lv_marked_rows.

CONCATENATE `{ "rows": [ ` lv_rows ` ], "fixedLeftRows": [ ` lv_fixed_left_rows ` ], "fixedRightRows": [ ` lv_fixed_right_rows
` ], "markedRows": [ ` lv_marked_rows ` ], "tableId": [ '` iv_table_id `' ], "rowIds": [ ` lv_row_ids ` ]}` INTO ev_html.

3.2 Client Side

The response that is created on the server side now must be handled on the client side for direct rendering without the need of a round trip.

Therefore the JavaScript scripts_.oo_tableview.js of BSP application THTMLB_SCRIPTS must be modified.
3.2.1 Modification in JavaScript

Basically the logic of the new Javascript function is the same as the function createFastRowsCallback where the system creates new empty rows in a Table View.
3.2.2 Function: asynchronousRenderingCallback

This function has following changes compared to the fast row creation logic:

  • Change the loading text of the no result line to take cases without any results into consideration

  • Hide the no result line in cases where we get at least one result Change text

var noResultRow = document.getElementById(values.tableId[0]+"_noresult");
var regexExp = new RegExp ("<%= cl_wd_utilities=>get_otr_text_by_alias( 'CRM_BSP_UI_FRAME_RECOBJ/LOADING' ) %>","g");
var noResultText = "<%= cl_wd_utilities=>get_otr_text_by_alias( 'BSP_DYN_CONFIG_TAG_LIB/NO_RESULT_FOUND' ) %>";
noResultRow.firstChild.innerHTML = noResultRow.firstChild.innerHTML.replace(regexExp,noResultText); Hide line

for (var i = 0, length = values.rows.length; i < length; i++) {
<%-- Hide no result line --%>
noResultRow.className += ' th-displayNone' ;
<%-- check if the row already exists in the case where the table has inserted empty rows --%>
rowFound = true;
row = document.getElementById(values.rowIds[i]);
if (values.fixedLeftRows[i] != "")
rowFixedLeft = document.getElementById(values.rowIds[i]+"_left");
if (values.fixedRightRows[i] != "")
rowFixedRight = document.getElementById(values.rowIds[i]+"_right");

4 Summary

This logic provides a good opportunity in scenarios where dedicated requests take a long time and therefore the entire visualization is slow. However, the modification in the JavaScript is highly critical. One mistake leads to heavy errors in the WebUI as this file is used in almost every view. As a result, I can't recommend this for productive systems. I hope, that this can help the SAP to create an improment note to support this functionality.