Wednesday, June 2, 2010

XPages: Avoid saving duplicate documents on browser refresh

Update 22.06.10: Parameters are carried over when the redirect happens.
Update 02.09.10: Parameters starting with _ are stripped when redirect occurs

A reader wanted to know if there was some way to avoid duplicate documents when saving a document, refreshing the browser, and pressing ok on the resulting dialog. I wasn't able to recreate the problem of duplicate documents, but here's a workaround for the "POST" dialog. It should also take care of any duplicate documents on browser refresh.

In JSF lingo, this is known as the POST-Redirect-GET pattern. This is quite easy to accomplish in XPages.

On the save button, put the save action, and an Execute Script action. In the script part, call on the function below (put it in a SSJS Script Library/add it to the page). This makes the server redirect to the document in a get request, and you avoid the double posting problem.

function redirectToCurrentDocument( switchMode:boolean ){
try {
if( typeof currentDocument === 'undefined' || viewScope.stopRedirect ){ return; }
// Gets the name of the XPage. E.g. /Person.xsp
var page = view.getPageName();

// Finds extra parameters, strips away XPages parameters/trims leading &
var parameters = context.getUrl().getQueryString().substring(1);
var extraParameters = parameters.replace(
/([&\?^]*_[a-z0-9=]+[&]{0,1})|(action=\w{4}Document[\&]{0,1})|(documentId=[\w]{32}[\&]{0,1})/ig, '').replace( /\&$/, '' );

// Gets the unid of the current document
var unid = currentDocument.getDocument().getUniversalID();

// Sets/changes the action according according to the mode the document is in
var isEditable = currentDocument.isEditable();
if( switchMode ){ isEditable = !isEditable; } // false -> true / true -> false

var action = ( isEditable ) ? 'editDocument' : 'openDocument';

// Open the current document via a get request
var redirectUrl = page + '?documentId=' + unid + '&action=' + action;
if( extraParameters ){ redirectUrl += '&' + extraParameters; }
context.redirectToPage( redirectUrl );
} catch(e){ /* Exception logging */ }
}

25 comments:

Karthikeyan A said...

Thanks a lot, Tommy. This has surely helped me jump "the great wall".

I have posted me experience with this issue in the following location with screen shots...

http://ozinisle.blogspot.com/2010/06/
9-issue-in-xpage-xpage-refresh-results.html

I still have the duplicate document issues, when I save documents with out using a submit button, but a normal button that creates a backed document out of existing values on the screen and saves them....I am speaking about using multiple datasources in a page with separate save buttons on the Xpage page.

Can you please give me directions on that as well.

Thanks again.

Tommy Valand said...

No problem.. :)

Regarding multiple editable data sources on a page.. That's not something I've worked a lot with :)

I have one suggestion though.. You can bind a data source to a Panel. If you separate the two (or more) data sources onto their own panel/add a regular save action to each data source, I think the page would behave more predictably.

E.g.
<xp:panel id="person">
<xp:this.data>
..
</xp:this.data>
..fields/buttons..
</xp:panel>

<xp:panel id="location">
<xp:this.data>
..
</xp:this.data>
..fields/buttons..
</xp:panel>

易英易英 said...
This comment has been removed by a blog administrator.
Karthikeyan A said...
This comment has been removed by the author.
Karthikeyan A said...

Thank you for the suggestion Tommy.
It is already the way you suggest.
I thought of few other work arounds like validating and submitting them on fly using a button which is of type button and not submit...

I had no other choice,but to split them back into multiple xpages and accomplish my task for want of time. :)

If possible, suggest one solution for the following scenario too...


I have loaded a Xpage into a frame, say "leftFrame". (in the default frameset design element provided by notes)
I have explicity mentioned the target for the link in the same as the frame ,say "rightFrame".

Now when I click links on the xpage it does not open up the url as expected in the rightFrame, instead it opens it up on an entirely new tab. (Notes Client)

I am working on 8.5.1 and the scenario I have mentioned is for both client as well as web.

Once again, Thanks a lot for helping me and thanks for taking time in answering me :)

Tommy Valand said...

I did a little test.

The "default target.." property for a frame didn't seem to work. <base target="nameOfFrame" /> worked in Firefox, but not in IE7/8.

Setting the target in "All properties" on the link worked in IE/FF.

By worked, I mean that the links opened up in the correct frame.

The most common reason that links pop up in new tabs/windows is that the name of the frameset is mistyped. I suggest going over the frameset/links again, and look for typos.

Karthikeyan A said...

Thanks again for the suggestion :)

Yes I tested it works fine in IE/FF.

It works well with Chrome too...

But !!! Even this fails in Lotus Notes Client 8.5.1 :(....

:)

Tommy Valand said...

Probably a bug in Notes..

Have you tried <base target="frameName" /> in the Notes Client. If you're lucky, this might work.

Karthikeyan A said...

Wow Tommy, I never expected that one comming... I'm honored. :) Thanks a lot

Tommy Valand said...

No problem..

As a great british author once wrote.. Share and enjoy ;)

Karthikeyan A said...

Hi Tommy,

I am definitely going to remember that you quoted it, for a long time :)

Another question !!!. :)

Is there a way to programatically expand and collapse XPage Section Control on the screen...?

I have checked with all the properties of the section control element...

I see 2 check boxes one says "Disable" and the other in the Options sector says "Disable Expand and Collapse"

checking or unchecking either of them gets reflected in the other(I mean updates the other)...
then y give 2 separate options to update the same value... I dont understand that..

Coming back to programatically expanding and collapsing the section issue, I have checked with the "All Properties Section" as well...

I don't find any thing relevant that may help me....
Am I missing some thing ..?

Thanks again

Tommy Valand said...

I suggest you take a look at the XPages API Inspector I made a while back.

It's a custom control that you can pop into an XPage. You get a combobox of all the controls in the page. When you select a ViewPanel, you get the full API for that kind of control.

Pop it into the page with the section, select the section, and you will get all the methods for the section object.

All my blogposts related to the API Inspector

boyet said...

Hi Tommy,

I am having a similar problem here, but the extra documents I am getting are just blanks, and not duplicates. I tried the solution you described above, but I still get the same results: extra documents that are just blanks. Please help. Thanks.

Tommy Valand said...

Hard to say just from your description.

XPages is so complex that it's hard to say without having the XPage code in front of me..

Anonymous said...

Coming back to programatically expanding and collapsing the section issue, I have checked with the "All Properties Section" as well...

I am not sure if this is the proper way. But it could be used as a possible work around. Add the following code to a button or a link -

XSP.showSection("#{id:section1}",true);

where the arguments being id of the section and expand - true or false

Anonymous said...

Hello Karthikeyan A,

Could you tell me how did you get an XPage display inside of a Notes frameset?

Thanks!
Adam

fenaten said...

Hi Tommy,
I implemented your redirectToCurrentDocument successfuly. Thanks!
I modified it so that it works also for XPages working with new documents (extraparamenters containing 'action=newDocument'). The portion of the replace that changes is '(action=\w{3,}Document[\&]{0,1})'.

Anonymous said...

Excellent code, thank you!
Also thanx fenaten for the additional new document parameter.
jlehocz

Al said...

Hi - I am having the same issue. However, I have an agent which executes on the PostSaveDocument, sending some alerts and setting some values on the document, and then savinf the document. I have tried your code, however it does not stop the button from executing the code as many times as the person clicks it. Any thoughts?

Tommy Valand said...

Simplest way is to set the button disabled onclick with javascript in the browser.

Steve Zavocki said...

Thanks Tommy for this post. Xpages can do crazy things sometimes.

I wanted to post for a slight change I need to make for cases when you have multiple data sources on a page.

On the function line that reads: var unid = currentDocument.getDocument().getUniversalID();

simply replace the currentDocument with document1 of whatever the name is of the main page is bound to, and it works beautifully. Thanks for sharing this with the community

Steve Zavocki said...

I found another issue after my last comment. I fixed it, and thought I would share the results in case others had the same issue.

I open the xpage from another xpage where I create the document. In that xpage I open based on getNoteID() and not getUniversalID(). Since the function uses the latter, it creates a runtime error for new documents when it is run. This is because you are mixing both the noteID and universalID in the same URL. I changed the function to noteID and it all worked. I am guessing I could have done vice-versa as well. It created duplicate NotesIDs in the same query string, athough this is redundent it works and no one is likely to notice.

Tommy Valand said...

Good you made it work :)

Venkatesh said...

Great Code!! How can I make this to work when the user hits the browser back button. I have a case where the user, puts the document in edit mode, makes some changes to the document, saves the document and opens the previous page after save. At that point, when the user clicks on browser back button, the xpage displays the previous state of the document in edit mode without any changes that the user made in the previous step. So the user can make new changes, upon clicking "save" a conflict document is created. How can I make the browser's back button display the latest state of the document?

Tommy Valand said...

If the URL changes when the user does something, you will get the previous version on back button.

I'm not sure if there is a simple way to prevent this. You could probably write a routine (setInterval/ajax calls) that checks if the data shown is outdated/do a refresh.

If you make sure that the url is the same when opening the document for editing->save/redirect I don't think you should get this issue.