Sunday, February 10, 2013

ZK Pivottable: Sync the Open Status of Pivotmodel



Introduction

Assume there are two pivottables with different pivotmodel, the two pivotmodels have the same structure but different value.

This article describe how to synchronize the open status from one pivot model to another, i.e., when open or close the node of one pivottable, the corresponding node in another pivottable will also be opened or closed.

Pre-request

Display Data in Pivottable
http://ben-bai.blogspot.tw/2012/07/zk-pivottable-display-data-in-zk.html

The Program

index.zul

Contains two pivottable, the first one use a custom pivottable that post onPivotNodeOpen event to itself, the open status of second pivottable will follow the first pivottable.

<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>
            <!-- pivottable, get model from window's composer -->
            <pivottable id="pivottable" model="${win$composer.pivotModel}" use="test.ListenOpenPivottable" />
            <pivottable id="pivottableTwo" model="${win$composer.pivotModelTwo}" />
        </vlayout>
    </window>
</zk>


TestComposer.java

Synchronize the open status from first pivot model to second pivot model when the node in first pivottable is opened/closed.

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.TabularPivotModel;

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
    private Pivottable pivottableTwo;

    private TabularPivotModel _pivotModel;
    private TabularPivotModel _pivotModelTwo;

    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"
        });
    }
    @Listen ("onPivotNodeOpen = #pivottable")
    public void syncOpenStatus () {
        syncOpenStatus(_pivotModel.getRowHeaderTree().getRoot(), _pivotModelTwo.getRowHeaderTree().getRoot(), false);
        syncOpenStatus(_pivotModel.getColumnHeaderTree().getRoot(), _pivotModelTwo.getColumnHeaderTree().getRoot(), false);

        // reset model
        pivottableTwo.setModel(null);
        pivottableTwo.setModel(_pivotModelTwo);
    }
    /**
     * 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

Custom pivottable, will post onPivotNodeOpen event back.

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/HuW2n4kixiXG

Reference

The start point of a bunch 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/SyncPivotmodelStatus

No comments:

Post a Comment