I have a simple controller extension with has a method which returns a
Map<String, Map<String, Boolean>
.public class UserExtension { private final User theUser; public UserExtension(ApexPages.StandardController stdController) { this.theUser = (User)stdController.getRecord(); } public Map<String, Map<String, Boolean>> getMap() { Map<String, Map<String, Boolean>> theMap = new Map<String, Map<String, Boolean>>(); theMap.put('Outer 1', new Map<String, Boolean> { 'Inner 1' => true, 'Inner 2' => false }); theMap.put('Outer 2', new Map<String, Boolean> { 'Inner 1' => true, 'Inner 2' => true }); return theMap; } }
If I use this with an
apex:repeat
then everything works as expected:<apex:page standardController="User" extensions="UserExtension"> <apex:pageBlock > <apex:repeat value="{!Map}" var="outerKey"> {!outerKey} <apex:repeat value="{!Map[outerKey]}" var="innerKey"> {!innerKey} {!Map[outerKey][innerKey]} </apex:repeat> </apex:repeat> </apex:pageBlock> </apex:page>
However, if I then try and switch the outer
apex:repeat
to anapex:pageBlockTable
I get the following error on my page:Map key null not found in map
Error is in expression ‘{!Map[outerKey]}’ in component in page userpage<apex:page standardController="User" extensions="UserExtension"> <apex:pageBlock > <apex:pageBlockTable value="{!Map}" var="outerKey"> <apex:column value="{!outerKey}"/> <apex:repeat value="{!Map[outerKey]}" var="innerKey"> <apex:column headerValue="{!innerKey}"> {!Map[outerKey][innerKey]} </apex:column> </apex:repeat> </apex:pageBlockTable> </apex:pageBlock> </apex:page>
Is it possible to use nested maps with
apex:pageBlockTable
? Based on the behaviour ofapex:repeat
I would think it is. In that case, have I come across a bug or am I missing something really obvious?
Answer
Things often get a bit hairy when you start to use maps of maps (or mapception :D). You can represent tables of data in this way if you change your variable model.
Change the map of maps to be a list of a new inner class that contains simply the name of the inner property and a map of the outer properties to boolean values. Also add a method to retrieve a list of the column names, like so:
public class UserExtension
{
private final User theUser;
public UserExtension(ApexPages.StandardController stdController)
{
this.theUser = (User)stdController.getRecord();
}
public List<String> getColumns()
{
return new List<String>{'Outer1','Outer2'};
}
public List<innerClass> getMap()
{
List<innerClass> theMap = new List<innerClass>{};
theMap.add(new innerClass('Inner1', new Map<String, Boolean> { 'Outer1' => true, 'Outer2' => false}));
theMap.add(new innerClass('Inner2', new Map<String, Boolean> { 'Outer1' => true, 'Outer2' => true }));
return theMap;
}
public class innerClass
{
public String Name {get;set;}
public Map<String, Boolean> outers {get;set;}
public innerClass(String Name, Map<String, Boolean> outers)
{
this.Name = Name;
this.outers = outers;
}
}
}
With this in place, then your page code changes to the following:
<apex:page standardController="User" extensions="UserExtension">
<apex:pageBlock >
<apex:pageBlockTable value="{!Map}" var="innerKey">
<apex:column value="{!innerKey.Name}"/>
<apex:repeat value="{!columns}" var="col">
<apex:column headerValue="{!col}">
{!innerKey.outers[col]}
</apex:column>
</apex:repeat>
</apex:pageBlockTable>
</apex:pageBlock>
Your table should now generate as required.
Attribution
Source : Link , Question Author : Alex Tennant , Answer Author : Alex Tennant