Sunday, January 8, 2012

CSS and Javascript trick - The Fancy Checkbox

Below is a normal checkbox


<input type="checkbox" value="normal checkbox" onclick="alert('checked: ' + this.checked);">normal checkbox</input>


It works well and looks... normal, sometimes we want a more beautiful, fancy checkbox.

How to style it? There are several steps, described as follows:

Step 1: Separate the checkbox and label.

So we can create a fancy element to replace the normal checkbox and style it,
and style the label as need.

The outer 'i' tag also can handle the 'onclick' event that bubble up from fancy element,

and make easier to get fancy checkbox instance if we assign a CSS class to it.

The checkbox after this step becomes:


<head>
 <style type="text/css"> 
  .fancy_label { 
   font-style: normal;
  } 
 </style> 
</head>
<body>
 <i>
  <input type="checkbox" value="normal checkbox" onclick="alert('checked: ' + this.checked);"></input>
  <span class="fancy_label">normal checkbox</span>
 </i>
</body>


nothing changed, just separate the box and label.

Step 2: Throw out the normal checkbox.

Because we want replace it with a fancy one, we have to throw it out at first. We can not just drop it because we would like to use the functionality of original checkbox.

The checkbox after this step becomes:


<head>
 <style type="text/css">
  .normal_checkbox {
   position: absolute;
   left: -9999px;
  }
  .fancy_label { 
   font-style: normal;
  }
 </style> 
</head>
<body>
 <i>
  <input class="normal_checkbox" type="checkbox" value="normal checkbox" onclick="alert('checked: ' + this.checked);"></input>
  <span class="fancy_label">normal checkbox</span>
 </i>
</body>


The box is out of the screen, we can only see the label.

Step 3: Add the fancy element before the label.

We have to add a fancy element as the box of the fancy checkbox.

The checkbox after this step becomes:


<head>
    <style type="text/css">
        .normal_checkbox {
            position: absolute;
            left: -9999px;
        }
        .fancy_label { 
            font-style: normal;
        }
        .fancy_default {
            display: inline-block;
            height: 15px;
            width: 15px;
            background-repeat: no-repeat;
            background-image: url('img/fancy_default.png');
        }
        .fancy_over {
            background-image: url('img/fancy_over.png');
        }
        .fancy_down {
            background-image: url('img/fancy_down.png');
        }
        .fancy_checked {
            background-image: url('img/fancy_checked.png');
        }
        .fancy_disabled {
            background-color: #AAFFAA;
        }
    </style> 
</head>
<body>
    <i>
        <input class="normal_checkbox" type="checkbox" value="normal checkbox" onclick="alert('checked: ' + this.checked);"></input>
        <div class="fancy_default"></div>
        <span class="fancy_label">fancy default</span>
    </i>
</body>


the image above just list all status of fancy checkbox,

only the fancy default displayed now.

Step 4: Add the event with respect to each status.

To make it active, the event control is required.

Add style with mouseover/mousedown event and remove it while mouseout/mouseup,

also sync the checked/disabled status from the normal checkbox.


<html>
    <head>
        <script type="text/javascript">
            onload = function initFancyCheckbox () {
                var fancyElements,
                    element,
                    old;
                fancyElements = getElementsByClassName('fancy_default');
                for (var i =  0; i < fancyElements.length; i++) {
                    old = element;
                    element = fancyElements[i];
                    // IE need fix inline
                    if (navigator.appName == 'Microsoft Internet Explorer')
                        element.className += ' fancy_fix_inline';
                    // add style fancy_over while mouse over
                    element.onmouseover = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var clsnm = fancy.className;
                            if (clsnm.indexOf('fancy_over') == -1)
                                fancy.className += ' fancy_over';
                        }
                    }
                    // remove style fancy_over while mouse out
                    element.onmouseout = function (event) {
                        if (!event) // IE will not pass event
                            event = window.event;
                        // IE do not have currentTarget
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        fancy.className = fancy.className.replace(/fancy_over/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                    }
                    // add style fancy_down while mouse down
                    element.onmousedown = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var clsnm = fancy.className;
                            if (clsnm.indexOf('fancy_down') == -1)
                                fancy.className += ' fancy_down';
                        }
                    }
                    // remove style fancy_down while mouse up
                    element.onmouseup = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        fancy.className = fancy.className.replace(/fancy_down/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                    }
                    element.onclick = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var normal = fancy.previousSibling;
                            // try find the checkbox before the fancy element
                            while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                                normal = normal.previousSibling;
                            // not found, do nothing
                            if (!normal) return;
                            // change the checked status
                            normal.checked = !normal.checked;
                            syncChecked(fancy);
                        } else {
                            // stop the event bubble up
                            if (event.stopPropagation)
                                event.stopPropagation();
                            else
                                event.cancelBubble = true;
                        }
                    }
                    syncChecked(element);
                    syncDisabled(element);
                }
            }
            // get elements by class name with respect to specific element
            function getElementsByClassName (clsnm, parent) {
                var result = [];
                if (!parent)
                    parent = document;
                // IE do not have getElementsByClassName
                if (parent.getElementsByClassName)
                    result = parent.getElementsByClassName(clsnm);
                else {
                    var tmp = parent.getElementsByTagName('*');
                    for (var i = tmp.length-1; i >= 0; i--)
                        if (tmp[i].className && tmp[i].className.match(new RegExp('(\\s|^)'+clsnm+'(\\s|$)')))
                            result.push(tmp[i]);
                }
                return result;
            }
            // sync the checked status
            function syncChecked (element) {
                var clsnm = element.className,
                    normal = element.previousSibling;
                // try find the checkbox before the fancy element
                while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                    normal = normal.previousSibling;
                // not found, do nothing
                if (!normal) return;
                if (normal.checked) {
                    if (clsnm.indexOf('fancy_checked') == -1)
                        element.className += ' fancy_checked';
                } else {
                    element.className = element.className.replace(/fancy_checked/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                }
            }
            // sync the checked status
            function syncDisabled (element) {
                var clsnm = element.className,
                    normal = element.previousSibling;
                // try find the checkbox before the fancy element
                while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                    normal = normal.previousSibling;
                // not found, do nothing
                if (!normal) return;
                if (normal.disabled) {
                    if (element.className.indexOf('fancy_disabled') == -1)
                        element.className += ' fancy_disabled';
                } else
                    element.className = element.className.replace(/fancy_disabled/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
            }
        </script>
        <style type="text/css">
            .normal_checkbox {
                position: absolute;
                left: -9999px;
            }
            .fancy_checkbox {
                
            }
            .fancy_label { 
                font-style: normal;
            }
            .fancy_default {
                display: inline-block;
                height: 15px;
                width: 15px;
                background-repeat: no-repeat;
                background-image: url('img/fancy_default.png');
            }
            .fancy_over {
                background-image: url('img/fancy_over.png');
            }
            .fancy_down {
                background-image: url('img/fancy_down.png');
            }
            .fancy_checked {
                background-image: url('img/fancy_checked.png');
            }
            .fancy_disabled {
                background-color: #AAFFAA;
            }
            .fancy_fix_inline {
                display: inline;
                margin-right: 2px;
            }
        </style>
    </head>
    <body>
        <i id="fancy_checkbox_one" onclick="alert('checked: ' + this.getElementsByClassName('normal_checkbox')[0].checked);">
            <input class="normal_checkbox" type="checkbox" disabled="true" checked="true" value="normal checkbox"></input>
            <div class="fancy_default"></div>
            <span class="fancy_label">fancy default</span>
        </i><br />
    </body>
</html>


the image above is a fancy checkbox with initial checked and disabled.

Step 5: Create a test case.

After the fancy checkbox is done, we should create a use case and test it.

the test case:


<html>
    <head>
        <script type="text/javascript">
            onload = function initFancyCheckbox () {
                var fancyElements,
                    element,
                    old;
                fancyElements = getElementsByClassName('fancy_default');
                for (var i =  0; i < fancyElements.length; i++) {
                    old = element;
                    element = fancyElements[i];
                    // IE need fix inline
                    if (navigator.appName == 'Microsoft Internet Explorer')
                        element.className += ' fancy_fix_inline';
                    // add style fancy_over while mouse over
                    element.onmouseover = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var clsnm = fancy.className;
                            if (clsnm.indexOf('fancy_over') == -1)
                                fancy.className += ' fancy_over';
                        }
                    }
                    // remove style fancy_over while mouse out
                    element.onmouseout = function (event) {
                        if (!event) // IE will not pass event
                            event = window.event;
                        // IE do not have currentTarget
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        fancy.className = fancy.className.replace(/fancy_over/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                    }
                    // add style fancy_down while mouse down
                    element.onmousedown = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var clsnm = fancy.className;
                            if (clsnm.indexOf('fancy_down') == -1)
                                fancy.className += ' fancy_down';
                        }
                    }
                    // remove style fancy_down while mouse up
                    element.onmouseup = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        fancy.className = fancy.className.replace(/fancy_down/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                    }
                    element.onclick = function (event) {
                        if (!event)
                            event = window.event;
                        var fancy = (event.currentTarget) ? event.currentTarget : event.srcElement;
                        if (fancy.className.indexOf('fancy_disabled') == -1) {
                            var normal = fancy.previousSibling;
                            // try find the checkbox before the fancy element
                            while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                                normal = normal.previousSibling;
                            // not found, do nothing
                            if (!normal) return;
                            // change the checked status
                            normal.checked = !normal.checked;
                            syncChecked(fancy);
                        } else {
                            // stop the event bubble up
                            if (event.stopPropagation)
                                event.stopPropagation();
                            else
                                event.cancelBubble = true;
                        }
                    }
                    syncChecked(element);
                    syncDisabled(element);
                }
            }
            // get elements by class name with respect to specific element
            function getElementsByClassName (clsnm, parent) {
                var result = [];
                if (!parent)
                    parent = document;
                // IE do not have getElementsByClassName
                if (parent.getElementsByClassName)
                    result = parent.getElementsByClassName(clsnm);
                else {
                    var tmp = parent.getElementsByTagName('*');
                    for (var i = tmp.length-1; i >= 0; i--)
                        if (tmp[i].className && tmp[i].className.match(new RegExp('(\\s|^)'+clsnm+'(\\s|$)')))
                            result.push(tmp[i]);
                }
                return result;
            }
            // sync the checked status
            function syncChecked (element) {
                var clsnm = element.className,
                    normal = element.previousSibling;
                // try find the checkbox before the fancy element
                while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                    normal = normal.previousSibling;
                // not found, do nothing
                if (!normal) return;
                if (normal.checked) {
                    if (clsnm.indexOf('fancy_checked') == -1)
                        element.className += ' fancy_checked';
                } else {
                    element.className = element.className.replace(/fancy_checked/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
                }
            }
            // sync the checked status
            function syncDisabled (element) {
                var clsnm = element.className,
                    normal = element.previousSibling;
                // try find the checkbox before the fancy element
                while (normal && (!normal.tagName || normal.tagName != 'INPUT'))
                    normal = normal.previousSibling;
                // not found, do nothing
                if (!normal) return;
                if (normal.disabled) {
                    if (element.className.indexOf('fancy_disabled') == -1)
                        element.className += ' fancy_disabled';
                } else
                    element.className = element.className.replace(/fancy_disabled/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
            }
            // disable/enable a fancy checkbox by its instance or id
            function setDisable (fancyCheckbox, disable) {
                // assume it is an id if type is string
                if (typeof fancyCheckbox === 'string')
                    fancyCheckbox = document.getElementById(fancyCheckbox);
                var normal = getElementsByClassName('normal_checkbox', fancyCheckbox)[0],
                    fancy = getElementsByClassName('fancy_default', fancyCheckbox)[0];
                normal.disabled = disable;
                if (disable) {
                    if (fancy.className.indexOf('fancy_disabled') == -1)
                        fancy.className += ' fancy_disabled';
                } else
                    fancy.className = fancy.className.replace(/fancy_disabled/g, '').replace(/(^[\\s]*)|([\\s]*$)/g, "");
            }
            function updateDisable() {
                var food_list = getElementsByClassName('fancy_checkbox', document.getElementById('food_list')),
                    food_group = document.getElementById('food_group'),
                    checked = getElementsByClassName('normal_checkbox', food_group)[0].checked;
                for (var i = food_list.length-1; i >= 0; i--) {
                if (checked)
                        setDisable(food_list[i], false);
                    else
                        setDisable(food_list[i], true);
                }
            }
            function showResult() {
                var str = 'The food you chose: ',
                    food_list = getElementsByClassName('fancy_checkbox', document.getElementById('food_list'));
                for (var i = food_list.length-1; i >= 0; i--) {
                    if (getElementsByClassName('normal_checkbox', food_list[i])[0].checked)
                        str = str + getElementsByClassName('fancy_label', food_list[i])[0].innerHTML + ', ';
                }
                alert(str);
            }
        </script>
        <style type="text/css">
            .normal_checkbox {
                position: absolute;
                left: -9999px;
            }
            .fancy_checkbox {
                
            }
            .fancy_label { 
                font-style: normal;
            }
            .fancy_default {
                display: inline-block;
                height: 15px;
                width: 15px;
                background-repeat: no-repeat;
                background-image: url('img/fancy_default.png');
            }
            .fancy_over {
                background-image: url('img/fancy_over.png');
            }
            .fancy_down {
                background-image: url('img/fancy_down.png');
            }
            .fancy_checked {
                background-image: url('img/fancy_checked.png');
            }
            .fancy_disabled {
                background-color: #AAFFAA;
            }
            .fancy_fix_inline {
                display: inline;
                margin-right: 2px;
            }
        </style>
    </head>
    <body>
        <i id="food_group" onclick="updateDisable();" class="fancy_checkbox">
            <input class="normal_checkbox" type="checkbox" value="normal checkbox" onclick="alert('checked: ' + this.checked);"></input>
            <div class="fancy_default"></div>
            <span class="fancy_label">Choose food</span>
        </i><br />
        <div id="food_list">
            <i id="bread" style="margin-left: 3px" class="fancy_checkbox">
                <input class="normal_checkbox" type="checkbox" value="normal checkbox" disabled="true" onclick="alert('checked: ' + this.checked);"></input>
                <div class="fancy_default"></div>
                <span class="fancy_label">bread</span>
            </i><br />
            <i id="ham" style="margin-left: 3px" class="fancy_checkbox">
                <input class="normal_checkbox" type="checkbox" value="normal checkbox" checked="true" disabled="true" onclick="alert('checked: ' + this.checked);"></input>
                <div class="fancy_default"></div>
                <span class="fancy_label">ham</span>
            </i><br />
            <i id="beef" style="margin-left: 3px" class="fancy_checkbox">
                <input class="normal_checkbox" type="checkbox" value="normal checkbox" disabled="true" onclick="alert('checked: ' + this.checked);"></input>
                <div class="fancy_default"></div>
                <span class="fancy_label">beef</span>
            </i><br />
        </div>
        <button onclick="showResult()">show the result</button>
    </body>
</html>


Download
The full resource with a demo flash can be downloaded from github

https://github.com/benbai123/HTML_CSS_Javascript_practice/tree/master/fancy_checkbox

References:

Fancy checkboxes and radio buttons with CSS

No comments:

Post a Comment