'Map key null not found in map' when using apex:pageBlockTable
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.
Here is a work around for your problem.
Add null key in controller
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 });
theMap.put(null, new Map<String, Boolean> { 'Inner 1' => true, 'Inner 2' => true });
return theMap;
Do this modification inside VF page
<apex:page standardController="User" extensions="UserExtension">
<apex:pageBlock >
<apex:pageBlockTable value="{!Map}" var="outerKey">
<apex:column rendered="{! outerKey != null }" value="{!outerKey}"/>
<apex:repeat rendered="{! outerKey != null }" value="{!Map[outerKey]}" var="innerKey">
<apex:column rendered="{! outerKey != null }" headerValue="{!innerKey}">
{!Map[outerKey][innerKey]}
</apex:column>
</apex:repeat>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>
The page block will start working.
One workaround I have found (which keeps the column headers intact) so far is to add a getHeaders
method to my controller and change my apex:repeat
definition.
Whilst this works perfectly in this situation (since I know the size of the inner map will always be the same), it doesn't feel like a very clean solution.
Controller
public class UserExtension
{
private final User theUser;
public UserExtension(ApexPages.StandardController stdController)
{
this.theUser = (User)stdController.getRecord();
}
public List<String> getHeaders()
{
return new List<String>(getMap().values().get(0).keySet());
}
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 });
theMap.put('Outer 3', new Map<String, Boolean> { 'Inner 1' => true, 'Inner 2' => true });
theMap.put('Outer 4', new Map<String, Boolean> { 'Inner 1' => true, 'Inner 2' => false });
return theMap;
}
}
Page
<apex:page standardController="User" extensions="UserExtension">
<apex:pageBlock >
<apex:pageBlockTable value="{!Map}" var="outerKey">
<apex:column value="{!outerKey}"/>
<apex:repeat value="{!IF(outerKey != null, PermissionsMap[outerKey], Headers)}" var="innerKey">
<apex:column headerValue="{!innerKey}">
{!Map[outerKey][innerKey]}
</apex:column>
</apex:repeat>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:page>