[SalesForce] Passing SelectOption[] to MultiselectPicklist Custom Component results in Cannot convert the value of ‘{!leftOptions}’ to the expected type

I have a Map in custom controller as follows

Map<String, SelectOption[]> mapSelectOption;

and populate this Map as follows in constructor of custom controller

Schema.DescribeSobjectResult R = Account.sObjectType.getDescribe();

//Initialize map to store each object and its fields
mapSelectOption = new Map<String, SelectOption[]>();

//Fetch all child relations for Account
List<Schema.ChildRelationship> C = R.getChildRelationships(); 

Integer j = 0;
//Iterate through all Child objects
for(Schema.ChildRelationship ch : C){

    //In case child object is a custom setting ignore it
    if(!ch.getChildSObject().getDescribe().isCustomSetting()){
        //Describe Each child object, add object name and corresponding fields to a map
        Map<String, Schema.SobjectField> fields = (ch.getChildSobject()).getDescribe().fields.getMap();

        SelectOption[] tempList = new SelectOption[fields.size()];
        Integer i = 0;
        for(Schema.SObjectField s : fields.values()){
            Schema.DescribeFieldResult fieldDescribe = s.getDescribe();
            tempList[i] = new SelectOption(fieldDescribe.getName(),fieldDescribe.getName(), false);
            i++;
        }
        if(i > 0) {
            mapSelectOption.put('key'+j,tempList);
        }
        j++;
    }
}

I am using a custom component from this repo.

This custom component has two attributes of SelectOption[] as type

<apex:attribute name="leftOption" description="Options list for left listbox." type="SelectOption[]" required="true" assignTo="{!leftOptions}" />

<apex:attribute name="rightOption" description="Options list for right listbox." type="SelectOption[]" required="true" assignTo="{!rightOptions}" />

and here is controller code from where the assignTo will call the setter method of leftOptions.

/*
 * MultiselectController synchronizes the values of the hidden elements to the
 * SelectOption lists.
 */
public with sharing class MultiselectController {

    // SelectOption lists for public consumption
    public SelectOption[] leftOptions { get; set; }
    public SelectOption[] rightOptions { get; set; }

    // Parse &-separated values and labels from value and 
    // put them in option
    private void setOptions(SelectOption[] options, String value) {
        options.clear();
        String[] parts = value.split('&');
        for (Integer i=0; i<parts.size()/2; i++) {
            options.add(new SelectOption(EncodingUtil.urlDecode(parts[i*2], 'UTF-8'), 
              EncodingUtil.urlDecode(parts[(i*2)+1], 'UTF-8')));
        }
    }

    // Backing for hidden text field containing the options from the
    // left list
    public String leftOptionsHidden { get; set {
           leftOptionsHidden = value;
           setOptions(leftOptions, value);
        }
    }

    // Backing for hidden text field containing the options from the
    // right list
    public String rightOptionsHidden { get; set {
           rightOptionsHidden = value;
           setOptions(rightOptions, value);
        }
    }
}

Now, I try to use this component in my Visualforce page as follows

<apex:repeat value="{!mapSelectOption}" var="mapKey">
    <c:MultiselectPicklist leftLabel="Available fields" leftOption="{!mapSelectOption[mapKey]}" rightLabel="Selected fields" rightOption="{!mapSelectOption[mapKey]}" size="14" width="150px"/>
</apex:repeat>

It results in following error

Cannot convert the value of '{!leftOptions}' to the expected type.

Also, I have tried following code to see if the Map is populated correctly and I see the results as per the attached image.

<apex:repeat value="{!mapSelectOption}" var="mapKey">
    <apex:outputLabel>{!mapKey}</apex:outputLabel>
    <apex:selectList size="10">
        <apex:selectOptions value="{!mapSelectOption[mapKey]}"/>
    </apex:selectList>
    <br />
</apex:repeat>

enter image description here

PS: I want to understand what is the difference between passing {!mapSelectOption[mapKey]} to the custom component and passing the same to value attribute of an apex:selectOptions tag where the later accepts it and the former results in error.

Any help is much appreciated.

Best Answer

There certainly seems to be some weird bug going on to do with generic types (e.g. using a list or map). After a bit of experimentation I found a workaround, and that is to use a wrapper class as follows:

public class OptionsList {
    public List<SelectOption> options { get; set; }

    public OptionsList() {
        this.options = new List<SelectOption>();
    }

    public void push(SelectOption opt) {
        this.options.add(opt);
    }
}

At the top of your custom controller you can now create a list of this wrapper class:

public List<OptionsList> optionsLists { get; set; }

And your page can iterate over this as follows:

<apex:repeat value="{!listOptions}" var="ol">
        <c:MultiselectPicklist leftLabel="Available fields" leftOptions="{!ol.options}" rightLabel="Selected fields" rightOptions="{!ol.options}" size="14" width="150px"/>
</apex:repeat>

Here is a link to a gist containing your code with my adjustments: https://gist.github.com/lukemcfarlane/a90a78e3a4b1d05e34c2

Related Topic