XPages event code executing twice rather then once - xpages

Here’s what I want to do:
On an XPage I want to have an edit box control that calls some server-side code (Java bean or SSJS) when the user presses enter. The main use case for this is that this page will be run from an an iPad and a bluetooth barcode scanner is attached. This scanner emulates a keyboard. The user will tap into the edit box. They will then scan with the scanner and the barcode will be entered along with an “enter” key. The edit box needs to be immediately cleared for the next scan and the value of the scan needs to be processed. The processing will do various backend things to a java bean and then return a message to be displayed on the page. Basically one of these:
1. Item not found, 2. Item is available and assigned, 3. Item is already assigned but you can have it anyway 4. Item Unavailable
I have code for this and overall the core code is working fine. I’ve normally used the onchange event but since I had this problem I moved to the onkeypress event. That was as much an attempt to get it to run in IE. Again the goal is only for the iPad/Safari, but I’d love to get it working on desktop browsers as well. I’m currently testing only with Chome.
In the CSJS piece of the edit box I have this code:
var e = dojo.fixEvent(thisEvent);
if (e.keyCode == dojo.keys.ENTER) {
return true;
} else {
return false;
}
The thinking is that I only want to call the SSJS on Enter.
Here’s the SSJS code that clears the edit box, processes the value and then sets a couple viewScope variables.
// Get the value off the page and put it in to the key variable
var key:String = getComponent("scanBox").getValue();
// clear the scanbox so it's ready for the next scan.
getComponent("scanBox").setValue("");
//Managed Bean to hold the last scan value - since the scanbox is cleared we do want to show the value
Scan.setLastScan(key);
// Managed Bean to process the item.
AddToJob.processItem(key);
viewScope.put("vsShowAssignPanel", true);
viewScope.put("vsShowMessagePanel", false);
The SSJS code is set to partially update the main content of the page. I’m using bootstrap and am trying to refresh everything on the page except a top and bottom nav bar. The editbox itself IS in the partial refresh zone since I’m clearing the value after the scan.
Finally here’s the problem:
The SSJS code of the button is being run TWICE. Not all for some reason but maybe 90% of the time. I’m not clicking twice. The way I test is by using Chrome on my desktop and I paste a value into the field and press enter once on the keyboard.
If I’m trying to scan in and assign an Item with a barcode. It first runs fine. the it updates the objects and saves a field to the backend NotesDocument to mark it assigned. Just want I want. But the second run through hits and it’s deterring that the item is ALREADY assigned and giving me back the wrong message.
After a lot of trial and error I’m fairly certain that something in the JSF lifecycle is causing this to run twice. Why I have no idea. I have no idea how to get it once.
The only good workaround that I’ve found so far is in the “AddToJob.processItem” method. “AddToJob” is a managed bean in the viewScope. In there I store and keep the last barCode. Then I do a check to see of the value is the same. If so then I assume it’s on the second run and stop processing.
I’ve used this basic concept of calling code in the on change event of an edit control for years. But that was inside XPages Mobile Controls and sometimes things do behave a little differently in there. I’m trying to re-write this app to use Bootstrap rather then XPages Mobile controls.
Below is the full XML markup of the edit control if that helps.
<xp:inputText id="scanBox" styleClass="newTarget">
<xp:this.attrs>
<xp:attr name="placeholder" value="Tap to Scan..." />
<xp:attr name="autocorrect" value="off" />
</xp:this.attrs>
<xp:this.dojoAttributes>
<xp:dojoAttribute name="autocorrect" value="off" />
</xp:this.dojoAttributes>
<xp:eventHandler event="onkeypress" submit="true" refreshMode="partial" refreshId="mainPanel">
<xp:this.script><![CDATA[var e = dojo.fixEvent(thisEvent);
if (e.keyCode == dojo.keys.ENTER) {
return true;
} else {
return false;
}]]></xp:this.script>
<xp:this.action><![CDATA[#{javascript:// Get the value off the page and put it in to the key variable
var key:String = getComponent("scanBox").getValue();
// clear the scanbox so it's ready for the next scan.
getComponent("scanBox").setValue("");
//Managed Bean to hold the last scan value - since the scanbox is cleared we do want to show the value
Scan.setLastScan(key);
// Managed Bean to process the item.
AddToJob.processItem(key);
viewScope.put("vsShowAssignPanel", true);
viewScope.put("vsShowMessagePanel", false);}]]></xp:this.action>
</xp:eventHandler></xp:inputText>
ANY information is appreciated. Thank you very much!!!

The Enter key is a special one because pressing enter can also submit the form you are currently in. In this case you actually have two POST requests hitting the server, one for the partial request and then another when the form is submitted.
What you need to do it prevent the enter key from submitting the form using an event.preventDefaults() call. Here is the updated CSJS
var e = dojo.fixEvent(thisEvent);
if (e.keyCode == dojo.keys.ENTER) {
e.preventDefault();
return true;
} else {
return false;
}
Now the form won't get submitted but the partial refresh will fire correctly.

David,
Not really a specific XPages solution but I had to do this in the Notes client - where a barcode was scanned in - it was processed and then the field was cleared and the next one could then be scanned in. The operator was not at the client (its a bluetooth scanner).
My solution and I admit was a bit nasty was to use a NotesTimer - whereby the field contents were checked to see if it was empty or if it had the same value as previously. To make sure I didn't catch a partial scan I did the check twice and if the value did not change then I processed it.
So after all that - couldn't you use a Javascript timer and follow the same logic?

Please forgive me if I'm missing something here, but it seems to me you have set up an unnecessarily complex design.
Why is CSJS or SSJS even needed?
Meaning, have you tried just some simple markup and a bean to do the processing?
Example Markup:
<xp:panel
id="panelbarcode">
<xp:inputText
id="barcode1"
value="#{MyBean.barcode}">
<xp:eventHandler
event="onchange"
submit="true"
refreshMode="partial"
refreshId="panelbarcode" />
</xp:inputText>
</xp:panel>
Example Bean Code:
public class MyBean implements Serializable {
private static final long serialVersionUID = 8541L;
// Zero-Argument Constructor
public MyBean() {}
public String getBarcode() {
// give out a barcode here,
// or return "" for an empty barcode
return "";
}
public void setBarCode(String barcode) {
// process the scanned in barcode
}
}
(edited to get rid of stupid "onClick" event)

Related

How can I refresh the field in CSJS

How to refresh one field in csjs? the code is here:
<xp:repeat id="repeat1" rows="30" var="currentDetail" indexVar="detailIndex" value="#{LeaveBean.details}">
<xp:inputText id="leavefrom" value="#{currentDetail.subfromtime}">
<xp:eventHandler event="onblur" submit="false" refreshMode="partial" refreshId="repeat1">
<xp:this.script><![CDATA[
XSP.partialRefreshPost('#{id:view:_id1:repeat1:0:leavefrom}');
var detailIndex = document.getElementById("#{id:detailIndexText}").innerHTML;
myRPC.checkfromdate(detailIndex).addCallback(function(returnAlert){alert(returnAlert);});
]]></xp:this.script>
</xp:eventHandler>
</xp:inputText>
</xp:repeat>
what I want to do is call ssjs from csjs as you can see, but the strange thing to me is the click thing didn't work immediately, for example, when I first open the page, and click the input field, and enter "1" in it, it should be popup alert just when i left the field, but nothing happend, then I click this field again, and enter "2", then I left the field, but I just got the alert "1" on the secreen...sorry for my poor expressions...
so my question is I should refresh the field just when I left the field immediately, but why the XSP.partialRefreshPost not working? I tried to refresh repeat1, not working either
update 2015/06/13:
At last, I found view.postScript....so....
One obvious problem is the partialRefreshPost id won't work. You're combining #{id:...} which runs server-side to calculate the corresponding client-side ID. But the ID that you're passing is a client-side ID for the first row of the repeat. Even if that corresponded to a server-side component, you're trying to hard-code every row to map to a field in row 1 of the repeat ("repeat1:0").
Is there a reason for not using a normal partial refresh and using view.postScript in your SSJS to post CSJS back to the browser? You're triggering a partial refresh posting data back anyway. I'm not sure what the final line of your code is doing, but it seems like it's calling another server-side request to add a response. So the code seems to do two calls to the server, to trigger an alert. I don't know the design and what that subsequent rpc call is doing, but if it can be achieved in a single partial refresh, that would seem better.

XPages Managed Bean and Repeat Control issue: how to reload value?

For my XPage I developed some Managed Beans to keep data. The page itself contains a Repeat control, it's like this at the moment:
<xp:panel styleClass="form">
<xp:this.dataContexts>
<xp:dataContext value="#{javascript:PageData.getForm(compositeData.formName, compositeData.dataSource);}"
var="myFormData">
</xp:dataContext>
<xp:dataContext var="myFields">
<xp:this.value><![CDATA[#{javascript:return myFormData.getFieldsAsJSON();}]]></xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
<xp:table style="width:100%" cellspacing="1" cellpadding="0">
<xp:repeat var="thisfield" rows="#{javascript:compositeData.rows}" disableTheme="true" repeatControls="true"
disableOutputTag="true" value="#{myFields}">
The PageData object is a bean, it contains info about a form and its fields. The dropdown fields contain options, but they can be dynamically created. When that happens, as a result of a partial refresh, the form is redisplayed but... the repeat 'value' itself (myFields) isn't reloaded into the repeat, so new dropdown options aren't made available.
How can I make XPages reload the 'value' property?
UPDATE
More info, as requested...
From the FormData bean:
public Object getFieldsAsJSON() {
ArrayObject list = null;
try {
System.err.print("FormData: getFieldsAsJSON");
list = new ArrayObject();
for (Entry<String, FieldData> e : fields.entrySet()) {
FieldData field = e.getValue();
list.addArrayValue(field.getJSON());
}
} catch (Exception e) {
}
return list;
}
and from the FieldData bean:
public ObjectObject getJSON() throws InterpretException {
ObjectObject json = new ObjectObject();
json.put("name", FBSUtility.wrap(name));
json.put("label", FBSUtility.wrap(field.getLabel()));
json.put("options", FBSUtility.wrap(values));
return json;
}
IMHO the problem isn't in these beans. It's more an XPages issue, because for some strange reason the structure is only fetched once, and never again. Apparently, XPages considers the json to be static, which it isn't.
Before the bean-era, I had everything in SSJS, creating the JavaScript object on the fly, and it was reloaded every time. Why not anymore, what is the difference? Is there something to tell XPages that the repeat-value is 'stale' and should be re-read?
With your current code myFormData and myFields will be recalculated during every partial refresh as well. Is that necessary?
I'm not certain, but looking at what you're seeing, I suspect the repeat control needs the contents of the dataContexts at a particular phase of the XPages lifecycle. But the dataContexts will be recalculated at every phase of the XPages lifecycle, which may result in them being null when the repeat wants to use the values.
My best advice is to debug what's happening at which phases in the partial refresh. You'll probably need to change your code to work around what's calculated when.
I'm not convinced dataContexts set to be computed dynamically are a good approach unless you're using them for rendered properties or read-only data. If you're using a managed bean, lazy-loading the data and retrieving it directly from the bean may be a better approach, as well as making it easier to identify when it's being called.

Session Expiration Warning

I have an XPages app that is supposed to save the main doc (and do a couple of other things) when the user clicks a button on the dialog box. The dialog box is presented by a CSJS function which runs according to a setInterval command. The dialog box and all the programming that present it are on a Custom Control. I have no problem displaying the dialog box, but have been unable to come up with a way to save the underlying document. I have other dialog boxes on their respective XPages, and saving the doc with a click (and some SSJS) works fine, but the same process doesn't seem to work if the dialog box is on a CC. I have searched all over the web, including StackOverflow, and haven't been able to find just what I need. Any ideas as to what I am missing?
Here's the code from one of my more recent attempts:
<xp:button value="Continue" id="button1">
<xp:eventHandler event="onclick" submit="false" id="eventHandler1">
<xp:this.script>
<![CDATA[
if (intvlID2) {
clearInterval(intvlID2);
}
var b18 = dojo.query('[id$="button18"]')[0];
if (b18) {
b18.click();
} else {
location.reload(true);
}
XSP.closeDialog('#{id:WarningDialog}');
]]>
</xp:this.script>
</xp:eventHandler>
</xp:button>
As you can see, it clears the interval first, then looks for a button on the underlying form. I've been able to verify that it does, in fact, find the button, but nothing happens with the click() command. The button it finds includes this save command that doesn't appear to execute:
document1.getDocument(true).save();
The button works perfectly when clicked by mouse or tapped by finger, but not when 'clicked' programmatically by another button on a CC.
The currentDocument global variable can be used to access the nearest dominoDocument datasource. Alternatively, the Extension Library dialog box has an additional parameter when closing the dialog box, the id of the refresh area. Maybe you can use that to trigger a save, something like setting a requestScope variable on close, and in the refresh area having code that does a save if the requestScope variable is set and, after save, clearing the requestScope variable?

xpages how to force a partial refresh afterPageLoad

I asked this question a couple months ago, but there has been no response so I thought I would try again. I have a block of SSJS that manipulates a number of values. There are some computed fields that are impacted by these changes and they do not display correctly. I have a refresh button on the XPage and it does a partial refresh after which everything works fine. I need to somehow trigger a partial refresh on the a specific item (a panel in this case) as the last step in the afterPageLoad in the SSJS.
Any ideas?
Thanks
Refreshing Panels from SSJS without a button, link or something like that is a bit tricky.
But i think your problem is more located in the order in wich you are trying to compute your fields what about triggering your Code in a erlier event before those Fields are rendered, to avoid displaying the wrong values in the first place, maby post some example Code.
If you only want to refresh a the panel again after the page has bean loaded try this in the onClientLoad ClientEvent this could also fix it:
<xp:eventHandler event="onClientLoad" submit="false">
<xp:this.script><![CDATA[dojo.ready(function(){
XSP.partialRefreshGet('#{id:yourPanel}',{})
});]]></xp:this.script>
</xp:eventHandler>

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 :-)

Resources