Checkboxes – Ensuring a user can only select one checkbox in a VisualForce page

Checkboxes – Ensuring a user can only select one checkbox in a VisualForce page

I had an interesting problem with checkboxes on a VisualForce page this week.

Users needed to be able to select one item in a list as the “Main” record. Only one record can be the main, so when a box was ticked, the other boxes needed to be unticked.

I couldn’t really find a complete solution anywhere online, so I came up with my own and decided to publish it for other people having the same issue.

The Problem

I had a list of SObjects, and one of the records needed to be selectable as the “Main” record.  Easy enough, right?  For a straightforward example, let’s imagine we have a VisualForce page which shows all contacts for an account and allows the user to select one of the contacts as the “Main” contact for the account.  Something like this:

Checkboxes screenshot


The idea here is that the user checks one of the checkboxes, and the following things occur:

  1. All other checkboxes are unchecked
  2. An attribute on the contact is set to true (Main_Contact__c) in our case
  3. The Main Contact section is re-rendered, showing the new main contact

Ordinarily, a radio button would be used for this.  That’s the whole idea of a radio button – only one can be selected at a time.  For some reason they just don’t play nice in this scenario with VisualForce.  I thought this may be due to me overlooking something obvious, but a quick search online showed that other people were using checkboxes to address the problem.

The Solution

A couple of lines of jQuery was all that was required.

Given that I’ve never really done much jQuery before, and that I didn’t know how to hook jQuery into a VF page, and the fact that it’s not immediately obvious how you’re supposed to combine javascript, jQuery, and controller actions, it took me absolutely ages to figure this all out!

Luckily, you, lucky reader, won’t have these problems.

Here’s our example controller – fairly straightforward:

public class MainContactSelectControllerExtension
	public List<Contact> AccountContacts { get; set; }
	public Contact MainContact { get; set; }
	public MainContactSelectControllerExtension(ApexPages.StandardController controller)
		Id accountId = controller.getId();
		// Retrieve all contacts or this account
		AccountContacts = [SELECT Id, Name, Email, Main_Contact__c
						   FROM Contact
						   WHERE AccountId = :accountId];
		// If a main contact is already set for this account, display ot
	// When a checkbox is changed, find the main contact
	public void SetMainContact()
		MainContact = null;
		for (contact con : AccountContacts)
			if (con.Main_Contact__c)
			MainContact = con;

The controller’s constructor retrieves all of the contacts for the account and stores them in the AccountContacts property for display by the VF page.

The SetMainContact method will be called when a checkbox is changed, and is responsible for finding the “Main” contact and setting it to the MainContact property to be displayed on the page.

Here’s the pertinent part of our VF page – the pageBlockTable displaying the contacts:

<apex:pageblocktable var="contact" value="{!AccountContacts}">
	<apex:column value="{!contact.Name}" headerValue="Name" />
	<apex:column value="{!contact.Email}" headerValue="Email" />
	<apex:column headervalue="Main Contact?">
		<apex:inputcheckbox value="{!contact.Main_Contact__c}" html-data-cbType="mainContactCheckbox">
				<apex:actionsupport action="{!SetMainContact}" event="onchange" reRender="MainContactSection" onSubmit="checkboxChanged(this)" />

Let’s ignore the “html-data-cbType” attribute on the checkbox for the moment, we’ll come back to that. The important part of this code is this line:

<apex:actionsupport action="{!SetMainContact}" event="onchange" reRender="MainContactSection" onSubmit="checkboxChanged(this)" />

Nothing groundbreaking here – when the onchange event occurs, call the SetMainContact method on the controller extension, then re-render the MainContactSection to show the new “Main Contact”. Note that we are passing “this” to the checkBoxChanged method, which passes the checkbox itself into the method.

We have a problem though – we haven’t hooked our javascript in yet. That’s what the “onSubmit” attribute on the actionsupport does. it allows us to call some javascript before our controller method is called. This is the piece of the puzzle which was only discovered after I’d spent a longer time than I’d care to admit scratching my head.

Let’s hook in a jquery library. I’m going to be lazy and call it from the google CDN:

<apex:includeScript value=""/>

And here comes our javascript method, with jQuery selector:

	$j = jQuery.noConflict();
	function checkboxChanged(checkbox) {
		// Get the name of the checkbox which changed
		var changedCheckboxId = $j(checkbox).attr('name');            
		// Get all checkboxes
		$j(":checkbox[data-cbType='mainContactCheckbox']").each(function(index) {
			// Set all checkboxes EXCEPT the recently changed one to be unchecked
			if (changedCheckboxId != $j(this).attr('name')) {
					$j(this).attr('checked', false);

Fairly simple stuff – we get the name (note: not the ID) of the checkbox which was passed into the method. We then find all of the other checkboxes and set their “checked” attribute to false.

Remember I mentioned that html-data-cbType attribute earlier? That adds an html custom attribute to the checkboxes in the pageBlockSection called “data-cbType”, with the value “mainContactCheckbox”. You may be wondering why we need that. It is going to allow us to select only the checkboxes in that are relevant. Without the “[data-cbType=’mainContactCheckbox’]” part of our jQuery selector, every checkbox on the page will be unchecked whenever one of our Main Contact boxes are checked. In our simple example here, that doesn’t really matter, but it can lead to frustration if you have other checkboxes on a more complex page.

That’s it! I’ve been playing with jQuery quite a bit lately, so it was nice to be able to put it into practice.

The full code for this post can be found on GitHub.

If you have any questions, feel free to fire them over to me on twitter or in the comments section of the blog.

Leave a Reply