Wednesday, January 9, 2013

ZK Datebox: Customize Datebox as Yearbox/Monthbox


Introduction

This article describe how to create a Yearbox/Monthbox based on Datebox with limited view.

The Program

datebox_with_limited_view.zul

<zk>
    <!-- tested with ZK 6.0.2 -->
    <script><![CDATA[
        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 (jq(this.parent.$n()).hasClass('datebox-year-only')) {
                        var view = this._view;
                        // switch to year view as needed
                        if (view == 'month' || view == 'day')
                            this._setView("year");
                    } else if (jq(this.parent.$n()).hasClass('datebox-month-only')) {
                        // 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 (jq(this.parent.$n()).hasClass('datebox-month-only')
                        || jq(this.parent.$n()).hasClass('datebox-year-only')) {
                        // 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,
                            date = 1;
                        // set date value
                        this._setTime(year, month, 1);
                        if (view == 'decade') {
                            // switch to year view if at decade view
                            this._setView("year");
                        } else if (jq(this.parent.$n()).hasClass('datebox-month-only')
                            && view == 'year') {
                            // switch to month view if at year view and the month view is allowed
                            this._setView("month");
                        } else if (jq(this.parent.$n()).hasClass('datebox-month-only') && view == 'month'
                            || jq(this.parent.$n()).hasClass('datebox-year-only') && 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
                    }
                }
            });
        });
    ]]></script>
    <vbox>
        <label value="datebox that do not allow the month/day view" />
        <datebox id="dbx" sclass="datebox-year-only"
            format="yyyy">
            <attribute name="onChange"><![CDATA[
                Date date = self.getValue();
                alert("Year: " + (date.getYear()+1900));
            ]]></attribute>
        </datebox>

        <label value="datebox that do not allow the day view" />
        <datebox id="dbx2" sclass="datebox-month-only"
            format="yyyy-MM">
            <attribute name="onChange"><![CDATA[
                Date date = self.getValue();
                alert("Year: " + (date.getYear()+1900) + "\n"
                    + "Month: " + (date.getMonth()+1));
            ]]></attribute>
        </datebox>
        <label value="a normal datebox" />

        <datebox id="dbx3">
            <attribute name="onChange"><![CDATA[
                Date date = self.getValue();
                alert("Year: " + (date.getYear()+1900) + "\n"
                    + "Month: " + (date.getMonth()+1) + "\n"
                    + "Day: " + date.getDate());
            ]]></attribute>
        </datebox>
    </vbox>
</zk>


The Result

View demo online
http://screencast.com/t/EejPLWg1iLyx

Reference

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

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

ZK Client Side Programming
http://books.zkoss.org/wiki/Small_Talks/2010/April/Client_Side_Programming

Download

Files at github:

datebox_with_limited_view.zul
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/WebContent/datebox_with_limited_view.zul

datebox_with_limited_view.swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/datebox_with_limited_view.swf

29 comments:

  1. Hi

    How we can create that as separate ZK Component, so that if i want to use this in multiple zul pages, then i do not want copy and paste the code in every zul file.

    ReplyDelete
    Replies
    1. You can define a component extends datebox then do customization, I'll describe it in another article this weekend.

      Delete
  2. Hi
    I tried myself. Here is the link.
    http://emrpms.blogspot.in/2013/01/zk-datebox-customize-datebox-as.html

    But still my objective is not solved. You can see my objective at the bottom of the post.

    ReplyDelete
  3. Replies
    1. Sure, I'll do it these two days.

      Delete
    2. Done, http://ben-bai.blogspot.tw/2013/01/extending-and-customizing-zk-component.html

      Delete
  4. Hi,
    Is this working on zk 5? Cause i tried this on zk 5 and still no luck. Please help,,

    ReplyDelete
    Replies
    1. Probably the js api is changed, I'll check this.

      Delete
    2. just remove the redraw function then it should work
      the redraw function:

      // switch the view after redraw or open as needed
      redraw: function (out) {
      _Cwgt.redraw.apply(this, arguments); //call the original method
      this._customChangeView ();
      },

      Delete
    3. No problem, I'm happy to be helpful :)

      Delete
  5. This great 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
    2. Is there a solution for ZK7?

      Delete
    3. Yes, please refer to
      http://zkfiddle.org/sample/ld4v50/1-ZK7-Monthbox-Yearbox

      Delete
    4. pastebin backup
      http://pastebin.com/zmhexXZ2

      Delete
  6. Perfect! Thanks for this post.
    Is it possible to take the constraint into account to forbidden the user to click on a wrong year/month ?
    For example, if I put the constraint "after 20130101" on the "yearbox", I can click on 2012 and the error message is displayed.

    ReplyDelete
    Replies
    1. You can store a valid date and reset the value of datebox when error occured, please refer to https://gist.github.com/benbai123/8277053

      Delete
  7. I want to add a custom clear button with datebox to clear the value of datebox when datebox readonly attribute is set to true.

    ReplyDelete
    Replies
    1. probably you can use custom datebox and override setReadable method as below:

      public void setReadbale (boolean readable) {
      if (readable) setValue("");
      super.setReadable();
      }

      why an extra button is needed? For some specific flow or purpose?

      Delete
    2. In my application lots of dateboxes are there.I restrict the user only to select the date not to enter. But in some cases I need to clear the date. Because of read only I am not able to clear it manually. I need to customize the datebox in a single place which will reflect other places.Thats y I want clear cutomized datebox

      Delete
    3. This comment has been removed by the author.

      Delete
    4. You can either clear it with a button click or key event, please refer to https://gist.github.com/benbai123/8616110

      Delete
  8. Hi ben,

    if I am doing this method in my custom Datebox extended datebox class
    public void setReadonly(boolean readable) {
    if (readable)
    setValue(null);
    super.isReadonly();

    }


    It allows the user to enter the values which I donot want.

    ReplyDelete
    Replies
    1. my fault, it should be super.setReadonly(readable);

      Delete
  9. Hi ben,
    Please look at this question
    http://stackoverflow.com/questions/21274789/zk-customize-calender-popup

    ReplyDelete
  10. Hello Ben, while the post is a little old, I would like to ask you how can I create a datebox displaying (and limiting selection) to year ONLY.

    ReplyDelete
    Replies
    1. Please refer to this one for ZK7
      http://zkfiddle.org/sample/ld4v50/1-ZK7-Monthbox-Yearbox

      It works well with ZK 8.0.3

      Delete