XPages: Handle Browser Back Button - xpages

I created a XPage for a Notes form. I added an edit-button which performs some logic and then switches the document-mode to "edit" and a save-and-close-button which saves the document and redirects to a XPage of a Notes view.
This works fine but when the browsers back-button is pressed after returning to the view, the document gets shown again in edit-mode. Is it possible to return to the document but in read-only-mode?
Because some logic is performed when pressing the edit-button and i need to ensure that this logic is also executed when the user returns to the document via browser back-button and not just jump right in the edit-mode without performing the logic.

Ignoring the request parameters is not an option for me.
I solved it by disabling the client browser cache (= force client browser to reload the page everytime instead of displaying a cached version of the page) and adding a "was the edit-mode entered via the edit-button?" check.
beforeRenderResponse: Disable browser cache for this page
var response:java.lang.Object = facesContext.getExternalContext().getResponse();
response.setHeader("Expires", "Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past (= expired)
response.setHeader("Pragma", "no-cache"); // HTTP/1.0
response.setHeader("Cache-Control", "no-store"); // HTTP/1.1
beforePageLoad:
Check if edit-mode was entered via edit-button.
If not, change to read-only-mode
if (!<was edit-mode entered via edit-button?>) {
var pagePart:string = view.getPageName() + "?";
var documentPart:string = "documentId=" + context.getUrlParameter("documentId");
var actionPart:string = "&action=openDocument";
context.redirectToPage(pagePart + documentPart + actionPart);
}

Related

Browser Back Button - Breaking onClick event

I have a repeat control setup which contains a table. The table row(s) contain information from an entry in a view. When the user clicks on the entry, it will redirect them to another xPage which contains the information pertaining to the item they clicked.
I noticed that if you click the browser back button (in this case mobile iPhone Safari), it will redirect you to the previous page, but the onClick events do not fire if you click a row in the repeat control.
I'm not exactly sure what is causing this to break, but has anyone run into this in the past or have any idea no what may fix this issue?
var entry:NotesViewEntry = data;
var docId = entry.getDocument().getUniversalID();
var url = context.getUrl().getAddress().replace(view.getPageName(), '');
context.redirectToPage(url + "/xCall.xsp?documentId=" + docId + "&action=openDocument");
Edit:
The issue appears to only occur when using mobile Safari (for iPhone). It works fine in Chrome/Internet Explorer.
Another interesting detail, if you click on an item and it redirects you to a page, then you click the browser back button, you can click on the repeat control and nothing happens. However if you way 15-20 seconds and then click, it works like expected. If you click anytime before 15-20 seconds, nothing happens
Second Edit:
I ended up coming across this link: https://github.com/christophery/pushy/issues/51 which described a problem where the browser back button was creating weird behavior. I ended up using the following code which seems to have fixed the issue.
// for jQuery
$(window).bind("pageshow", function(event) {
if (event.originalEvent.persisted) {window.location.reload();}
});
// for regular JS
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload()
}
};
This might be caused by the browser caching the previous page. Add this to the beforePageLoad event of your XPage in order to tell the browser not to cache the page:
<xp:this.beforePageLoad><![CDATA[#{javascript:
// Do not cache the HTML pages in the browser
var exCon = facesContext.getExternalContext();
var response=exCon.getResponse();
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Cache-Control", "no-store");
}]]></xp:this.beforePageLoad>

How to stop a user from opening a new browser session in XPages

I have a front end document locking process that creates an Application Scope variable with the UNID/Username/time in it, then a timer that updates this information every 30 seconds. If someone tries opening the document to edit I check (using the UNID) to see if anyone else has the document. If the time is greater than 30 seconds I cancel the lock and allow the new user to open it.
I have considered do something similar with a database 'lock' which is pretty simple but I need a unique Identifier of the browser session. I can build the applicationScope variable using ReplicaID/UserName/time but I need one more piece of information that will identify this browser session.
Is there such a piece of information available somewhere? Something like a browser sessionID?
You want to allow only one open browser window/tab per user and database.
For every XPage you open in browser you have to test if the same database is open in another browser window/tab already. If so, decline opening XPage with e.g. redirecting to an error XPage.
It is impossible to do this on server side only as the server don't know in which browser tab an Xpage gets opened.
So, the server needs client's help. The client have to give the server a unique browser window/tab id.
A browser window/tab doesn't have a unique id. But, opening a new window/tab we can create an random id and store it in browsers sessionStorage. This is a unique storage for every window/tab.
This window/tab id can be send to server with a partial refresh GET and stored on server for the user in an application scope variable.
Also, the server has to know when a window/tab gets closed so that the same database can be opened in another window/tab. For this the client has to tell the server "I am alive" every X seconds.
This is the complete solution:
Create a Custom Control "UniqueBrowserTab"
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:eventHandler
event="onClientLoad"
submit="false">
<xp:this.script><![CDATA[
var secondsIamAliveInterval = 5;
var tabId = sessionStorage.getItem("tabId");
if (!tabId) {
tabId = Math.random();
sessionStorage.setItem("tabId", tabId);
}
function sendTabIdToServer() {
XSP.partialRefreshGet("#{id:browserTabControl}", {
params: {'tabId': tabId}
});
}
sendTabIdToServer();
setInterval(function() {
sendTabIdToServer();
}, secondsIamAliveInterval * 1000);
]]></xp:this.script>
</xp:eventHandler>
<xp:panel id="browserTabControl">
<xp:this.rendered><![CDATA[#{javascript:
var secondsIgnoreOtherSession = 7;
if (param.tabId) {
var userName = session.getEffectiveUserName();
var userData = applicationScope.get(userName);
var now = new Date().getTime();
if (userData) {
if (userData.tabId !== param.tabId) {
if (userData.time + secondsIgnoreOtherSession*1000 > now) {
context.redirectToPage("Error.xsp");
}
}
}
applicationScope.put(userName, {tabId : param.tabId, time: now});
}
return true
}]]></xp:this.rendered>
</xp:panel>
</xp:view>
Include the Custom Control "UniqueBrowserTab" into every XPages or e.g. into application layout's Custom Control.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xc="http://www.ibm.com/xsp/custom">
<xc:UniqueBrowserTab />
...
</xp:view>
Create an XPage Error.xsp wich has not included the Custom Control "UniqueBrowserTab".
This will allow only one browser window/tab per database for a user across all browsers.
There is the SessionID cookie which is unique for the browser. I believe it's passed as part of the partial refresh information.
Just bear in mind that the SessionID would be the same for multiple windows of the browser. I'm not sure if that is an issue.
Thanks to all that have posted here. In actual fact there are two similar but different issues.
In the first instance it is to block multiple instances of the application in the same browser, and Sven's post answers that one.
The second issue is opening the same application in multiple browsers. In this case I am going to use an applicationScope variable appSession which is a Map with the key being UserName~DBRepID then two 'fields'
Time which is refreshed by a Client Side timer event which updates the time every "N" seconds.
sessionID = facesContext.getExternalContext().getRequest().getSession().getId();
In the before page loads (perhaps as a PhaseListener ??) I lookup appSession with the key UserName~RepID if the sessionID matches open the page. if the Time in appSession is time expired then set the time to the current time and set the sessionID to the current getID() and open the page. If not then open error page.
The sessionID has to have a timer on it so that if the user closes the browser or tab it will eventually time out. I'm using something like this for an XPage Document locking routine and it works well.
I have not put this all together yet but it looks to me as if it should get the job done. If anyone see a better way to do this I would be happy to hear.

xpages: compute Confirm Action confirmation text does not work in XPiNC

I'm using Notes/Domino 8.5.3. I've added a button control to an xpage. The button uses a Confirm Action to display a client-side prompt to the user before continuing with the next action defined for the button. When I use static text for the Confirmation Text for the Confirm Action, the confirmation prompt is displayed. However, when I change the Confirmation Text to be computed, and retrieve the text from a profile document, the confirmation prompt it not displayed at all in XPiNC. The confirmation prompt with the computed confirmation text is displayed just fine in a browser. Is there a work-around for this issue with XPiNC?
Following is the code I'm using in the Confirm Action to get the text for the prompt:
var server = database.getServer();
var dbProfile:NotesDocument = database.getProfileDocument("DBProfile", "");
var msg = dbProfile.getItemValueString("ContactsInitUpdatePrompt");
return msg;
To further my comments, this is a work around I use the below code for an app that uses the bootstrap extension library on the web but uses basic functionality with xpinc.
If the values for xPinc are different you could make the confirm action different in the browser and in the client.
if (#ClientType()== "Notes")
{
<action>;
}
else{
<action>;
}
I think that profile documents are a bad idea in xPages though. Having to restart HTTP to get a new value ruins the point I think. Almost better to hard code values at that point. I think you can set application scope to handle the work of profile documents. But then application scope in xpinc is just on the current machine as the server is the client.

XPages: execute context.redirectToPage after client side function?

Why does context.redirectToPage behave differently when executed in a view root-event instead of an event handler?
This question came up when I tried to set the language of an xpages application to the language saved in the user profile, once the user is logged in. I use a properties-file with translated strings in a resource bundle, and retrieve the strings like this:
<xp:text value="${langString['WELCOME_TEXT']}" />
When the language is changed and so a different properties-file is loaded, the page needs to be refreshed in order to update those strings. This worked fine when I added a full-refresh event handler to the login button, that executed a server side context.redirectToPage(). No luck with client side refreshes like location.reload or window.location.href=window.location.href (the login-function itself is a client side function though).
But of course the user expects that he is also logged in when he presses the enter key instead of the button after he has entered his credentials. So I added an onkeypress-event to the username and password input fields, and checked for the enter key (if (thisEvent.keyCode==13) dosomething...) before executing the login function.
But now the event handler is called every time a key is pressed and of course I do not want the context.redirectToPage to be executed all the time.
Thus I removed the server side event handlers and changed the login function so that it terminated with a partial refresh of the div containing the whole page:
var p = {"execLogin":"true"}; XSP.partialRefreshPost( '#{id:wholePage}', {params: p} );
The parameter sent via the partial refresh now triggers an event in which our context.redirectToPage is executed:
<xp:this.beforeRenderResponse><![CDATA[#{javascript:if (param.containsKey('execLogin') && param.get('execLogin').toString().equals('true')) {
print("test");
context.redirectToPage(context.getUrl().toSiteRelativeString(context),true);
}}]]></xp:this.beforeRenderResponse>
The page is refreshed and "test" is printed out, but I still see the old language strings. I have to refresh the page manually again to make the new user language take effect.
Any idea how to execute a genuine full refresh this way, or maybe another way to update the strings retrieved from the property bundle?
Thanks in advance. Regards,
Sarah
EDIT
I now have:
<xp:inputText id="cc_login_panel_login_username" styleClass="span2">
<xp:eventHandler event="onkeypress" submit="true" refreshMode="complete">
<xp:this.script><![CDATA[if (thisEvent.keyCode!=13) {
return false;
} else {
doLogin();
return true;
}]]></xp:this.script>
<xp:this.action><![CDATA[#{javascript:context.redirectToPage(context.getUrl().toSiteRelativeString(context));}]]></xp:this.action>
</xp:eventHandler>
Because context.reloadPage() didn't even log me in somehow (strange) I got back to using redirectToPage. The server side event is fired once and at the right time *thumbs up*, but the language properties-bevaviour is still the same.
$ is only set on page load, whereas # is set each time during the partial refresh.
I don't think a partial refresh will work at all though. This will refresh the computed field. However, it will need a full refresh to refresh the part of the XPage that includes the properties file. In other words, you would be refreshing the computed field, but using the same properties file.
I wonder if context.redirectToPage or context.reloadPage is somehow going to the same page but with the cached properties files.
If you're always wanting to come back to the same page, a full refresh instead of partial refresh may be the best option.
I think this has something to do with using the $ parameter. this tells the runtime to retrieve the language file the first time the current page is created in the back-end. When a user does a refresh it is actualy retrieving a saved version of the page you are viewing.
I see you're calling "context.redirectToPage(context.getURL().toSiteRelativeString(context)))" within an xp:this.action tag for the xp:eventHandler.
Try using xp:this.onComplete in place of xp:this.action.
According to the Designer tooltip for the action, the expected return is a string to be passed to the navigation handler. So instead giving the onComplete will execute the redirect command when it's done with the eventHandler group of events.
Thanks for all the helpful answers, in fact all of them did work, the problem turned out to be my misunderstanding of when the properties-file is loaded. It is loaded in an early phase, long before my new language is set to the sessionScope (that sessionScope variable is then used as a part of the name of the properties-file to be loaded, via the VariableResolver).
Now I use a double full refresh to load the new file. When the login function terminates successfully, it executes:
window.location.href = window.location.href + "?doRefresh=true";
And to the view root element I added the following event:
<xp:this.beforeRenderResponse><![CDATA[#{javascript:
if (context.getUrlParameter("doRefresh")!=null&&context.getUrlParameter("doRefresh").equals("true")) {
var url = context.getUrl().toSiteRelativeString(context);
url = url.replace("?doRefresh=true","");
context.redirectToPage(url);}
}]]></xp:this.beforeRenderResponse>
This is not a very sophisticated solution, but at least it works :-)

Xpages partial refresh trigger full refresh

Trigger full refresh when logged out in 8.5.3. I don't care if the data is lost, I just want to trigger full refresh if partial refresh fails in case user is logged out or session has timed out.
I have two windows open. If I press the logout button on window 2, and then come back onto window 1 and try to do a partial refresh, it returns nothing (just blank value for that control). So how can I trigger a full refresh when the user is logged out?
I tried onComplete, onStart and onError client side events, but cannot figure out how I could find out when this logout has happened, I tried the following code:
var strUserName = '#{javascript: #Name("[CN]", #UserName());}';
alert("Start: " + strUserName);
It always returns the username rather than anonymous in window 1, even when user is logged out in window 2!
onError event never fires when partial refresh fails or returns blank value.
Any ideas or help would be greatly appreciated.
Did you try to lock the database down (Anonymous = NoAccess)? Then you should get an 404 and the error routine fires.
Update
You can:
add a redirect meta header to the page that kicks in when a session expires (cheapo solution, but might catch already most used cases)
Still lock down the database but give Anonymous the right "view public pages" and then have the page(s) that anonymous is allowed to see marked with the attribute "available for public access"
Make sure your partial refresh checks if nothing comes back and reloads the page then (most work since you need to alter business logic) - some sample of your code would be needed
onComplete has one weakness. The value bindings are evaluated when the page is rendered, not after a partial refresh, as you might think. This is due to the way the request works. When a request is sent, the callbacks for the request are bound (onStart,onComplete, etc.). Therefore the client side handler code has to be available before the refresh happens.
If the partial refresh doesn't refresh the item with the event handler(onComplete/onStart/onError), I think the value binding is only updated on page load.
There are several ways to solve this.
Have a client side function that does partial refresh on a hidden (by CSS) xp:text/etc. that shows the current user name. Fetch the content using theNodeReferenceVariable.innerHTML. If Anonymous is the current user:
document.location.href = document.location.href
You could also do document.location.reload(), but that might result in submits being resent.
Use the XPages Extension Library REST services. Same as above, if Anonymous, reload page.

Resources