Wednesday 6 February 2013

Multiselect picklist with up and down arrows


Visualforce Component
=====================

<!--
  The MultiselectPicklist component implements a multiselect picklist similar
  to that seen when adding tabs to a Force.com application.
  
  HTML elements use the same classes as the native multiselect picklist, to
  keep visual consistency in the UI.
  
  In addition to the visible elements, the component contains two hidden input
  elements, the purpose of which is to hold a string representation of the
  contents of each listbox. As options are added, removed or moved within the
  listboxes, the content of the hidden elements is synchronized to the content
  of the listboxes. When the Visualforce page is submitted, the 
  MultiselectController updates its SelectOption[] variables from these hidden 
  elements.
 -->
<apex:component controller="MultiselectController">
  <apex:attribute name="leftLabel" description="Label on left listbox."
    type="String" required="true" />
  <apex:attribute name="rightLabel" description="Label on right listbox."
    type="String" required="true" />
  <apex:attribute name="size" description="Size of listboxes."
    type="Integer" required="true" />
  <apex:attribute name="width" description="Width of listboxes."
    type="String" required="true" />

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

  <apex:outputPanel id="multiselectPanel" layout="block" styleClass="duelingListBox">
    <table class="layout">
      <tbody>
        <tr>
          <td class="selectCell">
            <apex:outputPanel layout="block" styleClass="selectTitle">
              <!-- 
                Visualforce prepends the correct prefix to the outputLabel's 
                'for' attribute
              -->
              <apex:outputLabel value="{!leftLabel}" 
                for="multiselectPanel:leftList" />
            </apex:outputPanel>
            <select id="{!$Component.multiselectPanel}:leftList" 
              class="multilist" multiple="multiple" size="{!size}" 
              style="width: {!width};">
              <apex:repeat value="{!leftOptions}" var="option">
                <option value="{!option.value}">{!option.label}</option>
              </apex:repeat>
            </select>
          </td>
          <td class="buttonCell">
            <apex:outputPanel layout="block" styleClass="text">Add</apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="text">
              <apex:outputLink value="javascript:moveSelectedOptions('{!$Component.multiselectPanel}:leftList', 
                  '{!$Component.multiselectPanel}:rightList', '{!$Component.leftHidden}', 
                  '{!$Component.rightHidden}');"
                id="btnRight">
                <apex:image value="/s.gif" alt="Add" styleClass="rightArrowIcon"
                  title="Add" />
              </apex:outputLink>
            </apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="text">
              <apex:outputLink value="javascript:moveSelectedOptions('{!$Component.multiselectPanel}:rightList', 
                  '{!$Component.multiselectPanel}:leftList', '{!$Component.rightHidden}', 
                  '{!$Component.leftHidden}');"
                id="btnLeft">
                <apex:image value="/s.gif" alt="Remove"
                  styleClass="leftArrowIcon" title="Remove" />
              </apex:outputLink>
            </apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="duelingText">Remove</apex:outputPanel>
          </td>
          <td class="selectCell">
            <apex:outputPanel layout="block" styleClass="selectTitle">
              <apex:outputLabel value="{!rightLabel}" for="multiselectPanel:rightList" />
            </apex:outputPanel>
            <select id="{!$Component.multiselectPanel}:rightList" 
              class="multilist" multiple="multiple" size="{!size}" 
              style="width: {!width};">
              <apex:repeat value="{!rightOptions}" var="option">
                <option value="{!option.value}">{!option.label}</option>
              </apex:repeat>
            </select>
          </td>
          <td class="buttonCell"><apex:outputPanel layout="block"
              styleClass="text">Up</apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="text">
              <apex:outputLink value="javascript:slideSelectedOptionsUp('{!$Component.multiselectPanel}:rightList', 
                  '{!$Component.rightHidden}');"
                id="upBtn">
                <apex:image value="/s.gif" alt="Up" styleClass="upArrowIcon"
                  title="Up" />
              </apex:outputLink>
            </apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="text">
              <apex:outputLink value="javascript:slideSelectedOptionsDown('{!$Component.multiselectPanel}:rightList', 
                  '{!$Component.rightHidden}');"
                id="downBtn">
                <apex:image value="/s.gif" alt="Down" styleClass="downArrowIcon"
                  title="Down" />
              </apex:outputLink>
            </apex:outputPanel>
            <apex:outputPanel layout="block" styleClass="text">Down</apex:outputPanel>
          </td>
        </tr>
      </tbody>
    </table>
    <apex:inputHidden value="{!leftOptionsHidden}" id="leftHidden" />
    <apex:inputHidden value="{!rightOptionsHidden}" id="rightHidden" />
  </apex:outputPanel>
  <script type="text/javascript">
    if (!buildOutputString) {
      // Create a string from the content of a listbox
      var buildOutputString = function(listBox, hiddenInput) {
        var str = '';

        for ( var x = 0; x < listBox.options.length; x++) {
          str += encodeURIComponent(listBox.options[x].value) + '&'
              + encodeURIComponent(listBox.options[x].text) + '&';
        }
        str.length--;

        hiddenInput.value = str.slice(0, -1);
      }
    }

    if (!moveSelectedOptions) {
      // Move the selected options in the idFrom listbox to the idTo
      // listbox, updating the corresponding strings in idHdnFrom and
      // idHdnTo
      var moveSelectedOptions = function(idFrom, idTo, idHdnFrom, idHdnTo) {
        listFrom = document.getElementById(idFrom);
        listTo = document.getElementById(idTo);

        for ( var x = 0; x < listTo.options.length; x++) {
          listTo.options[x].selected = false;
        }

        for ( var x = 0; x < listFrom.options.length; x++) {
          if (listFrom.options[x].selected == true) {
            listTo.appendChild(listFrom.options[x]);
            x--;
          }
        }

        listTo.focus();

        buildOutputString(listFrom, document.getElementById(idHdnFrom));
        buildOutputString(listTo, document.getElementById(idHdnTo));
      }
    }

    if (!slideSelectedOptionsUp) {
      // Slide the selected options in the idList listbox up by one position,
      // updating the corresponding string in idHidden
      var slideSelectedOptionsUp = function(idList, idHidden) {
        listBox = document.getElementById(idList);

        var len = listBox.options.length;

        if (len > 0 && listBox.options[0].selected == true) {
          return;
        }

        for ( var x = 1; x < len; x++) {
          if (listBox.options[x].selected == true) {
            listBox.insertBefore(listBox.options[x],
                listBox.options[x - 1]);
          }
        }

        listBox.focus();

        buildOutputString(listBox, document.getElementById(idHidden));
      }
    }

    if (!slideSelectedOptionsDown) {
      // Slide the selected options in the idList listbox down by one position,
      // updating the corresponding string in idHidden
      var slideSelectedOptionsDown = function(idList, idHidden) {
        listBox = document.getElementById(idList);

        var len = listBox.options.length;

        if (len > 0 && listBox.options[len - 1].selected == true) {
          return;
        }

        for ( var x = listBox.options.length - 2; x >= 0; x--) {
          if (listBox.options[x].selected == true) {
            listBox.insertBefore(listBox.options[x + 1],
                listBox.options[x]);
          }
        }

        listBox.focus();

        buildOutputString(listBox, document.getElementById(idHidden));
      }
    }
    
    // initialize the string representations
    buildOutputString(document.getElementById('{!$Component.multiselectPanel}:leftList'), 
        document.getElementById('{!$Component.leftHidden}'));
    buildOutputString(document.getElementById('{!$Component.multiselectPanel}:rightList'), 
        document.getElementById('{!$Component.rightHidden}'));
  </script>
</apex:component>

Controller for above component:
================================
/*
 * 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);
        }
    }
}

=====================
Visualforce Page
=======================
<apex:page controller="MultiselectExampleController" showHeader="false">
    <apex:form >
        <apex:pageBlock title="Contacts">
            <c:MultiselectPicklist leftLabel="Available Contacts"     leftOptions="{!allContacts}"  rightLabel="Selected Contacts"
                rightOptions="{!selectedContacts}"       size="14"   width="150px"/>
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!save}"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
    <apex:outputText >{!message}</apex:outputText>
</apex:page>
=================================
Controller for the visualforce page
===============================public with sharing class MultiselectExampleController {

    public SelectOption[] selectedContacts { get; set; }
    public SelectOption[] allContacts { get; set; }
    
    public String message { get; set; }
    
    public MultiselectExampleController() {
        selectedContacts = new List<SelectOption>();
        
        List<Contact> contacts = [SELECT Name, Id FROM Contact];    
        allContacts = new List<SelectOption>();
        for ( Contact c : contacts ) {
            allContacts.add(new SelectOption(c.Id, c.Name));
        }
    }

    public PageReference save() {
        message = 'Selected Contacts: ';
        Boolean first = true;
        for ( SelectOption so : selectedContacts ) {
            if (!first) {
                message += ', ';
            }
            message += so.getLabel() + ' (' + so.getValue() + ')';
            first = false;
        }
        
        return null;       
    }
}
=====================
Ouput:
=========

4 comments:

  1. Good one... its saved my time...

    ReplyDelete
  2. Awesome.. Thank you so much

    ReplyDelete
  3. It is not working in lightning mode

    ReplyDelete
  4. There is definately a great deal to know about this subject. I like all of the points you've made. multiselect

    ReplyDelete

How to include a screen flow in a Lightning Web Component

 Hi, Assume  you have a flow called "Quick Contact Creation" and API Name for the same is "Quick_Contact_Creation". To i...