Saturday, May 31, 2014

Visualforce Multiselect Drop-down Menu with jQuery Multiselect

Introduction


Building powerful user interactive  GUI (Graphical User Interfaces) requires letting your users have more control over the content they are consuming. This can be done by letting them filter the content to see only what they are interested it. Following my post on customizable drop-down Menus using jQuery Minimalect, I will show you today how to build even more powerful and more interactive drop-dowm menus that let users filter the content they are viewing by specify multiple options at once. This feature is called multi-select. For this article and demonstration I will use jQuery library or plugin call Multiselect.

When building Web applications that are heavy on user interaction, just be careful that the heavy user interaction isn't implemented at the expense of  performance, usability etc. Plan your application carefully before you begin implementing anything. Always remember, JUST BECAUSE YOU CAN DO IT, DOES NOT MEAN YOU SHOULD DO IT.

Use Case

As a use case, imagine you are building a custom Visualforce page which let's your Users browse Opportunities. Since Users in your organization work on opportunities at different stages, they might want to filter opportunities by stages. In this demonstration, we will create a multi-select drop-down Menu, which lets Users request only Opportunities they are interested in by specify the opportunity stages. Our end result looks like this.




So let's dive in.

Apex


Begin by using the Schema.DescribefieldResult to get and return a list of all stage names. This will be used on the page to build the multi-select drop-down. Use the Schema.DescribefieldResult  as opposed to hard coding your stages, so that you will not have to change your code, when the stages in your organization are customized.

1:  public List<String> opportunityStages {  
2:       get {  
3:            if(opportunityStages == null){  
4:                 opportunityStages = new List<String>();  
5:                 Schema.Describefieldresult stageNameField = Opportunity.StageName.getDescribe();  
6:                 List<Schema.Picklistentry> stages = stageNameField.getPickListValues();  
7:                 for(Schema.Picklistentry stage : stages){  
8:                      opportunityStages.add(stage.getValue());  
9:                 }  
10:            }  
11:            return opportunityStages;  
12:       }  
13:       private set;  
14:  }  

Visualforce


First start by downloading Multiselect  and adding the JavaScript and CSS to the page. Add them to the page as follows;

1:  <apex:stylesheet value="{!URLFOR($Resource.tutorials, '/css/jquery.multiselect.css')}" />  
2:  <apex:includeScript value="{!URLFOR($Resource.tutorials, '/js/jquery-1.9.1.js')}" />  
3:  <apex:includeScript value="{!URLFOR($Resource.tutorials, '/js/jquery.multiselect.js')}" />  

The look and feel of the examples here come from the default themes and styles. But you can apply you own themes and styles to suit the look and feel of your application.

Also note the version of jQuery I am using above. I am mentioning this here, because while preparing this article I initially used the latest version of jQuery which as of this writing is 1.11.1. But I couldn't get this to work together with the current version of Multiselect and had to downgrade my jQuery Version .1.9.1. So if you choose a different version of jQuery and run into an "Uncaught type error, undefined is not a function", then you might have to switch to a different version of jQuery.

Next build your drop-down as follows;

1:  <apex:outputPanel id="opportunityStageMultiSelectContainer">  
2:       <select id="opportunityMultiSelect" style="display:none" multiple="multiple">  
3:            <apex:repeat value="{!opportunityStages}" var="stage">  
4:                 <option value="{!stage}">{!stage}</option>  
5:            </apex:repeat>  
6:       </select>  
7:  </apex:outputPanel>  


The <apex:repeat> component is used to iterate over the list of stage names built in the Controller and an option is created for each stage name. The most important attribute here is the multiple attribute defined on the select element. This attribute is what makes multiple selection of options possible.

Créme de la Créme


And of course my favorite Créme de la Créme. Here we initialize the Multiselect plugin and specify options. These options define how the plugin will build our multiselect drop-down and how it will behave.


1:  $("#opportunityMultiSelect").multiselect({  
2:    selectedList: 3,  
3:    selectedText: "# of # selected",  
4:    noneSelectedText: "Opportunity Stages",  
5:    click: function(event, ui){  
6:            getSelectedStages();       
7:    },  
8:    checkAll: function(){  
9:            getSelectedStages();  
10:    },  
11:    uncheckAll:function(){  
12:            getSelectedStages();  
13:    }  
14:  });  

When the page is initially loaded and no options have been selected yet, the noneSelectedText is displayed. As options are selected, they are displayed until the number in selectedList is reached. After this the selectedText kicks in and says how many options have been selected so far;


SelectedText
Selected Options at the top

We have also defined what will happen when the user selects a single option, or all by clicking on Check all or none by clicking on Uncheck all. Although they all call the same function getSelectedStages() in our example, you could implement specific customized behavior here which suits your needs and the requirements you are working with.

1:  function getSelectedStages(){  
2:       var selected = $("#opportunityMultiSelect").multiselect("getChecked");  
3:       var stages = [];  
4:       $.each(selected, function(index, stage){  
5:            stages.push(stage.value);  
6:       });  
7:       passSelectedStagesToController(JSON.stringify(stages));  
8:  }  


In the getSelectedStages() method, we call the method getChecked() provided by the plugin to get all selected options. Please check out the documentation for all methods available and options you can set. For example, you can also use the options to set the text to be displayed in place of Check all  in this case All Opportunities



















Finally we create an array, fill it with the selected stage names, create a JSON string and pass this to an actionFunction passSelectedStagesToController;


1:  <apex:actionFunction name="passSelectedStagesToController" action="{!retrieveOpportunitiesForSelectedStages}" rerender="hiddenDiv">               
2:       <apex:param assignTo="{!selectedStages}" name="selectedStages" value=""/>  
3:  </apex:actionFunction>  


The JSON String with the stage names is assigned to a String property on the Controller selectedStages. Notice that we have a rerender attibute on the actionFunction. You will normally rerender the portion of the display which is showing your results. Here the rerender attibute also plays a very important role. Without this attribute, the selectedStages parameter will not be set. As of this writing, I am guessing that this is a Salesforce Bug. A little googling has revealed that quite a few people have this problem as well. Also specifying the onComplete attribute on the actionFunction component will also cause the selectedStages parameter to be set. Very spooky stuff.

On the controller, in the action method specified in the actionFunction, you can then collect the stage names into a Set.

1:  public void retrieveOpportunitiesForSelectedStages(){  
2:       Set<String> stages = (Set<String>)JSON.deserialize(this.selectedStages, Set<String>.class);  
3:  }  


This set could then be used in your the WHERE condition of your SOQL query using the IN keyword. I will leave this up to you. But in this way you will be able to retrieve and pass only those Opportunities belonging to a particular stage back to the page.


Conclusion

I hope that from this article you were able to get a feeling of the end-to-end process of implementing the multi-select feature using, Apex, Visualforce and jQuery's Multiselect plugin. I hope that you are now able to appreciate the possibilities you have from using this. Challenge this article and propose alternative solutions and as any open questions you may have. Hope you enjoyed reading

13 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. HI.. This kind of layout is not working for me..I have used the same files for jQuery. I have removed the style attribute for the select because it was not displaying anything. I am getting box for multiselect, not a dropdown menu! Can you help me out? Thanks in advance.

    ReplyDelete
    Replies
    1. Hi Patel, this screenshots and code are from my dev org which means everything worked there fine. I will need more info to be able to help. Something like this will most likely mean a JavaScript error occured somewhere and is causing the functionality to break. So if you are not already doing this, please use the inspect Element feature on Chrome of Firefox and try to find out if all your JS resources are loaded properly and if any JS errors occured. You can then take it from there or post the error here so we can figure out what is going wrong.
      PS-> you need the style attribute for select. Initially when you load your page you are not supposed to see anything. In fact you never see the select. It is always hidden. What the plugin does is that, it builds the drop-down as a list of elements using what you defined in the select tag. So begin by putting back the display:none style and debug for JavaScript errors

      Delete
    2. @Apurva if you are looking for the entire source - goto the resource page that Viban listed above for Multiselect and get the jQuery.multiselect.css and jQuery.multiselect.js (for some reason I had issues with the min file). Save them both in a zip file named jQueryMS and upload to your static resource. I also added a theme from google cdn. Uncomment the script and select tags below.

      Here is the Visualforce page:












      var j$ = jQuery.noConflict();
      j$(document).ready(function(){
      j$("#opportunityMultiSelect").multiselect({
      selectedList: 3,
      selectedText: "# of # selected",
      noneSelectedText: "Opportunity Stages",
      click: function(event, ui){
      getSelectedStages();
      },
      checkAll: function(){
      getSelectedStages();
      },
      uncheckAll:function(){
      getSelectedStages();
      }
      });
      });

      function getSelectedStages(){
      var selected = j$("#opportunityMultiSelect").multiselect("getChecked");
      var stages = [];
      j$.each(selected, function(index, stage){
      stages.push(stage.value);
      });
      passSelectedStagesToController(JSON.stringify(stages));
      }














      Here is the apex code:
      public with sharing class selectedStages {
      public selectedStages(ApexPages.StandardController controller) {

      }
      public List opportunityStages {
      get {
      if(opportunityStages == null){
      opportunityStages = new List();
      Schema.Describefieldresult stageNameField = Opportunity.StageName.getDescribe();
      List stages = stageNameField.getPickListValues();
      for(Schema.Picklistentry stage : stages){
      opportunityStages.add(stage.getValue());
      }
      }
      return opportunityStages;
      }
      private set;
      }
      public String selectedStages {get; set;}

      public void retrieveOpportunitiesForSelectedStages(){
      Set stages = (Set)JSON.deserialize(this.selectedStages, Set.class);
      System.debug('Stages are: '+stages);
      }

      }


      Delete
  3. Hi Viban, great post. I have been developing the same in Visualforce for multi-select with Eric Hynds jQuery multi-select library. Have you tried using the multifilter. I tried but wasn't able to filter the picklist results. Appreciate any feedback. Also any feedback with using optgroup like list in Salesforce.

    Thanks again for the wonderful post.
    Raj

    ReplyDelete
    Replies
    1. Hi Raj,
      thanks.
      Did you already figure this out or is there still an issue. If yes then let me know exactly what you mean by you couldn't filter the picklist results? are these picklist from an object on salesforce? How are you putting together data to pass to your Multiselect plugin?.
      There is no optgroup in Salesforce, only picklist and multpicklist. So if you want to implement something similar, you will have to get creative depending on your requirement. So what is it you want to be able to achieve with the optgroup. Look at this Salesforce Community Idea for some workarounds https://success.salesforce.com/ideaview?id=087300000007PfYAAU

      Delete
  4. The link to your Multiselect file is broken

    ReplyDelete
    Replies
    1. Thanks for the heads up. I just updated the link

      Delete
  5. You should modify "$(...).multiselect" to "$(...).multiSelect" (capital S) else it returns the error "multiselect is not a function".

    Thank you

    ReplyDelete
  6. Can I get source code of this page please

    ReplyDelete
  7. hi Vivan,
    Can you please share the css theme you have used in your example.
    i need a dropdown multiselect for design.

    ReplyDelete
  8. hi
    can you please share the Jquery
    files for us . i want the same functionality to be working and not getting the results in github this files are not avialable .only css is there


    2:
    3:

    ReplyDelete
  9. jquery-1.9.1.js and jquery.multiselect.js files . advance thanks if those js files are available with you to us

    ReplyDelete