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

Sunday, December 30, 2012

Convert Mouse Position To Lat/Lng in Google Maps



Introduction

This article describe how to convert the mouse position x/y to the map lat/lng in Google Maps.

The Program

Simply get the bounds of map and calculate the lat/lng based on the mouse x/y and the width/henght of map div.

The result has a little bit bias, but accurate enough.

<!DOCTYPE html>
<!-- tested with google chrome -->
<html>
    <head>
        <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
        <script>
            var map,
                mLat,
                mLng;
                
            function initialize() {
                var mapOptions = {
                    zoom: 12,
                    center: new google.maps.LatLng(33.397, -81.644),
                    mapTypeId: google.maps.MapTypeId.ROADMAP
                };
                map = new google.maps.Map(document.getElementById('map_canvas'),
                    mapOptions
                );
                // add a click event listener to
                // get the lat/lng from click event of map
                google.maps.event.addListener(map, 'click', function(event) {
                    mLat = event.latLng.lat();
                    mLng = event.latLng.lng();
                    document.getElementById('latMap').value = mLat;
                    document.getElementById('lngMap').value = mLng;
                });
            }
            function mapDivClicked (event) {
                var target = document.getElementById('map_canvas'),
                    posx = event.pageX - target.offsetLeft,
                    posy = event.pageY - target.offsetTop,
                    bounds = map.getBounds(),
                    neLatlng = bounds.getNorthEast(),
                    swLatlng = bounds.getSouthWest(),
                    startLat = neLatlng.lat(),
                    endLng = neLatlng.lng(),
                    endLat = swLatlng.lat(),
                    startLng = swLatlng.lng(),
                    lat = startLat + ((posy/350) * (endLat - startLat)),
                    lng = startLng + ((posx/500) * (endLng - startLng));

                document.getElementById('posX').value = posx;
                document.getElementById('posY').value = posy;
                document.getElementById('lat').value = lat; // calculated lat
                document.getElementById('lng').value = lng; // calculated lng

                // the error rate
                document.getElementById('latErr').value = ((mLat - lat) / (endLat - startLat) * 100).toFixed(2);
                document.getElementById('lngErr').value = ((mLng - lng) / (endLng - startLng) * 100).toFixed(2);
            }
            google.maps.event.addDomListener(window, 'load', initialize);
        </script>
    </head>
    <body>
        <div id="map_canvas" onclick="mapDivClicked(event);" style="height: 350px; width: 500px;"></div>
        <div>
            mouse x: <input id="posX" />
            mouse y: <input id="posY" />
        </div>
        <div>
            lat: <input id="lat" />
            lng: <input id="lng" />
        </div>
        <div>
            lat from map: <input id="latMap" />
            lng from map: <input id="lngMap" />
        </div>
        <div>
            lat error rate: <input id="latErr" /> %
            lng error rate: <input id="lngErr" /> %
        </div>
    </body>
</html>


The Result



Reference

https://developers.google.com/maps/documentation/javascript/reference

Download

File at github
https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/WebService_WebAPIs/Google/GoogleMap/mouse_position_to_latlng.html

Thursday, December 20, 2012

ZK Listbox: Inplace Editing with Renderer and MVVM


Introduction

This article describe how to implement the inplace editing with renderer in MVVM.

Pre-request

ZK Listbox: Event Processing with Renderer and MVVM
http://ben-bai.blogspot.tw/2012/12/zk-listbox-event-processing-with.html

The Program

index.zul

A simple View that get/set value from ViewModel and trigger some Command as needed.

<zk>
    <!-- Tested with ZK 6.0.2 -->
    <div apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('test.viewmodel.TestVM')">
        <hlayout>
            <vlayout>
                <listbox width="400px" model="@load(vm.personModel)"
                    selectedIndex="@bind(vm.selectedIndex)"
                    itemRenderer="test.renderer.InplaceEditingPersonRenderer"
                    onModelDataChange="@command('updateModelData')">
                </listbox>
                <hlayout>
                    <button label="show current data" onClick="@command('displayCurrentData')" />
                    <button label="add new" onClick="@command('addNew')" />
                    <button label="delete selected" onClick="@command('delSel')" />
                </hlayout>
            </vlayout>
            <textbox rows="10" width="350px" value="@load(vm.currentData)" />
        </hlayout>
    </div>
</zk>


TestVM.java

Provide data and handle data changed event as needed.

package test.viewmodel;

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

import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;

import test.data.Person;
import test.event.ModelDataChangeEvent;

/**
 * tested with ZK 6.0.2
 * @author ben
 *
 */
public class TestVM {
    private ListModelList<Person> _personModel;
    private int _selectedIndex = -1;
    /**
     * The hard coded person model
     * @return ListModelList contains several Person
     */
    public ListModel<Person> getPersonModel () {
        if (_personModel == null) {
            List<Person> l = new ArrayList<Person>();
            l.add(new Person("First Name One", "Last Name One", 21));
            l.add(new Person("First Name Two", "Last Name Two", 23));
            l.add(new Person("First Name Three", "Last Name Three", 25));
            _personModel = new ListModelList<Person>(l);
        }
        return _personModel;
    }
    /**
     * save the selected index while select a listitem
     * @param selectedIndex
     */
    public void setSelectedIndex (int selectedIndex) {
        _selectedIndex = selectedIndex;
    }
    /**
     * update selected index while delSel
     * @return
     */
    public int getSelectedIndex () {
        return _selectedIndex;
    }
    /**
     * return all Persons' data, can be considered as "save data"
     * @return
     */
    public String getCurrentData () {
        StringBuilder sb = new StringBuilder("");
        if (_personModel != null) {
            if (_selectedIndex >= 0) {
                Person p = _personModel.getElementAt(_selectedIndex);
                if (isValidPerson(p)) {
                    sb.append("Selected Person: \r\n")
                        .append(p.getFirstName()).append(" ")
                        .append(p.getLastName()).append(", age = ")
                        .append(p.getAge()).append("\r\n\r\n");
                }
            }
            for (Person p : _personModel.getInnerList()) {
                if (isValidPerson(p)) {
                    sb.append(p.getFirstName()).append(" ")
                        .append(p.getLastName()).append(", age = ")
                        .append(p.getAge()).append("\r\n");
                }
            }
        }
        return sb.toString();
    }
    /**
     * add a new Person to _personModel,
     * added to tail if no selected index,
     * added below current selected item if selected idnex >= 0
     */
    @Command
    public void addNew () {
        if (_selectedIndex >= 0) {
            _personModel.add(_selectedIndex+1, new Person("", "", null));
        } else {
            _personModel.add(new Person("", "", null));
        }
    }
    /**
     * remove selected Person from model
     */
    @Command
    @NotifyChange("_selectedIndex")
    public void delSel () {
        if (_selectedIndex >= 0) {
            _personModel.remove(_selectedIndex);
            _selectedIndex = -1;
        }
    }
    /**
     * update the value of a Person based on the index and Data
     * in the ModelDataChangeEvent
     * @param event ModelDataChangeEvent contains the information with respect the index and changed data
     */
    @Command
    public void updateModelData (
            @ContextParam(ContextType.TRIGGER_EVENT) ModelDataChangeEvent event) {
        Person oldPerson = _personModel.get(event.getIndex());;
        Person newPerson = (Person)event.getData();
        oldPerson.setFirstName(newPerson.getFirstName());
        oldPerson.setLastName(newPerson.getLastName());
        oldPerson.setAge(newPerson.getAge());
    }
    /**
     * simply notify that the currentData should be updated
     */
    @Command
    @NotifyChange("currentData")
    public void displayCurrentData () {

    }
    /**
     * confirm all fields of a Person is valid
     * @param p the Person to check
     * @return whether all fields of the Person is valid
     */
    private boolean isValidPerson (Person p) {
        return !(p.getFirstName() == null
                || p.getFirstName().isEmpty()
                || p.getLastName() == null
                || p.getLastName().isEmpty()
                || p.getAge() == null
                || p.getAge() < 0);
    }
}


InplaceEditingPersonRenderer.java

Render listheaders and listitems, initial sorting, the inner textbox/intbox will handle the onChange event and pass it to listbox as needed.

package test.renderer;

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listhead;
import org.zkoss.zul.Listheader;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Textbox;

import test.data.Person;
import test.data.PersonComparator;
import test.event.ModelDataChangeEvent;

/**
 * tested with ZK 6.0.2
 * @author ben
 *
 */
public class InplaceEditingPersonRenderer implements ListitemRenderer<Person> {
    @SuppressWarnings("unchecked")
    /**
     * render Person to a listitem
     */
    public void render (Listitem listitem, Person data, int index) {
        Listbox listbox = listitem.getListbox();
        if (index == 0 && listbox.getListhead() == null) {
            createListhead().setParent(listbox);
        }
        listitem.setValue(data);

        addTextboxCell(listitem, data.getFirstName())
            .addEventListener(Events.ON_CHANGE, getFirstnameChangedListener(listbox, data, listitem));
        addTextboxCell(listitem, data.getLastName())
            .addEventListener(Events.ON_CHANGE, getLastnameChangedListener(listbox, data, listitem));
        Intbox ibx = addIntboxCell(listitem, data.getAge());
        ibx.addEventListener(Events.ON_CHANGE, getAgeChangedListener(listbox, ibx, data, listitem));
    }
    /**
     * create listhead
     * @param listbox, append the listhead to it
     */
    private Listhead createListhead () {

        Listhead lh = new Listhead();
        createListheader("First Name", "firstName").setParent(lh);
        createListheader("Last Name", "lastName").setParent(lh);
        createListheader("Age", "age").setParent(lh);
        return lh;
    }
    /**
     * create a listheader
     * @param label the label of the listheader
     * @param fieldToCompare the value to set to PersonComparator's 'field' field
     * @return
     */
    private Listheader createListheader (String label, String fieldToCompare) {
        Listheader lhr = new Listheader(label);
        lhr.setSortAscending(new PersonComparator(true, fieldToCompare));
        lhr.setSortDescending(new PersonComparator(false, fieldToCompare));
        return lhr;
    }
    /**
     * add a listcell contains a textbox and return the textbox
     * @param listitem the listitem to append this listcell
     * @param value the value to set to the textbox
     * @return Textbox the created textbox
     */
    private Textbox addTextboxCell (Listitem listitem, String value) {
        Listcell lc = new Listcell();
        Textbox tbx = new Textbox(value);
        tbx.setParent(lc);
        lc.setParent(listitem);
        return tbx;
    }
    /**
     * add a listcell contains an intbox and returh the intbox
     * @param listitem the listitem to append this listcell
     * @param value the value to set to the intbox
     * @return Intbox the created intbox
     */
    private Intbox addIntboxCell (Listitem listitem, Integer value) {
        Listcell lc = new Listcell();
        Intbox ibx = new Intbox();
        if (value != null) {
            ibx.setValue(value);
        }
        ibx.setParent(lc);
        lc.setParent(listitem);
        return ibx;
    }
    /**
     * The EventListener for firstName changed
     * @param listbox the listbox, post event to it
     * @param oldData the original Person, need the lastName and age of it
     * @param listitem the listitem contains the textbox of this event, need the index of it
     * @return EventListener that listen to firstName changed
     */
    @SuppressWarnings("rawtypes")
    private EventListener getFirstnameChangedListener (final Listbox listbox, final Person oldData, final Listitem listitem) {
        return new EventListener () {
            public void onEvent (Event event) {
                InputEvent ievent = (InputEvent)event;
                Events.postEvent(ModelDataChangeEvent
                    .getModelDataChangeEvent(listbox,
                        new Person(ievent.getValue(), oldData.getLastName(), oldData.getAge()),
                        listitem.getIndex()));
            }
        };
    }
    /**
     * The EventListener for lastName changed
     * @param listbox the listbox, post event to it
     * @param oldData the original Person, need the firstName and age of it
     * @param listitem the listitem contains the textbox of this event, need the index of it
     * @return EventListener that listen to lastName changed
     */
    @SuppressWarnings("rawtypes")
    private EventListener getLastnameChangedListener (final Listbox listbox, final Person oldData, final Listitem listitem) {
        return new EventListener () {
            public void onEvent (Event event) {
                InputEvent ievent = (InputEvent)event;
                Events.postEvent(ModelDataChangeEvent
                    .getModelDataChangeEvent(listbox,
                        new Person(oldData.getFirstName(), ievent.getValue(), oldData.getAge()),
                        listitem.getIndex()));
            }
        };
    }
    /**
     * The EventListener for age changed
     * @param listbox the listbox, post event to it
     * @param oldData the original Person, need the firstName and lastName of it
     * @param listitem the listitem contains the intbox of this event, need the index of it
     * @return EventListener that listen to age changed
     */
    @SuppressWarnings("rawtypes")
    private EventListener getAgeChangedListener (final Listbox listbox, final Intbox intbox, final Person oldData, final Listitem listitem) {
        return new EventListener () {
            public void onEvent (Event event) {
                Events.postEvent(ModelDataChangeEvent
                    .getModelDataChangeEvent(listbox,
                        new Person(oldData.getFirstName(), oldData.getLastName(), intbox.getValue()),
                        listitem.getIndex()));
            }
        };
    }
}


ModelDataChangeEvent.java

Contains the changed data and the index to apply this change in it.

package test.event;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;

/**
 * tested with ZK 6.0.2
 * @author ben
 *
 */
public class ModelDataChangeEvent extends Event {
    private static final long serialVersionUID = 3645653880934243558L;

    // the changed data, can be any Object
    private final Object _data;
    // the index to apply this change
    private int _index;

    /**
     * Get a ModelDataChangeEvent
     * @param target the target to post this event
     * @param data the changed data
     * @param index the index to apply this change
     * @return ModelDataChangeEvent
     */
    public static ModelDataChangeEvent getModelDataChangeEvent (Component target, Object data, int index) {
        return new ModelDataChangeEvent("onModelDataChange", target, data, index);
    }
    /**
     * 
     * @param name event name, should starts with "on"
     * @param target
     * @param data
     * @param index
     */
    public ModelDataChangeEvent (String name, Component target, Object data, int index) {
        super(name, target);
        _data = data;
        _index = index;
    }
    public Object getData () {
        return _data;
    }
    public int getIndex () {
        return _index;
    }
}


Person.java

Contains some fields of a Person, simple POJO.

package test.data;

public class Person {
    private String _firstName;
    private String _lastName;
    private Integer _age;

    public Person (String firstName, String lastName, Integer age) {
        _firstName = firstName;
        _lastName = lastName;
        _age = age;
    }
    public void setFirstName (String firstName) {
        _firstName = firstName;
    }
    public void setLastName (String lastName) {
        _lastName = lastName;
    }
    public void setAge (Integer age) {
        _age = age;
    }
    public String getFirstName () {
        return _firstName;
    }
    public String getLastName () {
        return _lastName;
    }
    public Integer getAge () {
        return _age;
    }
}


PersonComparator.java

Use it to compare two person, on specific field, in ascending or descending way.

package test.data;

import java.util.Comparator;

/**
 * the Comparator to compare a Person of fields firstName,
 * lastName or age in ascending or descending order
 * @author ben
 *
 */
public class PersonComparator implements Comparator<Person> {
    private boolean _asc;
    private String _field;

    /**
     * Constructor
     * @param asc true: ascending, false: descending
     * @param field to compare, firstName, lastName or age
     */
    public PersonComparator (boolean asc, String field) {
        _asc = asc;
        _field = field;
    }
    /**
     * compare two person
     */
    public int compare(Person p1, Person p2) {
        int result = 0,
            mult =  _asc ? 1 : -1;
        result = "firstName".equals(_field)?
                    p1.getFirstName().compareTo(p2.getFirstName()) * mult
                : "lastName".equals(_field)?
                    p1.getLastName().compareTo(p2.getLastName()) * mult
                : "age".equals(_field)?
                    compareAge(p1.getAge(), p2.getAge()) * mult
                : 0;
        return result;
    }
    /**
     * function for compare age
     */
    private int compareAge (Integer a1, Integer a2) {
        int a = (a1 == null || a1 < 0)? 0 : a1.intValue();
        int b = (a2 == null || a2 < 0)? 0 : a2.intValue();
        return a > b? 1
                : a < b? -1
                : 0;
    }
}


The Result

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

Download

Project at github
https://github.com/benbai123/ZK_Practice/tree/master/Pattern/MVVM/ListboxMVVMInplaceEditing

Demo flash
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Pattern/MVVM/Renderer_MVVM_Inplace_Editing.swf

ZK Listening/Processing Event in ZUL Page or Java Code



Introduction

In ZK, you can listen to and process an event of a component in ZUL page, or listen to and process an event by adding an EventListener by java code.

This article will describe the two methods to listen/process event.

Pre-request

ZK Basic MVC Pattern with GenericForwardComposer
http://ben-bai.blogspot.tw/2012/10/zk-basic-mvc-pattern-with.html

The Program

listening_and_processing_event_in_zul_or_java.zul

There are two buttons in this page, the first one listening and processing the onClick event in zul page directly, an EventListener will be added to the second button by java code in ListeningProcessingEventTestComposer.java.

<zk>
    <!-- tested with ZK 6.0.2 -->
    <div apply="test.ListeningProcessingEventTestComposer">
        <textbox id="tb" />
        <!-- the button below listening and processing in zul page
             -->
        <button label="button one">
            <!-- the fragment below is equal to
                addEventListener("onClick", new EventListener () {
                    public void onEvent (Event event) {
                        tb.setValue("button one clicked !");
                    }
                });
                where the 'tb' here will mapping to the component that id is 'tb'
                in the zul page by ZK automatically -->
            <attribute name="onClick"><![CDATA[
                tb.setValue("button one clicked !");
            ]]></attribute>
        </button>
        <button id="btnTwo" label="button two" />
    </div>
</zk>


ListeningProcessingEventTestComposer.java

Simply add an EventListener to btnTwo in doAfterCompose method

package test;

import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Button;
import org.zkoss.zul.Textbox;

/**
 * tested with ZK 6.0.2
 * @author ben
 *
 */
@SuppressWarnings("serial")
public class ListeningProcessingEventTestComposer extends GenericForwardComposer {
    Button btnTwo;
    Textbox tb;
    public void doAfterCompose (Component comp) throws Exception {
        super.doAfterCompose(comp);
        btnTwo.addEventListener( // add an EventListener
            "onClick", // listen to onClick event
            new EventListener () { // create an EventListener instance to add
                public void onEvent (Event event) { // the method will be called while onClick event triggered
                    tb.setValue("button two clicked !"); // set some string to the Textbox tb !
                }
            }
        );
    }
}


The Result

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

Reference

Event listening and processing
http://books.zkoss.org/wiki/ZK%20Developer's%20Guide/ZK%20in%20Depth/Event%20listening%20and%20processing

Download

Files at github

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

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

Demo flash
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/listening_processing_event_in_zul_or_java.swf