Thursday, August 30, 2012

ZK Pivottable: Handling huge data in ZK Pivottable



Introduction

In the previous post http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html, we load all data into pivot model at once, in this case the memory consumption may be a problem if the raw data is huge.

In this post, we will try to handle data paging and field control by ourself to load data partially, for the sake of reducing memory consumption. However this may need some additional processing time which would be a trade-off but is useful if the data is really huge.

The Program

PartialRenderComposer.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.Pivottable;
import org.zkoss.pivot.impl.TabularPivotModel;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zkmax.zul.Chosenbox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Timer;

/**
 * The scenario is to reduce memory consumption, get only required
 * data from data base each time and purge it after the data for pivot
 * model are ready
 * 
 * @author ben
 *
 */
public class PartialRenderComposer extends GenericForwardComposer {
    private Pivottable pivottable; // pitottable
    private Intbox paging; // custom paging control
    private Intbox info;
    private Intbox nodes;
    private Intbox memInfo;
    private Chosenbox rowFieldChosen;
    private Chosenbox columnFieldChosen;
    private Chosenbox dataFieldChosen;
    private Timer checkTimer;

    private TabularPivotModel _pivotModel;

    private Runtime rt = Runtime.getRuntime();

    // fake test data, render only once
    private static List<DataClass> _testData; // data that simulate a db resource
    private List<String> _rowFieldList;
    private List<String> _columnFieldList;
    private List<String> _dataFieldList;
    private List<String> _unusedFieldList;
    private int _size = 0;
    private int _currentPage = 1; // page of custom paging
    private int _pageNodesLimit = 10; // number of first level node of a page
    private int _testDataSize = 200000;

    // ------------------------------- //
    // flow control                    //
    // ------------------------------- //
    @SuppressWarnings("unchecked")
    public void doAfterCompose (Component comp) throws Exception {
        super.doAfterCompose(comp);
        info.setValue(_size);

        updateFieldLists();

        nodes.setValue(_pageNodesLimit);
        // show all data in one page
        pivottable.setPageSize(_size);
    }

    // ------------------------------- //
    // Event handling                  //
    // ------------------------------- //
    /**
     * update page number then update pivot model
     */
    public void onChanging$paging (InputEvent e) {
        _currentPage = Integer.parseInt(e.getValue());
        if ((_currentPage-1) * _pageNodesLimit >= 10) {
            // return to first page if overflow
            _currentPage = 1;
            paging.setValue(_currentPage);
        }
        updatePivotModel();
    }
    /**
     * update first-level-nodes per page then update pivot model
     * @param e
     */
    public void onChange$nodes (InputEvent e) {
        _pageNodesLimit = Integer.parseInt(e.getValue());
        updatePivotModel();
    }

    /**
     * update field list and data then update models
     * @param e
     */
    public void onSelect$rowFieldChosen (SelectEvent e) {
        moveItem(e, _rowFieldList, _unusedFieldList);
        updatePivotModel();
        updateFieldLists();
    }
    /**
     * update field list and data then update models
     * @param e
     */
    public void onSelect$columnFieldChosen (SelectEvent e) {
        moveItem(e, _columnFieldList, _unusedFieldList);
        updatePivotModel();
        updateFieldLists();
    }
    /**
     * update field list and data then update models
     * @param e
     */
    public void onSelect$dataFieldChosen (SelectEvent e) {
        moveItem(e, _dataFieldList, _unusedFieldList);
        updatePivotModel();
        updateFieldLists();
    }
    /**
     * require gc and update memory info
     */
    public void onTimer$checkTimer () {
        System.gc();
        memInfo.setValue(new Long((rt.totalMemory()-rt.freeMemory())/1024/1024).intValue());
    }

    // ------------------------------- //
    // data management                 //
    // ------------------------------- //
    public TabularPivotModel getPivotModel () throws Exception {
        List<List<Object>> rawData = getPivotData();
        _pivotModel = new TabularPivotModel(rawData, getColumns());
        List<String> l;
        int i;

        for (i = 0, l = getRowFieldList(); i < l.size(); i++) {
            _pivotModel.setFieldType(l.get(i), PivotField.Type.ROW);
        }
        for (i = 0, l = getColumnFieldList(); i < l.size(); i++) {
            _pivotModel.setFieldType(l.get(i), PivotField.Type.COLUMN);
        }
        for (i = 0, l = getDataFieldList(); i < l.size(); i++) {
            _pivotModel.setFieldType(l.get(i), PivotField.Type.DATA);
        }
        return _pivotModel;
    }
    /**
     * update pivot model
     */
    private void updatePivotModel() {
        try {
            _pivotModel = getPivotModel();
            pivottable.setModel(_pivotModel);
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
    }
    /**
     * update chosenbox field list
     */
    private void updateFieldLists () {
        ListModelList model = getChosenModel(getRowFieldList(), getUnusedFieldList());
        rowFieldChosen.setModel(model);
        model.setSelection(_rowFieldList);

        model = getChosenModel(getColumnFieldList(), getUnusedFieldList());
        columnFieldChosen.setModel(model);
        model.setSelection(_columnFieldList);

        model = getChosenModel(getDataFieldList(), getUnusedFieldList());
        dataFieldChosen.setModel(model);
        model.setSelection(_dataFieldList);
    }

    /**
     * Get partial data for pivot model
     * @return
     * @throws Exception
     */
    private List<List<Object>> getPivotData() throws Exception {
        List<List<Object>> l = new ArrayList<List<Object>>();
        l.addAll(getData());

        // update size info
        _size = l.size();
        if (info != null) {
            info.setValue(_size);
        }
        if (pivottable != null) {
            // show all data in one page
            pivottable.setPageSize(_size);
        }

        _testData.clear();
        _testData = null;
        return l;
    }
    /**
     * Move field from specific field list to unused field list ((removed))
     * or vice versa (added)
     * @param e
     * @param origin
     * @param unuse
     */
    private void moveItem (SelectEvent e, List origin, List unuse) {
        Object target = null;
        List objs = new ArrayList();
        boolean add = false;
        
        objs.addAll(e.getSelectedObjects());
        // check whether field removed
        for (Object obj : origin) {
            if (!objs.contains(obj)) {
                target = obj;
                break;
            }
        }
        // check whether field added
        if (target == null) {
            add = true;
            for (Object obj : unuse) {
                if (objs.contains(obj)) {
                    target = obj;
                    break;
                }
            }
        }
        if (add) {
            // field added, move it to self list
            origin.add((String)target);
            unuse.remove(target);
        } else {
            // field removed, move it to unused
            unuse.add((String)target);
            origin.remove(target);
        }
    }
    /**
     * Get the data list from fake db and take only required data,
     * for pivot model, based on the 'used' fields and data range
     * 
     * @return
     * @throws Exception
     */
    private List<List<Object>> getData() throws Exception {
        List<List<Object>> rawData = new ArrayList<List<Object>>();
        _rowFieldList = getRowFieldList(); // current row fields
        _columnFieldList = getColumnFieldList(); // current column fields
        _dataFieldList = getDataFieldList(); // current data fields
        // size for inner object array
        int arraySize = _rowFieldList.size() + _columnFieldList.size() + _dataFieldList.size();
        int index = 0;
        // data range
        int first = (_currentPage - 1)*_pageNodesLimit + 1;
        int last = (_currentPage - 1)*_pageNodesLimit + _pageNodesLimit;

        prepareTestData(); // prepare the test data
        for (DataClass d : _testData) {
            Object[] objs = new Object[arraySize];

            // check whether is in range
            String rowHead = (String)getValue(d, _rowFieldList.get(0));
            int nodeNo = Integer.parseInt((rowHead).substring(rowHead.indexOf("__") + 2, rowHead.length()));
            // Only load rows are in page range
            if (nodeNo >= first && nodeNo <= last) {
                // add values to array
                for (int i = 0; i < _rowFieldList.size(); i++) {
                    objs[index] = getValue(d, _rowFieldList.get(i));
                    index++;
                }
                for (int i = 0; i < _columnFieldList.size(); i++) {
                    objs[index] = getValue(d, _columnFieldList.get(i));
                    index++;
                }
                for (int i = 0; i < _dataFieldList.size(); i++) {
                    objs[index] = getValue(d, _dataFieldList.get(i));
                    index++;
                }
                index = 0; // reset index
                // add array to rawData
                rawData.add(Arrays.asList(objs));
            }
        }

        return rawData;
    }
    /**
     * Get value from DataClass with respect to the field name
     * @param d DataClass object
     * @param name field name
     * @return
     */
    private Object getValue (DataClass d, String name) {
        return "Row_One".equals(name)? d.getRowOne()
                : "Row_Two".equals(name)? d.getRowTwo()
                : "Row_Three".equals(name)? d.getRowThree()
                : "Column_One".equals(name)? d.getColumnOne()
                : "Column_Two".equals(name)? d.getColumnTwo()
                : "Column_Three".equals(name)? d.getColumnThree()
                : "Data_One".equals(name)? d.getDataOne()
                : "Data_Two".equals(name)? d.getDataTwo() : d.getDataThree();
    }
    /**
     * Fake db's data,
     * will be cleared after the data for PivotModel are ready
     */
    private void prepareTestData () {
        if (_testData == null) { // generate random data
            _testData = new ArrayList<DataClass>();
            Random r = new Random();
            for (int i = 0; i < _testDataSize; i++) {
                _testData.add(new DataClass(
                        "Row_One__" + (r.nextInt(10) + 1), "Row_Two__" + (r.nextInt(10) + 1), "Row_Three__" + (r.nextInt(10) + 1),
                        "Column_One__" + (r.nextInt(3) + 1), "Column_Two__" + (r.nextInt(3) + 1), "Column_Three__" + (r.nextInt(3) + 1),
                        r.nextInt(1000) + 1, r.nextInt(1000) + 1, r.nextInt(1000) + 1));
            }
        }
    }
    /**
     * Get the field names for pivottable
     * @return
     */
    private List<String> getColumns() {
        List<String> l = new ArrayList<String>();
        l.addAll(getRowFieldList());
        l.addAll(getColumnFieldList());
        l.addAll(getDataFieldList());
        return l;
    }
    /**
     * Get all row field names
     * @return
     */
    private List<String> getRowFieldList () {
        if (_rowFieldList == null) { // init
            _rowFieldList = new ArrayList<String>();
            _rowFieldList.add("Row_One");
            _rowFieldList.add("Row_Two");
            _rowFieldList.add("Row_Three");
        }
        return _rowFieldList;
    }
    /**
     * Get all column field names
     * @return
     */
    private List<String> getColumnFieldList () {
        if (_columnFieldList == null) { // init
            _columnFieldList = new ArrayList<String>();
            _columnFieldList.add("Column_One");
            _columnFieldList.add("Column_Two");
            _columnFieldList.add("Column_Three");
        }
        return _columnFieldList;
    }
    /**
     * Get all data field names
     * @return
     */
    private List<String> getDataFieldList () {
        if (_dataFieldList == null) { // init
            _dataFieldList = new ArrayList<String>();
            _dataFieldList.add("Data_One");
            _dataFieldList.add("Data_Two");
            _dataFieldList.add("Data_Three");
        }
        return _dataFieldList;
    }
    /**
     * Get all unused field names,
     * for chosenbox model only
     * @return
     */
    private List<String> getUnusedFieldList () {
        if (_unusedFieldList == null) { // init
            _unusedFieldList = new ArrayList<String>();
        }
        return _unusedFieldList;
    }
    /**
     * Get the list for chosenbox model by specific list (row/column/data)
     * plus unusedFieldList
     * @param fields The specific list and unusedFieldList
     * @return
     */
    private ListModelList<String> getChosenModel (List<String>... fields) {
        List<String> l = new ArrayList<String>(); 
        for (List<String> field : fields) {
            l.addAll(field);
        }
        return new ListModelList<String>(l);
    }
}


In this composer, we update the pivot model with respect to the current page, (first level) node amount per page and required fields. Only the data that will be shown are loaded into pivot model.

There is a timer to perform gc and update the value of memory consumption.

DataClass.java

package test;

public class DataClass {
    String _rowOne;
    String _rowTwo;
    String _rowThree;

    String _columnOne;
    String _columnTwo;
    String _columnThree;

    int _dataOne;
    int _dataTwo;
    int _dataThree;

    public DataClass (String rowOne, String rowTwo, String rowThree,
                    String columnOne, String columnTwo, String columnThree,
                    int dataOne, int dataTwo, int dataThree) {
        _rowOne = rowOne;
        _rowTwo = rowTwo;
        _rowThree = rowThree;
        _columnOne = columnOne;
        _columnTwo = columnTwo;
        _columnThree = columnThree;
        _dataOne = dataOne;
        _dataTwo = dataTwo;
        _dataThree = dataThree;
    }
    public String getRowOne() {
        return _rowOne;
    }
    public void setRowOne(String _rowOne) {
        this._rowOne = _rowOne;
    }
    public String getRowTwo() {
        return _rowTwo;
    }
    public void setRowTwo(String _rowTwo) {
        this._rowTwo = _rowTwo;
    }
    public String getRowThree() {
        return _rowThree;
    }
    public void setRowThree(String _rowThree) {
        this._rowThree = _rowThree;
    }
    public String getColumnOne() {
        return _columnOne;
    }
    public void setColumnOne(String _columnOne) {
        this._columnOne = _columnOne;
    }
    public String getColumnTwo() {
        return _columnTwo;
    }
    public void setColumnTwo(String _columnTwo) {
        this._columnTwo = _columnTwo;
    }
    public String getColumnThree() {
        return _columnThree;
    }
    public void setColumnThree(String _columnThree) {
        this._columnThree = _columnThree;
    }
    public int getDataOne() {
        return _dataOne;
    }
    public void setDataOne(int _dataOne) {
        this._dataOne = _dataOne;
    }
    public int getDataTwo() {
        return _dataTwo;
    }
    public void setDataTwo(int _dataTwo) {
        this._dataTwo = _dataTwo;
    }
    public int getDataThree() {
        return _dataThree;
    }
    public void setDataThree(int _dataThree) {
        this._dataThree = _dataThree;
    }
}


index.zul

<zk>
    <window id="win" xmlns:w="client"
        apply="test.PartialRenderComposer">
        <!-- custom paging that control data loading partially -->
        Page: <intbox value="1" id="paging" />
        <!-- how many data under current settings -->
        Pivot model data size: <intbox id="info" />
        <div></div>

        Nodes limit: <intbox id="nodes" />
        Memory used (MB): <intbox id="memInfo" />
        <div></div>

        Rows: <chosenbox id="rowFieldChosen" width="300px" />
        Columns: <chosenbox id="columnFieldChosen" width="300px" />
        <div></div>
        Datas: <chosenbox id="dataFieldChosen" width="300px" />
        <vlayout>
            <!-- the pageSize denotes how many rows to display in one page,
                you can set it as data size if you want display all data in one page -->
            <pivottable id="pivottable" model="${win$composer.pivotModel}"
                pageSize="30"
                onPivotNodeOpen="" />
        </vlayout>
        <timer id="checkTimer" delay="1000" repeats="true" />
    </window>
</zk>


The Result

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

Download

The full project
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/ShowPartialDataInPivottable

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

References

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

Tuesday, August 28, 2012

HTML5 Canvas: Basic functions


Introduction

Canvas is a new element in HTML5 used to draw graphics, on the fly, on a web page. This post shows some basic functions of canvas including draw line, draw rect, draw circle, bezier curve, quadratic curve, image and text.

The Program:

<html>
    <head>
        <style>
            #testCanvas {
                position: absolute;
                left: 0px;
                top: 0px;
                z-index: 2;
            }
            #tmpCanvas {
                position: absolute;
                left: 0px;
                top: 0px;
                z-index: 1;
            }
            #controls {
                position: absolute;
                left: 600px;
                top: 0px;
                width: 200px;
                height: 800px;
            }
        </style>
        <script type="text/javascript">
                
            function drawLine () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.strokeStyle = '#AAAAAA'; // line color
                ctx.lineWidth = 3; // line width
                ctx.beginPath(); // start
                ctx.moveTo(3, 3); // start point
                ctx.lineTo(40, 20); // end point
                ctx.stroke(); // draw line
            }
            function drawRect () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.fillStyle = 'rgba(54, 76, 175, 0.6)'; // fill style
                ctx.strokeStyle = '#AAAAAA'; // line color
                ctx.lineWidth = 3; // line width
                ctx.fillRect(30, 30, 80, 80); // start x, start y, width, height
                ctx.strokeRect(30, 30, 80, 80);
            }
            function drawCircle () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.fillStyle = 'rgba(254, 76, 175, 0.6)'; // fill style
                ctx.strokeStyle = '#AAAAAA'; // line color
                ctx.beginPath();
                ctx.arc(300, 300, 100, // center x, center y, radius
                        0 * Math.PI, 2 * Math.PI, // start, finish, change the two values to draw a part of circle
                        false); // drawing direction, true: anticlockwise, false: clockwise
                ctx.fill();
                ctx.lineWidth = 5;
                ctx.strokeStyle = "black";
                ctx.stroke();
            }
            function drawBezierCircle () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.fillStyle = 'rgba(254, 76, 175, 0.6)'; // fill style
                ctx.strokeStyle = '#AAAAAA'; // line color
                ctx.lineWidth = 3; // line width
                ctx.beginPath();
                ctx.moveTo(50, 50); // start x, start y
                ctx.bezierCurveTo(95, 80, 95, 110, 50, 140); // cp1 x, cp1 y, cp2 x, cp2 y, end x, end y
                                                            // where cp denotes control point
                ctx.bezierCurveTo(5, 110, 5, 80, 50, 50);

                ctx.fill();
                ctx.stroke();
            }

            function drawQuadraticCircle () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.fillStyle = 'rgba(54, 236, 175, 0.3)'; // fill style
                ctx.strokeStyle = '#AAAAAA'; // line color
                ctx.lineWidth = 3; // line width
                ctx.beginPath();
                ctx.moveTo(120, 120); // start x, start y
                ctx.quadraticCurveTo(145, 125, 150, 150); // cp x, cp y, end x, end y
                ctx.quadraticCurveTo(145, 175, 120, 180);
                ctx.quadraticCurveTo(95, 175, 90, 150);
                ctx.quadraticCurveTo(95, 125, 120, 120);

                ctx.fill();
                ctx.stroke();
            }
            function loadImage() {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                var img = new Image();
                img.src = 'imgs/test.png';
                img.onload = function(){
                    ctx.drawImage(img, 200, 200, 300, 200); // x, y, width, height
                }
            }
            function loadCropedImage () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                var img = new Image();
                img.src = 'imgs/test.png';
                img.onload = function(){
                    ctx.drawImage(img, 90, 0, 500, 550, // crop area: start x, start y, width, height
                                    200, 200, 300, 200); // display area: x, y, width, height
                }
            }
            function showText () {
                var ctx = getCanvas('testCanvas').getContext('2d'); // get Canvas context
                ctx.font = '60pt Calibri'; // font, size and family
                ctx.fillStyle = 'rgba(254, 76, 175, 0.6)'; // fill style
                ctx.strokeStyle = 'blue'; // line color
                ctx.lineWidth = 3; // stroke size
                ctx.fillText("test text", 100, 400); // fill the text: text, x, y
                ctx.strokeText("test text", 100, 400); // stroke the text: text, x, y
            }
            function getCanvas (id) {
                var canvas = document.getElementById(id);
                if (canvas && canvas.getContext)
                    return canvas;
                return null;
            }

        </script>
    </head>

    <body>
        <canvas id="testCanvas" height="500px" width="500px" style="border: 1px solid #3648AE;">
            current browser does not support canvas
        </canvas>
        
        <div id="controls">
            <button onclick="drawLine();">draw line</button>
            <button onclick="drawRect();">draw rect</button>
            <button onclick="drawCircle();">draw circle</button>
            <button onclick="drawBezierCircle();">draw bezier circle</button>
            <button onclick="drawQuadraticCircle();">draw quadratic circle</button>
            <button onclick="loadImage();">load image</button>
            <button onclick="loadCropedImage();">load a part of image</button>
            <button onclick="showText();">show text &quot;test text&quot;</button>
        </div>
    </body>

</html>


Download:

https://github.com/benbai123/HTML_CSS_Javascript_practice/blob/master/Html5/Canvas/basic_canvas_test.html

References: 

http://www.tutorialspoint.com/html5/html5_canvas.htm
http://www.html5canvastutorials.com/tutorials/html5-canvas-image-crop/

Saturday, August 11, 2012

ZK Gmaps Extension: Gdirection


Introduction

This post is about a custom ZK component (Gdirection) that let ZK Gmaps works with Google Direction Service.

Warm Up

To understand this post well, you may reed the articles below first:

ZK Quick Start

ZK Component Development Tutorial: Getting Started

The Program

Let's skipp some files, only take a look at Gdirection.java, Gdirection.js  and test zul file here

Gdirection.java

package org.zkoss.gmaps.extended.components;

import org.zkoss.gmaps.Gmaps;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zul.Div;
import org.zkoss.zul.impl.XulElement;

public class Gdirection extends XulElement {
    private String _panelId;
    private Div _panel;
    private String _mapId;
    private Gmaps _map;
    private String _start;
    private String _end;
    private String[] _direction;

    /**
     * Set panel to google direction display,
     * note you should make sure the panel is already attached to page before call this function
     * because the uuid will be changed when attache to page
     * @param panel
     */
    public void setPanel (Div panel) {
        _panel = panel;
        setPanelId(panel.getUuid());
    }
    /**
     * set panel id directly
     * @param panelId
     */
    public void setPanelId (String panelId) {
        _panelId = panelId;
        smartUpdate("panelId", _panelId);
    }
    /**
     * get panel id
     * @return
     */
    public String getPanelId () {
        return _panelId;
    }
    /**
     * Set map to google direction display,
     * note you should make sure the map is already attached to page before call this function
     * because the uuid will be changed when attache to page
     * @param map
     */
    public void setMap (Gmaps map) {
        _map = map;
        setMapId(map.getUuid());
    }
    /**
     * set map id directly
     * @param mapId
     */
    public void setMapId (String mapId) {
        _mapId = mapId;
        smartUpdate("mapId", _mapId);
    }
    /**
     * get map id
     * @return
     */
    public String getMapId () {
        return _mapId;
    }
    /**
     * Sets the start point of direction
     * @param start
     */
    public void setStart (String start) {
        _start = start;
        setDirection (_start, _end);
    }
    /**
     * Sets the end point of direction
     * @param end
     */
    public void setEnd (String end) {
        _end = end;
        setDirection (_start, _end);
    }
    /**
     * set direction to route
     * @param start the start point
     * @param end the end point
     */
    public void setDirection (String start, String end) {
        if (start != null && end != null) {
            _direction = new String[] {start, end};
            smartUpdate("direction", _direction);
        } else {
            _direction = null;
        }
    }
    /**
     * set direction to route
     * @param direction the String array [start, end]
     */
    public void setDirection (String[] direction) {
        if (direction.length != 2)
            throw new WrongValueException ("the direction should exactly contains [start point, end point]");
        _direction = direction;
        smartUpdate("direction", _direction);
    }
    public String[] getDirection () {
        return _direction;
    }
    public String getZclass() {
        return _zclass == null ? "z-gdirection" : _zclass;
    }
    //-- ComponentCtrl --//
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
    throws java.io.IOException {
        super.renderProperties(renderer);
        if (_mapId != null)
            render(renderer, "mapId", _mapId);
        if (_panelId != null)
            render(renderer, "panelId", _panelId);
        if (_direction != null)
            render(renderer, "direction", _direction);
    }
}


It will render start point, end point and the id of gmap and panel div to client side.

Gdirection.js


gmaps.extended.Gdirection = zk.$extends(zul.Widget, {
    $define: {
        /**
         * sets the mapId of the map for direction display
         * @param v
         */
        mapId: function (v) {
            var map,
                service,
                direction;
            map = this._map = zk.Widget.$('#' + v);
            if (service = this._directionsService) {
                // do route if ready
                if (direction = this._direction)
                    this.setDirection(direction, {force: true});
            } else
                this._init();
        },
        /**
         * sets the panelId of the panel for direction display
         * @param v
         */
        panelId: function (v) {
            var panel,
                service,
                direction;
            panel = this._panel = jq('#' + v)[0];
            if (service = this._directionsService) {
                // do route if ready
                if (direction = this._direction)
                    this.setDirection(direction, {force: true});
            } else
                this._init();
        },
        /**
         * sets the direction to route
         * @param v
         */
        direction: function (v) {
            var display = this._directionsDisplay;

            if (display = this._directionsDisplay) {
                var s = v? $eval(v) : null,
                        service;
                // wrong arguments or not binded
                if (s.length != 2 || !(service = this._directionsService)) return;
                var start = s[0],
                    end = s[1],
                    request = {
                        origin: start,
                        destination: end,
                        travelMode: google.maps.DirectionsTravelMode.DRIVING
                    };

                service.route(request, function(response, status) {
                    if (status == google.maps.DirectionsStatus.OK) {
                        display.setDirections(response);
                    }
                });
            }
        }
    },
    bind_: function () {
        this.$supers(gmaps.extended.Gdirection, 'bind_', arguments);
        this._tryBind();
    },
    
    _tryBind: function () {
        var mapId, panelId;
        // init if google api, mapId and panelId are ready
        if (window.google && window.google.maps
            && (mapId = this._mapId)
            && (panelId = this._panelId))
            this._init();
        else if ((mapId = this._mapId)
                && (panelId = this._panelId)) {
            // retry if the info for init is ready
            var wgt = this;
            setTimeout (function () {wgt._tryBind()}, 100);
        }
    },
    _init: function () {
        var mapId = this._mapId,
            panelId = this._panelId,
            map,
            panel,
            direction,
            directionsDisplay;

        if (!(map = this._map))
            map = this._map = zk.Widget.$('#' + mapId);
        if (!(panel = this._panel))
            panel = this._panel = jq('#' + panelId)[0];
        // prevent multiple init
        if (directionsDisplay = this._directionsDisplay)
            return;

        // while map and panel are ready
        if (map && map._gmaps && panel) {
            this._directionsService = new google.maps.DirectionsService();
            this._directionsDisplay = directionsDisplay = new google.maps.DirectionsRenderer();

            if ((map = this._map)
                && (map = map._gmaps))
                directionsDisplay.setMap(map);
            if (panel = this._panel)
                directionsDisplay.setPanel(panel);

            if (map
                && panel
                && (direction = this._direction))
                this.setDirection(direction, {force: true});
        } else if (mapId
                && panelId) {
            // retry if the info for routing is ready
            var wgt = this,
                timer;
            if (timer = this._initTimer) clearTimeout(timer);
            this._initTimer = setTimeout (function () {wgt._init()}, 100);
        }
    },
    getZclass: function () {
        return 'z-gdirection';
    }
});


This will contunuously trying initiate Google Direction objects if the informations (map/panel id) are ready, and then call route API to show the result if has directions.

google_direction_test.zul

<zk xmlns:w="client">
    <combobox id="cbxOne" value="Chicago">
        <comboitem label="Chicago" />
        <comboitem label="Hammond" />
        <comboitem label="Joliet" />
        <comboitem label="Kankakee" />
        <comboitem label="St Louis" />
        <comboitem label="Peoria" />
        <attribute name="onChange">
            gdirection.setDirection(self.getValue(), cbxTwo.getValue());
        </attribute>
    </combobox>
    to
    <combobox id="cbxTwo" value="St Louis">
        <comboitem label="Chicago" />
        <comboitem label="Hammond" />
        <comboitem label="Joliet" />
        <comboitem label="Kankakee" />
        <comboitem label="St Louis" />
        <comboitem label="Peoria" />
        <attribute name="onChange">
            gdirection.setDirection(cbxOne.getValue(), self.getValue());
        </attribute>
    </combobox>
    <hbox id="hbox">
        <gmaps id="gmaps" width="500px" height="500px" />
        <div id="panel" width="300px" height="500px" style="overflow: auto;" />
    </hbox>
    <gdirection id="gdirection" start="chicago" end="st louis"
        onCreate='self.setMap(gmaps); self.setPanel(panel);' />
</zk>


The Result

Demo flash on line
http://screencast.com/t/2cpHVkMlGC

Reference
https://google-developers.appspot.com/maps/documentation/javascript/examples/directions-simple

Download

Full project
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/GmapsPractice

gmapsextended.jar
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Addon_Practice/GmapsPractice/target/gmapsextended.jar

demo swf
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/addon/GmapsWithGoogleDirectionService.swf


ZK Component Development Tutorial: Getting Started


Introduction

This post is about how to create a new ZK Component, step by step, with a very simple component 'errmsg' in Eclipse.

The errmsg component will display specified msg in error style and store the specified description.

After this article, you can also refer to Index Page of ZK CDT (Component Development Tutorial) Walkthrough to see more advanced articles.

The Steps

Step 1

This is starts from http://ben-bai.blogspot.tw/2012/06/zk-quick-start.html, a simple Dynamic Web Project with basic ZK jars.

Step 2

Some files should at the pre-defined location,
where lang-addon.xml should under classpath/metainfo/zk,
and the widget js/css files should under classpath/web/js

You can simply create a folder under project root and prepare the folders described above,
then edit the Project -> Properties to add it as Source folder



Finally the project structure are as below:



where the folders under CustomComponentsResources will be copied to classpath after compile

Step 3

Prepare the lang-addon.xml to define this component,
please refer to the inline-document

lang-addon.xml

<!-- ZK will load this file
    (classpath/metainfo/zk/lang-addon.xml) automatically -->

<language-addon>
    <!-- addon name, 
        required, it's better to be unique -->
    <addon-name>custom</addon-name>
    <!-- specifies the name of a language definition
        should be unique in a language definition,
        xul/html is predefinied language in ZK
        so can be used in lang-addon directly -->
    <language-name>xul/html</language-name>

    <!-- version
        optional,
        if the version specified in version-class is not
        the same as version-uid, or the real ZK version is smaller
        then zk-version, the addon will be ignored -->
    <version>
        <version-class>custom.zk.Version</version-class>
        <version-uid>0.0.1</version-uid>
        <zk-version>5.0.0</zk-version><!-- or later -->
    </version>

    <!-- define a component -->
    <component>
        <!-- the tag name of this component
            required,
            must be unique -->
        <component-name>errmsg</component-name>
        <!-- fully-qualified java class name at server side
            required for a new component that not extends another component -->
        <component-class>custom.zk.components.Errmsg</component-class>
        <!-- widget class, 'custom.zk.components.Errmsg'
            also specify the
            package of widget class 'custom.zk.components'
            required for a new component that not extends another component

            from my test,
            when you say a widget class is custom.zk.components.Errmsg
            it denotes there should have a file
            web/js/custom/zk/components/Errmsg.js under classpath,
            and have "<package name="custom.zk.components" ...>...</package>"
            in web/js/custom/zk/components/zk.wpd
            and "custom.zk.components.Errmsg = ..."
            in Errmsg.js to define that client widget
            
            You may try play around to find whether you can
            make them different, but it's better to let them
            sync even if you can make them different -->
        <widget-class>custom.zk.components.Errmsg</widget-class>
        <!-- mold
            required for a new component that not extends another component
            or has self widget-class
            
            a mold denotes the files that to render and style this comopnent -->
        <mold>
            <!-- default mold is required -->
            <mold-name>default</mold-name>
            <!-- relative path based on widget-class' path
                (web/js/custom/zk/components/)
                
                where errmsg.js (required) contains a function that
                will render the html of the comopnent,
                errmsg.css.dsp (optional) contains
                css styles of this component -->
            <mold-uri>mold/errmsg.js</mold-uri>
            <css-uri>css/errmsg.css.dsp</css-uri>
        </mold>
    </component>
</language-addon>


Step 4

Prepare the widget js file for this component

Errmsg.js

//error message widget class
//this file will be loaded because
//the widget-class --  'custom.zk.components.Errmsg'
//specified in lang-addon.xml and zk.wpd
custom.zk.components.Errmsg = zk.$extends(zul.Widget, {
//    block for define attributes
//    where 'anAttr: null' in this block will
//    generate 3 items in this widget
//    1. this._anAttr
//    2. this.setAnAttr(value)
//    3. this.getAnAttr(value)
    
//    'anAttr: function (v)' will also generate the 3 items above,
//    the function will be called by setter,
//    i.e., setAnAttr(value) {
//        ...
//        function(v)
//        ...
//    }
//    so you can specify what you want to do while the setter is called

//    for more information, please refer to
//    http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#$extends(zk.Class, _global_.Map, _global_.Map)
//    http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#define(zk.Class, _global_.Map)
    $define: {
        msg: function (v) {
            var n;
            if (v
                && (n = this.$n()))
                n.innerHTML = v;
        },
        description: null
    },
    // function to provide css class
    getZclass: function () {
        var zcls = this._zclass;
        return zcls? zcls : 'z-errmsg';
    }
});


Step 5

Prepare the renderer js file / css style file for default mold

errmsg.js

//error message html renderer
//this file will be loaded because
//the package of widget-class --  'custom.zk.components'
//and the mold-uri of mold -- 'mold/errmsg.js'
//specified in lang-addon.xml
function (out) {
    // the rendered msg
    var msg = this._msg;

    // this.uuid is the default attribute that
    // will assigned by ZK framework
    
    // this.getZclass() is overridden in Errmsg.js
    
    // after this line, the tmp result is
    // <span id="xxxxx" class="z-errmsg">
    out.push('<span id="', this.uuid,
            '" class="', this.getZclass(), '">');

    // output msg if exists
    if (msg)
        out.push(msg);
    // output end tag of span
    out.push('</span>');

    // finally, the result will be
    // <span id="xxxxx" class="z-errmsg">some message</span>
    // or
    // <span id="xxxxx" class="z-errmsg"></span>
}


errmsg.css.dsp

/* error message style */
/* this file will be loaded because */
/* the package of widget-class 'custom.zk.components' */
/* and the css-uri of mold 'css/errmsg.css.dsp' */
/* specified in lang-addon.xml */
.z-errmsg {
    font-weight: bold;
    font-size: 22px;
    color: red;
}


Step 6

Prepare zk.wpd to config widget's loading

zk.wpd

<!-- this file define the package name and the order to load widgets -->
<!-- depends="zul" denotes this package should -->
<!-- be loaded after the zul package, -->
<!-- i.e., if you load this package but the zul package is not loaded, -->
<!-- the framework will load zul package first automatically -->
<package name="custom.zk.components" language="xul/html" depends="zul">
    <widget name="Errmsg"/>
</package>


Step 7

Prepare the Version class to specify version of this addon and component class.

Version.java

package custom.zk;

// the Versio file that version UID will be compared
// with the version-uid specified in lang-addon.xml
public class Version {
    /** Returns the version UID.
     */
    public static final String UID = "0.0.1";
}


Errmsg.java


package custom.zk.components;


import org.zkoss.zul.impl.XulElement;
// this file will be loaded because the
// <component-class>custom.zk.components.Errmsg</component-class>
// specified in lang-addon.xml
public class Errmsg extends XulElement {
    private String _msg;
    private String _description;

    public void setMsg (String msg) {
        _msg = msg;
        // this will call client side widget's setMsg(_msg)
        smartUpdate("msg", _msg);
    }
    public String getMsg () {
        return _msg;
    }
    public void setDescription (String description) {
        _description = description;
        // this will call client side widget's setDescription(_description)
        smartUpdate("description", _description);
    }
    public String getDescription () {
        return _description;
    }
    public String getZclass() {
        return _zclass == null ? "z-errmsg" : _zclass;
    }
    //-- ComponentCtrl --//
//    the renderProperties will be called by framework automatically,
//    remember to render super's properties first
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
    throws java.io.IOException {
        super.renderProperties(renderer);
        // this will call client side widget's setMsg(_msg)
        if (_msg != null)
            render(renderer, "msg", _msg);
        // this will call client side widget's setDescription(_description)
        if (_description != null)
            render(renderer, "description", _description);
    }
}


Step 8

Finally use a zul file to test it

errmsg_component_test.zul


<zk>
<!--     an errmsg thst display msg and will -->
<!--     alert its discription while clicked -->

<!--     the id attribute is specified in the super class -->
<!--     the onClick event also listened by super class -->
<!--     we can just simply use them -->
    <errmsg id="errMsg" msg="error"
        description="this is a test message to test component errmsg"
        onClick="alert(self.getDescription());" />
<!--     use a ZK button to change errmsg's property -->
<!--     the 'errMsg' below is an errmsg instance that has id 'errMsg' -->
    <button label="test">
        <attribute name="onClick">
            errMsg.setMsg(" new message");
            errMsg.setDescription(" new description");
        </attribute>
    </button>
</zk>


Export component(s) to a jar

Apply following steps to export component(s) to a jar file

File -> Export



Select Jar file -> Next



Select CustomComponentsResources and src, specify jar location then click Finish



Finally you can use the component in other ZK Project with the generated jar file.

Result

See demo on line
http://screencast.com/t/3LUWQOdn

Download

Full project:
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Development

Demo:
https://github.com/benbai123/ZK_Practice/blob/master/Components/demos/component_development/zk_custom_component__errmsg_demo.swf

Exported jar:
https://github.com/benbai123/ZK_Practice/blob/master/Components/projects/Components_Development/target/ZKCustomComponents.jar


References:

http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#$extends(zk.Class, _global_.Map, _global_.Map)

http://www.zkoss.org/javadoc/latest/jsdoc/_global_/zk.html#define(zk.Class, _global_.Map)

http://books.zkoss.org/wiki/ZK_Client-side_Reference/Language_Definition

Wednesday, August 1, 2012

JAVA: Convert Image to Base64 String and Base64 String to Image


Introduction

Sometimes we will want to convert an image to a string, for example, store it in database or transfer it via XML. This post will show how to convert image to string/string to image.

The Program

ImageUtils.java

package test;

import java.io.IOException;

import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;

public class ImageUtils {

    /**
     * Decode string to image
     * @param imageString The string to decode
     * @return decoded image
     */
    public static BufferedImage decodeToImage(String imageString) {

        BufferedImage image = null;
        byte[] imageByte;
        try {
            BASE64Decoder decoder = new BASE64Decoder();
            imageByte = decoder.decodeBuffer(imageString);
            ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
            image = ImageIO.read(bis);
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return image;
    }

    /**
     * Encode image to string
     * @param image The image to encode
     * @param type jpeg, bmp, ...
     * @return encoded string
     */
    public static String encodeToString(BufferedImage image, String type) {
        String imageString = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        try {
            ImageIO.write(image, type, bos);
            byte[] imageBytes = bos.toByteArray();

            BASE64Encoder encoder = new BASE64Encoder();
            imageString = encoder.encode(imageBytes);

            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageString;
    }

    public static void main (String args[]) throws IOException {
        /* Test image to string and string to image start */
        BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
        BufferedImage newImg;
        String imgstr;
        imgstr = encodeToString(img, "png");
        System.out.println(imgstr);
        newImg = decodeToImage(imgstr);
        ImageIO.write(newImg, "png", new File("files/img/CopyOfTestImage.png"));
        /* Test image to string and string to image finish */
    }
}


Reference
Working with Images
http://docs.oracle.com/javase/tutorial/2d/images/index.html


Download
File at github
https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/JAVA/Commons/src/test/ImageUtils.java