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

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.

Related

How to update bean in database on a RequestScoped view?

First of all, sorry for my english. I have a RequestScoped ManagedBean in order to send parameters to other views, without getting the The scope of the object referenced by expression is shorter than the referring managed beans error. I also have in the same RequestScoped view a p:dataTable showing these beans objects, with an update button for each row, that retrieves this bean to another form in the same view to be update with new values.
The problem is, when I hit the submit button to record the new values, another record is created, instead of the older one being updated. Of course, because the bean is killed when the submit button is pressed (RequestScoped), creating a new bean and another record in the DB. How can I fix it in this scope?
I've seen some alternatives using #PostConstruct here, however I'm not entirely sure it would solve my specific problem.
EDIT:
After researching a bit more into this topic, I came to another doubt: I am using the same Bean in both views (in my case, ProjectBean), should I create a new Bean with RequestScoped annotation (something like ProjectIdBean), set the older one to ViewScoped (so I can reproduce updates naturally on my Database), and let this new Bean handle the requests for other views?
Submit button:
<p:commandButton value="Gravar" action="#{projetoBean.gravar}"
process="#form" update="#form :formTabelaProjetos:tabelaProjetos" />
'Gravar' method:
public void gravar() {
System.out.println("Gravando projeto " + this.projeto.getNome());
ProjetoDAO dao = new ProjetoDAO();
if (this.projeto.getId() == null) {
dao.adiciona(this.projeto);
projetosAtivos = this.getProjetosAtivos();
} else {
dao.atualiza(this.projeto);
}
this.projeto = new Projeto();
}
You can use request scoped backing bean for updating entities. The problem is, that the request life cycle ends when your page is rendered. So anything you loaded will get discarded. The submit creates another request, that will try to reload resources, but it is a different request than the previous one and for example request parameters often do not contain what the programmer expects. But this is what you found out already. There are two ways how to deal with the problem:
1) use simple getters and setters to set "String, Integer" and similar variables in your request scoped bean, that you use to reconstruct and modify the entity you want to update. It is not convenient for the programmer but request scoped beans save resources of your server.
2) change the scope of your backing bean. Session scope is not ideal, because it can stay in memory for a really long time. And you might realize you need to clean it up manually. Much better is ViewScoped bean as it allows you to work with the entities you loaded over several steps. It gets wiped out when the user leaves the page.
#javax.faces.bean.ViewScoped
#javax.faces.bean.ManagedBean
public class SomethingBean {
......
}

dataContext binding to data

I am trying to understand the dataContext better and tried creating a dataContext referencing a document.
<xp:this.dataContexts>
<xp:dataContext var="doc1">
<xp:this.value>
<![CDATA[#{javascript:
var db:NotesDatabase = sessionAsSigner.getDatabase("","privDb.nsf");
var adoc:NotesDocument = db.createDocument();
return adoc }]]>
</xp:this.value>
</xp:dataContext>
</xp:this.dataContexts>
I then tried using EL and javascript to bind fields on my xpage to the dataContext
<xp:inputText id="inputText2" value="#{doc1.lastname}"></xp:inputText>
<xp:inputText id="inputText1" value="${javascript:doc1.firstname}"></xp:inputText>
But when I save, it does't save anything.
<xp:button value="save" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:
print(doc1.getClass().getName() )
doc1.save();
}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
The print command showing me the class name is showing up as lotus.domino.local.Document
The document is saved to the database, but it has no values other than $UpdatedBy. I can't seem to bind fields to the edit boxes.
The reason I am going down this path is twofold, 1. I want to use sessionAsSigner so I can keep the database security on the remote db (privDb.nsf) at No Access for anonymous and default, and 2. I want to learn a little more about dataContext, data sources and binding. I have read the "easy" way of using a Public Document, using the $PublicAccess field, etc, which is the "Old School" Notes way, and yes, I could do it that way, but want to understand how to do it using dataContexts, if possible.
A dataContext is basically a scoped variable, scoped lower than viewScope but higher than requestScope, scoped to a component. That component can be an XPage, a Custom Control or a Panel (yes, dataContexts can be added to a Panel too).
Like other scoped variables on the page, a simple save action does not save dataContexts. If you want a variable that's create-able and save-able, that's the Data Object. That has specific createObject and saveObject properties, where you define what should happen when they're called by the XPages runtime.
Similarly, like the other scoped variables, it needs to be serialized, so you cannot store a Domino object in them. So you can't store a NotesDocument in them. You need to wrap a normal Java object around a NotesDocument. With a greater understanding of XPages it becomes apparent that's what the dominoDocument datasource is doing (creating properties for all fields on the document, storing its note id, UNID, adding other properties like whether it's in edit mode or new etc).
A final point, as Jesse says, dataContexts are re-evaluated multiple times during a partial refresh. I haven't re-tested recently, but under 8.5.3 dataContexts bound to an XPage or Custom Control were re-evaluated more than dataContexts bound to a Panel, so I'd recommend the latter.
The immediate problem that you're running into is that #{}-bound dataContexts are re-evaluated constantly, several times during a page load and, I believe, every time they're referenced. Generally, the rule of thumb with dataContexts (and don't get me wrong - I like them) is that they should either be extremely low-cost, like a quick mathematical calculation, or be ${}-bound. The latter wouldn't work here, though, since the document wouldn't survive past the first load.
The tack you may want to try is to use a dataContext like this:
<xp:dataContext var="docData" value="${javascript: new java.util.HashMap() }" />
Basically, using a simple object as a holding pen. Then, in the save action, create the new document and set all of the values from "docData" there, like:
var db = sessionAsSigner.getDatabase("", "privDb.nsf");
var doc = db.createDocument();
doc.replaceItemValue("firstname", docData.get("firstname"));
doc.replaceItemValue("lastname", docData.get("lastname"));
doc.save();
There are a few caveats to this approach:
Each save will create a new document, rather than editing the existing one. You could change this by storing the UNID back into the map and fetching it from the DB, though
It should work as-is in this case, but you'll have to be mindful of data types. For example, if you have a multi-value control, then the XPages runtime will probably create an ArrayList, which you would have to convert to a Vector for storage unless you're using the OpenNTF Domino API
And as a final note, that binding you have for firstname is almost definitely not what you want. That may just be an artifact of the testing you were doing, but I'd be remiss if I didn't mention it.

XPages event code executing twice rather then once

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)

index of jsf component that fired event

This has to be a dumb question, but I can't seem to find the right keywords to google on: I have an action listener that can receive an event from any one of multiple checkboxes that were all generated from the same line of jsp in a dataTable. How can I tell from the action listener which one issued the event?
In particular, I need the index of the component so I can map it to an ordered list in the model. I know I can get the UIComponent object, and from there I can get the client ID of the component. And knowing that the client ID has the component's index embedded in it, yes I could do the sleazy thing, and parse the index from the client ID. But I know that would be a horrible, fragile and unmaintainable hack.
What's the right way to do this?
After an initial search, I think this could help you.
http://illegalargumentexception.blogspot.com/2009/02/jsf-working-with-component-ids.html
Have you tried to use f:param in addition to the checkbox values to pass custom parameters, so that would be more cleaner than working with ID's to manipulate business logic. ID.
Using the DataTables var attribute, you should be able to do this
<h:dataTable ... var="currentRow">
....
<h:selectBooleanCheckbox ... actionListener="#{blah.doThis}">
<f:attribute name="curRec" value="#{currentRow}" />
</h:selectBooleanCheckbox>
bean:
public void doThis(ActionEvent ae)
{
TreeMap myMap = (TreeMap)ae.getComponent().getAttributes().get("curRec");
...
}
Edit: The binding variable of your datatable should have the method getRowIndex();. That should give you the index of the record that caused the event in the table. I'm referencing an ICEfaces project, so I apologize if that isn't correct. Let me know, thx.

JSF validation error, lost value

I have a update form, with composite keys All composite keys are displayed in outputbox as I have hidden field for each composite keys. These outputbox values are empty after validation error. How do I resolve this. I am on the same page so doesn't it has to have the values.
This is indeed a non-intuitive behaviour of the h:inputHidden (I've ever filed a issue against it at the Mojarra issue list, but they didn't seem to do anything with it). The whole problem is that the component's value unnecessarily is also taken into the entire validation cycle while there's no means of user-controlled input. It will get lost when the validation fails. There are at least three ways to fix this non-intuitive behaviour.
First way is to use the binding on the h:inputHidden instead:
<h:inputHidden binding="#{bean.hidden}" />
This way the value won't undergo the unnecessary validation cycle. This however requires changes in the way you get/set the values in the backing bean code. For example:
private HtmlInputHidden hidden = new HtmlInputHidden(); // +getter +setter.
public void setHiddenValue(Object hiddenValue) {
hidden.setValue(hiddenValue);
}
public Object getHiddenValue() {
return hidden.getValue();
}
Second (and IMHO the preferred way) is to use Tomahawk's t:saveState instead.
<t:saveState value="#{bean.property}" />
The major advantage is that you don't need to change anything in the backing bean code. It will restore the value early before the apply request values phase. You only need to add extra libraries if not done yet, but as Tomahawk provides much more advantages than only the t:saveState, such as the in basic JSF implementation missing components/features t:inputFileUpload, t:dataList, t:dataTable preserveDataModel="true", t:selectOneRadio layout="spread" and so on, it is worth the effort.
The third way is to store it in a session scoped bean, but you actually don't want to do that for request scoped variables. It would only give "wtf?" experiences when the enduser has multiple tabs/windows open in the same session.

Resources