| <!-- | 
|   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" id="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="showUpDown" description="show Up Down btn on right listbox." | 
|     type="Boolean" 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"> | 
|             <select id="{!$Component.multiselectPanel}:leftList"  | 
|               class="multilist" multiple="multiple" size="{!size}"  | 
|               style="width: {!width};"> | 
|               <!--  | 
|                 Visualforce prepends the correct prefix to the outputLabel's  | 
|                 'for' attribute | 
|               --> | 
|               <optgroup style="text-decoration:none;" label="{!leftLabel}"> | 
|               <apex:repeat value="{!leftOptions}" var="option"> | 
|                 <option value="{!option.value}">{!option.label}</option> | 
|               </apex:repeat> | 
|               </optgroup> | 
|             </select> | 
|           </td> | 
|           <td class="buttonCell"> | 
|             <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> | 
|           </td> | 
|           <td class="selectCell"> | 
|             <select id="{!$Component.multiselectPanel}:rightList"  | 
|               class="multilist" multiple="multiple" size="{!size}"  | 
|               style="width: {!width};"> | 
|               <optgroup style="text-decoration:none;" label="{!rightLabel}"> | 
|               <apex:repeat value="{!rightOptions}" var="option"> | 
|                 <option value="{!option.value}">{!option.label}</option> | 
|               </apex:repeat> | 
|               </optgroup> | 
|             </select> | 
|           </td> | 
| <apex:outputPanel layout="none" rendered="{!showUpDown}"> | 
|           <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> | 
| </apex:outputPanel> | 
|         </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).children[0]; | 
|         listTo = document.getElementById(idTo).children[0]; | 
|   | 
|         for ( var x = 0; x < listTo.children.length; x++) { | 
|           listTo.children[x].selected = false; | 
|         } | 
|   | 
|         for ( var x = 0; x < listFrom.children.length; x++) { | 
|           if (listFrom.children[x].selected == true) { | 
|             listTo.appendChild(listFrom.children[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> |