Wednesday, February 13, 2013

ZK Pivottable: Sync Structure of Pivot Model


Introduction

This article describe how to copy the structure from one pivottable to another, the structure here denotes row field, column field, data field, calculator and open status.

Pre-request

Sync the Open Status of Pivotmodel
http://ben-bai.blogspot.tw/2013/02/zk-pivottable-sync-open-status-of.html

The Program

index.zul

There are two pivottables, a button and a pivot field control, the button will add Min calculator to first pivot table and then call the function to copy the structure to second pivottable, the pivot field control controls the first pivottable, the second pivottable will copy the structure from first pivottable while fields are changed.

<zk>
    <!-- Tested with ZK 6.0.2 EE and ZK Pivottable 2.0.0 -->
    <!-- window, apply a SelectorComposer -->
    <window id="win"
        apply="test.TestComposer">
        <vlayout>
            <button id="addBtn" label="add Min calculator to RowOne" />
            <!-- pivottable, get model from window's composer -->
            <hlayout>
                <pivottable id="pivottable" model="${win$composer.pivotModel}"
                    width="500px"
                    grandTotalForColumns="false"
                    use="test.ListenOpenPivottable" />
                <pivot-field-control id="pfc" width="300px" />
            </hlayout>
            <pivottable id="pivottableTwo" model="${win$composer.pivotModelTwo}"
                width="500px"
                grandTotalForColumns="false" />
        </vlayout>
    </window>
</zk>


TestComposer.java

Provide pivot model, listen to button click, pivot field control change and pivot node open event to sync the structure of second pivottable.

package test;

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

import org.zkoss.pivot.Calculator;
import org.zkoss.pivot.PivotField;
import org.zkoss.pivot.PivotHeaderNode;
import org.zkoss.pivot.Pivottable;
import org.zkoss.pivot.impl.StandardCalculator;
import org.zkoss.pivot.impl.TabularPivotField;
import org.zkoss.pivot.impl.TabularPivotModel;
import org.zkoss.pivot.ui.PivotFieldControl;

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

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

    @Wire
    PivotFieldControl pfc;
    @Wire
    private Pivottable pivottableTwo;

    private TabularPivotModel _pivotModel;
    private TabularPivotModel _pivotModelTwo;

    @SuppressWarnings("unchecked")
    public void doAfterCompose (Component comp) throws Exception {
        super.doAfterCompose(comp);
        pfc.setModel(getPivotModel());
    }
    public TabularPivotModel getPivotModel () throws Exception {
        if (_pivotModel == null) {
            _pivotModel = getPivotModel(getData(), getColumns());
        }
        return _pivotModel;
    }
    public TabularPivotModel getPivotModelTwo () throws Exception {
        if (_pivotModelTwo == null) {
            _pivotModelTwo = getPivotModel(getData(), getColumns());
        }
        return _pivotModelTwo;
    }

    public TabularPivotModel getPivotModel (List<List<Object>> data, List<String> columns) throws Exception {
        TabularPivotModel pivotModel = new TabularPivotModel(data, columns);

        // assign rows, the order matches to the level of row node field
        pivotModel.setFieldType("RowOne", PivotField.Type.ROW);
        pivotModel.setFieldType("RowTwo", PivotField.Type.ROW);
        pivotModel.setFieldType("RowThree", PivotField.Type.ROW);

        // assign columns, the order matches to the level of column node field
        pivotModel.setFieldType("ColumnOne", PivotField.Type.COLUMN);
        pivotModel.setFieldType("ColumnTwo", PivotField.Type.COLUMN);

        // assign datas, the order matches to the order of data field
        pivotModel.setFieldType("DataOne", PivotField.Type.DATA);

        PivotField field = pivotModel.getField("RowOne");
        pivotModel.setFieldSubtotals(field, new Calculator[] {StandardCalculator.SUM, StandardCalculator.MAX});
        return pivotModel;
    }

    /**
     * prepare the data for pivottable's model
     * The order of object put into data list should match
     * the order of column name's
     * @return
     * @throws Exception
     */
    public List<List<Object>> getData() throws Exception {
        List<List<Object>> result = new ArrayList<List<Object>>();
        Random r = new Random();

        for (int i = 0; i < 100; i++) {
            List<Object> data = new ArrayList<Object>();
            data.add("RowOne - " + (r.nextInt(2) + 1));
            data.add("RowTwo - " + (r.nextInt(2) + 1));
            data.add("RowThree - " + (r.nextInt(2) + 1));
            data.add("ColumnOne - " + (r.nextInt(2) + 1));
            data.add("ColumnTwo - " + (r.nextInt(2) + 1));
            data.add(r.nextInt(10));
            result.add(data);
        }
        return result;
    }

    /**
     * prepare columns name for pivottable's model
     * @return
     */
    public List<String> getColumns() {
        return Arrays.asList(new String[]{
                "RowOne", "RowTwo", "RowThree",
                "ColumnOne", "ColumnTwo",
                "DataOne"
        });
    }
    /**
     * called when the node of first pivottable is opened/closed
     */
    @Listen ("onPivotNodeOpen = #pivottable")
    public void syncOpenStatus () {
        syncOpenStatus(_pivotModel.getRowHeaderTree().getRoot(), _pivotModelTwo.getRowHeaderTree().getRoot(), false);
        syncOpenStatus(_pivotModel.getColumnHeaderTree().getRoot(), _pivotModelTwo.getColumnHeaderTree().getRoot(), false);

        pivottableTwo.setModel(null);
        pivottableTwo.setModel(_pivotModelTwo);
    }
    /**
     * called when the fields of first pivottable are changed
     */
    @Listen ("onPivotFieldControlChange = #pfc")
    public void syncModelStructure () {
        TabularPivotField[] fields = _pivotModel.getFields();
        for (TabularPivotField f : fields) {
            _pivotModelTwo.setFieldType(f.getFieldName(), f.getType());

            // sync field
            PivotField field = _pivotModelTwo.getField(f.getFieldName());
            // sync calculator
            _pivotModelTwo.setFieldSubtotals(field, f.getSubtotals());
        }
        syncOpenStatus();
    }
    /**
     * called when add button clicked
     */
    @Listen ("onClick = #addBtn")
    public void addCalculator () {
        PivotField field = _pivotModel.getField("RowOne");
        _pivotModel.setFieldSubtotals(field, new Calculator[] {StandardCalculator.SUM, StandardCalculator.MAX, StandardCalculator.MIN});
        syncModelStructure();
    }
    /**
     * Synchronize the open status of two pivot header trees
     * 
     * @param root the root of the base pivot header tree (or its sub trees)
     * @param rootTwo the root of the pivot header tree to sync (or its sub trees)
     * @param checkAll whether sync whole tree, <br>
     * true: sync whole tree, put every node of base pivot header tree into open list to sync<br>
     * false: sync only current view, only put the displayed node into open list to sync
     */
    private void syncOpenStatus (PivotHeaderNode root, PivotHeaderNode rootTwo, boolean checkAll) {
        Map<Object, PivotHeaderNode> originalOpenMap = new HashMap<Object, PivotHeaderNode>();

        // sync displayed node only if not checkAll
        // so do not need to scan whole header tree
        for (PivotHeaderNode node : root.getChildren()) {
            // checkAll: sync each node
            // !checkAll: sync displayed node
            if (checkAll
                || (node.getDepth() == 1 || node.getParent().isOpen())) {
                originalOpenMap.put(node.getKey(), node);
            }
        }
        // for each node in children of rootTwo
        for (PivotHeaderNode newNode : rootTwo.getChildren()) {
            PivotHeaderNode node = originalOpenMap.get(newNode.getKey());
            if (node != null) {
                newNode.setOpen(node.isOpen());
                // recursively sync sub trees
                syncOpenStatus(node, newNode, checkAll);
            }
        }
    }
}


ListenOpenPivottable.java

The same as ListenOpenPivottable.java in SyncStatus

package test;

import org.zkoss.pivot.Pivottable;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.ui.event.Events;

/**
 * Tested with ZK 6.0.2 and Pivottable 2.0.0
 *
 */
public class ListenOpenPivottable extends Pivottable {

    private static final long serialVersionUID = 4770700380255057252L;

    public void service(AuRequest request, boolean everError) {
        String cmd = request.getCommand();
        super.service(request, everError);
        // post onPivotNodeOpen event
        if ("onPivotNodeOpen".equals(cmd)) {
            Events.postEvent("onPivotNodeOpen", this, request.getData());
        }
    }
}


The Result

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

Reference

Start point of APIs
http://www.zkoss.org/javadoc/latest/zkpvt/org/zkoss/pivot/impl/TabularPivotModel.html

Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Addon_Practice/PivottableTest/SyncPivotmodelStructure

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

No comments:

Post a Comment