Tuesday, September 27, 2011

MultiSelect Picklist Solution for a VisualForce Page

I had to use a VF Page for Person Accounts. On adding an InputField bound to a picklist to the VisualForce Page - I kept getting this error 'The first validation error encountered was "Record Type ID: value not valid for the entity: Account".' on loading the VF Page. I had to resort to using a picklist solution as given here

But when it comes to MultiSelect Picklists, the UI was not much great, having multiselect = "True" on the VF SelectList Component - It just served the purpose. The UI required, using the Shift + a Select with your mouse or Ctrl + a Select with your mouse. Not very User-Friendly!! :(

A multiselect component which has two list boxes - one containing the available values and the other containing the chosen values and a VF Page which uses the same for displaying the multiselect values is worth a try. A User-Friendly Solution! Just works like how any multi-select field would behave on an Edit Page! :)

The component for MultiSelect can be used on the Person Account VF Page, just like any other component.

<c:MultiselectComponent AvailableList="{!availableList}" ChosenList="{!chosenList}"/>


The below code can be used to get the picklist values

Schema.DescribeFieldResult optionFieldDescription = Account.MultiSelect__c.getDescribe();
for(Schema.PicklistEntry pleOptions : optionFieldDescription.getPicklistValues()){
availableList.add(new SelectOption(pleOptions.getvalue(),pleOptions.getLabel()));
}


When there is a need for an update to the field value:
The multi-select picklist options are saved in salesforce as a string, separated by a semi-colon. A method can be used to combine the options before saving into the database.

private static String MULTIPICKLIST_SEPERATOR = ';';
private String combineOptions(List<SelectOption> values) {
String result = '';
for(SelectOption s: values) {
result = result == '' ? s.getValue() : result + MULTIPICKLIST_SEPERATOR + s.getValue();
}
return result.length() > 0 ? result.substring(0, result.length()): result;
}


The multiSelect component

<apex:component controller="MultiSelectComponentController">
<apex:attribute name="AvailableList" type="selectOption[]" description="Available List from the Page" assignTo="{!options}" required="True"/>
<apex:attribute name="ChosenList" type="selectOption[]" description="Chosen List from the Page" assignTo="{!selectedOptions}" required="True"/>

<script type="text/javascript">
function selection() {
selection();
}
function deselection() {
deselection();
}
</script>
<!-- Apex function called to move the selected values from available list to chosen list and vice versa -->
<apex:actionFunction name="selection" action="{!selecting}" reRender="multiselect"/>
<apex:actionFunction name="deselection" action="{!deselecting}" reRender="multiselect"/>
<apex:outputPanel id="panel">
<apex:pageBlockSection columns="4" >
<apex:selectList multiselect="true" size="5" value="{!selected}">
<apex:selectOption value="{!Available}"/>
<apex:selectOptions value="{!options}" />
<apex:actionSupport event="ondblclick" action="{!selecting}" rerender="panel" status="waitingStatus" />
</apex:selectList>
<apex:pageBlockSection columns="1">
<apex:commandButton reRender="panel" id="select" action="{!selecting}" value=">" status="waitingStatus"/>
<apex:commandButton reRender="panel" id="deselect" action="{!deselecting}" value="<" status="waitingStatus"/>
</apex:pageBlockSection>
<!-- An action status to show that the operation of moving between the lists is in progress--->
<apex:actionStatus id="waitingStatus" startText="Please wait..." stopText=""/>
<apex:selectList multiselect="true" size="5" value="{!deselected}">
<apex:selectOption value="{!Chosen}"/>
<apex:selectOptions value="{!selectedOptions}" />
<apex:actionSupport event="ondblclick" action="{!deselecting}" rerender="panel" status="waitingStatus" />
</apex:selectList>
</apex:pageBlockSection>
</apex:outputPanel>
</apex:component>


Corresponding Apex functions in the MultiSelectComponentController:

public void selecting() {
for(String toSelect: selected) {
Integer i = 0;
While(i<options.size()) {
if(options[i].getvalue()==toSelect) {
selectedOptions.add(new SelectOption(toSelect,toSelect));
options.remove(i);
}
i++;
}
}
}

public void deselecting() {
for(String toDeselect: deselected) {
Integer i = 0;
While(i<selectedOptions.size()) {
if(selectedOptions[i].getvalue()==toDeselect) {
options.add(new SelectOption(toDeselect, toDeselect));
selectedOptions.remove(i);
}
i++;
}
}
}

6 comments:

  1. fantastic! To create something like this was on my back burner. Great to see someone beat me to it.

    Only thing, I wonder if we could improve on the n-squared performance of selecting() and deselecting(). In most cases the lists will be small so it shouldn't matter so much performance wise, but at least for elegance.

    Great Job!

    Dovid

    ReplyDelete
  2. Hi Dovid Bleier,

    Thanks for the compliment.

    Regarding, improving the n-squared performance, it might not be possible to get rid of one loop at least. Coz, SelectOption is not supported in sets and we can remove an element using its value, only in sets. When it comes to lists, removing an element is possible using the index which needs the while loop there.

    The code can be optimized further by having just 1 method [as logic is same - moving elements right and left] with
    - "selected" and "options" as arguements passed for selection and
    - "deselected" and "selectedOptions" as arguements passed for deselection

    Cheers!

    ReplyDelete
  3. Id there any chance you could publish the full code for this, especially the MultiSelectComponentController. I am not quite following fully.

    ReplyDelete
  4. can you please provide full code i need to save selected values from account to opportunity and i need to display also in same page

    ReplyDelete
  5. Nice Post, really useful, user friendly and knowledgeable articles share or publishe3d by you. It is really happy reading your complete an articles and I hope that you will published more articles for us, will come back on your blogs or website for gathering some useful info, I think I have visited a lot of website and blogs but there is not get good information but when I have visited your website then I get a great article content with clear discuss on topics.
    You can find here Sales Force Automation software to handle your sales business.
    Lastly I would like to huge thanks for sharing…………………………..

    ReplyDelete
  6. @Joy walker: Use a function to traverse through the values of selectedOptions and save it in your database table.
    For(Loop through selectedOptions) {
    YourdatabasetableRecord.field= eachValue;
    }
    update YourdatabasetableRecord;

    ReplyDelete