Partial Refresh Repeat Control and Managed Bean running issue - xpages

I have a panel within an xPage set to partial refresh on a timer. As indicated by the Time fields the page outside the partial refresh panel does not refresh, however, a Managed Bean in a Repeat Control outside of the refresh panel initialises on each timer event. If I replace the Repeat Control with a ComputedField the Bean does not run. This is creating unnecessary load on the server as a lot of data is collected by the Managed Bean. The problem exist with a button event in addition to the timer.
The following is my test page. Any help is appreciated.
<xp:this.beforeRenderResponse><![CDATA[#{javascript:var time = new Date();
var timeString= ("0" + time.getHours()).slice(-2) + ":" +
("0" + time.getMinutes()).slice(-2) + ":" +
("0" + time.getSeconds()).slice(-2);
viewScope.put("time",timeString)}]]></xp:this.beforeRenderResponse>
<xp:scriptBlock
id="scriptBlockRefresh">
<xp:this.value>
<![CDATA[
setInterval(function()
{XSP.partialRefreshGet("#{id:refreshPanel}", {})},
10 * 1000);]]>
</xp:this.value>
</xp:scriptBlock>
<xp:panel id="refreshPanel">
<xp:text escape="true" value="#{classABean.classAText}"></xp:text>
<xp:text escape="true" value="#{viewScope.time}"></xp:text>
</xp:panel>
<xp:panel id="restOfPagePanel">
<xp:text escape="true" id="computedField4" value="#{classBBean.classBText}"></xp:text>
<xp:repeat id="repeat1" rows="30" value="#{classBBean.classBText}" var="rowData">
<xp:text escape="true" value="#{rowData}"></xp:text>
</xp:repeat>
<xp:text escape="true" value="#{viewScope.time}"></xp:text>
</xp:panel>
Test Managed Beans
import java.io.Serializable;
public class ClassA implements Serializable{
private static final long serialVersionUID = 1L;
private String classAText;
public String getClassAText() {
System.out.println("Running Class A");
classAText = "I am Class A";
return classAText;}
Same again for ClassB
FacesConfig
<managed-bean>
<managed-bean-name>classABean</managed-bean-name>
<managed-bean-class>uk.networkconnect.wallboardutils.ClassA</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
<managed-bean>
<managed-bean-name>classBBean</managed-bean-name>
<managed-bean-class>uk.networkconnect.wallboardutils.ClassB</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
Button code used for testing.
<xp:button value="Refresh Panel A" id="button1">
<xp:eventHandler event="onclick" submit="false" >
<xp:this.script><![CDATA[XSP.partialRefreshGet("#{id:refreshPanel}",{})
]]></xp:this.script>
</xp:eventHandler>
</xp:button>

Your code should work but there's a catch - I can't say if you have already taken care of it or not. The fact is that with JSF you must make sure you load the data only the first time and be defensive about the subsequent calls. In fact, the engine might invoke the same method twice during the life cycle.
By taking as example the mockup you provided the code should look like this:
public class ClassA implements Serializable {
private static final long serialVersionUID = 1L;
private String text;
public String getClassText() {
if (text == null) {
String name = getClass().getName();
System.out.println("Running class " + name);
text = "I am class " + name;
}
return text;
}
}
Basically you set text only when it evaluates to null. Once the variable is set the code won't go inside the block anymore, no matter how many calls the engine makes to the method.
The XPage doesn't need modification.
<xp:scriptBlock
value="
setInterval(function() {
XSP.partialRefreshGet('#{id:refreshPanel}');
}, 10 * 1000)" />
<xp:panel id="refreshPanel" style="background-color: red">
<xp:text escape="true" value="#{a.classText}" />
<xp:text escape="true" value="#{viewScope.time}" />
</xp:panel>
<xp:panel id="restOfPagePanel" style="background-color: grey">
<xp:text escape="true" id="computedField4" value="#{b.classText}" />
<xp:repeat id="repeat1" rows="30" value="#{b.classText}"
var="rowData">
<xp:text escape="true" value="#{rowData}" />
</xp:repeat>
<xp:text escape="true" value="#{viewScope.time}" />
</xp:panel>
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true"
execMode="partial" refreshMode="partial" refreshId="refreshPanel" />
</xp:button>

There is a difference between what's refreshed to the browser and what's processed on the server. refreshId covers what area of the XPage should have HTML passed back to the browser. But the HTML there may vary depending on updates made by the user elsewhere on the page. This is the purpose of the execId, which determines the area the server should process. The value property of components in the exec area still needs to get calculated during the lifecycle. If your bean's getter is just running code each time, as Shillem says, lazy loading (if null, set the value, else use the value) is a good approach. It's a significant benefit over SSJS approaches and standard in Java.
Another alternative, if the value in the bean never changes, is to set the value property of the repeat control to computed on page load - ${b.classText} instead of #{b.classText}.
There are additional settings on a repeat control that can also be set to handle non-changing content. repeatControls="true" sets the value of the repeat at page load and creates a copy of row's components in the component tree. By default, there will be one abstract row of components which will get iterated every refresh. removeRepeat="true" will remove the repeat control once all sets of components have been loaded into the component tree.

Related

Data handling with nested repeat controls

In my Application I, as a user, can create "items" through an XPage.
Those items have an undefined amount of entries in a list (a simple text list) which I create myself. Those entries will be used to generate checkBoxes later.
I have a list of customers. I can add multiple items to each customer. The items are displayed inside a repeat control. Each row has an inner repeat control for the checkBoxes (generated from the text list).
Now my problem is, that I'm not sure how to handle the data or more precisely the checking/unchecking of the checkBoxes because I have an undefined number of items and an undefined number of the checkBoxes inside the items plus the checkBoxes are editable.
I hope my issue is clear. Please let me know if there is need for clarification or if you need some code - even though I think the problem lies purely with the logic in my head.
EDIT:
The custom control that is used in the customer XPage the checkBoxes I mentioned above are used for `status.
As you will see below I simply get the search keys for the items and use them to find the text lists to create the checkboxes.
Since the items are merely templates that are used for every user that logs in, I can't use those documents to save the state of the checkboxes.
The only thing I can think of at this moment is to actually create a document for every item that is being added to the customer and use this document as the storage - but isn't there a better way?
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:styleSheet href="/item.css"></xp:styleSheet>
</xp:this.resources>
<xp:repeat id="repeat1" rows="30" var="rowData" indexVar="rowIndex" style="border: 1px solid #ccc;">
<xp:this.value>
<![CDATA[#{javascript:return currentForm.getItemValue("itemSearchkey");}]]>
</xp:this.value>
<xp:div>
<xp:button styleClass="accordion" id="button1">
<xp:this.value>
<![CDATA[#{javascript:
var itemTitle = "";
if(!"".equals(rowData) || rowData != null) {
itemTitle = itemBean.getItemTitle(rowData);
}
return itemTitle;
}]]>
</xp:this.value>
<xp:eventHandler event="onclick" submit="false">
<xp:this.script>
<![CDATA[
// ... some code for the accordion to work
]]>
</xp:this.script>
</xp:eventHandler>
</xp:button>
<xp:div styleClass="panel">
<xp:repeat id="repeat2" rows="30" var="rowDataStatus" indexVar="rowIndexStatus">
<xp:this.value>
<![CDATA[#{javascript:
var statusVector = null;
if(!"".equals(rowData) || rowData != null) {
statusVector = itemBean.getStatusList(rowData);
}
return statusVector;
}]]>
</xp:this.value>
<xp:checkBox id="checkBox1" checkedValue="true" uncheckedValue="false">
<xp:this.text><![CDATA[#{javascript:return rowDataStatus;}]]></xp:this.text>
</xp:checkBox>
</xp:repeat>
</xp:div>
</xp:div>
</xp:repeat>
</xp:view>
To create the list that is used for the checkBoxes I use a simple inputTextArea in a separate custom control. currentForm is the datasource:
...
<xp:inputTextarea id="statusList" value="#{currentForm.StatusList}"
multipleSeparator="#{javascript:#NewLine()}" rows="8">
</xp:inputTextarea>
...

How to do a partial refresh of multiple items in Xpages correctly

I have read many of the blog posts on doing a partial refresh on more than one element, but I cannot seem to get it to work.
I made a "toy" example, an Xpage with two fields and one button. Both fields are bound to a sessionScope counter. The button increments the counter and does a partial refresh on the containing panel.
I want to do a partial refresh on the first field, which is not in the panel [in my real Xpage the two fields are very far apart on the form.
Tried many different things to get this to work, but none worked. Is it even possible to do this?
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
xmlns:xe="http://www.ibm.com/xsp/coreex"
xmlns:xc="http://www.ibm.com/xsp/custom"
style="background-position:center left"
xmlns:on="http://www.openntf.org/domino/xsp" viewState="nostate"
xmlns:debug="http://www.openntf.org/xsp/debugtoolbar">
First Var<br></br>
<xp:text escape="true" id="computedField1" value="#{sessionScope.number}">
</xp:text>
<br></br>
<xp:br></xp:br>
<br></br>
Second Var<xp:panel id="pnl1">
<xp:text escape="true" id="computedField2" value="#{sessionScope.number}">
</xp:text>
<br></br>
<br></br>
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="pnl1" execMode="partial"
execId="pnl1">
<xp:this.action><![CDATA[#{javascript:var t = sessionScope.number + 1;
sessionScope.number = t;}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:panel>
</xp:view>
Read Knut's link and got this to work. I made a change to the event handler of the button and it worked. Here is the code:
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="pnl1" execMode="partial"
execId="pnl1">
<xp:this.action><![CDATA[#{javascript:var t = sessionScope.number + 1;
sessionScope.number = t;}]]></xp:this.action>
<xp:this.onComplete><![CDATA[XSP.partialRefreshPost("#{id:computedField1}");]]></xp:this.onComplete>
</xp:eventHandler>
Use XSP.partialRefreshPost(): http://avatar.red-pill.mobi/tim/blog.nsf/d6plinks/TTRY-84B6VP

How do I refresh a repeat that is updated using a dialog

I'm having a problem getting a repeat to refresh when the underlying value is changed using a dialog.
This is the div that contains the repeat:
<xp:div style="display:none;">
<xp:inputText id="linkages" value="#{procureDoc.Linkages}" multipleTrim="true" style="color:cornflowerblue;" multipleSeparator=";">
</xp:inputText>
</xp:div>
<xp:label value="Linkages:" id="linkageLabel" style="font-weight:bold;"></xp:label>
<xp:div id="linkageDiv">
<ul>
<xp:repeat id="linkagesDisplayRepeat" rows="30" var="rowData" indexVar="index" value="#{procureDoc.Linkages}">
<li>
<xp:text escape="true" id="computedField7">
<xp:this.value><![CDATA[#{javascript:rowData;}]]></xp:this.value>
</xp:text>
</li>
</xp:repeat>
</ul>
</xp:div>
Here's the save button from the dialog. It does happen to sit in another custom control, but I don't think that's the problem.
<xp:button value="Save" id="saveButton">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.script><![CDATA[var linkageSelection = document.getElementById("#{id:linkageComboBox}").value;
var linkageUpload = "";
if (linkageSelection == "Use Category") {
linkageUpload = document.getElementById("#{id:linkageCategoryComboBox}").value;
}
else { linkageUpload = linkageSelection;}
var currentLinkages = document.getElementById("#{id:linkages}").value;
if ( currentLinkages == "" ) {
document.getElementById("#{id:linkages}").value = linkageUpload;
} else {
document.getElementById("#{id:linkages}").value = document.getElementById("#{id:linkages}").value + ";" + linkageUpload;
}
XSP.closeDialog('#{id:linkageDialog}');]]></xp:this.script>
</xp:eventHandler>
</xp:button>
I can put a button on the XPage that just refreshes and it will refresh my repeat with the value selected in the dialog, but just using my save button doesn't do it.
I'm sure I'm missing something simple, but I just can't see it.
You could use the onComplete event from your SSJS eventhandler to call a CSJS script like
XSP.partialRefreshGet("#{id:linkagesDisplayRepeat}");
to refresh the repeat control.
There's a second parameter to the client-side closeDialog() method, denoting the ID of the component to partially refresh. That basically adds the onComplete for you.
SSJS corresponding method uses the ID of the component to subsequently refresh as the only parameter (obviously the method is called on the component being closed, which is the first parameter in CSJS).

Xpages Dynamic dojo dialog control

I have a list of products (created using a repeat control) and wish to click on a particular product and bring up a dialog with further information about that particular product. I don't really want to generate dijit.dialog thing for every single product on that page, so how can I do this dynamically possibly using AJAX and partial refresh.
A similar non xpages example can be seen here: http://www.replacementkeys.co.uk/window?dir=asc&limit=12&mode=grid&order=position - where you hover over an image and a quick view button comes up, which then dynamically loads the content for that product.
Any ideas would be truly appreciated.
We build the dialog outside the repeat control and then the action that launches or shows it also sets a viewScope variable that is used UNID for the data source in the dialog. Just make sure you refresh the contents of the dialog as you open it...
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
<xp:this.data>
<xp:dominoView var="promptView" viewName="dlgBoxes">
</xp:dominoView>
</xp:this.data>
<xp:panel>
<xp:repeat id="repeat1" rows="30" value="#{promptView}" var="promptEntry">
<xp:panel tagName="div">
<xp:text escape="true" id="computedField1" value="#{promptEntry.dlgName}">
</xp:text>
 
<xp:button value="details" id="button1">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:
var pe:NotesViewEntry = promptEntry;
viewScope.put("dlgDocUnid", pe.getUniversalID());
getComponent("dialog1").show();
}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:panel>
</xp:repeat>
</xp:panel>
<xe:dialog id="dialog1" keepComponents="false" partialRefresh="true">
<xe:this.title><![CDATA[#{javascript:
var unid = viewScope.get("dlgDocUnid");
if(!unid) return "";
var doc:NotesDocument = database.getDocumentByUNID(unid);
return doc.getItemValueString("dlgName");}]]></xe:this.title>
<xp:panel>
<xp:this.data>
<xp:dominoDocument var="dlgDoc" formName="dlgBox" action="openDocument">
<xp:this.documentId><![CDATA[#{javascript:viewScope.get("dlgDocUnid");}]]></xp:this.documentId>
</xp:dominoDocument>
</xp:this.data>
<xp:text escape="true" id="computedField2" value="#{dlgDoc.Title}">
</xp:text>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:text escape="true" id="computedField3" value="#{dlgDoc.dlg}">
</xp:text>
</xp:panel>
</xe:dialog>
</xp:view>
Happy coding
/Newbs
You can combine your repeat control with the Extension Library dialog control in order to be able to launch a dialog when the user clicks on the individual row. Chris Toohey has created an excellent article called Popup Dialog Forms from Views in XPages that demonstrates this.

How do I get the value of nth Edit box in a repeat control?

I have an EditBox control in a repeat control. Its iteration formula is:
return 5;
It is successfully displaying 4 edit boxes (the starting index is set to 1).
In SSJS, how can I get the value of the nth Edit Box?
You could set a sessionScope variable (or any scope variable) on the onchange event of the edit box and then in your SSJS reference the sessionScope variable. Here is some sample code, the bottom bit just shows your sessionScope variables on the page.
<?xml version="1.0" encoding="UTF-8"?>
<xp:repeat id="repeat1" rows="30" value="#{javascript:5}"
indexVar="rptIndex">
<xp:inputText id="inputText1">
<xp:eventHandler event="onchange" submit="true" refreshMode="complete">
<xp:this.action>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:sessionScope['text'+rptIndex] = getComponent("inputText1").getValue()}]]></xp:this.script>
</xp:executeScript>
</xp:this.action></xp:eventHandler></xp:inputText>
</xp:repeat>
<xp:table styleClass="debug">
<xp:tr>
<th>Variable Name</th>
<th>Variable Content</th>
</xp:tr>
<xp:repeat id="varRepeat" rows="30" value="#{javascript:sessionScope.keySet();}" var="scopeData">
<xp:tr>
<xp:td>
<xp:text escape="true" id="varName" value="#{javascript:scopeData}" />
</xp:td>
<xp:td>
<xp:text escape="true" id="varValue" value="#{javascript:sessionScope.get(scopeData)}" />
</xp:td>
</xp:tr>
</xp:repeat>
</xp:table>
</xp:view>
When you add a submission to an onChange event you create a rather chatty application - might bite you. The solution for setting the focus is rather different. First: focus is a client side operation, so you need a client script that 'knows what control is the first failure. The good news: XPages adds to all fields that failed a server side validation the attribute aria-invalid=true.
So you can use a dojo.onLoad script that queries that and sets the focus to the first member of the result. See also http://dontpanic82.blogspot.com/2011/07/xpages-styling-invalid-field.html
And for the query syntax:
http://dojotoolkit.org/reference-guide/1.7/dojo/query.html
Repeats are fun to deal with to say the least. If you look at examples in the teamroom template mobileThread custom control you'll see a repeat for displaying a list of replies, you'll also notice a lot of javascript to go along with it as for example running script on one button click would run on all buttons in the repeat.
If you are looking for the validation problem stwissel's solution looks the best. If this is something else and at some point you just need the value of any given edit box, maybe you should think about something like:
var domEl = dojo.byId(' <repeatControlId> ');
var textBoxes = domEl.getElementsByTagName("input");
var certainValue = textBoxes[3].value;
Now certainValue contains the value of a given edit box.
haven't tried this out, might need a little tweaking but the general idea should work I would think.
added another comment so i could add code.
Did a quick test and works fine for me, see my example below. Hope it helps. Try adding some print outs to see is it getting each bit.
<xp:repeat id="TestRepeat" rows="100" var="rowData"
indexVar="commentIndex" first="0" rendered="true">
<xp:this.value><![CDATA[#{javascript:
var dataArray = new Array();
dataArray.push(" Test");
dataArray.push(" Test");
dataArray.push(" Test");
dataArray.push(" Test");
dataArray.push(" Test");
return dataArray;
}]]></xp:this.value>
<xp:panel>
<xp:label value="Test"></xp:label>
<xp:inputText id="inputText1" value="Test" defaultValue="Test">
</xp:inputText>
<xp:br></xp:br>
</xp:panel>
</xp:repeat>
<xp:button value="Test" id="button1">
<xp:eventHandler event="onclick" submit="false">
<xp:this.script>
<xp:executeClientScript>
<xp:this.script><![CDATA[
var domEl = dojo.byId('#{id:TestRepeat}');
var textBoxes = domEl.getElementsByTagName("input");
alert( "Value 1: " + textBoxes[0].value);
]]></xp:this.script>
</xp:executeClientScript>
</xp:this.script>
</xp:eventHandler>
</xp:button>

Resources