1
votes

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();    
}
1
Could you please post a code snippet so that we can take a look?MarkyRoden
I replaced the repeat control values with view entry collections. The same issue is still there. But one thing is clear now. it is not the java code that is causing the issue, but it has something to do with the repeat controls.Reevs

1 Answers

3
votes

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