Repeat control inside a repeat control: The hashmap seems to be overwritten - xpages

I have an outer repeat control that gets it's collection as a hashmap(variable name is hmOuterCollection). Inside this repeat control there is another repeat control which gets it's collection as a hashmap (variable name is hmInnerCollection) as well. The inner repeat controls collection is based on the outer repeat entry's key. What happens is, the inner repeat control entries seems to overwrite the previous entries as I click through the outer repeat entries.
For example, consider the following
Barcelona (outer entry, clicked first)
Messi
Xavi
Puyol
Manchester United (outer entry, clicked second)
Rooney,
xxx
Real Madrid (outer entry, clicked third)
Ronaldo
Kaka
After I expanded all these soccer teams, I go back and click the player named Messi. It prints the name Ronaldo on the server console. If I click the name Xavi, it prints Kaka.
I just cant figure what's going on here. I tried the "repeatControls" and "removeRepeat" properties also. No luck. Is this a java hashmap caching or something wrong with the repeat control behavior.
Please let me know if anyone has any idea.
Here is the XPage source
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.resources>
<xp:script src="/xpTestCacheIssue.jss" clientSide="false"></xp:script>
</xp:this.resources>
<xp:repeat id="repeat1" rows="30" value="#{javascript:getTeams()}"
var="entryTeam">
<xp:panel>
<xp:link escape="true"
text="#{javascript:entryTeam.getValue()}" id="lnkTeam">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="panelPlayers"
action="#{javascript:viewScope.teamID=entryTeam.getKey()}">
</xp:eventHandler>
</xp:link>
</xp:panel>
<xp:panel id="panelPlayers">
<xp:repeat id="repeat2" rows="30"
value="#{javascript:getPlayers(viewScope.teamID)}"
var="entryPlayer">
<xp:panel style="margin-left:20px;padding:10px">
<xp:link escape="true"
text="#{javascript:entryPlayer.getValue()}" id="lnkPlayer">
<xp:eventHandler event="onclick" submit="true"
refreshMode="partial" refreshId="selectedPlayer"
execMode="partial" execId="lnkPlayer">
<xp:this.action><![CDATA[#{javascript:viewScope.PlayerName=entryPlayer.getValue();}]]></xp:this.action>
</xp:eventHandler>
</xp:link>
</xp:panel>
</xp:repeat>
</xp:panel>
</xp:repeat>
<xp:panel id="selectedPlayer" style="border:1px solid green;padding:20px;background-color:yellow;font-weight:bold">
<xp:text escape="true" id="computedField1"
value="#{javascript:viewScope.PlayerName}">
</xp:text>
</xp:panel>
Here is the java code that gets the hashmaps for these repeats. There is a SSJS function that calls the java methods.
public Map<String,String> getSoccerTeams() {
Map<String,String> hmTeams=new HashMap<String,String>();
try {
ViewEntryCollection vec=vwTeams.getAllEntries();
ViewEntry ve=vec.getFirstEntry();
while (ve!=null) {
hmTeams.put(ve.getUniversalID(), ve.getDocument().getItemValueString("TeamName"));
ve=vec.getNextEntry(ve);
}
} catch (Exception e) {
e.printStackTrace();
}
return hmTeams;
}
public Map<String,String> getPlayers(String idPlayer) {
HashMap<String,String> hmPlayers=new HashMap<String,String>();
try {
View vwPlayers=this.dbCur.getView("playersview");
DocumentCollection dc=vwPlayers.getAllDocumentsByKey(idPlayer, true);
Document doc=dc.getFirstDocument();
while (doc!=null) {
hmPlayers.put(doc.getUniversalID(), doc.getItemValueString("PlayerName"));
doc=dc.getNextDocument(doc);
}
} catch (Exception e) {
e.printStackTrace();
}
return hmPlayers;
}
Here is the SSJS code that calls the java methods.
function getTeams() {
var Teams=new LoadTeams();
var hmTeams=Teams.getSoccerTeams();
return hmTeams.entrySet();
}
function getPlayers(playerID) {
var Teams=new LoadTeams();
var hmPlayers=Teams.getPlayers(playerID);
return hmPlayers.entrySet();
}

Your problem is here:
value="#{javascript:getPlayers(viewScope.teamID)}"
It should rather read like:
value="#{javascript:getPlayers(entryTeam.getKey())}"
the outer variable is available in the inner repeat. If you stuff code into a scope object it will refer to the last value in there.
And please recycle your Notes objects in the Java code. You also would want to go and cache the values in your bean, so you don't need to read through the view every time. Lastly you can eliminate the SSJS from the code and use just EL -> even faster
On EL:
Let's presume your Java Class is registered as the ManagedBean "Soccer" you can write instead of:
<xp:repeat id="repeat1" rows="30" value="#{javascript:getTeams()}"
var="entryTeam">
this one:
<xp:repeat id="repeat1" rows="30" value="#{Soccer.teams}"
var="entryTeam">
and instead of:
<xp:repeat id="repeat2" rows="30"
value="#{javascript:getPlayers(entryTeam.getKey())}"
var="entryPlayer">
you write:
<xp:repeat id="repeat2" rows="30"
value="#{Soccer.teams[entryTeam.key]}"
var="entryPlayer">
Since EL can deal with Maps, you actually only need the getTeams() function, no getPlayers() function required. You want to adjust your Java class a little to avoid reading the view over and over again:
public class SoccerHandler {
private Map<String,Collection<String>> allTheTeams = null;
public Map<String,String> getTeams() {
if (allTheTeams == null) {
this.loadTheTeams();
}
return allTheTeams;
}
private void loadTheTeams() {
// Add your own try/catch
this.allTheTeams = new HashMap<String,Collection<String>>();
View vwPlayers=this.getPlayersView(); // <-- Don't keep Notes objects in a BEAN
ViewEntryCollection vec=vwPlayers.getAllEntries();
String lastTeamName = "";
Collection<String> curTeam = null;
ViewEntry ve=vec.getFirstEntry();
while (ve!=null) {
Vector colVals = ve.getColumnValues();
String curTeamName = colVals.get(0); // 1st column = team
String curPlayerName = colVals.get(1); // 2nd column = player
if (!curTeamName.equals(lastTeamName)) { // New team found
if (curTeam != null) {
this.allTheTeams.put(lastTeamName, curTeam);
}
curTeam = new ArrayList<String>();
lastTeamName = curTeamName;
}
curTeam.put(curPlayerName);
ve=vec.getNextEntry(ve);
}
// Make sure we don't miss the last team
if (curTeam != null) {
this.allTheTeams.put(lastTeamName, curTeam);
}
ve.recycle();
vec.recyle();
}
// If you want to be able to force a re-read
public void reset() {
this.allTheTeams = null;
}
// more code here like getPlayerView that uses the resolver
}
Hope that helps

Related

Repeat control bound to array of arrays returning string in the repeat collection

I am attempting to display a subset of a view in a repeat control by getting a NotesViewEntryCollection and then looping through this collection to build an array for which each value in the array contains an array corresponding to the column values in the entry.
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:repeat id="repeat1" rows="30" var="rowData" indexVar="rowIndex">
<xp:this.value><![CDATA[#{javascript:
var view:NotesView = database.getView("CatalogEntries");
var entryColl:NotesViewEntryCollection = view.getAllEntriesByKey(compositeData.catalog, true);
if (entryColl.getCount() == 0) return ["empty"];
var entries = [];
var columnVals = [];
var entry:NotesViewEntry = entryColl.getFirstEntry();
do {
columnVals = [];
columnVals.push(entry.getColumnValues()[0]);
columnVals.push(entry.getColumnValues()[1]);
columnVals.push(entry.getColumnValues()[2]);
columnVals.push(entry.getColumnValues()[3]);
columnVals.push(entry.getColumnValues()[4]);
columnVals.push(entry.getColumnValues()[5]);
entries.push(columnVals);
entry = entryColl.getNextEntry(entry);
} while(!(entry == null))
return entries;}]]></xp:this.value>
<xp:text escape="true" id="computedField1" value="#{javascript:rowData[rowIndex][0]}"></xp:text>
</xp:repeat>
</xp:view>
But I am getting an error at this line:
<xp:text escape="true" id="computedField1" value="#{javascript:rowData[rowIndex][0]}"></xp:text>
The error is:
Unknown member '0' in Java class 'java.lang.String'
Any ideas on how I can fix this?
I feel that this can - no should! - be simplified. The basic idea is that you can either feed a Domino view datasource or your entire NotesViewEntryCollection object as it is into your repeat object. This way you end up with rowData representing a single NotesViewEntry object. Your computedField value can then directly reference any element from the entry's columnValues Vector. This way you don't even need to bother recycling any objects:
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoView var="myView" viewName="CatalogEntries" keys=compositeData.catalog keysExactMatch="true">
</xp:dominoView>
</xp:this.data>
<xp:repeat id="repeat1" rows="30" var="rowData" value="#{myView}">
<xp:panel id="pnInner">
<xp:text escape="true" id="computedField1">
<xp:this.value><![CDATA[#{javascript:
if(rowData){
return rowData.getColumnValues()[0].toString();
}else{
return "empty";
}}]]>
</xp:this.value>
</xp:text>
</xp:panel>
</xp:repeat>
</xp:view>
Filtering of your view data is done at the datasource level.
In your push method you do not pass in the array index.
columnVals.push(entry.getColumnValues()[0]);
should be
columnVals.push(entry.getColumnValues());
Howard

Xpages REST Service does not recompute view name hen partialRefresh is done on parent panel

Want to check if rest service control does not honour context.getSubmittedValue() for viewName property?
REST Service view name is not being computed when partialRefresh is done on parent panel.
I am trying to load different views (DataTable used for display) and need to work with JSON object. However, when I click on accordion entry which updates a panel in another custom control, I can see submitted value changing using computed field but my REST service control will still return old data. Here is the code should you want to have a look:
<?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">
<xp:panel id="panelMiddle">
<xp:text escape="true" id="computedField1" value="#{javascript:context.getSubmittedValue()}"></xp:text>
<xe:restService id="restService1" pathInfo="data" preventDojoStore="true" state="false">
<xe:this.service>
<xe:viewItemFileService defaultColumns="true" contentType="application/json" compact="true" systemColumns="0">
<xe:this.viewName><![CDATA[#{javascript:var v = context.getSubmittedValue();
if (null == v){
v = application.getString("defaultView");
}
}]]></xe:this.viewName>
<xe:this.count><![CDATA[#{javascript:
var v = context.getSubmittedValue();
if (null == v){
v = application.getString("defaultView");
}
database.getView(v).getEntryCount();
}]]></xe:this.count>
</xe:viewItemFileService>
</xe:this.service>
</xe:restService>
<div id="demo"></div>
<xp:eventHandler event="onClientLoad" submit="false">
<xp:this.script><![CDATA[$(document).ready(function() {
$.ajax({
url:"New.xsp/data",
dataType:"json",
success:
function(data){
var colHeaders = [];
var tarr = [];
$.each(data.items[0], function (key, val) {
if(key.indexOf("#")==-1){
colHeaders.push({"title":key});
//colHeaders.push({"data":key});
}
});
$.each(data.items,function(v,i){
var temp = [];
$.each(i,function(k,vv){
if(k.indexOf("#")==-1){temp.push(vv)};
})
tarr.push(temp);
})
$('#demo').html( '<table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered table-hover" id="example"></table>' );
$('#example').DataTable( {
"data": tarr,
"columns": colHeaders
} );
}
});
});]]></xp:this.script>
</xp:eventHandler>
</xp:panel>
</xp:view>
After much trial with scope variables and computed fields, found out that only sessionScope works in this scenario. Not sure if this is an ideal solution as people may open more than one tab in a browser and it may cause conflicts.
Ideas are welcome.

Calculating and storing data on page level for use in custom-controls

I have to do some calculations which many of my controls needed, and i just want to do it once on every request and store it. (e.g. beforeRenderResponse)
I can not figure out a clean way to achieve this in a custom-control.
I do not want to use the dataContext, because i read some creepy article regarding its performance (Multiple calculations on a singe JSF-cycle).
See: http://hasselba.ch/blog/?p=1112
So i went to objectData, just to figure out, that it has the same problem than the compositeData - It is not available in any custom-control events but beforePageLoad and afterPageLoad.
See: http://hasselba.ch/blog/?p=1099
Current solutions
I got two (unsatisfying) solutions for that problem.
I marked the solutions with <!-- Solutions --> in the markup below.
Solution #1:
Abuses the property "value" of a xp:text to update the objectData.
Solution #2:
Requires to call the update() method manually.
Test-setup
XPage
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom">
<xc:dataControl />
</xp:view>
Custom-Control (dataControl)
<?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"
beforePageLoad="#{javascript:object1.sayHello('beforePageLoad')}"
afterPageLoad="#{javascript:object1.sayHello('afterPageLoad')}"
afterRestoreView="#{javascript:object1.sayHello('afterRestoreView')}"
beforeRenderResponse="#{javascript:object1.sayHello('beforeRenderResponse')}"
afterRenderResponse="#{javascript:object1.sayHello('afterRenderResponse')}">
<xp:this.data>
<xe:objectData var="object1">
<xe:this.createObject><![CDATA[#{javascript://
print("object1 is created");
return new ds.DataTest(); }]]>
</xe:this.createObject>
</xe:objectData>
</xp:this.data>
<!-- Solutions -->
<!-- Solution #1 -->
<xp:text id="computedField1" value="#{javascript: object1.update(); return ''; }" />
<!-- Solution #2 -->
<xp:button value="dataObject update" id="updateButton">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action><![CDATA[#{javascript:object1.update()}]]></xp:this.action>
</xp:eventHandler>
</xp:button>
<!-- /Solutions -->
<div>
Object1 current value:
<xp:text escape="true" id="output" value="#{object1.value}" />
</div>
<xp:button value="Update" id="updateButton2">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete" />
</xp:button>
</xp:view>
Java-class (ds.DataTest)
package ds;
import java.io.Serializable;
public class DataTest implements Serializable {
private static final long serialVersionUID = 1L;
private String value = "";
public DataTest() { /* Constructor */ }
public void sayHello(String text){
System.out.println("Hello from: " + text);
}
public void update() {
value = Double.toString( Math.random() );
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
What i like to do is
<xp:view beforeRenderResponse="#{javascript:object1.update()}">
but that seems not possible, see what happens with the sayHello() calls.
First request
Followup requests
I appreciate every idea or hint :)
An important design principle for any custom control is encapsulation. If you drag it onto a canvas (page, customControl) it should work without any dependency on existing conditions.
So parameters a control uses should be, well parameters. Define them as custom parameters in your control definition.
Then you have 2 options: use a SsJS object you keep in the viewScope or use a managed bean in the viewScope.
Let's say your controls need a parameter called color, then the control would be called like this:
<xc:myControl color="#{pageBean.color}"></xc:myControl>
In your bean you could do all the computations in the constructor which is called exactly once per page (that's what you want isn't it?) or lazy loaded when needed:
public synchronized String getColor() {
if(this.color==null) {
this.initColor();
}
return this.color;
}
Lazy loading only makes sense if the values don't interdepend.
Let us know how it goes.

XPages Managed Bean setter not firing on checkbox change

I've created a custom control that is basically a checkbox. I want the checkbox read the value of the dataSource I pass in - which would be a managed bean. I can get the checkbox field to read from the bean but I'm not seeing anything happen when I change the checkbox. It doesn't look like the setter in the bean ever gets called.
The key snippets of my bean are:
private boolean categoriesOn;
...
public boolean isCategoriesOn() {
System.out.println("Getting On Value");
return categoriesOn;
}
public void setCategoriesOn(boolean newValue) {
System.out.println("Setting On : " + newValue);
this.categoriesOn = newValue;
}
The control on the XPage looks like this:
<xp:checkBox id="flipSwitch"
styleClass="onoffswitch-checkbox"
value="${compositeData.dataSource}"
checkedValue="#{javascript:true}"
uncheckedValue="#{javascript:false}">
<xp:eventHandler event="onchange" submit="true"
refreshMode="complete">
</xp:eventHandler>
</xp:checkBox>
I pass the bean to the custom control with a custom property:
<xc:crtl_toggleSwitch
dataSource="#{exhibitorInfo.categoriesOn}"
refreshID="computedField6">
</xc:crtl_toggleSwitch>
dataSource is set to use Methodbinding.
I've tried with partial and full refresh. I'm just not sure what I need to do to get the changed value back into the bean.
thanks for any advice.
As indicated in Peter's answer on the question Per linked to, checkboxes cannot be bound directly to booleans (which is admittedly ridiculous). Add these methods:
public String getCategoriesOnAsString(){
return isCategoriesOn() ? "1" : "0";
}
public void setCategoriesOnAsString(String value){
setCategoriesOn("1".equals(value));
}
Then bind your checkbox to #{exhibitorInfo.categoriesOnAsString}, and set checkedValue and uncheckedValue to "1" and "0", respectively.

Managed property to an Object in a list from a bean

I would do something with a Managed Bean but I dont' find a solution
To explain what I will do I will show a small example:
I have created a Object Data with the following structure
public class Data implements Serializable{
private static final long serialVersionUID = 5156829783321214340L;
String value="";
public Data() {
}
public String getValue() {
return value;
}
void setValue(String data) {
this. value = data;
}
}
As you can see ist a simple dataholder with one property
now I created a secound Object whitch will be my bean it only holds a list of Data Objects
public class Databean implements Serializable{
private static final long serialVersionUID = 9205700558419738494L;
private ArrayList<Data> datalist;
public Databean()
{
datalist = new ArrayList<Data>();
Data newItem;
for (int i=0; i<5; i++) {
newItem = new Data();
datalist.add(newItem);
}
}
public ArrayList<Data> getDatalist() {
return datalist;
}
public void setDatalist(ArrayList<Data> datalist) {
this.datalist = datalist;
}
}
The Declaration in the Faces-config to publish the bean is no Problem
<managed-bean>
<managed-bean-name>managedBean</managed-bean-name>
<managed-bean-class>de.itwu.Databean</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
</managed-bean>
So now to my problem:
I would like to create a Managed Property or something else to make a connection to an inputtext
in a repreat control e.g:
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
does anyone have an Idea how this could work?
So exmaple corrected but it doesen't work the Ich ich set Datualt values in the Data-Object they are shown. But when I edit the values in the Inputtextfields they are not automatically written back to the Object. I Thing the Problem is the Daclaration in the Faces-Config. Ideas?
The variable assigned in the repeat to var (rowData) will contain an instance of your Data class. To bind each input control to the value field you refer to that property. Because you have a getValue() and setValue() defined a value binding will be created and you will be able to edit the content. If only a getValue() method is defined a method binding is created and the field will not be editable.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" value="#{rowData.value}"></xp:inputText>
</xp:repeat>
Your binding is wrong.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
rowData contains Data object, which populates getter/setter for field value, not datavalue.

Resources