Passing a map to a custom component
Updated 17/04/2018:
How this answer has remained for so long whilst being incorrect I have no idea, but the correct answer should be:
Use type="map"
not type="Map"
.
Old Answer:
As far as I'm aware there is no way to create a map directly in Visualforce, you will have to get a Controller/Extension involved at some point.
In terms of using a Controller or an Extension, passing Maps to Components is a tricky subject... in that I've never gotten it to work personally (even though according to the documentation it should be simple), nor have I ever seen a working example.
The documentation states:
The type attribute defines the Apex data type of the attribute. Only the following data types are allowed as values for the type attribute:
- Maps, specified using type="map". You don’t need to specify the map’s specific data type.
Based on that, the simplest example of this is demonstrated below.
BasicMapComponent
<apex:component >
<apex:attribute name="map" description="aMap" type="Map" />
</apex:component>
BasicMapPage
<apex:page controller="BasicMapPageController">
<c:basicmapcomponent map="{!aMap}"></c:basicmapcomponent>
</apex:page>
BasicMapPageController
public class BasicMapPageController
{
public Map<String, String> aMap {get;set;}
}
However, this doesn't work. If you try and use this sample you'll get the following error:
Error: Wrong type for attribute
<c:basicmapcomponent map="{!aMap}">
. Expected Map, foundMAP<String,String>
Apparently this is a long-standing bug with the platform (all of the references to it are from 2011) and based on it's age is unlikely to be fixed any time soon.
So far, there are only two viable methods I've come across to get the behaviour you're after. In terms of picking between them it comes down to if you want your clients to use clicks vs. code to configure your component.
Solution 1 (Clicks)
If you want to keep the configuration of your component completely click based and not require your clients to write any Apex then you'll have to create a custom object/setting to emulated a map, which you can then query in your components controller.
Something like the following could potentially work, although it limits you to only one set of parameters for all instances of your component in an org (you could make the key a compound between the page name and the actual key value, alternatively don't enforce unique values and trust your clients - probably not the best plan):
MapCustomObject__c
- Key__c (String, Unique values only)
- Value__c (String)
MapComponentController
public class MapComponentController
{
private Map<String, String> aMap;
public MapComponentController()
{
List<MapCustomObject__c> mapObjects =
[
SELECT
Key__c, Value__c
FROM
MapCustomObject__c
];
// Populate the aMap variable here
}
}
Solution 2 (Code)
If you don't mind clients of your component needing to write a little Apex then the easiest method is to add a wrapper class which your clients need to initialize and pass into your component.
MapWrapper
public class MapWrapper
{
private Map<Object, Object> theMap;
public MapWrapper()
{
theMap = new Map<Object, Object>();
}
// Expose Map methods from your wrapper here, I'll do get() and values() as an example
public Object get(Object key)
{
return theMap.get(key);
}
public List<Object> values()
{
return theMap.values();
}
// Etc, etc...
}
You can then update your component to accept an attribute of type MapWrapper
instead of Map
and you should be good to go.
MapComponent (note the type attribute has changed)
<apex:component >
<apex:attribute name="mapWrapper" description="aMapWrapper" type="MapWrapper" />
</apex:component>
MapPage
<apex:page controller="MapPageController">
<c:mapcomponent mapWrapper="{!aMapWrapper}"></c:basicmapcomponent>
</apex:page>
MapPageController
public class MapPageController
{
public MapWrapper aMapWrapper {get;set;}
// Your clients will have to handle building the MapWrapper
}
Obviously you can give your MapWrapper
a nicer name, probably something along the lines of MyComponentNameOptions
or something similar to make it less obvious to your clients what is going on (i.e. that you are using a work-around).
I would prefer better to user the generic 'Object' type and in the controller type cast it accordingly.
I got same error when saving the VF page that uses the component. Then I modified the apex:attribute parameter type as type="map" from type="Map" and it worked.