Home > Visualforce > Custom Multi-Select Buttons

Custom Multi-Select Buttons

June 18th, 2009

A little feature that I have always thought was cool is the ‘Clone’ multi select button on an opportunity. I even created an idea here, Custom Multi Select Buttons, and it has received a decent amount of votes. Something like this could really stream line the design of certain Visualforce pages. Sure, you could create essentially the same functionality with a pick list and then add a commandButton next to it but the concept of a multi select button seems much more polished.

I decided to take a stab at creating my own custom component for a multiSelectButton. Using Firebug to inspect how the clone button worked and some recently acquired knowledge on how javascript can modify css I have successfully created this component. Once setup it is very easy to use.

To see this in action you can check out my examples here: 
http://tehnrd-developer-edition.na7.force.com/multibutton/

To see how it all works lets first take a look at the custom component. There are only two attributes. The text that should be displayed on the button and a uniqueId string to ensure all the div Ids are unique because if you placed this component on the page multiple times without this uniqueId there would be issues. The name of this component will be “multiSelectButton”.

<apex:component >
	<apex:includeScript value="{!$Resource.multiSelectButton_js}"/>
	<apex:attribute name="buttonText" description="Text displayed." type="String" required="true"/>
	<apex:attribute name="uniqueId" description="Unique Id for this componenet. Must be unique across all multiSelectButtons. This is not the component Id." type="String" required="true"/>
 
	<div name="multiButton" id="button{!uniqueId}" class="menuButton" onclick="showOptions('{!uniqueId}');" >
		<div id="CloneButton{!uniqueId}" class="menuButtonButton" onmouseover="divHover = 'CloneMenu{!uniqueId}';" onmouseout="divHover = '';">{!buttonText}</div>
		<div name="multipButtonOpts" id="CloneMenu{!uniqueId}" class="menuButtonMenu">
			<apex:componentBody/>
		</div>
	</div>
</apex:component>

As you can see the component is importing a javascript and file here it is. To set this up you will want to copy this text, paste it into the notepad editor of choice and then save with a .js file extension. You will then need to upload it as a static resource with the name ‘multiSelectButton_js’. This javascript file controls the showing and hiding of the button options.

var divHover = '';
 
/*When a multi select button is clicked this method executes. First it hides all options for every
multiselect button on the page the show but options for the button that was clicked. This way, if
you click one button and then click another button the options on the first will disappear. */
 
function showOptions(objId){
	var multiButtons = document.getElementsByName("multiButton");
	for(var i=0; i < multiButtons.length; i++){
		multiButtons[i].style.position = '';
	}
 
	var multiButtonOpts = document.getElementsByName("multipButtonOpts");
	for(var i=0; i < multiButtonOpts.length; i++){
		multiButtonOpts[i].style.display = 'none';
	}		
 
	document.getElementById('CloneMenu' + objId).style.display = 'block';
	document.getElementById('CloneMenu' + objId).style.top = '17px';
	document.getElementById('button' + objId).style.position = 'relative';
}
 
/*Any time the document is click and the mouse is not over a multiselect button hide 
all options */
document.onclick = function(){
	if(divHover == '' || divHover == null){
		var divs = document.getElementsByTagName('div');
		for(i=0; i < divs.length; i++){
			if(divs[i].getAttribute('name') == 'multiButton'){
				divs[i].style.position = '';
			}
			if(divs[i].getAttribute('name') == 'multipButtonOpts'){
				divs[i].style.display = 'none';
			}
		}
	}
}

Next we will take a look at the page. This component is very easy to use. Simply create the component by filling in the two required attributes and fill the content of this custom component with either outputLinks or commandLinks. This page has a few examples of how this can be used.

<apex:page controller="multiButton">
	<apex:form >
		Normal html links:<br/>
		<c:multiSelectButton uniqueId="show1" buttonText="Links">
			<apex:outputLink value="http://www.digg.com">digg.com</apex:outputLink>
			<apex:outputLink value="http://www.engadget.com">engadget.com</apex:outputLink>
			<apex:outputLink value="http://www.gizmodo.com">gizmodo.com</apex:outputLink>
		</c:multiSelectButton><br/><br/>
 
		Links that pass params:<br/>
		<apex:inputText value="{!searchValue}"/>
		<c:multiSelectButton uniqueId="show3" buttonText="Search">
			<apex:commandLink value="Google" action="{!doSearch}">
				<apex:param assignTo="{!searchEngine}" value="www.google.com/search?q="/>
			</apex:commandLink>
			<apex:commandLink value="Yahoo" action="{!doSearch}">
				<apex:param assignTo="{!searchEngine}" value="search.yahoo.com/search?p="/>
			</apex:commandLink>
			<apex:commandLink value="Bing" action="{!doSearch}">
				<apex:param assignTo="{!searchEngine}" value="www.bing.com/search?q="/> 
			</apex:commandLink>
		</c:multiSelectButton><br/><br/>
 
 
	    These options are bound to action methods in the controller:<br />
		<apex:pageBlock id="tables">
 
			<apex:pageBlockButtons location="top">
				<c:multiSelectButton uniqueId="show2" buttonText="View:">
					<apex:commandLink value="Accounts" action="{!showAccts}" rerender="tables" />
					<apex:commandLink value="Opportunities" action="{!showOpps}" rerender="tables" />
				</c:multiSelectButton>
			</apex:pageBlockButtons>
 
			<apex:pageBlockTable value="{!opps}" var="o" rendered="{!viewList = 'opps'}">
				<apex:column value="{!o.Name}" />
				<apex:column value="{!o.StageName}" />
				<apex:column value="{!o.Amount}" />
			</apex:pageBlockTable>
 
			<apex:pageBlockTable value="{!accts}" var="a" rendered="{!viewList = 'accts'}">
				<apex:column value="{!a.Name}" />
				<apex:column value="{!a.BillingCity}" />
				<apex:column value="{!a.BillingCountry}" />
			</apex:pageBlockTable>
 
		</apex:pageBlock>
	</apex:form>
</apex:page>

And finally we have the custom controller with all of the logic. I won’t go into very much detail as it should be pretty clear how everything works.

public class multiButton {
 
	public List opps {get; set;}
	public List accts {get; set;}
	public String viewList {get; set;}
	public String searchValue {get; set;}
	public String searchEngine {get; set;}
 
	public multiButton(){
		//Query in the contructor for this example
		opps = [select Name, StageName, Amount from Opportunity limit 10];
		accts = [select Name, BillingCity, BillingCountry from Account limit 10];
	}
 
	public void showAccts(){
		viewList = 'accts';
	}
 
	public void showOpps(){
		viewList ='opps';
	}
 
	public pageReference doSearch(){
		PageReference searchPage = new PageReference('http://' + searchEngine + searchVAlue );
		searchPage.setRedirect(true);
		return searchPage;
	}
}

The one glaring area that needs improvement is the requirement to use a uniqueId attribute. Without this I could not figure out a way to guarantee that all of the div Ids would be unique if there are multiple components. If any knows please let me know and I can update the code.

Another thing to watch out for is the fact that I used salesforce.com style sheets. This is not a best practice because if these change it may hose the look and feel of the buttons. I used them in this demo to keep it simple but you may want to create your own to ensure they do not change.

Categories: Visualforce
  1. Johann
    July 21st, 2009 at 12:04 | #1

    Thanks, this is really helpful, and I really appreciate the post. However I did find some issues with the java JavaScript file.
    1. Code above displays “<” instead of the greater than symbol (<) so copying and pasting it doesn’t work well.
    2. Menu doesn’t disappear where you click somewhere else, nor on another multiselect button for some reason.

    If you decide to update I would certainly like to know

  2. July 21st, 2009 at 21:48 | #2

    Thanks for the feedback. I have fixed issue #1.

    It appears issue #2 is a Internet Explorer issue. With Firefox 3.5 and the latest version of Chrome it works fine. I will assume Safari works as well as it is built on the same code as Chrome. I’ll try to fix this but to be brutally honest javascript is probably my weakest area.

    Thanks again for the comment.

  3. July 22nd, 2009 at 21:13 | #3

    I have updated the code and it should now work with all of the major browsers: IE, Firefox, Safari, and Chrome.

    The main issue is that IE does not support getElementsByName() with div tags. More info can be found here: http://jszen.blogspot.com/2004/07/whats-in-name.html