Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

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 an apex: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 of apex:repeat I would think it is. In that case, have I come across a bug or am I missing something really obvious?

share|improve this question
    
Apex:repeat and apex:column kind of "compete" in who/how gets rendered. With a simple example, try looking at the HTML source code to see what apex really prints out –  AmatorVitae Jul 23 at 13:49

3 Answers 3

up vote 4 down vote accepted

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.

share|improve this answer

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.

share|improve this answer
    
Thanks, but using this workaround causes the column headers not to be rendered. –  Alex Tennant Jul 23 at 11:17

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>
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.