BSP / HowTo: BACK Navigation – the nightmare of statefull Web applications
Introduction
Who never got the following message using the BACK Button to resume the last visited page?
<<I>”Warning: Page has Expired. The page you requested was created using information you submitted in a form.
This page is no longer available. As a security precaution, Internet Explorer does not automatically resubmit your information for you.
To resubmit your information and view this Web page, click the Refresh button. “
> Users do not like that warning page and me too <
I recently worked on the
BACK Navigation
in a project where a great B2B Portal has been created via a custom BSP application designed according to the MVC pattern.
I discovered some helpful settings, JavaScript statements and finally I implemented the new powerful BSP TAG <zbn:DisableBackNavigation>.
OK, now let’s go into technical details:
APPROACH 1 – SETTINGS – Tuning cache settings
Via SE80 it is possible to specify caching parameters on each controller.
An example:
The first thing to know is that the “Page has Expired” warning page is generated by the browser only if Browser or Server cache value is not set.> No cache, then get “Warning: Page has Expired. …”. <
Are you working with
stateless
applications? If yes, set the Browser cache to 3600 and skip the rest of the Blog.
If not, it’s a pity. You are not so lucky. Wait before setting
statefull
applications to use cache.
You will solve the “Page has Expired” problem but you will you introduce even a worst problem.
> Statefull application with cache, then get the client (Browser) to be NOT synchronized with the server (BSP application) <
Let me explain: let’s take a shopping cart application.
The collection of items in the cart will be maintained in an ABAP internal table binded in the cart view. Then, if a user perform a BACK Navigation he/she will see a previous cart; different items will be displayed in the browser then what are in the ABAP table.
I don’t want to think what will happen if an action is requested for an item that is no more in the cart (add_item(1),add_item(2),drop_item(1),BACK Navigation, BACK Navigation, update_item(1))…
APPROACH 2 – SETTINGS – Hiding BACK Button to the user
In order to avoid BACK Navigation problem, it would be nice to disable the BACK command on the browser.
Too easy. Life is not so.
You could start your application in a new window disabling the all the browser’s buttons.
> Statefull application in a window without buttons – If your users like it… <
Please note that
right-clicking
on the page the Back command is still there..
In any case, in order to disable the Backspace Navigation,
> do not forget to make use of the <htmlb:document disableBackspaceNavigation=”TRUE”> <
APPROACH 3 – JAVASCRIPT – An easy and powerful solution
A simple JavaScript function may be coded in your BSP in order to disable the BACK Navigation.
> With <B>history.forward()</B>, BACK Navigation is no more a Nightmare <
Picture – BSP Extension – DisableBackspaceNavigation
Picture – BSP Extension – DisableBackspaceNavigation – Attributes
Picture – BSP Extension – Class – Attribute
Picture – BSP Extension – Class – Methods
DO_AT_BEGINNING source code:
Version improved by Mario Vangerven (it uses a cookie):
METHOD if_bsp_element~do_at_beginning . DATA: html TYPE string. DATA: prev_out TYPE REF TO if_bsp_writer. prev_out = me->get_previous_out( ). CASE me->
returnlinkgenerate.
WHEN ‘TRUE’.
-
Get returnPage
DATA: l_return_page TYPE string.
DATA: o_http_request_warning_page TYPE REF TO if_http_request.
o_http_request_warning_page = me->m_page_context->get_request( ).
l_return_page = o_http_request_warning_page->if_http_entity~get_form_field( name = ‘returnPage’ ).
-
Render A HREF TAG
CONCATENATE html cl_abap_char_utilities=>cr_lf ‘Server Step Id: ‘ me->server_step_id ‘ ”);’
cl_abap_char_utilities=>cr_lf ” cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
”
”’);’ cl_abap_char_utilities=>cr_lf
‘document.write(”
”);’ cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
INTO html.
WHEN OTHERS.
CASE me->type.
-
Render client-side Java Script function history.forward()
WHEN ‘CLIENT’.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘Server Step Id: ‘ me->server_step_id ‘ ”);’ cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
-
Get AgentStepId
INTO html.
WHEN ‘SERVER’.
-
Get the cookie from the root path
-
=> now we can even jump from one application to the other and still disable the back button
DATA: o_http_request TYPE REF TO if_http_request.
o_http_request = me->m_page_context->get_request( ).
CALL METHOD o_http_request->if_http_entity~get_cookie
EXPORTING
name = ‘AgentStepId’
path = ‘/’
IMPORTING
-
Calculate ServerStepId
value = me->agent_step_id.
DATA: l_server_step_id_int TYPE i.
DATA: l_server_step_id_str TYPE string.
IF me->agent_step_id IS INITIAL.
me->server_step_id = ‘0’.
ELSE.
IF me->agent_step_id > me->server_step_id.
me->server_step_id = me->agent_step_id.
ADD 1 TO me->server_step_id.
ELSE.
me->server_step_id = me->agent_step_id.
ADD 1 TO me->server_step_id.
-
Render complex client-side Java Script function
ENDIF.
ENDIF.
-
me->agent_step_id = o_http_request->if_http_entity~get_form_field( name = ‘AgentStepId’ ).
DATA: o_bsp_runtime TYPE REF TO if_bsp_runtime.
o_bsp_runtime = me->m_page_context->get_runtime( ).
CONCATENATE html cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
”
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘Server Step Id: ‘ me->server_step_id ‘ ”);’
-
Set the cookie from the root path
-
=> now we can even jump from one application to the other and still disable the back button
cl_abap_char_utilities=>cr_lf
‘jsv_cookie = “AgentStepId=”+history.length+”; path=/”;’ cl_abap_char_utilities=>cr_lf
‘document.cookie=jsv_cookie’ cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
” cl_abap_char_utilities=>cr_lf
INTO html.
ENDCASE.
ENDCASE.
*
prev_out->print_string( html ).
*
rc = co_element_continue.
ENDMETHOD.
-
Replace < with <
-
Replace > with >
-
that because in the weblog special characters are not fully supported in the text
Version without cookie:
*!!!!!!!! ATTENTION !!!!!!!!!*
********************************
METHOD if_bsp_element~do_at_beginning .
DATA: html TYPE string.
DATA: prev_out TYPE REF TO if_bsp_writer.
-
Get returnPage
prev_out = me->get_previous_out( ).
CASE me->returnlinkgenerate.
WHEN ‘TRUE’.
DATA: l_return_page TYPE string.
-
Render A HREF TAG
DATA: o_http_request_warning_page TYPE REF TO if_http_request.
o_http_request_warning_page = me->m_page_context->get_request( ).
l_return_page = o_http_request_warning_page->if_http_entity~get_form_field( name = ‘returnPage’ ).
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<script>’ cl_abap_char_utilities=>cr_lf
‘document.write(”<B>Server Step Id: </B> ‘ me->server_step_id ‘</br>”);’ cl_abap_char_utilities=>cr_lf
‘</script>’ cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<!– DisableBackNavigation Extension – Begin –!>’
‘<script language=JavaScript>’ cl_abap_char_utilities=>cr_lf
‘document.write(”<form method=”POST” name=”formWBN” action=”‘ l_return_page ‘”>”);’ cl_abap_char_utilities=>cr_lf
‘document.write(”<input name=”AgentStepId” type=”HIDDEN” value=””+history.length+””/>”);’ cl_abap_char_utilities=>cr_lf
‘document.write(”<A HREF=”javascript:void(document.formWBN.submit())”>’ me->returnlinktext ‘</A>”);’ cl_abap_char_utilities=>cr_lf
‘document.write(”</form>”);’ cl_abap_char_utilities=>cr_lf
‘</script>’ cl_abap_char_utilities=>cr_lf
‘<!– DisableBackNavigation Extension – End –!>’ cl_abap_char_utilities=>cr_lf
INTO html.
-
Render client-side Java Script function history.forward()
WHEN OTHERS.
CASE me->type.
WHEN ‘CLIENT’.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<script>’ cl_abap_char_utilities=>cr_lf
‘document.write(”<B>Server Step Id: </B> ‘ me->server_step_id ‘</br>”);’ cl_abap_char_utilities=>cr_lf
‘</script>’ cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<!– DisableBackNavigation Extension – Begin –!>’ cl_abap_char_utilities=>cr_lf
‘<script language=JavaScript>’ cl_abap_char_utilities=>cr_lf
‘history.forward();’ cl_abap_char_utilities=>cr_lf
‘</script>’ cl_abap_char_utilities=>cr_lf
-
Get AgentStepId
‘<!– DisableBackNavigation Extension – End –!>’ cl_abap_char_utilities=>cr_lf
INTO html.
WHEN ‘SERVER’.
-
Calculate ServerStepId
DATA: o_http_request TYPE REF TO if_http_request.
o_http_request = me->m_page_context->get_request( ).
me->agent_step_id = o_http_request->if_http_entity~get_form_field( name = ‘AgentStepId’ ).
DATA: l_server_step_id_int TYPE i.
DATA: l_server_step_id_str TYPE string.
IF me->agent_step_id IS INITIAL.
me->server_step_id = ‘0’.
ELSE.
IF me->agent_step_id > me->server_step_id.
me->server_step_id = me->agent_step_id.
ADD 1 TO me->server_step_id.
ELSE.
me->server_step_id = me->agent_step_id.
-
Render complex client-side Java Script function
ADD 1 TO me->server_step_id.
ENDIF.
-
me->agent_step_id = o_http_request->if_http_entity~get_form_field( name = ‘AgentStepId’ ).
ENDIF.
DATA: o_bsp_runtime TYPE REF TO if_bsp_runtime.
o_bsp_runtime = me->m_page_context->get_runtime( ).
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<!– DisableBackNavigation Extension – Begin –!>’ cl_abap_char_utilities=>cr_lf
‘<script language=JavaScript>’ cl_abap_char_utilities=>cr_lf
‘ if (‘ me->server_step_id ‘>0)’ cl_abap_char_utilities=>cr_lf
‘ {‘ cl_abap_char_utilities=>cr_lf
‘ if (history.length<’ me->server_step_id ‘)’ cl_abap_char_utilities=>cr_lf
‘ history.forward();’ cl_abap_char_utilities=>cr_lf
‘ else ‘ cl_abap_char_utilities=>cr_lf
‘ {‘ cl_abap_char_utilities=>cr_lf
‘ if (history.length>’ me->server_step_id ‘)’ cl_abap_char_utilities=>cr_lf
‘ {‘ cl_abap_char_utilities=>cr_lf
‘ location.href=”’ me->warningpage ‘?returnPage=’ o_bsp_runtime->page_name ”’;’ cl_abap_char_utilities=>cr_lf
‘ }’ cl_abap_char_utilities=>cr_lf
‘ }’ cl_abap_char_utilities=>cr_lf
‘ }’ cl_abap_char_utilities=>cr_lf
‘</script>’
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
‘<script language=JavaScript>’ cl_abap_char_utilities=>cr_lf
‘document.write(”<B>Server Step Id: </B> ‘ me->server_step_id ‘</br>”);’ cl_abap_char_utilities=>cr_lf
‘document.write(”<input name=”AgentStepId” type=”HIDDEN” value=””history.length””/>”);’ cl_abap_char_utilities=>cr_lf
‘</script>’ cl_abap_char_utilities=>cr_lf
‘<!– DisableBackNavigation Extension – End –!>’ cl_abap_char_utilities=>cr_lf
INTO html.
ENDCASE.
ENDCASE.
*
prev_out->print_string( html ).
*
rc = co_element_continue.
*
ENDMETHOD.
A SAMPLE APPLICATION
Create a statefull BSP application:
Picture – Web application
Create following controllers:
Picture – Controller for the client implementation
Picture – Controller for the Server implementation
Picture – Controller for the warning Page
Create following views:
mainClientimplementation.htm
That’s all.
Looking forward to try this out when things have settled down here a bit (first user test for our main application coming up).
Thanks a million for this weblog. I just got to showcase my (teeny-weeny) talent of how I know about caching after reading this weblog to a bunch of "expert" BSP programmers.
Regards,
Subramanian V.
I have a huge application where from time to time I navigate to another page using the navigation->goto_page() method. In this case your extension doesn't retain the ServerStepID.
To solve this I changed your DO_AT_BEGINNING() method to use client side cookies in stead of a hidden input field. This seems to do the trick.
Here is the modified method
*==============================================
*!!!!!!!! ATTENTION !!!!!!!!!*
* Replace < with <<br/>* Replace > with >
* that because in the weblog special characters are not fully supported in the text
********************************
METHOD if_bsp_element~do_at_beginning .
DATA: html TYPE string.
DATA: prev_out TYPE REF TO if_bsp_writer.
prev_out = me->get_previous_out( ).
CASE me->returnlinkgenerate.
WHEN 'TRUE'.
* Get returnPage
DATA: l_return_page TYPE string.
DATA: o_http_request_warning_page TYPE REF TO if_http_request.
o_http_request_warning_page = me->m_page_context->get_request( ).
l_return_page = o_http_request_warning_page->if_http_entity~get_form_field( name = 'returnPage' ).
* Render A HREF TAG
CONCATENATE html cl_abap_char_utilities=>cr_lf
'Server Step Id: ' me->server_step_id '
'');' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
''
''');' cl_abap_char_utilities=>cr_lf
'document.write(''
'');' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
INTO html.
WHEN OTHERS.
CASE me->type.
WHEN 'CLIENT'.
* Render client-side Java Script function history.forward()
CONCATENATE html cl_abap_char_utilities=>cr_lf
'Server Step Id: ' me->server_step_id '
'');' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
INTO html.
WHEN 'SERVER'.
* Get AgentStepId
DATA: o_http_request TYPE REF TO if_http_request.
o_http_request = me->m_page_context->get_request( ).
* Get the cookie from the root path
* => now we can even jump from one application to the other and still disable the back button
CALL METHOD o_http_request->if_http_entity~get_cookie
EXPORTING
name = 'AgentStepId'
path = '/'
IMPORTING
value = me->agent_step_id
.
* Calculate ServerStepId
DATA: l_server_step_id_int TYPE i.
DATA: l_server_step_id_str TYPE string.
IF me->agent_step_id IS INITIAL.
me->server_step_id = '0'.
ELSE.
IF me->agent_step_id > me->server_step_id.
me->server_step_id = me->agent_step_id.
ADD 1 TO me->server_step_id.
ELSE.
me->server_step_id = me->agent_step_id.
ADD 1 TO me->server_step_id.
ENDIF.
ENDIF.
* Render complex client-side Java Script function
DATA: o_bsp_runtime TYPE REF TO if_bsp_runtime.
o_bsp_runtime = me->m_page_context->get_runtime( ).
* me->agent_step_id = o_http_request->if_http_entity~get_form_field( name = 'AgentStepId' ).
CONCATENATE html cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
''
INTO html.
CONCATENATE html cl_abap_char_utilities=>cr_lf
'Server Step Id: ' me->server_step_id '
'');' cl_abap_char_utilities=>cr_lf
'jsv_cookie = "AgentStepId="+history.length+"; path=/";' cl_abap_char_utilities=>cr_lf
* Set the cookie from the root path
* => now we can even jump from one application to the other and still disable the back button
'document.cookie=jsv_cookie' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
'' cl_abap_char_utilities=>cr_lf
INTO html.
ENDCASE.
ENDCASE.
*
prev_out->print_string( html ).
*
rc = co_element_continue.
ENDMETHOD.
*==============================================
To test this modification implent the method
DO_HANDLE_EVENT in the controller
METHOD do_handle_event .
IF htmlb_event->server_event = 'myCmd'.
navigation->goto_page( 'mainServerImpementation.do' ).
ENDIF.
ENDMETHOD.
this is a pleasure example of open-source.
Coding becomes better and better thanks to a community...
It would be nice if anybody will help me in discovering how to make the BSP extension working also when the Delta Handling is activated...
Sergio
You want to do the history.foward() only, if the user is not coming from an external page.
Have you tried reading the header field 'referrer'? (like this: request->get_header_field( 'referer' ). If you put the result into an hidden input field, you might be able to read it via JavaScript.