Saturday, January 19, 2013

Extending and Customizing ZK Component as Separate Component


Introduction

This article reply to Senthil's blog post (http://emrpms.blogspot.in/2013/01/zk-datebox-customize-datebox-as.html), describe how to define a custom component that extends ZK component and do customization as needed.

Pre-request

The yearbox/monthbox customization by client side programming
http://ben-bai.blogspot.tw/2013/01/zk-datebox-customize-datebox-as.html

The basic tutorial of how to create a component
http://ben-bai.blogspot.tw/2012/08/zk-component-development-tutorial.html

Project Structure

We specify src and src/archive as two source folders separately.

src for .java files (not used in this sample)
src/archive for others (lang-addon.xml, .js, .wpd and .css.dsp)



The Program

lang-addon.xml

Define all component here, since we put it in the location that will be loaded by ZK automatically so no need to specify it in zk.xml.

<!-- Tested with ZK 6.0.2 -->

<language-addon>
    <addon-name>customDatebox</addon-name>
    <language-name>xul/html</language-name>

    <!-- It specifies what language addons this addon
        depends on. If specified, this addon will be
        parsed after all the specified addons are parsed -->
    <depends>zul</depends>

    <!-- define yearbox -->
    <component>
        <component-name>yearbox</component-name>
        <!-- extends datebox component,
            this denotes using all files (java/js/css.dsp)
            of the original datebox by default if
            no other file(s) specified -->
        <extends>datebox</extends>
        <!-- specify custom widget class file -->
        <widget-class>custom.zk.components.Yearbox</widget-class>
    </component>
    <!-- define monthbox -->
    <component>
        <component-name>monthbox</component-name>
        <!-- extends datebox component,
            this denotes using all files (java/js/css.dsp)
            of the original datebox by default if
            no other file(s) specified -->
        <extends>datebox</extends>
        <!-- specify custom widget class file -->
        <widget-class>custom.zk.components.Monthbox</widget-class>
    </component>
</language-addon>


zk.wpd

Define the dependency and loading sequence

<!-- Tested with ZK 6.0.2 -->
<!-- note the depends attribute should be zul.db
    since ZK supports load package on demand 
    will not load all recuired package if you only
    specify zul
    
    according to lang.xml in zul project, it more
    likely just load zul and zul.wgt by default -->
<package name="custom.zk.components" language="xul/html" depends="zul.db">
    <widget name="Yearbox" />
    <widget name="Monthbox" />
    <script src="override.js"/>
</package>


Yearbox.js

The customized widget class for yearbox, simply extends original datebox since what we need is only the widget class "custom.zk.components.Yearbox" so we can use the API widget.$instanceof to detect whether a calendar pop is belongs to a yearbox.

//
// Tested with ZK 6.0.2
//
(function () {
    // simply extends Datebox,
    // what we need is only the widget class
    // custom.zk.components.Yearbox
    custom.zk.components.Yearbox = zk.$extends(zul.db.Datebox, {
    });
})();


Monthbox.js

The customized widget class for monthbox, simply extends original datebox since what we need is only the widget class "custom.zk.components.Monthbox" so we can use the API widget.$instanceof to detect whether a calendar pop is belongs to a monthbox.

//
// Tested with ZK 6.0.2
//
(function () {
    // simply extends Datebox,
    // what we need is only the widget class
    // custom.zk.components.Monthbox
    custom.zk.components.Monthbox = zk.$extends(zul.db.Datebox, {
    });
})();

override.js

Override zul.db.CalendarPop here, change default action according to the parent's class of calendar pop. We use the API widget.$instanceof to detect whether a calendar pop is belongs to a yearbox or a monthbox.

zk.afterLoad("zul.db", function () {
    // Datebox Calendar Renderer
    var _Cwgt = {};
    zk.override(zul.db.CalendarPop.prototype, _Cwgt, {
        // switch the view after redraw or open as needed
        redraw: function (out) {
            _Cwgt.redraw.apply(this, arguments); //call the original method
            this._customChangeView ();
        },
        open: function (silent) {
            _Cwgt.open.apply(this, arguments); //call the original method
            this._customChangeView ();
        },
        _customChangeView: function () {
            // cannot show month/day
            if (this._isYearboxCalPop()) {
                var view = this._view;
                // switch to year view as needed
                if (view == 'month' || view == 'day')
                    this._setView("year");
            } else if (this._isMonthboxCalPop()) {
                // cannot show day view
                // switch to month view as needed
                if (this._view == 'day')
                    this._setView("month");
            }
        },
        // customize the chooseDate function
        _chooseDate: function (target, val) {
            var view = this._view;
            if (this._isYearboxCalPop()
                || this._isMonthboxCalPop()) {
                // do customize chooseDate if the parent (datebox)
                // has specific class
                var date = this.getTime(),
                    year = (view == 'decade' || view == 'year')? val : date.getFullYear(),
                    month = view == 'month'? val : 0;
                // set date value
                this._setTime(year, month, 1);
                if (view == 'decade') {
                    // switch to year view if at decade view
                    this._setView("year");
                } else if (this._isMonthboxCalPop()
                    && view == 'year') {
                    // switch to month view if at year view and the month view is allowed
                    this._setView("month");
                } else if (this._isMonthboxCalPop() && view == 'month'
                    || this._isYearboxCalPop() && view == 'year') {
                    // close calendar and update value if already at the smallest allowed view
                    this.close();
                    this.parent.updateChange_();
                }
            } else {
                _Cwgt._chooseDate.apply(this, arguments); //call the original method
            }
        },
        _isYearboxCalPop: function () {
            return this.parent.$instanceof(custom.zk.components.Yearbox);
        },
        _isMonthboxCalPop: function () {
            return this.parent.$instanceof(custom.zk.components.Monthbox);
        }
    });
});


index.zul

Test page

<zk>
    <vbox>
        <label value="yearbox" />
        <yearbox format="yyyy">
            <attribute name="onChange"><![CDATA[
                Date date = self.getValue();
                alert("Year: " + (date.getYear()+1900));
            ]]></attribute>
        </yearbox>

        <label value="monthbox" />
        <monthbox format="yyyy-MM">
            <attribute name="onChange"><![CDATA[
                Date date = self.getValue();
                alert("Year: " + (date.getYear()+1900) + "\n"
                    + "Month: " + (date.getMonth()+1));
            ]]></attribute>
        </monthbox>
    </vbox>
</zk>


The Result

The result is the same as http://ben-bai.blogspot.tw/2013/01/zk-datebox-customize-datebox-as.html

Reference

Datebox.js
https://github.com/zkoss/zk/blob/6.0/zul/src/archive/web/js/zul/db/Datebox.js

lang.xml in Zul project
https://github.com/zkoss/zk/blob/6.0/zul/src/archive/metainfo/zk/lang.xml


Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Customization/DateboxCustomization

15 comments:

  1. Replies
    1. No problem, I'm happy to be helpful :)

      Delete
  2. Again me, please refer the following forum thread.
    http://forum.zkoss.org/question/86736/masked-input-component/

    http://forum.zkoss.org/question/65608/watermark-and-masks-dont-work-on-textboxes-in-mold-rounded/

    ReplyDelete
  3. Hi Ben

    I am trying to use new ZK 7 Navigation bar component for my new project. Look at the following post in my blog

    http://emrpms.blogspot.in/2013/09/zk-dynamic-menu-part-5.html

    In the above, i have used group box and toolbar as left navigation bar and created dynamically.

    I am just trying to replace the left navigation with new ZK 7 Nav Bar. But not sure whether it supports children binding and template concept ?

    ReplyDelete
    Replies
    1. Actually I'm also not sure since ZK7 still under RC version and probably will be changed if needed, I'd suggest postpone your upgrade process for now.

      Delete
  4. This monthbox and yearbox solution seems not work at the latest ZK7,
    After selecting month,then the box is still empty!

    ReplyDelete
    Replies
    1. Yes, dom structure changed in ZK7, almost need to redo all customization for ZK7.

      Delete
  5. man, your tutorial is very incomplete for new learners it can't be used at all.

    ReplyDelete
    Replies
    1. Yes, this article is for limited target audience that already know some knowledge of ZK (especially the complex structure of component definition), you probably need some other background knowledge to understand this article.

      Delete
  6. Hi Ben this is a great example. I need to ask you a thing like what changes do I need to make for making Datebox use Hijri calendar instead of default(GregorianCalendar)

    ReplyDelete
    Replies
    1. Do you mean thai buddhist calendar? If so, you need to set th_TH locale,

      e.g.,

      <datebox locale="th_TH"/>

      Delete
    2. I mean this chronology (http://joda-time.sourceforge.net/cal_islamic.html)
      instead of default GregorianCalendar the datebox must use Islamic calendar

      Delete
    3. I guess this is not implemented yet, you can ask a new feature at http://tracker.zkoss.org/secure/IssueNavigator.jspa?mode=hide&requestId=10002

      Delete
    4. Is there a way I can implement that feature I mean can u suggest me anything so that Datebox will use Islamic calendar by default instead of GregorianCalendar

      Delete
    5. Yes, but it is very difficult.

      You need to override/extend ZK's date formatter and client side renderer, they are very complex

      You can also ask on ZK Forum for more information

      http://forum.zkoss.org/questions/

      Delete