13

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?

1
  • 1
    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 Jul 23, 2014 at 13:49

3 Answers 3

9

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.

5

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.

1
  • 1
    Thanks, but using this workaround causes the column headers not to be rendered. Jul 23, 2014 at 11:17
3

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>

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .