November 23, 2012

Checkboxes and Columns, Line 'em Up

Oftentimes when you've been working on a specific platform for a while, you will fall into the trap of thinking that it'll do what you expect every time; mostly because you've learned the general structure of the platform and the idioms involved in using it for development. The aforementioned trap arises because you're making assumptions, and we all know what is said of such.

As a Salesforce.com Visualforce developer, one might assume that when using <apex:inputCheckboxes> inside an <apex:pageBlockSection> that the system would do the nice thing and put a checkbox per cell, be it with two columns or just one. Unfortunately, you'd be wrong, but, there is a way you can achieve what you desire (assuming, naturally, that your desires pertain the layout of elements on a Visualforce page).

The Problem

<apex:page controller="SomeController"> 
  <apex:form> 
    <apex:pageBlock title="Checkbox City!"> 
      <apex:pageBlockSection columns="2"> 
        <apex:selectCheckboxes> 
<apex:selectOptions value="{!Options}"/> 
</apex:selectCheckboxes> 
        <apex:pageBlockSectionItem> 
          <apex:outputLabel for="fakeField" value="Some Field"/> 
          <apex:outputText id="fakeField" value="Some Stuff!"/> 
        </apex:pageBlockSectionItem> 
  </apex:pageBlockSection> 
    </apex:pageBlock> 
  </apex:form> 
</apex:page>

As indicated, we use the regular Visualforce page structure, but the output is not really what we're after. Instead all the checkboxes appear in the same cell, as shown below.

Image showing 3 checkboxes in one cell
Easy, but not always what we're going to be looking for.

Sure, in some instances in which you want the output to look like this, and if that's the case then you can skip this post and read something else, but if you want a method to put each checkbox in it's own cell, read on.

Repeat to the Rescue?

Well, not exactly. My first impulse was to use an <apex:repeat> to spit out the checkboxes, but only <apex:selectOption> or <apex:selectOptions> tags are allowed as children of  the <apex:selectCheckboxes> tag, and so <apex:repeat> is out. Furthermore,  using a repeat on the outside is just not going to work because we need to save the options into one bound variable for the value. Back to the proverbial drawing board.

Rolling It Self-Assembly Style

One thing we can do, is generate our own HTML to insert into the page. Now this solution is not particularly elegant, but it does work, and my searching did not turn up any other solutions (or this one) and so I'm going with it. What we do is generate the HTML for checkboxes inside our controller, use an <apex:repeat> to inject the strings into the final page, and then use JavaScript to iterate over the checkboxes and collect the selected values.

The reason I don't just use straight HTML inside the repeat tag itself is that we need to indicate which boxes are checked, and this is effected through use the 'checked' attribute, and to generate this dynamically we'd have to embed an output text tag inside an input tag, and we're not allowed to do that (thought it would be handy if we could!).

<!-- This will not save! --> 
<input type="checkbox" <apex:outputText value="{!IF(shoudBeChecked, 'checked', '')}"/>/>

So we need a list of HTML strings to stick in the page representing our objects, and to insert HTML into  the page with <apex:outputText> requires us to specify escape="false", and this is typically regarded as somewhat dangerous; it could allow nasty people to inject bad script-fu into our page and start doing things we'd rather they did not do. In order to combat this, I create a wrapper class which includes a string for the label to show next to to our checkbox, and an HTML string for the checkbox itself; as you can see the HTML string has a private setter. The only way to set the HTML string is from inside the class, and the only code that does so is inside the constructor—there may still be a way to abuse this setup, but it's definitely more secure than making it accessible outside of the class.

private class CCheckbox 
{ 
  public String Label {get; set;} 
  public String HTML {get; private set;} 
      
  public CCheckbox(String Label, String value, Boolean IsChecked) 
  { 
     this.Label = Label; 
      this.HTML = '<input type="checkbox" class="cboxChoice" onClick="saveValues();" id="' + Value + '" ' + (IsChecked ? 'checked' : '') + '/>'; 
  } 
}

In the controller we create a public list of these objects, filling it as we would a list of SelectOption elements, and now we can use <apex:repeat> to generate our page content.

<apex:pageBlockSection title="Checkbox City!"> 
  <apex:repeat var="o" value="{!Options}"> 
  <apex:pageBlockSectionItem > 
  <apex:outputLabel value="{!o.label}"/> 
  <apex:outputText value="{!o.HTML}" escape="false"/> 
  </apex:pageBlockSectionItem> 
  </apex:repeat> 
  <!-- more fields and things --> 
</apex:pageBlockSection>

So far, so good. Our page is rendered as we want, but of course we're using plain old HTML—when we fire any actions on the controller we're not going to get any information about which checkboxes the user has selected, and that's not helpful.

Checkboxes in a cell each
Looking good, but using this could lead to some irate users.

The Final Piece

So how do we get the information from the checkboxes back to the controller? Well the eagle-eyed may well have noticed that the setup for this is already included within the controller-generated HTML—the checkboxes have all been given a specific class, and there is also an onClick JavaScript function specified. The idea is that the function indicated, saveValues(), uses jQuery to grab all of the checkboxes, loops through them, and concatenates the values (stored in the id attributes) of those selected into a string. To get this string back to the controller we simply use the well-known method of including an <apex:inputHidden> field which is bound to a String on the controller.

<script type="text/javascript"> 
  jQuery.noConflict(); 
  function saveCheckboxes() 
  { 
    var Checkboxes = ""; 
    jQuery(".cboxChoice").each(function(){ 
      if(jQuery(this).attr("checked") && jQuery(this).attr("id") != undefined) 
      { 
        Checkboxes += jQuery(this).attr("id") + ";"; 
      } 
    }); 
    document.getElementById('{!$Component.hiddenCheckboxes}').value = Checkboxes; 
  } 
</script> 
<apex:inputHidden id="hiddenCheckboxes" value="{!Checkboxes__c}"/>

And that's a wrap! There are multiple ways in which this solution can be improved upon, for starters if you you to have more than one group of checkboxes you'll need to use multiple class names in the HTML elements (saveCheckboxes() could easily be modified to take a corresponding string parameter), but it works well, and more importantly, it is unobtrusive—the user is not going to notice a difference between this implementation and the behaviour of the standard method which placed all checkboxes in a single table cell.