Tuesday, January 22, 2013

ZK Pivottable: Sort Pivottable Column Data


Introduction

We have implemented 'Display Data in ZK Pivottable' in previous post (http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html), in this post, we will try to sort the column data in pivottable.

The Composer

TestComposer.java

package test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.Pivottable;

import org.zkoss.pivot.event.PivotUIEvent;
import org.zkoss.pivot.impl.SimplePivotHeaderTree;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;

import org.zkoss.zk.ui.event.MouseEvent;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;

/**
 * Tested with ZK 6.0.1 CE and ZK Pivottable 2.0.0
 *
 */
@SuppressWarnings("rawtypes")
public class TestComposer extends SelectorComposer {
    /**
     * generated serial version UID
     */
    private static final long serialVersionUID = -2897873399288955635L;
    private TabularPivotModel _pivotModel;

    @Wire("#pivottable")
    private Pivottable pivottable;

    private int _rowLevelToSort = 1;    // the level of rows to sort
    private int _fieldIndexToSort = 0; // the index of data field under _sortColumn
    private PivotHeaderNode _columnNodeToSort; // the column node with respect to _sortColumn

    /**
     * Get pivottable's model
     * @return TabularPivotModel the pivottable's model
     * @throws Exception
     */
    public TabularPivotModel getPivotModel () throws Exception {
        if (_pivotModel == null) {
            _pivotModel = new TabularPivotModel(getData(), getColumns());

            // assign rows, the order matches to the level of row node field
            _pivotModel.setFieldType("Row_Level_001", PivotField.Type.ROW);
            _pivotModel.setFieldType("Row_Level_002", PivotField.Type.ROW);
            _pivotModel.setFieldType("Row_Level_003", PivotField.Type.ROW);
            _pivotModel.setFieldType("Row_Level_004", PivotField.Type.ROW);

            // assign columns, the order matches to the level of column node field
            _pivotModel.setFieldType("Column_Level_001", PivotField.Type.COLUMN);
            _pivotModel.setFieldType("Column_Level_002", PivotField.Type.COLUMN);

            // assign datas, the order matches to the order of data field
            _pivotModel.setFieldType("Data_Field_001", PivotField.Type.DATA);
            _pivotModel.setFieldType("Data_Field_002", PivotField.Type.DATA);
            _pivotModel.setFieldType("Data_Field_003", PivotField.Type.DATA);
        }
        return _pivotModel;
    }
    /**
     * prepare the data for pivottable's model
     * The order of object put into data list matches
     * the order of column name's order
     * @return
     * @throws Exception
     */
    public List<List<Object>> getData() throws Exception {
        List<List<Object>> result = new ArrayList<List<Object>>();
        Random r = new Random();

        for (int i = 0; i < 10000; i++) {
            List<Object> data = new ArrayList<Object>();
            data.add("Row_Level_001 - " + (r.nextInt(10) + 1));
            data.add("Row_Level_002 - " + (r.nextInt(10) + 1));
            data.add("Row_Level_003 - " + (r.nextInt(10) + 1));
            data.add("Row_Level_004 - " + (r.nextInt(10) + 1));
            data.add("Column_Level_001 - " + (r.nextInt(10) + 1));
            data.add("Column_Level_002 - " + (r.nextInt(10) + 1));
            data.add(r.nextInt(10000));
            data.add(r.nextDouble() * 10000.0);
            data.add(r.nextInt(100));
            result.add(data);
        }
        return result;
    }
    /**
     * prepare columns name for pivottable's model
     * @return
     */
    public List<String> getColumns() {
        return Arrays.asList(new String[]{
                "Row_Level_001", "Row_Level_002", "Row_Level_003", "Row_Level_004",
                "Column_Level_001", "Column_Level_002",
                "Data_Field_001", "Data_Field_002", "Data_Field_003"
        });
    }

    /**
     * ** Added **
     * sort column ascending
     */
    @Listen("onClick = #btnSortAsc")
    public void doSortAscending (MouseEvent event) {
        doSort(true);
    }

    /**
     * ** Added **
     * sort column descending
     */
    @Listen("onClick = #btnSortDesc")
    public void doSortDescending (MouseEvent event) {
        doSort(false);
    }
    /**
     * ** Added **
     * sort column
     * @param ascending boolean, true: ascending, false: descending
     */
    private void doSort (boolean ascending) {
        if (_columnNodeToSort != null) {
            // get the rowHeaderTree
            SimplePivotHeaderTree rowHeaderTree = (SimplePivotHeaderTree)_pivotModel.getRowHeaderTree();
            // create comparator
            ColumnHeaderComparator comparator = new ColumnHeaderComparator(_pivotModel,
                    _columnNodeToSort, _fieldIndexToSort, _rowLevelToSort, ascending);
            // do sort
            rowHeaderTree.sort(comparator);
            // reset model to trigger rerender
            pivottable.setModel(_pivotModel);
        }
    }

    /**
     * ** Added **
     * Update sort attributes from click event of pivottable
     */
    @Listen("onPivotPopup = #pivottable")
    public void updateSortAttributes (PivotUIEvent e) {
        PivotField dataField = e.getDataField();

        _rowLevelToSort = e.getRowContext() != null? e.getRowContext().getNode().getDepth() : 0;
        if (dataField != null) {
            _columnNodeToSort = e.getColumnContext().getNode();
            _fieldIndexToSort = getFieldIndexToSort(dataField.getFieldName(), _pivotModel.getDataFields());
        }
    }
    /**
     * ** Added **
     * get the index of the field to sort
     * @param fieldToSort the field to sort
     * @param fields all fields get from pivot model
     * @return int the index of the field to sort
     */
    private int getFieldIndexToSort (String fieldToSort, TabularPivotField[] fields) {
        for (int i = 0; i < fields.length; i++) {
            if (fieldToSort.equals(fields[i].getFieldName()))
                return i;
        }
        return 0;
    }
}


There are two parts regarding to the sort feature, one is 'public void updateSortAttributes (PivotUIEvent e)', it updates the sorting attributes when a data cell or a row head clicked, the other is 'private void doSort (boolean ascending)', it will sort a column's data with respect to the sorting attributes while sort ascending / descending button clicked.

ColumnHeaderComparator.java


package test;

import java.math.BigDecimal;
import java.util.Comparator;

import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.impl.TabularPivotModel;

/**
 * ** Added **
 *  the comparator for sort row header node
 *  Tested with ZK 6.0.1 CE and ZK Pivottable 2.0.0
 */
public class ColumnHeaderComparator implements Comparator<PivotHeaderNode> {
    private TabularPivotModel _pivotModel; // the data model
    private PivotHeaderNode _columnNodeToSort; // the column node to sort
    private int _fieldIndexToSort; // the field index of data fields to sort
    private int _rowLevelToSort; // the row level to sort
    private boolean _ascending; // sort direction

    /**
     * Constructor
     * @param pivotModel TabularPivotModel, The pivottable's model
     * @param columnNodeToSort PivotHeaderNode, The column to sort
     * @param fieldIndexToSort int, The index of the field under the columnNodeToSort to sort
     * @param rowLevelToSort int, The level of row header node to sort
     * @param ascending boolean, true: sort ascending, false: sort descending
     */
    public ColumnHeaderComparator (TabularPivotModel pivotModel, PivotHeaderNode columnNodeToSort,
            int fieldIndexToSort, int rowLevelToSort, boolean ascending) {
        _pivotModel = pivotModel;
        _columnNodeToSort = columnNodeToSort;
        _fieldIndexToSort = fieldIndexToSort;
        _rowLevelToSort = rowLevelToSort;
        _ascending = ascending;
    }
    /**
     * compare two node
     */
    public int compare (PivotHeaderNode n1, PivotHeaderNode n2) {
        int result = 0;
        // get the level of two node
        int l1 = n1.getDepth();
        int l2 = n2.getDepth();

        // only compare if node is at sort level
        if (l1 == _rowLevelToSort
            && l2 == _rowLevelToSort) {
            // get the values and compare
            Number v1 = _pivotModel.getValue(n1, -1, _columnNodeToSort, -1, _fieldIndexToSort);
            Number v2 = _pivotModel.getValue(n2, -1, _columnNodeToSort, -1, _fieldIndexToSort);
            result = (_ascending? 1 : -1 ) * (new BigDecimal(v1.toString()).compareTo(new BigDecimal(v2.toString())));
        }

        return result;
    }
}


This is the comparator, the sorting result will depends on how you implement it.

The ZUL Page

index.zul

<zk xmlns:w="client"><!-- ** Added ** namespace for Client Side Programming -->
    <!-- Tested with ZK 6.0.1 CE and ZK Pivottable 2.0.0 -->
    <vlayout>
        <div>
            <!-- ** Added ** the description of how to use sort feature -->
            <vbox>
                <label value="Description:" />
                <label value="Click data cell to choose the column field to sort" />
                <label value="The sort level will be the latest extended level of the clicked row" />
                <label value="Click row header node to change the sort level" />
                <label value="Click 'sort ascending' / 'sort descending' button to sort" />
            </vbox>
        </div>
        <!-- window, apply a SelectorComposer -->
        <window id="win" xmlns:w="client"
            apply="test.TestComposer">
            <!-- pivottable, get model from window's composer -->
            <pivottable id="pivottable" model="${win$composer.pivotModel}">
                <!-- ** Added ** Blank block for position the sort button -->
                <div></div>
                <!-- ** Added ** the sort button to do sort ascending / descending -->
                <div>
                    <!-- The sort ascending button -->
                    <button id="btnSortAsc" label="Sort Ascending" />
                    <!-- The sort descending button -->
                    <button id="btnSortDesc" label="Sort Descending" />
                </div>
            </pivottable>
        </window>
    </vlayout>
    <!-- ** Added ** define some style and override some function
                    for better user experience
                    actually the 'style' fragment and the 'script' fragment
                    are not related to the sort feature -->
    <style>
        <!-- style for focused data cell / row head -->
        .focused-data-cell .z-pivottable-field-wrapper {
            background-color: #CCCCCC;
        }
        .focused-row-head .z-pivottable-field-wrapper {
            background-color: #AADDDD;
        }
    </style>
    <script type="text/javascript"><![CDATA[
        var _Pwgt = {},
            _Bwgt = {};
        // override pivottable js function
        zk.afterLoad("pivot", function () {
            zk.override(pivot.Pivottable.prototype, _Pwgt, {
                // override pivottable bind_ for restore the scrollLeft
                bind_: function(desktop, skipper, after) {
                    _Pwgt.bind_.apply(this, arguments); //call the original method
                    var oldLeft;
                    if (this.id == 'pivottable') { // limit the affect target
                        if (oldLeft = pivot.Pivottable.pivottableOldLeft) {
                            var wgt = this;

                            setTimeout(function (){
                                wgt.$n('scroll').scrollLeft = oldLeft;
                                pivot.Pivottable.pivottableOldLeft = null;
                            }, 200);
                        }
                    }
                },
                // override pivottable doClick_ for style the clicked data cell or row head
                doClick_: function (evt) {
                    _Pwgt.doClick_.apply(this, arguments); //call the original method
                    // clear old scroll if any
                    pivot.Pivottable.pivottableOldLeft = null;
                    if (this.id == 'pivottable') { // limit the affect target
                        var tar = evt.domTarget,
                            jtar = jq(tar),
                            old;
                        if (!jtar.hasClass('z-pivottable-icon')) {
                            // click on data cell
                            if (jtar.hasClass('z-pivottable-cell-field')
                                || ((tar = tar.parentNode) && (jtar = jq(tar)) && jtar.hasClass('z-pivottable-cell-field'))) {
                                // remove old focus
                                if (old = this._oldFocusDataCell) {
                                    jq(old).removeClass('focused-data-cell');
                                }
                                // also remove old row head's style, denotes use latest level as sort level
                                if (old = this._oldFocusRowHead) {
                                    jq(old).removeClass('focused-row-head');
                                }
                                // add focus for denotes use clicked data field to do sort
                                jtar.addClass('focused-data-cell');
                                this._oldFocusDataCell = jtar[0];
                            } else if (jtar.hasClass('z-pivottable-row-field')
                                    || ((tar = tar.parentNode) && (jtar = jq(tar)) && jtar.hasClass('z-pivottable-row-field'))) {
                                // click on row header node
                                // remove old focus
                                if (old = this._oldFocusRowHead) {
                                    jq(old).removeClass('focused-row-head');
                                }
                                // add focus for denotes use specific sort level
                                jtar.addClass('focused-row-head');
                                this._oldFocusRowHead = jtar[0];
                            }
                        }
                    }
                }
            });
        });
        // override button js function
        zk.afterLoad("zul.wgt", function () {
            zk.override(zul.wgt.Button.prototype, _Bwgt, {
                // override doClick_ for store old pivottable scrollLeft
                doClick_: function (evt) {
                    _Bwgt.doClick_.apply(this, arguments); //call the original method
                    var id = this.id;
                    if (id == 'btnSortAsc'
                        || id == 'btnSortDesc') { // limit the affect target
                        // store the old scrollLeft of pivottable
                        pivot.Pivottable.pivottableOldLeft =
                            zk.Widget.$('$pivottable').$n('scroll').scrollLeft;
                    }
                }
            });
        });
    ]]></script>
</zk>


The majore part is the two button added, others are only make things better but not related to the sort feature.

The Result
View the demo flash on line
http://screencast.com/t/eD4z1UZjEWWG

You can find the flash file at github:
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/addon/SortPivottableColumn.swf

Reference
http://books.zkoss.org/wiki/ZK_Pivottable_Essentials


Download
The full project is at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/SortPivottableColumn

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

Saturday, January 12, 2013

Using CSS3 Animations to Perform Notification



Introduction

From W3schools:
With CSS3, we can create animations, which can replace animated images, Flash animations, and JavaScripts in many web pages.

The difference between transition and animation is transition can be applied to change a property smoothly once at a time and the changed property will be applied, animation can change style several times and the changed style do not need to really applied to element.

This article describe how to use CSS3 animation to perform a notification feature.

Try it

Try the sample below with modern browsers:

Mouseover 'User name' or 'Password' to test
User name:
The name used to login
Password:
The password of the name above


The HTML

animation_test.html

Define a keyframe (notificationAnima) while mouseover the parent div and make animation.

<!DOCTYPE html>
<html>
    <head>
        <style> 
            body {
                background-color: #333;
            }
            .container {
                width: 350px;
                background-color: white;
            }
            .notification {
                border: 2px solid #111;
                border-radius: 15px; /* rounded corner */
                background-color: #333;
                color: white;
                font-size: 18px;
                padding: 5px;

                opacity: 0; /* invisible at begining */
            }
            /* define the keyframes rule */
            /* define a keyframe named notificationAnima */
            /* animation in 5 seconds */
            .field:hover .notification {
                -webkit-animation: notificationAnima 5s; /* Safari and Chrome */
                -moz-animation: notificationAnima 5s; /* Firefox */
                -o-animation: notificationAnima 5s; /* Opera */
                animation: notificationAnima 5s;
            }
            /* control the notificationAnima frame */
            @-webkit-keyframes notificationAnima {
                75%        {opacity: 1;} /* display notification for a while */
                100%    {opacity: 0;} /* then hide it again */
            }
            @-moz-keyframes notificationAnima {
                75%        {opacity: 1;}
                100%    {opacity: 0;}
            }
            @-o-keyframes notificationAnima {
                75%        {opacity: 1;}
                100%    {opacity: 0;}
            }
            @keyframes notificationAnima {
                75%        {opacity: 1;}
                100%    {opacity: 0;}
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="field">
                User name: <input type="text"></input>
                <br />
                <span class="notification">The name used to login</span>
            </div>
            <div class="field">
                Password: <input type="text"></input>
                <br />
                <span class="notification">The password of the name above</span>
            </div>
        </div>
    </body>
</html>


The Result

View demo on line
http://screencast.com/t/MDvO7pHaEfe4

Reference

w3schools CSS3 Animations
http://www.w3schools.com/css3/css3_animations.asp

Download

animation_test.html
https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/CSS3/animation_test.html

animation_test.swf
https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/CSS3/demo/animation_test.swf

Friday, January 11, 2013

Using CSS3 Transitions to Perform Animation


Introduction

From W3School:
With CSS3, we can add an effect when changing from one style to another, without using Flash animations or JavaScripts.

Try it

Try the sample below with modern browsers (FF 18, Chrome, etc)

mouse over to test


The HTML

Here we change 3 different properties width, height and border-color with different transition properties, separated by comma.

<html>
    <head>
        <style>
            .block {
                border: 1px solid green;
                width: 100px;
                height: 100px;
                
            }
            .block:hover {
                width: 500px;
                height: 350px;
                border-color: red;


                /* property duration timing-function delay */
                /* width ->change width smoothly */
                /* 2s -> change it smoothly in 2 seconds */
                /* ease-out -> slow end */
                /* 0.5s -> delay 0.5 second at the begining */
                /* separate different properties by comma */
                /*
                /* similarly */
                /* change height smoothly in 1.5 seconds with 1 second delay, slow start */
                /* change border-color smoothly in 1 second with 1.5 seconds delay, slow end */

                -webkit-transition: width 2s ease 0.5s, height 1.5s ease-in 1s, border-color 1s ease-out 1.5s;
                -moz-transition: width 2s ease 0.5s, height 1.5s ease-in 1s, border-color 1s ease-out 1.5s;
                -o-transition: width 2s ease 0.5s, height 1.5s ease-in 1s, border-color 1s ease-out 1.5s;
                transition: width 2s ease 0.5s, height 1.5s ease-in 1s, border-color 1s ease-out 1.5s;
            }
            /* return to original style smoothly while mouseout */
            .block:not(hover) {
                -webkit-transition: width 2s, height 2s , border-color 2s;
                -moz-transition: width 2s, height 2s , border-color 2s;
                -o-transition: width 2s, height 2s , border-color 2s;
                transition: width 2s, height 2s , border-color 2s;
            }
        </style>
    </head>
    <body>
        <div class="block">
            mouse over to test
        </div>
    </body>
</html>


The Result

View demo on line
http://screencast.com/t/myM87X1NtuX

Reference

http://www.w3schools.com/css3/css3_transitions.asp

http://stackoverflow.com/questions/9670075/css-transition-shorthand-with-multiple-properties

Download

CSS3_transition.swf
https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/CSS3/demo/CSS3_transition.swf

transition_test.html
https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/CSS3/transition_test.html

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

Wednesday, January 2, 2013

ZK Tree: ROD and Load on Demand TreeModel/TreeNode


Introduction

In ZK, tree does not support Render on Demand (ROD) and Load on Demand directly

There is a load on demand sample of tree at official demo site http://www.zkoss.org/zkdemo/tree/load_on_demand?search=tree which handle data directly to create a custom tree model to achieve load on demand.

In this article, we will implement ROD TreeModel and ROD TreeNode, so any data can work with ROD easily.

Pre-request

ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html

The Program

tree_rod_load_on_demand.zul

Get model from VM and render node by template, also trigger updateSelectedDirectory while onSelect event to keep the selected data

<zk>
    <!-- tested with ZK 6.0.2 -->                                           
    <window apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('test.tree.rod.sample.TestVM')">
        <tree id="tree" model="@bind(vm.directoryModel)"
            width="600px" height="200px"
            onSelect="@command('updateSelectedDirectory')">
            <treecols>
                <treecol label="name" />
                <treecol label="path" />
            </treecols>
            <template name="model" var="node" status="s">
                <treeitem open="@load(node.open)">
                    <treerow>
                        <treecell label="@bind(node.data.name)" />
                        <treecell label="@bind(node.data.path)" />
                    </treerow>
                </treeitem>
            </template>
        </tree>
    </window>
</zk>


RODTreeNodeData.java

The interface that should be implemented by a data bean to make the data bean works with RODTreeNode/RODTreeModel

package test.tree.rod;

import java.util.List;

/**
 * tested with ZK 6.0.2
 * 
 * The interface for data bean that used in RODTreeModel and RODTreeNode
 * 
 * @author benbai123
 *
 */
public abstract class RODTreeNodeData {
    /**
     * get children of this data
     * @return
     */
    public abstract List<? extends RODTreeNodeData> getChildren();
    /**
     * get child count of this data, do not need to really get children
     * @return
     */
    public abstract int getChildCount ();
}


RODTreeNode.java

A TreeNode that supports ROD, let data bean handle the most of things with respect to the data (e.g., getChildren, getChildCount).

package test.tree.rod;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * tested with ZK 6.0.2
 * 
 * The TreeNode that supports ROD, with any data bean
 * that implements interface RODTreeNodeData<br><br>
 *
 * The basic rules:<br>
 * 
 * 1. Only call getChildren while you really want
 * to get children or the children has been loaded<br>
 * 2. Let data bean handle the 'data' side works
 * 
 * 
 * @author benbai123
 *
 * @param <T>
 */
public class RODTreeNode<T extends RODTreeNodeData> {

    private static final long serialVersionUID = -5106504924526127666L;

    // the data bean
    private T _data;
    // parent tree node
    private RODTreeNode<T> _parent;
    // child list
    private List<RODTreeNode<T>> _children;
    // whether this node is open
    private boolean _open;


    // constructor for receiving List children
    public RODTreeNode(T data, List<RODTreeNode<T>> children) {
        _data = data;
        _children = children;
        init();
    }
    // constructor for receiving Array children
    public RODTreeNode(T data, RODTreeNode<T>[] children) {
        _data = data;
        if (children != null
            && children.length > 0)
            _children = Arrays.asList(children);
        init();
    }

    /**
     * initial the parent-child relation
     */
    public void init () {
        if (_children != null) {
            for (RODTreeNode<T> child : _children) {
                child.setParent(this);
            }
        }
    }
    //setter/getter
    /**
     * @return T the data bean
     */
    public T getData () {
        return _data;
    }

    /**
     * @param open whether the node is open
     */
    public void setOpen (boolean open) {
        _open = open;
    }
    public boolean isOpen () {
        return _open;
    }
    public boolean getOpen () {
        return _open;
    }
    /**
     * @param parent parent tree node
     */
    public void setParent (RODTreeNode<T> parent) {
        _parent = parent;
    }
    public RODTreeNode<T> getParent () {
        return _parent;
    }
    /**
     * create TreeNode children based on the children of data bean
     * 
     * @see {@link RODTreeNodeData#getChildren()}
     * @return
     */
    @SuppressWarnings("unchecked")
    public List<RODTreeNode<T>> getChildren () {
        if (_children == null) {
            _children = new ArrayList<RODTreeNode<T>>();
            List<T> childrenData = (List<T>)_data.getChildren();
            for (T data : childrenData) {
                RODTreeNode<T> child = new RODTreeNode<T>(data, (List<RODTreeNode<T>>)null);
                child.setParent(this);
                _children.add(child);
            }
        }

        return _children;
    }
    /**
     * get child count from children size if children created,
     * or get child count from data
     * 
     * @see RODTreeNodeData#getChildCount()
     * @return
     */
    public int getChildCount () {
        int count = _children == null?
                    (_data == null? 0
                        : _data.getChildCount())
                : _children.size();
        return count;
    }
}


RODTreeModel

A TreeModel that supports ROD, implements getPath to prevent default dfs search. Also call getChildren as little as possible (e.g., replace node.getChildren().size() with node.getChildCount, let node to do the work, and then node can let data bean to do the work as needed).

package test.tree.rod;

import java.util.ArrayList;
import java.util.List;

import org.zkoss.zul.AbstractTreeModel;
import org.zkoss.zul.ext.TreeSelectableModel;

/**
 * tested with ZK 6.0.2
 * 
 * The TreeModel that supports ROD, with the RODTreeNode, and
 * any data bean that implements interface RODTreeNodeData<br><br>
 *
 * The basic rule is only call getChildren while you really want
 * to get children or the children has been loaded
 * 
 * @author benbai123
 *
 * @param <T> data bean implements RODTreeNodeData
 */
public class RODTreeModel<T extends RODTreeNodeData>
    extends AbstractTreeModel<RODTreeNode<T>> implements TreeSelectableModel {

    private static final long serialVersionUID = 7822729366554623684L;

    /**
     * Constructor, simply receive a root RODTreeNodeData
     */
    public RODTreeModel (RODTreeNode<T> root) {
        super(root);
    }
 
    /**
     * get child from parent node by index
     */
    public RODTreeNode<T> getChild(RODTreeNode<T> parent, int index) {
        List<RODTreeNode<T>> children = parent.getChildren();
        if (children == null) {
            return null;
        }
        if (index < 0 || index >= children.size()) {
            return null;
        }
        return children.get(index);
    }

    /**
     * call {@link RODTreeNode#getChildCount()} instead of
     * size of {@link RODTreeNode#getChildren()}
     */
    public int getChildCount(RODTreeNode<T> parent) {
        // get child count directly instead of get size of children
        return parent.getChildCount();
    }

    /**
     * simply determine whether node is a leaf node
     */
    public boolean isLeaf(RODTreeNode<T> node) {
        return getChildCount(node) == 0;
    }

    /**
     * here call getChildren since children
     * should already be loaded to get the child
     */
    public int getIndexOfChild(RODTreeNode<T> parent, RODTreeNode<T> child) {
        return parent.getChildren().indexOf(child);
    }

    /**
     * get path based on RODTreeNode, data independent
     */
    public int[] getPath (RODTreeNode<T> child) {
        if (child == null || child.getParent() == null) {
            int[] path = new int[1];
            path[0] = 0;
            return path;
        }
        int[] path = null;
        List<Integer> dPath = new ArrayList<Integer>();
        RODTreeNode<T> parent = child.getParent();
        while (parent != null) {
            dPath.add(0, parent.getChildren().indexOf(child));
            child = parent;
            parent = child.getParent();
        }
        path = new int[dPath.size()];
        for (int i = 0; i < dPath.size(); i++) {
            path[i] = dPath.get(i);
        }
        return path;
    }
}


FileBean.java

The data bean represent a file, implements the RODTreeNodeData so can work with ROD TreeModel/TreeNode

package test.tree.rod.sample;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import test.tree.rod.RODTreeNodeData;

/**
 * tested with ZK 6.0.2
 * 
 * The data bean of File, implements RODTreeNodeData
 * to work with RODTreeModel/RODTreeNode
 * 
 * @author benbai123
 *
 */
public class FileBean extends RODTreeNodeData {
    private List<FileBean> _children;

    private File _file;
    private String _name;
    private String _path;

    // constructor
    public FileBean (File file) {
        _file = file;
        _name = file.getName();
        _path = file.getAbsolutePath();
    }
    // getter, setter
    public String getName () {
        return _name;
    }
    public String getPath () {
        return _path;
    }
    /**
     * implement {@link RODTreeNodeData#getChildren()}
     */
    public List<FileBean> getChildren() {
        if (_children == null) {
            _children = new ArrayList<FileBean>();
            File f = new File(_path);
            File[] filelist = f.listFiles();
            if (filelist != null) {
                for (File file : filelist) {
                    _children.add(new FileBean(file));
                }
            }
        }
        return _children;
    }
    /**
     * implement {@link RODTreeNodeData#getChildCount()}
     */
    public int getChildCount () {
        int childCount = 0;
        if (_children != null) {
            childCount =  _children.size();
        } else if (_file.isDirectory()) {
            File[] filelist = new File(_path).listFiles();
            childCount =  filelist == null? 0 : filelist.length;
        }
        return childCount;
    }
}


TestVM.java

The sample view model, provide directory model and record the selected file.

package test.tree.rod.sample;

import java.io.File;
import java.util.List;
import java.util.Set;

import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zul.TreeModel;

import test.tree.rod.RODTreeModel;
import test.tree.rod.RODTreeNode;
/**
 * tested with ZK 6.0.2
 * 
 * @author benbai123
 *
 */
public class TestVM {
    RODTreeModel<FileBean> _directoryTreeModel;
    FileBean _selectedDirectory;
    File file = new File("C:" + File.separator + "Program Files");

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public TreeModel<RODTreeNode<FileBean>> getDirectoryModel () {
        RODTreeNode root = new RODTreeNode(null,
                new RODTreeNode[] {new RODTreeNode(new FileBean(file), (List)null)
        });
        if (_directoryTreeModel == null) {
            _directoryTreeModel = new RODTreeModel<FileBean>(root);
        }
        return _directoryTreeModel;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Command
    public void updateSelectedDirectory (@ContextParam(ContextType.TRIGGER_EVENT) SelectEvent event) {
        Set s = event.getSelectedObjects();
        if (s != null && s.size() > 0) {
            _selectedDirectory = ((RODTreeNode<FileBean>)s.iterator().next()).getData();
            System.out.println("selected: " + _selectedDirectory.getName() + ", path = " + _selectedDirectory.getPath());
        }
    }
}


The Result

View the demo flash on line
http://screencast.com/t/DaeBzKS8NX4R

References

ZK Template
http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/UI%20Patterns/Templating/Templates

Tree Load on Demand Sample
http://www.zkoss.org/zkdemo/tree/load_on_demand

Download

Files at github

RODTreeNodeData.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/tree/rod/RODTreeNodeData.java

RODTreeNode.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/tree/rod/RODTreeNode.java

RODTreeModel.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/tree/rod/RODTreeModel.java

FileBean.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/tree/rod/sample/FileBean.java

TestVM.java
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Practice/src/test/tree/rod/sample/TestVM.java

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

demo flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/tree_rod_load_on_demand.swf