Friday, May 30, 2014

Visualforce page with Custom Drop-down Menu (Select) using jQuery Minimalect

Introduction


In HTML we have the select element which can take either the option element or the option elements could be grouped using the optgroup element. These elements are used to build drop-down list. These list could be read-only or they could be used as drop-down menus to trigger all sorts of actions.

With Visualforce and jQuery you can take these elements to the next level, by using them to build simple yet powerful drop-down menus with the following features

  • filtering menu elements by typing
  • keyboard navigation
  • nicer styled controls and support themes


Apart from these slick usability features, a more technical aspect is the fact that such a drop-down menu can be used to control the amount of data loaded at anyone time. This can be very helpful in situations where we are required to load thousands of records at once.

The jQuery Library Minimalect will be used for the demonstrations in this article. You drop-down text box will look like this;



To begin this discussion on Minimalect, I would like to stress out a feature of Minimalect which is really cool. With Minimalect you can filter the options of the drop-down by simply typing in text in the text field shown above. As you type the text, the drop-down elements are filtered and matches are displayed. So you don't have to look through all options to find what you want. This is very useful if you have a long list and scroll bar



OK let's dive in.

Use Case

To demonstrate how you would use Visualforce, jQuery Minimalect and Apex to build a simple, powerful and user friendly drop-down menu, we will use the standard Account object. The idea is to group Accounts according to industries and then pass this information to the Visualforce page. This information is then used to build the drop-down menu. Imagine that Accounts in your organisation are huge, possibly with hundreds and even thousands of contacts and you want to browse these contacts by account by industry. So your drop-down menu will group accounts by industry, and when a user selects an account from the drop-down, the contacts for that account will be retrieved and returned to the page.

Apex: Back-end


First we will use the Schema.DescribefieldResult to get the Industry field on Acounts and then get it's values. Always avoid hard-coding picklist values, so that you wouldn't have to touch you code when the picklist is modified.

1:  public List<String> retrieveIndustryValuesOnAccount(){  
2:       List<String> menuItems = new List<String>();  
3:       Schema.Describefieldresult industryFieldOnAccount = Account.Industry.getDescribe();  
4:       List<Schema.Picklistentry> industries = industryFieldOnAccount.getPickListValues();  
5:       for(Schema.Picklistentry industry : industries){  
6:            menuItems.add(industry.getValue());  
7:       }  
8:       return menuItems;  
9:  }  

Then create a map in which each entry will be a list of Accounts belonging to a single industry. Then make sure that industries that do not have any Accounts are excluded;

1:  public Map<String, List<Account>> accountMenuItemsByIndustryMap { get; set; }  
2:  public void accountDropDownMenuItems(){  
3:       accountMenuItemsByIndustryMap = new Map<String, List<Account>>();  
4:       for(String industry : retrieveIndustryValuesOnAccount()){  
5:            accountMenuItemsByIndustryMap.put(industry, new List<Account>());  
6:       }  
7:       for(Account acct : [SELECT Name, Industry FROM Account]){  
8:            if(acct.Industry != null){  
9:                 accountMenuItemsByIndustryMap.get(acct.Industry).add(acct);       
10:            }  
11:       }  
12:       for(String industry : accountMenuItemsByIndustryMap.keySet()){  
13:            if(accountMenuItemsByIndustryMap.get(industry).isEmpty()){  
14:                 accountMenuItemsByIndustryMap.remove(industry);  
15:            }  
16:       }  
17:  }  



Visualforce: Front-end


On the Visualforce page, we will begin by adding the Minimalect resources (JavaScript and CSS) to the page. Download and add them to the Static Resources and then reference them like this;

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

Then build your drop-down like this. Notice that we use the <apex:repeat> element to step through the map and then group the accounts according to industry by using the optgroup element. We then further step through the list of Accounts still using the <apex:repeat> element and add the account names to the optgroup using the option element.

Notice the class and onclick attibutes added to the option element. We will go into those in a minute.

To better customize Minimalect, it is important to understand what happens behind the scenes. Whenever you use Minimalect, all menu items are enclosed in a list of list (ul) element and the options become list elements (li) and all this is wrapped in a div element. A list element is also added for the optgroup element. A text field is added as the first element in the list. It will hold the selected option and will be used to filter the options in the drop-down.
All these elements get default CSS classes and styles applied. You can set a bunch of options to customize these CSS classes when you initialize Minimalect. You can then apply custom styles to your drop-down.

1:  $(document).ready(function(){  
2:       $("#accountMenu").minimalect({placeholder:"Accounts",class_container:"minict_wrapper minict_larger"});  
3:  });  

While initializing Minimalect in the code above, we set the class_container option such that the CSS classes minict_wrapper and minict_larger will be added to the main div element which holds the drop-down menu items. The option placeholder is the default text which will be displayed when no option is select. Check out the documentation for all the options you can set.

CSS class="hidden" on each option element


Remember the CSS class="hidden" attribute defined for the option element? Well there is a good reason for this. Whenever our drop-down menu is displayed we want the options to be hidden and only be shown when we click on the industry.

To do this we have to add an onclick event listener to the each optgroup list element created by Minimalect. Minimalect unfortunately do not have an option to set this which in my opinion would be a very good idea. So you have two options; the more difficult one will be to work with JavaScript addEventListener or attachEvent (for Versions of IE older than IE9) to add an onclick event to the created list items. If you did not define any custom options for class_group (optgroup element), the default would be minict_group. So you can select all list items with this class and add an onclick to them.
The second option is much simpler and is what I did here. I went all Tarzan on the Minimalect library and added my onclick directly in the Minimalect code. I am a very pragmatic guy but I wouldn't recommend this unless you understand what you are doing fully and the library license gives you the permission to do this, else you might get yourself in some serious trouble.

The function I added to the optgroup li element onclick event listener uses jQuery's nextUntil() to get all entries (Accounts) in a single group toggle them, i.e show if hidden or hide if shown.

1:  function dropdownAccordion(obj){  
2:       $(obj).nextUntil(".minict_group").toggleClass("hidden");  
3:  }  

onclick="passSelectedAccountIdToController('{!acct.Id}');" on each option element


The event listener added to each option element, i.e. each item of the drop-down menu, calls an actionFunction element and passes it the Id of the selected Account.

1:  <apex:actionFunction name="passSelectedAccountIdToController" action="{!retrieveAllContactsForSelectedAccount}">               
2:       <apex:param assignTo="{!selectedAccountId}" name="selectedAccountId" value=""/>  
3:  </apex:actionFunction>  

The actionFunction causes a property on the controller selectedAccountId to be set with the Id of the selected account. After this the action function retrieveAllContactsForSelectedAccount on the account is then called to retrieve all contacts for the selected account.

CONCLUSION


If you leverage the power of jQuery properly on your Visualforce page, you can build trully amazing applications. My colleague argues that it is always best to stick to the standard elements provided by Salesforce as much as possible. Am sure the folks at SFDC will love him for this. I also agree totally. But there are times you will have to think out of the box to give your clients what they want. If your out of the box thinking leads you to JavaScript and jQuery be sure not to try to reinvent the wheel and use tools like Minimalect to make your life easy, while still building powerful applications.
I hope that you were able to get a feel of what you can do with Minimalect from this article. I challenge you to take a closer look at Minimalect whenever you need it because what I have done here is just brushing the tip of the Iceberg. If you have alternative solutions of Libraries you have use in the past, please let us know about them.
Thanks for reading

2 comments:

  1. I have tried your code, & I think you have missed out a few lines of code, I cannot make it work for me. Can you help me out here?

    ReplyDelete
  2. Hi Patel, if it doesn't work you will be getting some errors. All the code required to make this work is in the post. So please debug for JavaScript errors and you might be able to figure what is happening or if something is missing. If not post any errors in here so we can figure it out together. Thanks

    ReplyDelete