Saturday, January 14, 2012

ZK Custom Component - Fancy Checkbox

As discussed here, we can make a fancy checkbox withing pure HTML,
in this post, we will talk about how to make ZK Checkbox component fancy.

Below is a normal ZK Checkbox:

<zk>
 <checkbox label="normal checkbox" />
</zk>


It works well and looks... normal, sometimes we want make it fancy.


View Demo Online

http://screencast.com/t/7YQyHc1X

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

Step 1: Throw out the normal checkbox.

ZK already separate the checkbox and label as follows:


We want replace the checkbox element with a fancy one, we have to throw it out at first. We can not just drop it because already a bunch of function depends on it.

We do it by using zk.afterLoad client side API to override the bind_ method of ZK Checkbox.
The checkbox after this step becomes:

<zk>
    <script type="text/javascript"><![CDATA[
        zk.afterLoad("zul.wgt", function () {
            // store the old bind_ method
            var oldBind_ = zul.wgt.Checkbox.prototype.bind_;
            zul.wgt.Checkbox.prototype.bind_ = function (desktop) {
                // do the old bind_ method first
                oldBind_.apply(this, arguments);
                // get the checkbox element,
                // the $n(surfix) get the element which
                // id is 'Component_id' + '-surfix'
                var checkbox = this.$n('real');
                if (checkbox) {
                    // throw it out
                    checkbox.style.position = 'absolute';
                    checkbox.style.left = '-9000px';
                }
            };
        });
    ]]></script>
    <checkbox label="normal checkbox" />
</zk>


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


Step 2: Add the fancy element before the label.

The next step is add a fancy element as the box of the fancy checkbox.

The checkbox after this step becomes:

<zk>
    <script type="text/javascript"><![CDATA[
        zk.afterLoad("zul.wgt", function () {
            // store the old bind_ method
            var oldBind_ = zul.wgt.Checkbox.prototype.bind_;
            zul.wgt.Checkbox.prototype.bind_ = function (desktop) {
                // do the old bind_ method first
                oldBind_.apply(this, arguments);
                // get the checkbox element,
                // the $n(surfix) get the element which
                // id is 'Component_id' + '-surfix'
                var checkbox = this.$n('real');
                if (checkbox) {
                    // throw it out
                    checkbox.style.position = 'absolute';
                    checkbox.style.left = '-9000px';

                    var cls = 'fancy_default';
                    if (zk.ie < 8)
                        cls += ' fixinline';
                    // add fancy element after checkbox element
                    jq(checkbox).after('<div id="'
                            +this.uuid+'-fancy" class="'
                            +cls+'"></div>');
                }
            };
        });
    ]]></script>
    <style>
        .fixinline {
            display: inline;
        }
        .fancy_default {
            display: inline-block;
            height: 15px;
            width: 15px;
            margin-right: 2px;
            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>
    <checkbox label="fancy checkbox" />
</zk>



only the fancy default displayed now.


Step 3: 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.

<zk>
    <script type="text/javascript"><![CDATA[
        zk.afterLoad("zul.wgt", function () {
            // store the old bind_ and setDisabled method
            var oldBind_ = zul.wgt.Checkbox.prototype.bind_;
            var oldSetDisabled = zul.wgt.Checkbox.prototype.setDisabled;
            zul.wgt.Checkbox.prototype.bind_ = function (desktop) {
                // do the old bind_ method first
                oldBind_.apply(this, arguments);
                // get the checkbox element,
                // the $n(surfix) get the element which
                // id is 'Component_id' + '-surfix'
                var checkbox = this.$n('real');
                if (checkbox) {
                    // throw it out
                    checkbox.style.position = 'absolute';
                    checkbox.style.left = '-9000px';

                    var cls = 'fancy_default';
                    if (zk.ie < 8)
                        cls += ' fixinline';
                    // add fancy element after checkbox element
                    jq(checkbox).after('<div id="'
                            +this.uuid+'-fancy" class="'
                            +cls+'"></div>');
                    var fancy = this.$n('fancy');
                    if (fancy) {
                        var $fancy = jq(fancy),
                            label = fancy.nextSibling,
                            $label = jq(label);
                        if (checkbox.checked)
                            jq(fancy).addClass('fancy_checked');
                        if (checkbox.disabled)
                            jq(fancy).addClass('fancy_disabled');
                        // change style and
                        // trigger event on original checkbox
                        // with respect to fancy checkbox's event
                        $fancy.mouseover(function () {
                            jq(checkbox).mouseover();
                            if (!$fancy.hasClass('fancy_disabled'))
                                $fancy.addClass('fancy_over');
                        });
                        $fancy.mouseout(function () {
                            jq(checkbox).mouseout();
                            $fancy.removeClass('fancy_over');
                            $fancy.removeClass('fancy_down');
                        });
                        $fancy.mousedown(function () {
                            jq(checkbox).mousedown();
                            if (!$fancy.hasClass('fancy_disabled'))
                                $fancy.addClass('fancy_down');
                        });
                        $fancy.mouseup(function () {
                            jq(checkbox).mouseup();
                            $fancy.removeClass('fancy_down');
                        });
                        $fancy.click(function(){
                            if (!$fancy.hasClass('fancy_disabled')) {                                
                                jq(checkbox).click();
                                if (checkbox.checked)
                                    $fancy.addClass('fancy_checked');
                                else
                                    $fancy.removeClass('fancy_checked');
                            }
                        });
                        // change fancy checkbox's style
                        // with original label's event
                        $label.mouseover(function () {
                            if (!$fancy.hasClass('fancy_disabled'))
                                $fancy.addClass('fancy_over');
                        });
                        $label.mouseout(function () {
                            $fancy.removeClass('fancy_over');
                            $fancy.removeClass('fancy_down');
                        });
                        $label.mousedown(function () {
                            if (!$fancy.hasClass('fancy_disabled'))
                                $fancy.addClass('fancy_down');
                        });
                        $label.mouseup(function () {
                            $fancy.removeClass('fancy_down');
                        });
                        $label.click(function () {
                            // change style after original click
                            setTimeout(function () {
                                if (checkbox.checked)
                                    $fancy.addClass('fancy_checked');
                                else
                                    $fancy.removeClass('fancy_checked');
                            }, 0);
                        });
                    }
                }
            };
            // sync disable status after it set by API
            zul.wgt.Checkbox.prototype.setDisabled = function (v) {
                var checkbox,
                    fancy;
                oldSetDisabled.apply(this, arguments);
                if ((checkbox = this.$n('real'))
                    && (fancy = this.$n('fancy')))
                    if (checkbox.disabled)
                        jq(fancy).addClass('fancy_disabled');
                    else
                        jq(fancy).removeClass('fancy_disabled');
            };
        });
    ]]></script>
    <style>
        .fixinline {
            display: inline;
        }
        .fancy_default {
            display: inline-block;
            height: 15px;
            width: 15px;
            margin-right: 2px;
            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>
    <checkbox label="fancy checkbox" disabled="true" checked="true" />
</zk>


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

Step 4: Create a test case.

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

the test case:

<zscript>
    public void updateResult(Label result) {
        String s = "You chose: ";
        
        Page page = result.getPage();
        Checkbox cbx = (Checkbox)page.getFellow("bread");
        if (cbx.isChecked())
            s += "bread, ";
        cbx = (Checkbox)page.getFellow("ham");
        if (cbx.isChecked())
            s += "ham, ";
        cbx = (Checkbox)page.getFellow("beef");
        if (cbx.isChecked())
            s += "beef, ";
        result.setValue(s);    
    }
</zscript>
<div height="15px" />
<checkbox label="food group">
    <attribute name="onClick">
        bread.setDisabled(!self.isChecked());
        ham.setDisabled(!self.isChecked());
        beef.setDisabled(!self.isChecked());
    </attribute>
</checkbox>
<hbox>
    <div width="10px" />
    <vbox>
        <checkbox id="bread"
            label="bread" disabled="true"
            onClick="updateResult(result);" /> 
        <checkbox id="ham"
            label="ham" checked="true" disabled="true"
            onClick="updateResult(result);" />
        <checkbox id="beef"
            label="beef" disabled="true"
            onClick="updateResult(result);" />
    </vbox>
</hbox>
<label id="result" value="You chose: ham," />


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

The project:
fancy_checkbox.zul is under WebContent at
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Practice/


Demo:
ZK_fancy_checkbox_demo.swf is at
https://github.com/benbai123/ZK_Practice/tree/master/Components/demos

No comments:

Post a Comment