xpages - enableModifiedFlag -> possible to prevent default dialog at beforeunload event? - xpages

For my xpages app I want to set the enableModifiedFlag to true to have a dirty form functionality to check if changes are made to a page.
I tried to avoid that the default warning message will appear when moving away from the page by setting the page to not dirty but this not prevent/hinder that the default dialog appears. what am I doing wrong?
window.addEventListener('beforeunload',(event) =>{
var isdirty = XSP._isDirty();
console.log("check -> is dirty? " + isdirty);
if(XSP._isDirty()){
console.log("set dirty to false to avoid ugly standard alert dialog");
XSP._setDirty(false,"");
//add this to prevent default behaviour e.g. open another page
event.preventDefault();
//add here code to present a more fancy bootstrap dialog
//XSP.openDialog("dirtyDialog")
return false;
}
});

Haven't done exactly what you do...
However, if on a button you want to "cancel" then you only need to call
XSP._setDirty(false,"");
prior to navigating away. No need to call event.preventDefault() in that case. So I'm guessing that the default code is called prior to your "before unload" event - perhaps you can try and see if you could intervene a little before or somehow rethink the approach.
Perhaps I don't fully understand what you want to obtain when having this functionality but avoiding to use it when leaving the page - or is this just a test example?
/John

Related

Auto-collapse any item in PrimeFaces PanelMenu on page loading

I'm writing a Primefaces 5.1 portlet.
It consists in a unique page containing a panelMenu, and I need that it starts with any panel collapsed everytime a user change page (on page loading).
But, if I open a panel, then change page, it will start showing that panel still opened.
I wasn't able to find any option to achieve this goal (e.g. collapsed=true, ignoreCookie=true or something similar).
The only solution I found was the following Javascript code:
PrimeFaces.widgets.myPanelMenu.collapseRootSubmenu(PrimeFaces.widgets.myPanelMenu.headers);
The problem is that this code will collapse any opened panel (so on page loading user is able to see panel menu collapsing animation) but it seems it doesn't store this state in its cookie/localstorage... the result is that on any page loading user can see this animation.
I'm sure it doesn't save its state, because the only way to "solve" the problem is to manually re-open and re-collapse the panels... then, on following page change, these menus start closed (and there is no animation).
I also tried to use PrimeFaces.widgets.sideMenuPanel.saveState() after collapsing, but with no success.
Do you have any idea about?
Thank you...
I found a solution to the problem.
If you read my discussion with Kukeltje (comments on my question), you will find that latest Primefaces' versions will solve the problem.
Otherwise, if you want to avoid upgrade or modify sources, and you need a quick fix based on Javascript only please read the following part of the answer.
It directly works on the component's state using JavaScript.
First of all you need to have a variable reference to your component:
<p:panelMenu model="#{menuBackingBean.menuModel}" widgetVar="sidePanelMenu" />
Then you should add the following JS code on document ready:
var panelMenu = PrimeFaces.widgets.sidePanelMenu;
// 1. On page loading collapses possible opened panels
panelMenu.collapseRootSubmenu(panelMenu.headers);
// following line is commented because it never should be necessary is not necessary (unless unexpected situation I never verified)
//clearSidePanelMenuPreferences();
// 2. Call the "clear preferences" actions on click on two tpe of links: first level are the panel link (used to open/close the menu) and second level are the destination links
// We need to fork also on the first level links to be sure it works after user clicks there then exit from the page in another way
panelMenu.headers.children("a").click(function(){setTimeout(clearSidePanelMenuPreferences, 500)}); // setTimeout is necessary because this event should be fired after preferences are written
panelMenu.headers.siblings().find("a").click(function(){clearSidePanelMenuPreferences();});
The function called to clear preferences are the following:
function clearSidePanelMenuPreferences() {
var panelMenu = PrimeFaces.widgets.sidePanelMenu;
panelMenu.expandedNodes = []; // clear the opened panels lists
panelMenu.saveState(); // store this information
}
Hope it helps
Please check this block of code
PF('myPanelMenu').headers.each(
function(){
var header = jQuery(this);
PF('myPanelMenu').collapseRootSubmenu(header);
header.removeClass('ui-state-hover');
}
);
I prefer to do this in order to execute this method only once and keep the menu option selected.
$(document).ready(function() {
if(location.pathname == "/cotizador/" || location.pathname == "/cotizador/faces/login.xhtml"){
var panelMenu = PrimeFaces.widgets.sidePanelMenu;
// 1. On page loading collapses possible opened panels
panelMenu.collapseRootSubmenu(panelMenu.headers);
panelMenu.expandedNodes = []; // clear the opened panels lists
panelMenu.saveState();
}
});

Wait for chrome.tabs.update tab to finish loading

I'm trying to work on a chrome extension and am trying to clean up some of my code by relying on the sendMessage. However the callback function activates before the page has finished loading so in the case of a new tab, nobody receives and in the case of an existing tab the page that is being moved from is getting the message (but that isn't what I want). I've looked for other people asking about that problem with new tabs and there wasn't a clear answer, the best suggestion I've seen is to create a global variable and create a listener for tab loads and compare it against this global variable.
So the question is, is there a way to wait in the callback function until the page has loaded, or do I create an array of JS objects that contain the tab I'm waiting on and the information I want to send to that tab.
For reference here is the relevant code in the background javascript file.
chrome.tabs.sendMessage(tab.id, {info: "info"}, function(response)
{
//This line isn't used when I am navigating without changing tabs
chrome.tabs.create({url: response.info.linkUrl}, function(tab1)
{
chrome.tabs.update(tab1.id, {url: response.info.linkUrl}, function(tab2)
{
chrome.tabs.sendMessage(tab2.id, {info: "More Info"});
});
});
});
Otherwise I am able to confirm that all of my tab side code works, once my sendMessage was delayed enough for me to see that with my own eyes. My code is able to consistently make it past validation on the page being navigated away from, confirmed by checking document.url.
You can try injecting a second content script instead of a message.
It will execute in the same context as your other script.
Something along the lines of
chrome.tabs.executeScript(tab2.id,
{code: 'showInfo("More Info);', runAt: 'document_idle'}
);
where showInfo does the same as your message handler.
It's a bit of a hack and I'm not 100% sure the load order will be correct.
Other possible solutions are more complex.
For example, you can make the content script report back that it is ready and have a handler for that, for instance you can register a listener for onMessage in the background that waits for a message from that specific tab.id, sends "More Info" and then deregisters or disables itself.
Or, you could potentially switch to programmatic injection of your content script, which would let you control load order.

Chrome Extension - first link is auto-focused in popup

How do I stop my Google Chrome extension's default action to auto-focus the first link in my popup.html? I know I could probably do some roundabout hack with JS or change the :focus CSS, but I think this is throwing off something else I'm trying to do and I'd prefer to stop the root cause of it.
The easiest (and javascript free!) way is to simply add tabindex="-1" to any element which you don't want to receive automatic focus.
Perhaps auto-focus was intended for a convenience, but often it does a disservice. Since I see no way to stop the root cause, I found some roundabouts. One is using JavaScript. Chrome puts auto-focus after a short delay after displaying the popup. It's possible to unfocus it with blur() but unfocusing it too late will flash it momentarily, and trying to unfocus too early will do nothing. So to find the right time to unfocus is not easy, and this solution tries to do this several times during the first second after the popup is displayed:
document.addEventListener("DOMContentLoaded", function () {
var blurTimerId = window.setInterval(function() {
if (document.activeElement != document.body) {
document.activeElement.blur();
}
}, 200);
window.setTimeout(function() {
window.clearInterval(blurTimerId);
}, 1000);
});
Another pure HTML solution is to add tabindex="1" to the body tag.
Wrangling the initially focused element with a tabindex attribute is probably the best way to go, using:
tabindex="-1", as suggested by Paul Ferret to prevent an element from getting focus
tabindex="1", as suggested by link0ff, to specify which element should start with focus
If your situation is more complicated and you do want to bring in some javascript, I'd recommend using link0ff's solution, except, instead of trying to guess when to blur with timeouts, listen for the initial focus in event:
function onInitialFocus(event) {
// Any custom behavior your want to perform when the initial element is focused.
// For example: If this is an anchor tag, remove focus
if (event.target.tagName == "A") {
event.target.blur();
}
// ALSO, remove this event listener after it is triggered,
// so it's not applied to subsequent focus events
document.removeEventListener("focusin", onInitialFocus);
}
// NOTE: the "focusin" event bubbles up to the document,
// but the "focus" event does not.
document.addEventListener("focusin", onInitialFocus);
I don't believe the focus event is cancelable, so you can't just suppress the event.
Another easy alternative (which preserves "tabbability") is to just add an empty link () before your first actual link. It will invisibly "catch" the auto-focus from Chrome, but any users who want to tab through the links will still be able to do so normally.
The only minor downside of this approach is that it introduces a second "dead tab" when looping; that is, users will have to press tab three times to get from the last link back to the first, instead of just twice.
tabindex="-1" worked for me. I was adding autofocus to an input and it didn't work until I used this tabindex="-1" attribute for each link before the input.
Strange to say the least.
This is the best solution to the problem. The tabindex="-1" solution harms user experience, and as opposed to #link0ff's solution, this one removes the focus instantly.
This element should be the first focusable element in the DOM:
<button class="invisible-button"></button>
This simply removes the button once it's been focused:
function removeAutoFocus(e) {
if (e.target.classList.contains("invisible-button")) {
e.target.style.display = "none";
document.removeEventListener("focus", removeAutoFocus);
}
}
document.addEventListener("focus", removeAutoFocus, true);

xpages dojo validation textbox required

I have dojo validation textbox in my xpage with required property computed (compute dynamically) as below
var syn = getComponent("SynCboxGrp").getValue();
if (syn == "Yes")
{return true;
}else{
return false;
}
Here the component SynCboxGrp is radio button. The text box is required based on the radio button value. But its not working properly. Changing radio button value doesnt change the required behaviour.
Appreciate any help
Update
Thanks stwissel, Per Henrik Lausten and Eric.
I only have this ssjs in required property
<xe:djValidationTextBox id="UBOCAgent" value="#{dsRacDoc.UBOCAgent}" disableClientSideValidation="true">
<xe:this.required><![CDATA[#{javascript:var syn = getComponent("SynCboxGrp").getValue();
if (syn == "Yes")
{
return true;
}else{
return false;
}
}]]></xe:this.required>
</xe:djValidationTextBox>
I also tried this partial refresh code on my radio button onclick event XSP.partialRefreshGet("#{id:UBOCAgent}");
This still doesnt change the behaviour. It works based on the initial radio button value. On the negative side since its a get request it updates the field content from the server. I also tried Eric's suggestion disable client validation but that didnt help.
Eric, I am also trying to go with CSJS wherever possible but in this case the required property only has the SSJS option. So not sure how to try CSJS. Should i try creating my own dojo field instead of using one from entension lib? If so i am not sure how to compute required property for that. If you can help me with some sample code that would be great help.
Thanks for your time.
You're doing a partialRefreshGet, which is updating the Dojo Validation Text Box. But you never post back the updated value from the radio, so the server-side value for the radio button is still the initial value. Consequently, required is still false.
Check the markup when required is true on the Dojo Validation Text Box, but the validation triggers client-side, so there should be an attribute in the markup that you can manipulate via CSJS, if that is your preferred route.
Do you have particular latency issues that you need to work around? Picking up on this and the other question you have about doing a lookup to a database on click of a button, you're hitting a few problems. I don't know how experienced you are in XPages development, but it's not an approach I would recommend without a good understanding of client-side output and server-side component trees. The initial page load of your XPage will also be slower and the size of the HTML passed to the browser will be larger, because of the amount of CSJS passed to the browser.
I would recommend using the in-built partial refresh except for situations where browser to server network is a significant issue, and only then falling back to coding your own partial refresh posting a subset of data. In my experience, it's easier and quicker to develop, easier to debug and more flexible.
For this particular scenario, regardless of whether you code the partial refresh yourself or use inbuilt functionality, one point I'm not certain of is this. That once you set validation on the Dojo Validation Text Box, that validation runs client-side and may impact any attempt to un-set the required property by posting to the server. I haven't tested, so can't be certain.
One of two things:
as previously mentioned, to trigger your SSJS value change, ensure a partial (at least) refresh of the element containing your above code; can be done with a container element. Also, check to see if your field's disableClientSideValidation parameter is set (All Properties view).
convert your SSJS code (which merely returns a binary assessment of a Yes or otherwise, I'm assuming No, value) to CSJS
Of late, after the primary development of my current project, I have started favoring the CSJS approach, for the sake of decreasing server-side call backs; most especially in situations where display/rendering of a component is all I'm trying to achieve. If you go this route, remember that dojo.byId("#{id:myControlsServerNameHere}").value (for a text field, see below for scraping a radio button value) combined with setting the display or visible CSS properties can be very handy. As the docs describe, if you want it to exist on the page but not show (for formatting purposes, also, can preserve default value), go the visibility route, otherwise display property.
The CSJS scrape of radio values that I'm currently using is as follows:
var result=null;
for(i=0; i<document.forms[0].elements.length;i++){
if(document.forms[0].elements[i].name=="#{id:serverNameOfRadioElement}"){
if(document.forms[0].elements[i].checked == true){
result=document.forms[0].elements[i].value;
break;
}
}
}
//then handle your result, either with an if, or switch statement
Hope this helps. If there's more you're having trouble with, post back with more code to give the bigger picture.

XPages sometimes refresh and lose content

I hope someone can help me solve a very serious problem we face at the moment with a business critical application losing data when a user works in it.
This happens randomly - I have never reproduced this but the users are in the system a lot more than me.
A document is created with a load of fields on it, and there are 2 rich text fields. We're using Domino 8.5.3 - there are no extension lib controls in use. The document has workflow built in, and all validation is done by a SSJS function called from the data query save event. There is an insane amount of logging to the sessionscope.log and also this is (now) captured for each user in a notes document so I can review what they are doing.
Sometimes, a user gets to a workflow step where they have to fill in a Rich Text field and make a choice in a dropdown field, then they submit the document with a workflow button. When the workflow button is pressed (does a Full Update) some client side JS runs first
// Process any autogenerated submit listeners
if( XSP._processListeners ){ // Not sure if this is valid in all versions of XPages
XSP._processListeners( XSP.querySubmitListeners, document.forms[0].id );
}
(I added this to try and prevent the RTF fields losing their values after reading a blog but so far it's not working)
then the Server-side event runs and calls view.save() to trigger QS code (for validation) and PS code to run the workflow agent on the server.
95% of the time, this works fine.
5% of the time however, the page refreshes all the changes made, both to the RFT field (CKEditor) and the dropdown field are reloaded as they were previously, with no content. It's like the save hasn't happened, and the Full Update button has decided to work like a page refresh instead of a submit.
Under normal circumstances, the log shows that when a workflow button is pressed, the QuerySave code starts and returns True. Then the ID of the workflow button pressed is logged (so I can see which ones are being used when I am reviewing problems), then the PostSave code starts and finally returns true.
When there is a problem, The QuerySave event runs, returns true if the validation has passed, or false if it's failed, and then it stops. The ID of the workflow button is also logged. But the code should continue by calling the PostSave function if the QuerySave returns true - it doesn't even log that it's starting the PostSave function.
And to make matters worse, after the failure to call the PostSave code, the next thing that is logged is the beforePageLoad event running and this apparently reloads the page, which hasn't got the recent edits on it, and so the users loses all the information they have typed!
This has to be the most annoying problem I've ever encountered with XPages as I can find no reason why a successful QuerySave (or even a failure because mandatory fields weren't filled in) would cause the page to refresh like this and lose the content. Please please can someone help point me in the right direction??
It sounds as if in the 5% use cases, the document open for > 30mins and the XSP session is timing out - the submit causes the component tree to be re-created, and the now empty page returned back to the user. Try increasing the time out for the application to see if the issue goes away.
I would design the flow slightly different. In JSF/XPages validation belongs into validators, not into a QuerySave event. Also I'd rather use a submit for the buttons, so you don't need to trigger a view.save() in code. This does not interfere with JSF's sequence of things - but that's style not necessarily source of your problem.... idea about that:
As Jeremy I would as a first stop suspect a timeout, then the next stop is a fatal issue in your QuerySave event, that derails the runtime (for whatever reason). You can try something like this:
var qsResult = false;
// your code goes here, no return statements
// please and if you are happy
qsResult = true;
return qsResult;
The pessimistic approach would eventually tell you if something is wrong. Also: if there is an abort and your querySave just returns, then you might run in this trap
function noReturn() {return; } //nothing comes back!
noReturn() == true; --> false
noReturn() == false; --> false
noReturn() != false; --> true!!!!
What you need to check: what is your performance setting: serialize to disk, keep in memory or keep latest in memory? It could be you running foul of the way JavaScript libraries work.
A SSJS library is loaded whenever it is needed. Variables inside are initialized. A library is unloaded when memory conditions require it and all related variables are discarded. so if you rely on any variable in a JS Function that sits inside a SSJS library between calls you might or might not get the value back, which could describe your error condition. Stuff you want to keep should go into a scope (viewScope seems right here).
To make it a little more trickier:
When you use closures and first class functions these functions have access to the variables from the parent function, unless the library had been unloaded. Also functions (you could park them in a scope too) don't serialize (open flaw) so you need to be careful when putting them into a scope.
If your stuff is really complex you might be better off with a backing bean.
Did that help?
To create a managed bean (or more) check Per's article. Your validator would sit in a application bean:
<faces-config>
<managed-bean>
<managed-bean-name>workflowvalidator</managed-bean-name>
<managed-bean-class>com.company.WfValidator</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
</faces-config>
Inside you would use a map for the error messages
public Map<String,String> getErrorMessages() {
if (this.errorStrings == null) { // errorStrings implements the MAP interface
this.loadErrorDefinitions(); //Private method, loads from Domino
}
return this.errorStrings;
}
then you can use EL in the Error message string of your validators:
workflowvalidator.errorMessage("some-id");
this allows XPages to pick the right one directly in EL, which is faster than SSJS. You could then go and implement your own custom Java validator that talks to that bean (this would allow you bypass SSJS here). Other than the example I wouldn't put the notes code in it, but talk to your WfValidator class. To do that you need to get a handle to it in Java:
private WfValidator getValidatorBean() {
FacesContext fc = FacesContext.getCurrentInstance();
return (WfValidator) fc.getApplication()
.getVariableResolver()
.resolveVariable(fc, "workflowvalidator");
}
Using the resolver you get access to the loaded bean. Hope that helps!
My experience is that this problem is due to keeping page in memory. Sometimes for some reason the page gets wiped out of memory. I'm seeing this when there is a lot of partial refreshes with rather complex backend Java processing. This processing somehow seems to take the space from memory that is used by the XPage.
The problem might have been fixed in later releases but I'm seeing it at least in 8.5.2.
In your case I would figure out some other workaround for the CKEditor bug and use "Keep pages on disk" option. Or if you can upgrade to 9.0.1 it might fix both problems.

Resources