As I’ve mentioned before, sometimes XPages does not react the way your Notes users would expect it to react. In particular, I’ve had issues with ensuring that XPages warns the user when they try navigating away from a page that they’ve edited without first saving it. Such pages are “dirty”, since something has changed on them. Back in November we examined this in my post on Modified flags in XPages.
Unfortunately, sometimes I want to inform the user that they’re leaving a dirty page and other times I don’t. Also, sometimes, I seem to be able to add things that don’t warn the user that they’re leaving a dirty page. I’m sure there is consistency, but I haven’t figured out the rules, so I have added some code that handles these situations.
Simple warning of a dirty page
This was described in that prior post. Set the enableModifiedFlag to true and provide a couple of properties to handle it. This should fire whenever you close the XPage or navigate away via a standard link.
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" enableModifiedFlag="true"> <xp:this.modifiedControl> <![CDATA[#{javascript:if ( true ) {return "saveButton1"}}]]> </xp:this.modifiedControl> <xp:this.modifiedMessage> <![CDATA[#{javascript:"Purchase order modified. Click OK to save, cancel to continue without saving"}]]> </xp:this.modifiedMessage> ... </xp:view>
Unfortunately, I’ve found that sometimes, it doesn’t warn me.
Warning when clicking links
I’ve got some navigation on the left side of my XPages, set up in a lovely dojo accordian with links to different parts of the application. However, not every link is set up in the same way. Some are simple and use the modifiedControl to warn users, like this one:
<xp:link text="Attachments" escape="true" id="link3" value="/att_attachmentManagement_view.xsp"> </xp:link>
Some, however, run some scripts to remove some sessionscope variables when switching pages and I was having challenges getting the server-side Javascript to execute. So, I found a way to do both:
<xp:link text="Payment requests" escape="true" id="paymentLink" target="_self" value="#"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings(); context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action> </xp:eventHandler> </xp:link>
The unfortunate thing is that when I’m using the redirect, it ignores the dirtiness of the page. Fortunately, Per Henrik Lausten found a way to deal with it on StackOverflow (thanks to PSolano), and posted it as an XSnippet on OpenNTF.
if (XSP._isDirty()){ if (confirm ("Are you sure you want to navigate away from this page?" + "\n" + "\n" + "This document may contain unsaved changes." + "\n" + "\n" + "Press OK to continue, or Cancel to stay on the current page.")) { return true; } else { return false; } } else { return true; }
So, when I added that explicitly as client-side Javascript to each eventhandler, it worked beautifully. Now, being a fan of reusable code, I wanted to put it in a CSJS script library and invoke the function all over the place. My function was as shown above, with a little wrapper:
function isClean() { if (XSP._isDirty()){ .... } }
Then, I just needed to invoke it. My first attempt looked great to me. Call isClean() and the client side would warn the user, then stop them as appropriate.
<xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings(); context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action> <xp:this.script><![CDATA[isClean();]]></xp:this.script> </xp:eventHandler>
Ummmm, but that didn’t work. In order to prevent the server-side Javascript from executing, the client-side Javascript needs to RETURN false, not just compute it.
<xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action><![CDATA[#{javascript:clearDynamicViewSettings(); context.redirectToPage("/pro_paymentRequest_view.xsp");}]]></xp:this.action> <xp:this.script><![CDATA[return isClean();]]></xp:this.script> </xp:eventHandler>
That’s added bonus knowledge – not only are we learning how to stick to dirty pages, we’ve also learned that if you have the CSJS return a value of false, it will not execute the SSJS. I’m sure you can grasp the opportunities for validation provided there.
What about when you don’t want to warn them?
In our application, the user can select the action to perform from a combobox and then click a button to execute it. (Thanks to Scott Good, Henry Newberry and the folks at Teamwork Solutions!) The challenge here is that when the user changes the value in the combobox, the page is dirty! I mean, no data may have changed other than that combobox, but the XPage still feels dirty. Since some of those actions will result in saving the document and all of them result in navigation away from this page in an expected and managed way, we don’t want it to feel dirty. To dodge the problem, I just add a little CSJS to convince that page that it’s not really dirty.
<xp:this.script><![CDATA[XSP._setDirty(false,"");]]></xp:this.script>
So, then the modifiedControl is not tipped off and we execute the intended commands. Now, I’m pretty sure we don’t run into this problem with standard save buttons, but we’re in an unusual case of taking two steps to execute one action.
Note
YMMV. These private calls to XSP methods may go away and using them requires care to ensure you’ve got the right parameters, or so the XPages Portable Command Guide advises us. Of course, everything we code may call functions that will go away and we always need to get the parameters right. So, I’m gonna use them, though sparingly.