6
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Maven+Pro:400,700">
7
<link rel="stylesheet" href="../../build/cssgrids/grids-min.css">
8
<link rel="stylesheet" href="../assets/css/main.css">
9
<link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
10
<script src="../../build/yui/yui-min.js"></script>
18
<a href="#toc" class="jump">Jump to Table of Contents</a>
22
<div class="yui3-u-3-4">
24
<div class="content"><style scoped>
25
.yui3-js-enabled .yui3-checkboxes-loading { display: none; }
30
Using Progressive Enhancement to skin checkboxes with the help of the
31
<a href="../../api/Loader.html">Loader</a>,
32
<a href="../../api/module_classnamemanager.html">ClassNameManager
33
Utility</a>, and the Event Utility's <code>focus</code> and
34
<code>blur</code> events and the <code>delegate</code> method.
38
<div class="example yui3-skin-sam">
39
<div id="checkboxes" class="yui3-checkboxes-loading">
41
<label for="field-1">Field 1: </label>
44
<input type="checkbox" id="field-1" name="field-1" value="1">
49
<label for="field-2">Field 2: </label>
52
<input type="checkbox" id="field-2" name="field-2" value="2">
57
<label for="field-3">Field 3: </label>
60
<input type="checkbox" id="field-3" name="field-3" value="3">
68
// Load the stylesheet for the skinned checkboxes via JavaScript,
69
// since without JavaScript skinning of the checkboxes wouldn't
76
fullpath: "../assets/event/checkbox.css"
81
fullpath: "../assets/event/checkbox.js",
82
requires: ["classnamemanager", "event-focus", "node-event-delegate", "checkboxcss"]
93
<h2 id="challenges">Challenges</h2>
96
There are a few challenges when trying to skin an HTML checkbox using CSS. To start, most of the
97
<a href="http://developer.yahoo.com/yui/articles/gbs/#a-grade">A-grade browsers</a> don't provide
98
support for CSS properties like <code>border</code> and <code>background</code> on the
99
<code><input type="checkbox"></code> element. Additionally, IE 6 and IE 7 (Quirks Mode)
100
lack support for attribute selectors — necessary to style the <code>checked</code> and
101
<code>disabled</code> states. Additionally, IE 6 and 7 only support the <code>:focus</code> and
102
<code>:active</code> pseudo classes on <code><a></code> elements, both of which are needed
103
to style a checkbox when it is focused or depressed.
106
<h2 id="approach">Approach</h2>
108
Despite the shortcomings in CSS support, with a little extra markup and through the use of
109
JavaScript it is possible to skin an <code><input type="checkbox"></code> element
110
consistently well in all of the
111
<a href="http://developer.yahoo.com/yui/articles/gbs/#a-grade">A-grade browsers</a>.
115
<h4 id="markup">Markup</h4>
117
Since CSS support for the <code><input type="checkbox"></code> element is lacking, wrap
118
<code><input type="checkbox"></code> elements in one or more inline elements to provide the
119
necessary hooks for styling. In this example, each <code><input type="checkbox"></code>
120
element is wrapped by two <code><span></code>s.
123
<pre class="code prettyprint"><span>
125
<input type="checkbox">
127
</span></pre>
130
<h4 id="css">CSS</h4>
132
To style each checkbox, a class name of <code>yui3-checkbox</code> will be applied to the
133
outtermost <code><span></code> wrapping each <code><input type="checkbox"></code>
134
element. An additional class will be used to represent the various states of each checkbox. The
135
class name for each state will follow a consistent pattern: <code>yui3-checkbox-[state]</code>.
136
For this example, the following state-based class names will be used:
139
<dt><code>yui3-checkbox-focus</code></dt>
140
<dd>The checkbox has focus</dd>
141
<dt><code>yui3-checkbox-active</code></dt>
142
<dd>The checkbox is active (pressed)</dd>
143
<dt><code>yui3-checkbox-checked</code></dt>
144
<dd>The checkbox is checked</dd>
147
The styles for each checkbox comes together as follows:
150
<pre class="code prettyprint">.yui3-checkbox {
151
display: -moz-inline-stack; /* Gecko < 1.9, since it doesn't support "inline-block" */
152
display: inline-block; /* IE, Opera and Webkit, and Gecko 1.9 */
155
border: inset 2px #999;
156
background-color: #fff; /* Need to set a background color or IE won't get mouse events */
159
Necessary for IE 6 (Quirks and Standards Mode) and IE 7 (Quirks Mode), since
160
they don't support use of negative margins without relative positioning.
165
.yui3-checkbox span {
171
/* Position the checkmark for Gecko, Opera and Webkit and IE 7 (Strict Mode). */
172
margin: -5px 0 0 1px;
174
/* Position the checkmark for IE 6 (Strict and Quirks Mode) and IE 7 (Quirks Mode).*/
182
/* For Gecko < 1.9: Positions the checkbox on the same line as its corresponding label. */
183
.yui3-checkbox span:after {
184
content: ".";
190
Hide the actual checkbox offscreen so that it is out of view, but still accessible via
193
.yui3-checkbox input {
198
.yui3-checkbox-focus {
200
background-color: #9cf;
203
.yui3-checkbox-active {
204
background-color: #ccc;
207
.yui3-checkbox-checked span {
208
/* Draw a custom checkmark for the checked state using a background image. */
209
background: url(checkmark.png) no-repeat;
213
<h4 id="javascript">JavaScript</h4>
216
Application and removal of the state-based class names will be facilitated by JavaScript event
217
handlers. Each event handler will track the state of the
218
<code><input type="checkbox"></code> element, and apply and remove corresponding
219
state-based class names from its outtermost <code><span></code> —
220
making it easy to style each state. And since each JavaScript is required for state management,
221
the stylesheet for the skinned checkboxes will only be added to the page when JavaScript is
222
enabled. This will ensure that each checkbox works correctly with and without JavaScript enabled.
226
Since there could easily be many instances of a skinned checkbox on the page, all event
227
listeners will be attached to the containing element for all checkboxes. Each listener will
228
listen for events as they bubble up from each checkbox. Relying on event bubbling will improve the
229
overall performance of the page by reducing the number of event handlers.
233
Since the DOM <code>focus</code> and <code>blur</code> events do not bubble, use the Event Utility's
234
<a href="../../api/YUI.html#event_focus"><code>focus</code></a> and
235
<a href="../../api/YUI.html#event_focus"><code>blur</code></a> custom events, as an alternative to
236
attaching discrete focus and blur event handlers to the <code><input type="checkbox"></code>
237
element of each skinned checkbox. The
238
<a href="../../api/YUI.html#event_focus"><code>focus</code></a> and
239
<a href="../../api/YUI.html#event_focus"><code>blur</code></a> custom events leverage
240
capture-phase DOM event listeners, making it possible to attach a single focus and blur event
241
listener on the containing element of each checkbox — thereby increasing the performance
242
of the page. The complete script for the example comes together as follows:
245
<pre class="code prettyprint">YUI().use("*", function(Y) {
248
getClassName = Y.ClassNameManager.getClassName,
249
sCheckboxFocusClass = getClassName("checkbox", "focus"), // Create yui3-checkbox-focus
250
sCheckboxCheckedClass = getClassName("checkbox", "checked"), // Create yui3-checkbox-checked
251
sCheckboxActiveClass = getClassName("checkbox", "active"), // Create yui3-checkbox-active
252
bKeyListenersInitialized = false,
253
bMouseListenersInitialized = false,
254
forAttr = (UA.ie && UA.ie < 8) ? "htmlFor" : "for",
255
bBlockDocumentMouseUp = false,
256
bBlockClearActive = false,
261
var initKeyListeners = function () {
263
this.delegate("keydown", onCheckboxKeyDown, ".yui3-checkbox");
264
this.delegate("click", onCheckboxClick, ".yui3-checkbox");
265
this.delegate("blur", onCheckboxBlur, "input[type=checkbox]");
267
bKeyListenersInitialized = true;
272
var initMouseListeners = function () {
274
this.delegate("mouseover", onCheckboxMouseOver, ".yui3-checkbox");
275
this.delegate("mouseout", onCheckboxMouseOut, ".yui3-checkbox-active");
276
this.get("ownerDocument").on("mouseup", onDocumentMouseUp);
278
bMouseListenersInitialized = true;
283
var getCheckbox = function (node) {
285
return (node.hasClass("yui3-checkbox") ? node : node.ancestor(".yui3-checkbox"));
290
var getCheckboxForLabel = function (label) {
292
var sID = label.getAttribute(forAttr),
298
oInput = Y.one("#" + sID);
301
oCheckbox = getCheckbox(oInput);
311
var updateCheckedState = function (input) {
313
var oCheckbox = getCheckbox(input);
315
if (input.get("checked")) {
316
oCheckbox.addClass(sCheckboxCheckedClass);
319
oCheckbox.removeClass(sCheckboxCheckedClass);
325
var setActiveCheckbox = function (checkbox) {
327
checkbox.addClass(sCheckboxActiveClass);
328
oActiveCheckbox = checkbox;
333
var clearActiveCheckbox = function () {
335
if (oActiveCheckbox) {
336
oActiveCheckbox.removeClass(sCheckboxActiveClass);
337
oActiveCheckbox = null;
343
var onCheckboxMouseOver = function (event, matchedEl) {
345
if (oActiveCheckbox && oActiveCheckbox.compareTo(this)) {
346
oActiveCheckbox.addClass(sCheckboxActiveClass);
352
var onCheckboxMouseOut = function (event) {
354
this.removeClass(sCheckboxActiveClass);
359
var onDocumentMouseUp = function (event) {
363
if (!bBlockDocumentMouseUp) {
365
oCheckbox = getCheckbox(event.target);
367
if ((oCheckbox && !oCheckbox.compareTo(oActiveCheckbox)) || !oCheckbox) {
368
clearActiveCheckbox();
373
bBlockDocumentMouseUp = false;
378
var onCheckboxFocus = function (event) {
380
// Remove the focus style from any checkbox that might still have it
382
var oCheckbox = Y.one("#checkboxes").one(".yui3-checkbox-focus");
385
oCheckbox.removeClass(sCheckboxFocusClass);
388
// Defer adding key-related and click event listeners until
389
// one of the checkboxes is initially focused.
391
if (!bKeyListenersInitialized) {
392
initKeyListeners.call(event.container);
395
var oCheckbox = getCheckbox(this);
397
oCheckbox.addClass(sCheckboxFocusClass);
402
var onCheckboxBlur = function (event) {
409
var oCheckbox = getCheckbox(this);
411
oCheckbox.removeClass(sCheckboxFocusClass);
413
if (!bBlockClearActive && oCheckbox.compareTo(oActiveCheckbox)) {
414
clearActiveCheckbox();
417
bBlockClearActive = false;
422
var onCheckboxMouseDown = function (event) {
424
// Defer adding mouse-related and click event listeners until
425
// the user mouses down on one of the checkboxes.
427
if (!bMouseListenersInitialized) {
428
initMouseListeners.call(event.container);
435
if (this.get("nodeName").toLowerCase() === "label") {
437
// If the target of the event was the checkbox's label element, the
438
// label will dispatch a click event to the checkbox, meaning the
439
// "onCheckboxClick" handler will be called twice. For that reason
440
// it is necessary to block the "onDocumentMouseUp" handler from
441
// clearing the active state, so that a reference to the active
442
// checkbox still exists the second time the "onCheckboxClick"
443
// handler is called.
445
bBlockDocumentMouseUp = true;
447
// When the user clicks the label instead of the checkbox itself,
448
// the checkbox will be blurred if it has focus. Since the
449
// "onCheckboxBlur" handler clears the active state it is
450
// necessary to block the clearing of the active state when the
451
// label is clicked instead of the checkbox itself.
453
bBlockClearActive = true;
455
oCheckbox = getCheckboxForLabel(this);
464
// Need to focus the input manually for two reasons:
465
// 1) Mousing down on a label in Webkit doesn't focus its
466
// associated checkbox
467
// 2) By default checkboxes are focused when the user mouses
468
// down on them. However, since the actually checkbox is
469
// obscurred by the two span elements that are used to
470
// style it, the checkbox wont' receive focus as it was
471
// never the actual target of the mousedown event.
473
oInput = oCheckbox.one("input");
476
// Calling Event.preventDefault won't block the blurring of the
477
// currently focused element in IE, so we'll use the "bBlockBlur"
478
// variable to stop the code in the blur event handler
479
// from executing.
481
bBlockBlur = (UA.ie && oInput.get("checked"));
486
setActiveCheckbox(oCheckbox);
488
// Need to call preventDefault because by default mousing down on
489
// an element will blur the element in the document that currently
490
// has focus--in this case, the input element that was
491
// just focused.
493
event.preventDefault();
498
var onCheckboxClick = function (event) {
502
if (this.compareTo(oActiveCheckbox)) {
504
oInput = this.one("input");
506
if (!event.target.compareTo(oInput)) {
508
// If the click event was fired via the mouse the checked
509
// state will have to be manually updated since the input
510
// is hidden offscreen and therefore couldn't be the
511
// target of the click.
513
oInput.set("checked", (!oInput.get("checked")));
517
updateCheckedState(oInput);
518
clearActiveCheckbox();
525
var onCheckboxKeyDown = function (event) {
527
// Style the checkbox as being active when the user presses the
528
// space bar
530
if (event.keyCode === 32) {
531
setActiveCheckbox(this);
536
Y.all("#checkboxes>div>span").addClass("yui3-checkbox");
538
// Remove the "yui3-checkboxes-loading" class used to hide the
539
// checkboxes now that the checkboxes have been skinned.
541
Y.one("#checkboxes").removeClass("yui3-checkboxes-loading");
543
// Add the minimum number of event listeners needed to start, bind the
544
// rest when needed
546
Y.delegate("mousedown", onCheckboxMouseDown, "#checkboxes", ".yui3-checkbox,label");
547
Y.delegate("focus", onCheckboxFocus, "#checkboxes", "input[type=checkbox]");
552
<h4 id="progressive-enhancement">Progressive Enhancement</h4>
554
To account for the scenario where the user has CSS enabled in their browser but JavaScript
555
is disabled, the CSS used to style the checkboxes will be loaded via JavaScript
556
using the YUI instance's <a href="http://developer.yahoo.com/yui/3/yui#loader">built-in Loader</a>.
558
<pre class="code prettyprint">YUI({
560
base: "${buildDirectory}",
562
// Load the stylesheet for the skinned checkboxes via JavaScript,
563
// since without JavaScript skinning of the checkboxes wouldn't
564
// be possible.
568
"checkboxcss": {
569
type: "css",
570
fullpath: "${assetsDirectory}checkbox.css"
572
"checkboxjs": {
573
type: "js",
574
fullpath: "${assetsDirectory}checkbox.js",
575
requires: ["classnamemanager", "event-focus", "node-event-delegate", "checkboxcss"]
580
}).use("checkboxjs");</pre>
584
To prevent the user from seeing a flash unstyled content when JavaScript is enabled,
585
a style rule is created using YUI's <code>yui3-js-enabled</code> class name that will temporarily
586
hide the markup while the JavaScript and CSS are in the process of loading. For more on using the
587
<code>yui3-js-enabled</code> class name, see the
588
<a href="../../widget/#progressive">HIDING PROGRESSIVELY ENHANCED MARKUP</a> section of the
589
<a href="../../widget/">YUI Widget landing page</a>.
591
<pre class="code prettyprint">.yui3-js-enabled .yui3-checkboxes-loading {
599
<div class="yui3-u-1-4">
600
<div class="sidebar">
602
<div id="toc" class="sidebox">
604
<h2 class="no-toc">Table of Contents</h2>
610
<a href="#challenges">Challenges</a>
613
<a href="#approach">Approach</a>
616
<a href="#markup">Markup</a>
619
<a href="#css">CSS</a>
622
<a href="#javascript">JavaScript</a>
625
<a href="#progressive-enhancement">Progressive Enhancement</a>
635
<div class="sidebox">
637
<h2 class="no-toc">Examples</h2>
641
<ul class="examples">
644
<li data-description="Use the Event Utility to attach simple DOM event handlers.">
645
<a href="basic-example.html">Simple DOM Events</a>
650
<li data-description="Using the synthetic event API to create a DOM event that fires in response to arrow keys being pressed.">
651
<a href="synth-example.html">Creating an Arrow Event for DOM Subscription</a>
656
<li data-description="Supporting cross-device swipe gestures, using the event-move gesture events">
657
<a href="swipe-example.html">Supporting A Swipe Left Gesture</a>
677
<div class="sidebox">
679
<h2 class="no-toc">Examples That Use This Component</h2>
683
<ul class="examples">
692
<li data-description="Shows how to extend the base widget class, to create your own Widgets.">
693
<a href="../widget/widget-extend.html">Extending the Base Widget Class</a>
698
<li data-description="Creating an accessible menu button using the Focus Manager Node Plugin, Event's delegation support and mouseenter event, along with the Overlay widget and Node's support for the WAI-ARIA Roles and States.">
699
<a href="../node-focusmanager/node-focusmanager-3.html">Accessible Menu Button</a>
704
<li data-description="Use IO to request data over HTTP.">
705
<a href="../io/get.html">HTTP GET to request data</a>
710
<li data-description="Example Photo Browser application.">
711
<a href="../dd/photo-browser.html">Photo Browser</a>
716
<li data-description="Portal style example using Drag & Drop Event Bubbling and Animation.">
717
<a href="../dd/portal-drag.html">Portal Style Example</a>
730
<script src="../assets/vendor/prettify/prettify-min.js"></script>
731
<script>prettyPrint();</script>