It is a widely known fact, that form controls, and especially comboboxes, checkboxes and radiobuttons are not that flexible as the other html elements and are hardly susceptible to css skinning. Here is a simple client-side solution for checkbox skinning.

The tutorial is divided into four chapters, supplied with code listings, and explaining the different parts of the control – xhtml markup, css classes, javascript, checkbox states and more. In the beginning of each chapter there is a list of reference files.

Before starting, you may download the script from here.



1. Considering the (X)HTML Markup

Reference files:

• CheckBox.html

Prior to the W3C recommendations for xhtml compliance, semantics, accessibility and usability we will use an unordered list with four items, wrapped in a <fieldset>…</fieldset> with <legend>…</legend> and a <div>…</div> container.

Each of the items will represent the possible default states of the responding checkbox – default checked, default unchecked, default checked disabled and default unchecked disabled. We will also use hidden <input type="checkbox" /> controls. In that way we will not only preserve the semantics and logic of the structure, but will also make use of the inbuilt functionality of the real checkbox input without writing sophisticated scripts that resemble it.

Listing 1:

<div class="checkbox" id="">
 <fieldset>
  <legend>Checkbox List 1</legend>
  <ul>
   <li id="" class=""><input type="checkbox" name="" id="" /><label for="">Option 1</label></li>
   <li id="" class=""><input type="checkbox" name="" id="" /><label for=" ">Option 2</label></li>
   <li id="" class=""><input type="checkbox" name="" id="" /><label for="">Option 3</label></li>
   <li id="" class=""><input type="checkbox" name="" id="" /><label for=" ">Option 4</label></li>
  </ul>
</fieldset>
</div>

2. CSS Classes, Inheritance and ID’s

Reference files:

• CheckBox.html
• /CheckBox/Skins/Mac/Style.css
• /CheckBox/Scripts/CheckBox.css

In order to avoid unnecessary class accumulation, we start skinning the outermost element, as its child nodes will inherit these properties. I also decided to put some of the css properties – the most common ones and not directly related to the skins in a separate CheckBox/Scripts/CheckBox.css file.

The next thing to do is to create proper inner classes and to assign relevant ID’s to the list items and checkboxes. Although it’s not related to the scripting and the skinning of our checkboxes, we will also add name=”” attributes to the checkboxes similar to their ID’s:

Listing 2:

<div class="checkbox" id="CheckBoxGroup_1">
 <fieldset>
  <legend>Checkbox List 1</legend>
  <ul>
   <li id="CheckBoxGroup_1_skinned_1" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_1" id="CheckBoxGroup_1_hidden_1" /><label for="CheckBoxGroup_1_hidden_1">Option 1</label></li>
   <li id="CheckBoxGroup_1_skinned_2" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_2" id="CheckBoxGroup_1_hidden_2" /><label for="CheckBoxGroup_1_hidden_2">Option 2</label></li>
   <li id="CheckBoxGroup_1_skinned_3" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_3" id="CheckBoxGroup_1_hidden_3" /><label for="CheckBoxGroup_1_hidden_3">Option 3</label></li>
   <li id="CheckBoxGroup_1_skinned_4" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_3" id="CheckBoxGroup_1_hidden_4" /><label for="CheckBoxGroup_1_hidden_4">Option 4</label></li>
  </ul>
 </fieldset>
</div>

The logic of id’s is as follows – the wrapping element accepts CheckBoxGroup_[NumberOfTheGroup] – in our case it is CheckBoxGroup_1. Any other checkbox group on the page will have its consecutive number - CheckBoxGroup_1, CheckBoxGroup_2, etc.

The list item nodes accept the same id postfixed with _skinned_[NumberOfTheItem] – i.e. CheckBoxGroup_1_skinned_1, CheckBoxGroup_1_skinned_2, etc.

The checkbox in each list item accepts the id of the checkbox group postfixed with _hidden_[NumberOfTheCheckbox] – i.e. CheckBoxGroup_1_hidden_1, CheckBoxGroup_1_hidden_2, etc. Prior to the accessibility requirements, each checkbox is supplied with a <label>…</label> with for=”” attribute the id of the corresponding checkbox.

As our solution is client-side, this id convention is not obligatory, but as it follows a sort of hierarchy it is suitable for debugging and easier understanding.

3. The Checkbox Toggle Script

Reference files:

• CheckBox.html
• CheckBox/Scripts/CheckBox.js
• /CheckBox/Skins/Mac/Style.css

The skinnable checkbox engine is stored in the CheckBox/Scripts/CheckBox.js file. Take a look at the main function – toggleCheckBox():

Listing 3:

function toggleCheckBox(hiddenChkBox, skinnedCheckBoxId)
{
 if(!document.getElementById(hiddenChkBox).disabled)
 {
  if(document.getElementById(hiddenChkBox).checked)
  {
   document.getElementById(skinnedCheckBoxId).className = 'unchecked';
   document.getElementById(hiddenChkBox).checked = false;
  }
  else
  {
   document.getElementById(skinnedCheckBoxId).className = 'checked';
   document.getElementById(hiddenChkBox).checked = true;
  }
 }
}

There are two arguments – hiddenChkBox (the id of the real hidden checkbox) and skinnedCheckBoxId (the id of the list-item containing that checkbox), and their respective values are passed onclick on the list item. Here arises the first question – why we don’t assign the onclick handler to the label or even to the checkbox.

If you take a look at the the CheckBox/Scripts/CheckBox.css file you will notice that each <input type="checkbox" /> inside the .checkbox class is set to display: none; and it is obvious why we cannot use the checkbox for event handling. The <label>…</label> for each <input type="checkbox" /> also proves unusable – as the checkbox is hidden, the browser cannot set the focus to it, and respectively – cannot toggle it correctly. That is why we assign the onclick event to the list items:

Listing 4:

<div class="checkbox" id="CheckBoxGroup_1">
 <fieldset>
  <legend>Checkbox List 1</legend>
  <ul>
   <li id="CheckBoxGroup_1_skinned_1" onclick="toggleCheckBox('CheckBoxGroup_1_hidden_1', this.id);" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_1" id="CheckBoxGroup_1_hidden_1" /><label for="CheckBoxGroup_1_hidden_1">Option 1</label></li>
   <li id="CheckBoxGroup_1_skinned_2" onclick="toggleCheckBox('CheckBoxGroup_1_hidden_2', this.id);" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_2" id="CheckBoxGroup_1_hidden_2" /><label for="CheckBoxGroup_1_hidden_2">Option 2</label></li>
   <li id="CheckBoxGroup_1_skinned_3" onclick="toggleCheckBox('CheckBoxGroup_1_hidden_3', this.id);" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_3" id="CheckBoxGroup_1_hidden_3" /><label for="CheckBoxGroup_1_hidden_3">Option 3</label></li>
   <li id="CheckBoxGroup_1_skinned_4" onclick="toggleCheckBox('CheckBoxGroup_1_hidden_4', this.id);" class="unchecked"><input type="checkbox" name="CheckBoxGroup_1_hidden_3" id="CheckBoxGroup_1_hidden_4" /><label for="CheckBoxGroup_1_hidden_4">Option 4</label></li>
  </ul>
</fieldset>
</div>

When the function is fired, the script performs a check if the checkbox is disabled, then if it is checked or not. No function is fired if the item is disabled. If the checkbox is checked, the corresponding list-item gets the .unchecked class. If else – the .checked css class. Respectively, the checkboxes state is toggled as well.

4. Going Further

4.1 Setting Checkbox Skins

Reference files:

• CheckBox.html
• CheckBox/Scripts/CheckBox.js
• /CheckBox/Skins/Mac/Style.css

On the top of the CheckBox/Scripts/CheckBox.js file you can see two variables - CheckBoxSkin and ApplySkin. The first one creates a <link>…</link> tag in the <head>…</head> of the page and loads a selected skin via the following function:

Listing 5:

// load skin
var CheckBoxCss = document.createElement('link');
CheckBoxCss.type='text/css';
CheckBoxCss.rel='stylesheet';
CheckBoxCss.href='CheckBox/Skins/' + CheckBoxSkin + '/Styles.css';
document.getElementsByTagName('head')[0].appendChild(CheckBoxCss);
// load script classes
var CheckBoxScriptCss = document.createElement('link');
CheckBoxScriptCss.type='text/css';
CheckBoxScriptCss.rel='stylesheet';
CheckBoxScriptCss.href='CheckBox/Scripts/CheckBox.css';
document.getElementsByTagName('head')[0].appendChild(CheckBoxScriptCss);

Skins are stored in the CheckBox/Skins/[SkinName] directory. Each [SkinName] folder contains Styles.css and Images/ folder.

The ApplySkin variable is boolean and if set to false, it fires the following function:

Listing 6:

// do not load skin
var CheckBoxCss = document.createElement('link');
CheckBoxCss.type='text/css';
CheckBoxCss.rel='stylesheet';
CheckBoxCss.href='CheckBox/Skins/Default/Styles.css';
document.getElementsByTagName('head')[0].appendChild(CheckBoxCss);
// load script classes
var CheckBoxScriptCss = document.createElement('link');
CheckBoxScriptCss.type='text/css';
CheckBoxScriptCss.rel='stylesheet';
CheckBoxScriptCss.href='CheckBox/Scripts/CheckBox.css';
document.getElementsByTagName('head')[0].appendChild(CheckBoxScriptCss);

that removes any skinning, and in that manner we can have classic checkboxes if we like.

4.2 Setting Single or Multiline Checkbox Groups

Reference files:

• CheckBox.html

By default we have a multiline checkbox group, but it can easily become single-line, by referring the checkbox group ID as shown below:

Listing 7:

<div class="checkbox" id="CheckBoxGroup_1">
 [ … other markup goes here … ]
 <style type="text/css">
 /* set single line checkbox list */
 #CheckBoxGroup_1 ul li
 {
  float: left;
 }
 </style>
</div>

Of course we can refer the actual class of the wrapper - .checkbox, but if we have several checkbox group on the page, by using id’s we can set different float to each one. That is why I prefer referring the id instead of the class.

4.2 Enabling or Disabling Checkboxes and Setting Defaults

Reference files:

• CheckBox.html

The default state of the checkbox is unchecked, but with a few lines of code within each wrapping <div>…</div>, and after the list items we can assign different state for each item in a given checkbox group:

Listing 8:

<div class="checkbox" id="CheckBoxGroup_1">
 [ … other markup goes here … ]
 <script type="text/javascript">
 // default unchecked checkboxes are not specified, as this is their default state
   
 // set default checked checkboxes
 // [CheckBoxGroup_1_skinned_2]
 document.getElementById('CheckBoxGroup_1_skinned_2').className = 'checked';
 document.getElementById('CheckBoxGroup_1_hidden_2').checked = true;
   
 // set default checked and disabled
 // [CheckBoxGroup_1_skinned_3]
 document.getElementById('CheckBoxGroup_1_skinned_3').className = 'checked disabled';
 document.getElementById('CheckBoxGroup_1_hidden_3').disabled = 'disabled';
   
 // set default unchecked and disabled
 // [CheckBoxGroup_1_skinned_4]
 document.getElementById('CheckBoxGroup_1_skinned_4').className = 'unchecked disabled';
 document.getElementById('CheckBoxGroup_1_hidden_4').disabled = 'disabled';
 </script>
</div>


rahnev
About the Author

Stefan Rahnev

Stefan Rahnev (@StDiR) is Product Manager for Telerik Kendo UI living in Sofia, Bulgaria. He has been working for the company since 2005, when he started out as a regular support officer. His next steps at Telerik took him through the positions of Technical Support Director, co-team leader in one of the ASP.NET AJAX teams and unit manager for UI for ASP.NET AJAX and Kendo UI. Stefan’s main interests are web development, agile processes planning and management, client services and psychology.

Related Posts

Comments