CRM and CX Blogs by SAP
Stay up-to-date on the latest developments and product news about intelligent customer experience and CRM technologies through blog posts from SAP experts.
cancel
Showing results for 
Search instead for 
Did you mean: 
dmitry_sharshatkin
Active Participant


Parallelization in Web UI


Part2



4.     AJAX


 


Concept


AJAX (short for asynchronous JavaScript and XML) is a set of web development techniques utilizing many web technologies used on the client-side to create asynchronous Web applications. With AJAX, web applications can send data to and retrieve from a server asynchronously (in the background) without interfering with the display and behavior of the existing page. By decoupling the data interchange layer from the presentation layer, AJAX allows for web pages, and by extension web applications, to change content dynamically without the need to reload the entire page. Data can be retrieved using the XMLHttpRequest object.


 


AJAX is not a technology, but a group of technologies. HTML and CSS can be used in combination to mark up and style information. The DOM is accessed with JavaScript to dynamically display – and allow the user to interact with – the information presented. JavaScript and the XMLHttpRequestobject provide a method for exchanging data asynchronously between browser and server to avoid full page reloads.


For more information, please refer to the Wiki’ and W3Schools’ pages: https://en.wikipedia.org/wiki/Ajax_(programming)  and http://www.w3schools.com/ajax/



AJAX in SAP CRM WebUI


As mentioned above some of the AJAX features are implemented in the standard CRM. One of the nice examples, where you can do a reverse engineering, is the Recent Items in CRM WebUI.



 
                


View: CRM_BSP_RECOBJ/RecentObjects


In this case, the AJAX call is implemented, right on the BSP page: RecentObjects.htm


 










    <!--this code performs a callback to refresh the items list. Only executed the first time when the BOL objects are not loaded yet-->
<script>
thAVFMgr.fetch(document.getElementById(
"<%= controller->COMPONENT_ID %>"),'onRecentItemsRefresh');
</script>




In this example, it is using relatively old asynchronous value fetch (AVF) functionality. However, in newer releases, after SAP Note: 2181937, the call looks like below:


 











thShortCutMgr.fetch('<%=   controller->COMPONENT_ID %>','CL_CRM_BSP__RECENTOBJECT0_IMPL','display');




All the mentioned standard JS functions can be found in one of the JS files of THTMLB_SCRIPTS BSP application, e.g. scripts_areaframe.js, scripts_deltahandling_client.js. You can also search for the needed function right in the browser, e.g. in Chrome, activate Developer Tools (F12) à Sources à Menu à Search all Files (Ctrl + Shift + F).


How it works. A certain function (e.g. thAVFMgr.fetch, thShortCutMgr.fetch and many others) builds a proper AJAX URL, containing all necessary parameters. Then the function thtmlbAJAXCall.callBackend(AJAXUrl,CallBackEvent);is executed. The first parameter is our AJAX URL and the second is the call-back function that is registered to receive the result from the AJAX call.


Typical parameters that you need to provide to the back-end are the following: service handler, callback handler, DOM element id, sometimes type of the element.


 


Service Handler


Service handler, it’s the ICF service that you call on the backend. WebUI is using the standard service: /default_host/sap/webcuif/uif_callback, which has only one handler - CL_CRM_WEB_UTILITY. 



Callback Handler


Callback handler, it’s the class that implements a callback method, which will be called in the AJAX call. This class should implement the interface IF_CRM_WEB_CALLBACK. And the mandatory callback method is IF_CRM_WEB_CALLBACK~HANDLE_REQUEST. For the JS object thAVFMgr, its callbackhandler is hardcoded in the JS file and depend on the callback JS function:


 










case   "onRecentItemsRefresh":


if(!trigger) {


return;


}


var ajaxURL =   newSessionURL.URL + "/webcuif/uif_callback?crm_handler=CL_CRM_BSP__RECENTOBJECT0_IMPL";


ajaxURL +=   "&crm_controller=" + encodeURIComponent(trigger.id) + thtmlbBuildRequestTicketQuery();


}




DOM Element ID


When the AJAX callback handler is executed, it sends the HTML response back to the frontend. On the frontend, a corresponding JS function receives the response (reqObject) and processes it. Finally it needs to find a proper DOM element in order to do some actions with it. That’s when you need a DOM Element ID.


 











var myelement = document.getElementById(elementID);



See more: http://www.w3schools.com/js/js_htmldom_elements.asp


 


AJAX Call


When all parameters are known, you can perform an AJAX call. In WebUI a special THTMLB object is used for this.


 











thtmlbAJAXCall.callBackend(ajaxURL,   ajaxCallbackFunction);



You should be careful with visibility of the elements through different iframes and also in time.


 


JS Callback Function


You need a callback function to receive the response from your AJAX call, process it and update the corresponding DOM element. Below you can find an extract of a function used for asynchronous value fetch.


 










fetch: function(trigger,   avf_event, csvSourceFields, rowIndex, customRef, csvTargetFields){



thtmlbAJAXCall.callBackend(ajaxURL,   this.update);



}




update: function(reqObject) {



var responseText =   reqObject.request.responseText;


var responseBlocks =   responseText.split(";");



for(var i=0,   len=responseBlocks.length; i<len; i++) {


if(!responseBlocks[i]) {


continue;


}



var blockParts =   responseBlocks[i].split(",");


if(blockParts.length <   4) {


continue;


}


var tagclassname =   blockParts[0];


var elementid =   blockParts[1];


var attributeName =   blockParts[2];



var payload =   thtmlbDecodeBASE64(blockParts[3]);


var payloadValue =   thtmlb_decode_html(payload);


var targetElement =   thtmlbGetElement(elementid);



case 'tooltip':


if (targetElement.title   != payloadValue) {


targetElement.title =   payloadValue;


}



}




  Storing your JS Files


When developing your JS you need to make sure it is visible for your applications. In case you want to place a simple script to your page, you can do it right on the BSP page.


 










<!—Here you want to store some Javascript -->
<script type=
"text/javascript">
var MyElement = document.getElementById("<%= controller->component_id %>");
function WindowOpenPopup(varTxt)   { alert(varTxt); }


    </script>




You can also store it as a MIME object within your BSP application and include it on the page.


 











<script type="text/javascript" src="/sap/bc/bsp/sap/z<bspcmp>/scripts.js"></script>




For relatively big applications or if you want your JS code to be visible for the whole WebUI framework, you can store the reference to your JS file in eth table WCFC_ADD_JS.


 


IMG à Customer Relationship Management à UI Framework à UI Framework Definition à Define Path for JS Files



 



Note: Last option is tested only in SAP CRM 7.0 EHP3.




Simple case


Let us practice first on a simple case, lets update a simple object link (<thtmlb:link>). As usual, we need to create a WebUI component, a view containing the link and a viewset.  





When working with AJAX in WebUI you have to generate the view content via corresponding FACTORY methods.











<%@page language="abap" %>
<%@extension name="thtmlb" prefix="thtmlb" %>
<%@extension name="chtmlb" prefix="chtmlb" %>
<%@extension name="bsp" prefix="bsp" %>

<%
data: lv_link_bee type ref to cl_thtmlb_link.
if controller->gv_thtmlb_element_link is initial.
" Create Link
call method cl_thtmlb_link=>factory
exporting
id
            = 'dynamic_link'
reference
     = 'javascript:void(0);'
onclick      
= 'ZBTN_CLICK'
text
          = 'Loading...'
receiving
element      
= lv_link_bee.
" Store the link
controller
->gv_thtmlb_element_link ?= lv_link_bee.
"   Post AJAX call to update content
%>
<!--this code performs a callback to refresh the items list. Only executed the first time when the BOL objects are not loaded yet-->
<script type=
"text/javascript">
var MyElement = document.getElementById("<%= controller->COMPONENT_ID %>");
      zDMSHAVFMan.fetch(MyElement,
'ZDMSHAJAXCall');
</script>
<%
endif.
" Get teh link
lv_link_bee ?= controller
->gv_thtmlb_element_link.
" Set the text
if controller->gv_text is not initial.
lv_link_bee
->text = controller->gv_text.
endif.
" Render html element
lv_link_bee
->if_bsp_bee~render( _m_page_context ).
%>
</br>
</br>




In this case I just copied the standard JS AVF functionality (object thAVFMgr) into my own namespace and did desired modifications. You can also notice down that our DOM element is identified right on the page and the building of the right URL is a task for the copied JavaScript.





Here I am using my view controller class as a callback handler. This means that it must implement callback method: IF_CRM_WEB_CALLBACK~HANDLE_REQUEST. Update JS function (this.update) has not been changed.











  method if_crm_web_callback~handle_request.

data: lv_link_tag       type ref to cl_thtmlb_link,
lv_tag_class_name
type string,
lv_tag_parent_id 
type string,
lv_icon_mode     
type string,
lv_html          
type string,
lv_html_xstr     
type xstring,
lv_response      
type string,
lv_attribute_type
type string.

data: lr_controller type ref to zl_zdmsh_aj_mainframe_impl.

wait up to 3 seconds.

if ir_controller is bound.

lr_controller ?= ir_controller
.

lr_controller
->gv_text = 'Hello from AJAX !!!'.

try.
lv_link_tag ?= lr_controller
->gv_thtmlb_element_link.

lv_link_tag
->text = lr_controller->gv_text.

lv_tag_class_name
= 'CL_THTMLB_LINK'.
lv_tag_parent_id
= lv_link_tag->m_parent->id.
lv_attribute_type
= 'something'.

lv_html
= lv_link_tag->if_bsp_bee~render_to_string( page_context =


                                                  lr_controller->gv_thtmlb_element_link->m_page_context ).
lv_tag_parent_id
= lv_link_tag->m_parent->id.

call function 'SCMS_STRING_TO_XSTRING'
exporting
text   = lv_html
importing
buffer = lv_html_xstr.

lv_html
= cl_http_utility=>encode_x_base64( lv_html_xstr ).

concatenate lv_tag_class_name ',' lv_tag_parent_id ','


                      lv_attribute_type ',' lv_html ',' lv_link_tag->id


                 into lv_response.

ir_server
->response->set_cdata(
exporting
data   lv_response
).
catch cx_root.
endtry.
endif.
endmethod.




How does it look like:



  3 seconds later… 



HTTPWatch trace:




It takes only 300 ms to build a page and 3 seconds later the link is updated.




Table Rendering


In WebUI most of the data is presented in tables, and therefore the option that is more desirable would be to render such kind of BSP elements. This topic however has been already described on SDN already, so see more at: http://scn.sap.com/community/crm/webclient-ui-framework/blog/2015/09/03/asynchronous-rendering-of-ta... . But here we will consider an option without any modification to SAP standard objects.


So, as usual, we need our WEBUI component, view set and the view. But also we need a context node, a value node in our case.





Our page TableView.htm looks like below:


 










<%@page language="abap" %>
<%@extension name="thtmlb" prefix="thtmlb" %>
<%@extension name="chtmlb" prefix="chtmlb" %>
<%@extension name="bsp" prefix="bsp" %>
<%
* Conversion Cnode SelectionMode to Tag
data: lv_cellerator_selectionmode type string,
lv_cellerator_editmode 
type string,
lv_cellerator_selectioncolumn
type string.
cl_thtmlb_util
=>translate_selection_mode(
exporting
iv_selection_mode   
= SOMEDATA->SELECTION_MODE
iv_all_rows_editable
= space
importing
ev_selection_mode  
= lv_cellerator_selectionmode
ev_edit_mode       
= lv_cellerator_editmode
ev_selection_column
= lv_cellerator_selectioncolumn ).
"
"
data: lv_table_bee type ref to cl_chtmlb_config_cellerator.
"
if controller->gv_config_table is initial.
"
" Create Table
call method cl_chtmlb_config_cellerator=>factory
exporting
id                    = controller->gc_table_id
downloadToExcel      
= 'TRUE'
editMode             
= 'NONE'
onRowSelection       
= 'select'
personalizable       
= 'TRUE'
selectedRowIndex     
= somedata->selected_index
selectedRowIndexTable
= somedata->selection_tab
selectionColumn      
= lv_cellerator_selectioncolumn
selectionMode        
= lv_cellerator_selectionmode
table                 = somedata->table
_table               
= '//SOMEDATA/Table'
usage                
= 'EDITLIST'
visibleFirstRow      
= somedata->visible_first_row_index
visibleRowCount      
= '6'
width                
= '100%'
xml                  
= controller->configuration_descr->get_config_data( )
receiving
element      
= lv_table_bee.
"
" Store the link
controller
->gv_config_table ?= lv_table_bee.
%>
<!--this code performs a callback to refresh the table   -->
<script type=
"text/javascript">
debugger;
thtmlbAJAXCall.callBackend(
"<%= controller->create_ajax_url( ) %>",   


                                                        thtmlbCCelleratorManager.createFastRowsCallback);
</script>
<%
"
endif.
"
" Get the link
lv_table_bee ?= controller
->gv_config_table.
"
" Render html element
lv_table_bee
->if_bsp_bee~render( _m_page_context ).
%>
</br>




You can notice that in our case we perform an AJAX call directly (thtmlbAJAXCall.callBackend); the task to build a proper URL is addressed to ABAP method CREATE_AJAX_URL; and we use a standard callback function thtmlbCCelleratorManager.createFastRowsCallback to process the AJAX response.



How to build a proper URL for AJAX call? If you are going to use the standard AJAX service handler, there are many things that you need to consider: a handler class, a controller id, security session token, etc. But all this is considered in the method CL_CRM_WEB_UTILITY=> CREATE_SERVICE_URL. So finally our method looks very simple:











  method create_ajax_url.

data: lr_class_desc    type ref to cl_abap_typedescr.

lr_class_desc
= cl_abap_classdescr=>describe_by_object_ref( me ).

call method cl_crm_web_utility=>create_service_url
exporting
iv_handler_class_name
= lr_class_desc->get_relative_name( )
iv_controller_id     
= me->component_id
receiving
ev_url               
= rv_url.

endmethod.




In this case, we again use view controller class as a callback handler.



Next what we need is to implement IF_CRM_WEB_CALLBACK~HANDLE_REQUEST method. We do it in a very similar way as described here: http://scn.sap.com/community/crm/webclient-ui-framework/blog/2015/09/03/asynchronous-rendering-of-ta.... However, in our example, we are getting the data we need via parameter IR_CONTROLLER and therefore we do not need to change the standard SAP coding.











  method if_crm_web_callback~handle_request.

data: lr_controller                     type ref to zl_zdmsh_aj_tableview_impl.
data: lr_somedata                       type ref to zl_zdmsh_aj_tableview_cn00.
data: lr_wrapper                        type ref to cl_bsp_wd_collection_wrapper.
data: lv_html                           type string.

wait up to 3 seconds.

if ir_controller is bound.
try.

lr_controller ?= ir_controller
.
lr_somedata   ?= lr_controller
->typed_context->somedata.
lr_wrapper    ?= lr_somedata
->collection_wrapper.

if lr_controller is bound and
lr_somedata
is bound and
lr_wrapper
is bound.

lr_controller
->fill_context_node( ).

call method create_table_view_html_old
exporting
ir_server        
= ir_server
ir_controller    
= ir_controller
iv_binding_string
= '//SOMEDATA/TABLE'
iv_table_id      
= ir_controller->get_id( gc_table_id )
importing
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
exporting
ir_controller
= ir_controller.

endif.
catch cx_root.
      endtry.
endif.
endmethod.




Method CREATE_TABLE_VIEW_HTML is implemented in exactly the same way as it was described on SDN already. I just repeat it here for consistency.











  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 ).
endif.

    “ get model
call method cl_bsp_model=>if_bsp_model_util~split_binding_expression
exporting
binding_expression
= iv_binding_string
importing
attribute_path    
= lv_attribute_path
model_name        
= lv_model_name.



try.
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.
exit.
endtry.

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
exporting
iv_area_id        
= lv_row_id
iv_page_id        
= ir_controller->component_id
importing
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 ).
endif.
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.
else.
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.
endif.
endwhile.

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.

endmethod.




In the method FILL_CONTEXT_NODE we just populate our context node.











  method fill_context_node.

data: lt_but000      type table of but000.
data: ls_but000      type but000.
data: lr_wrapper     type ref to cl_bsp_wd_collection_wrapper.
data: lr_valuenode   type ref to cl_bsp_wd_value_node.
data: lr_table_line  type ref to but000.

lr_wrapper
= typed_context->somedata->get_collection_wrapper( ).
if lr_wrapper is bound.

lr_wrapper
->clear( ).

select * from but000 into table lt_but000 up to 2 rows
where type = 1
and bu_sort1 = 'DMITRY'.

loop at lt_but000 into ls_but000.

create data lr_table_line.

create object lr_valuenode
exporting
iv_data_ref
= lr_table_line.

lr_valuenode
->set_properties( ls_but000 ).

lr_wrapper
->add( lr_valuenode ).

endloop.

endif.

typed_context
->somedata->build_table( ).

endmethod.




How does it look like:





3 seconds after…






Note that all your GET-methods are taken into consideration and you can navigate normally as you would do it in the normal WebUI table.



HTTPWatch trace:





It takes only 300 ms to build a page and 3 seconds later the table is updated. 



P.S. See a second, better solution here:  Asynchronous Rendering of TAJAX Areas / Table Views   




5 Comments