org.sigmah.client.page.table.PivotGridPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.client.page.table.PivotGridPanel.java

Source

/*
 * All Sigmah code is released under the GNU General Public License v3
 * See COPYRIGHT.txt and LICENSE.txt.
 */

package org.sigmah.client.page.table;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.sigmah.client.AppEvents;
import org.sigmah.client.EventBus;
import org.sigmah.client.dispatch.Dispatcher;
import org.sigmah.client.dispatch.monitor.MaskingAsyncMonitor;
import org.sigmah.client.event.PivotCellEvent;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.icon.IconUtil;
import org.sigmah.client.util.DateUtilGWTImpl;
import org.sigmah.client.util.Notification;
import org.sigmah.shared.command.BatchCommand;
import org.sigmah.shared.command.GetIndicators;
import org.sigmah.shared.command.Month;
import org.sigmah.shared.command.UpdateMonthlyReports;
import org.sigmah.shared.command.result.IndicatorListResult;
import org.sigmah.shared.date.DateUtil;
import org.sigmah.shared.dto.IndicatorDTO;
import org.sigmah.shared.report.content.DimensionCategory;
import org.sigmah.shared.report.content.EntityCategory;
import org.sigmah.shared.report.content.MonthCategory;
import org.sigmah.shared.report.content.PivotTableData;
import org.sigmah.shared.report.content.PivotTableData.Axis;
import org.sigmah.shared.report.model.DateDimension;
import org.sigmah.shared.report.model.DateUnit;
import org.sigmah.shared.report.model.Dimension;
import org.sigmah.shared.report.model.DimensionType;
import org.sigmah.shared.report.model.PivotElement;

import com.extjs.gxt.ui.client.data.BaseTreeModel;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.store.Store;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.util.DelayedTask;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.treegrid.EditorTreeGrid;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasValue;
import com.google.inject.Inject;

/**
 * 
 * Independent component to display and edited pivoted site / indicator results.
 * 
 * @author Alex Bertram
 */
public class PivotGridPanel extends ContentPanel implements HasValue<PivotElement> {

    private EventBus eventBus;
    private final Dispatcher dispatcher;

    private PivotElement element;
    private EditorTreeGrid<PivotTableRow> grid;
    private TreeStore<PivotTableRow> store;
    private ColumnMapping columnMapping;

    private Map<Integer, IndicatorDTO> indicators;

    private DateUtil dateUtil = new DateUtilGWTImpl();

    private boolean showAxisIcons = true;
    private boolean showSwapIcon = false;

    @Inject
    public PivotGridPanel(EventBus eventBus, Dispatcher dispatcher) {
        this.eventBus = eventBus;
        this.dispatcher = dispatcher;

        setLayout(new FitLayout());

        store = new TreeStore<PivotTableRow>();

        PivotResources.INSTANCE.css().ensureInjected();
    }

    public boolean isShowAxisIcons() {
        return showAxisIcons;
    }

    public void setShowAxisIcons(boolean showAxisIcons) {
        this.showAxisIcons = showAxisIcons;
    }

    public boolean isShowSwapIcon() {
        return showSwapIcon;
    }

    public void setShowSwapIcon(boolean showSwapIcon) {
        this.showSwapIcon = showSwapIcon;
    }

    public class PivotTableRow extends BaseTreeModel {

        private PivotTableData.Axis rowAxis;

        public PivotTableRow(PivotTableData.Axis axis) {
            this.rowAxis = axis;
            set("header", HEADER_DECORATOR.decorateHeader(axis));

            updateFromTree();

            for (PivotTableData.Axis child : rowAxis.getChildren()) {
                add(new PivotTableRow(child));
            }
        }

        /**
         * Updates this model from the Cell values in the PivotTableData tree.
         */
        private void updateFromTree() {
            this.setProperties(Collections.EMPTY_MAP);
            for (Map.Entry<PivotTableData.Axis, PivotTableData.Cell> entry : rowAxis.getCells().entrySet()) {
                String property = columnMapping.propertyNameForAxis(entry.getKey());
                set(property, entry.getValue().getValue());
            }
        }

        public PivotTableData.Axis getRowAxis() {
            return rowAxis;
        }

        public PivotTableData.Axis getColAxis(String property) {
            return columnMapping.columnAxisForProperty(property);
        }

        public DimensionCategory getCategory(String property, Dimension dimension) {
            DimensionCategory category = findCategory(rowAxis, dimension);
            if (category == null) {
                return findCategory(getColAxis(property), dimension);
            }
            return category;
        }

        private DimensionCategory findCategory(PivotTableData.Axis leaf, Dimension dim) {
            while (leaf != null) {
                if (leaf.getDimension() != null && leaf.getDimension().equals(dim)) {
                    return leaf.getCategory();
                }
                leaf = leaf.getParent();
            }
            return null;
        }

        public int getIndicatorId(String property) {
            Set<Integer> indicatorRestrictions = element.getFilter().getRestrictions(DimensionType.Indicator);
            if (indicatorRestrictions.size() == 1) {
                return indicatorRestrictions.iterator().next();
            }
            EntityCategory cat = (EntityCategory) getCategory(property, new Dimension(DimensionType.Indicator));
            if (cat != null) {
                return cat.getId();
            }
            return -1;
        }

        public int getSiteId(String property) {
            Set<Integer> siteRestrictions = element.getFilter().getRestrictions(DimensionType.Site);
            if (siteRestrictions.size() == 1) {
                return siteRestrictions.iterator().next();
            }
            EntityCategory cat = (EntityCategory) getCategory(property, new Dimension(DimensionType.Site));
            if (cat != null) {
                return cat.getId();
            }
            return -1;
        }

        public Month getMonth(String property) {
            MonthCategory cat = (MonthCategory) getCategory(property, new DateDimension(DateUnit.MONTH));
            if (cat != null) {
                return new Month(cat.getYear(), cat.getMonth());
            }
            if (element.getFilter().getDateRange().isClosed()) {
                // TODO(alex) : this should check to see whether the date range
                // is actually a month range
                // but dateUtil is behaving weirdly because of conflicting
                // timezones
                return new Month(dateUtil.getYear(element.getFilter().getMinDate()),
                        dateUtil.getMonth(element.getFilter().getMinDate()));
            }
            throw new UnsupportedOperationException(
                    "This cell at property '" + property + "' is not constrained by month");
        }

    }

    public void clear() {
        if (grid != null) {
            grid.removeAllListeners();
            removeAll();
        }
        store.removeAll();
    }

    public void setData(final PivotElement element) {
        clear();

        this.element = element;
        PivotTableData data = element.getContent().getData();

        this.columnMapping = new ColumnMapping(data, new RendererProvider(), HEADER_DECORATOR);

        for (PivotTableData.Axis axis : data.getRootRow().getChildren()) {
            store.add(new PivotTableRow(axis), true);
        }

        grid = new EditorTreeGrid<PivotTableRow>(store, columnMapping.getColumnModel());
        grid.setView(new PivotGridPanelView());
        grid.getStyle().setNodeCloseIcon(null);
        grid.getStyle().setNodeOpenIcon(null);
        grid.setAutoExpandColumn("header");
        grid.setAutoExpandMin(150);
        grid.addListener(Events.CellDoubleClick, new Listener<GridEvent<PivotTableRow>>() {
            public void handleEvent(GridEvent<PivotTableRow> ge) {
                if (ge.getColIndex() != 0) {
                    eventBus.fireEvent(new PivotCellEvent(AppEvents.Drilldown, element, ge.getModel().getRowAxis(),
                            columnMapping.columnAxisForIndex(ge.getColIndex())));
                }
            }
        });
        grid.addListener(Events.HeaderClick, new Listener<GridEvent<PivotTableRow>>() {

            @Override
            public void handleEvent(GridEvent<PivotTableRow> event) {
                fireEvent(Events.HeaderClick,
                        new PivotGridHeaderEvent(event, columnMapping.columnAxisForIndex(event.getColIndex())));
            }
        });
        grid.addListener(Events.CellClick, new Listener<GridEvent<PivotTableRow>>() {

            @Override
            public void handleEvent(GridEvent<PivotTableRow> event) {
                if (event.getColIndex() == 0) {
                    fireEvent(Events.HeaderClick, new PivotGridHeaderEvent(event, event.getModel().getRowAxis()));
                } else {
                    fireEvent(Events.CellClick,
                            new PivotGridCellEvent(event, columnMapping.columnAxisForIndex(event.getColIndex())));
                }
            }
        });
        grid.addListener(Events.BeforeEdit, new Listener<GridEvent<PivotTableRow>>() {

            @Override
            public void handleEvent(GridEvent<PivotTableRow> be) {
                if (!be.getModel().getRowAxis().isLeaf()) {
                    be.setCancelled(true);
                }
                PivotGridCellEvent pivotEvent = new PivotGridCellEvent(be,
                        columnMapping.columnAxisForIndex(be.getColIndex()));
                IndicatorDTO indicator = indicators.get(pivotEvent.getIndicatorId());
                if (indicator.isDirectDataEntryEnabled()) {
                    prepareEditor(pivotEvent, indicator);
                } else {
                    be.setCancelled(true);
                    Notification.show(I18N.CONSTANTS.dataEntry(), I18N.CONSTANTS.indicatorDirectEntry());
                }
            }
        });
        grid.addListener(Events.AfterEdit, new Listener<GridEvent<PivotTableRow>>() {

            @Override
            public void handleEvent(GridEvent<PivotTableRow> event) {
                PivotGridCellEvent pivotEvent = new PivotGridCellEvent(event,
                        columnMapping.columnAxisForIndex(event.getColIndex()));
                updateTotalsAfterEdit(pivotEvent);
                fireEvent(Events.AfterEdit, pivotEvent);
            }
        });

        grid.addStyleName(PivotResources.INSTANCE.css().pivotTable());

        add(grid);

        layout();

        new DelayedTask(new Listener<BaseEvent>() {
            public void handleEvent(BaseEvent be) {
                for (PivotTableRow row : store.getRootItems()) {
                    grid.setExpanded(row, true, true);
                }
            }
        }).delay(1);

    }

    private IndicatorDTO indicatorForCell(PivotGridCellEvent event) {
        int indicatorId = event.getIndicatorId();
        return indicators.get(indicatorId);
    }

    protected void prepareEditor(PivotGridCellEvent event, IndicatorDTO indicator) {
        if (indicator != null) {
            ColumnConfig config = grid.getColumnModel().getColumn(event.getColIndex());
            IndicatorValueField field = (IndicatorValueField) config.getEditor().getField();
            field.setIndicator(indicator);
        }
    }

    private void updateTotalsAfterEdit(PivotGridCellEvent event) {
        // update the PivotTableData.Cell
        Double newValue = event.getModel().get(event.getProperty());
        event.getOrCreateCell().setValue(newValue);

        // update totals
        element.getContent().getData().updateTotals();

        syncGridWithContent();

    }

    private void syncGridWithContent() {
        for (PivotTableRow row : store.getAllItems()) {
            row.updateFromTree();
        }
        grid.getView().refresh(false);
    }

    private int findIndicatorId(PivotTableData.Axis axis) {
        while (axis != null) {
            if (axis.getDimension() != null && axis.getDimension().getType() == DimensionType.Indicator) {
                return ((EntityCategory) axis.getCategory()).getId();
            }
            axis = axis.getParent();
        }
        return -1;
    }

    @Override
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<PivotElement> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    @Override
    public PivotElement getValue() {
        return element;
    }

    @Override
    public void setValue(final PivotElement value) {
        int databaseId = value.getFilter().getRestrictions(DimensionType.Database).iterator().next();
        dispatcher.execute(GetIndicators.forDatabase(databaseId),
                new MaskingAsyncMonitor(this, I18N.CONSTANTS.loading()), new AsyncCallback<IndicatorListResult>() {

                    @Override
                    public void onFailure(Throwable caught) {
                        // handled by monitor
                    }

                    @Override
                    public void onSuccess(IndicatorListResult result) {
                        indicators = new HashMap<Integer, IndicatorDTO>();
                        for (IndicatorDTO indicator : result.getData()) {
                            indicators.put(indicator.getId(), indicator);
                        }
                        setData(value);
                    }
                });

    }

    @Override
    public void setValue(PivotElement value, boolean fireEvents) {
        setData(element);
        if (fireEvents) {
            ValueChangeEvent.fire(this, value);
        }
    }

    public Store<PivotTableRow> getStore() {
        return store;
    }

    public BatchCommand composeSaveCommand() {
        BatchCommand batch = new BatchCommand();
        for (Record record : store.getModifiedRecords()) {
            PivotTableRow row = (PivotTableRow) record.getModel();
            for (String property : record.getChanges().keySet()) {
                UpdateMonthlyReports.Change change = new UpdateMonthlyReports.Change();
                change.indicatorId = row.getIndicatorId(property);
                change.month = row.getMonth(property);
                change.value = row.get(property);
                batch.add(new UpdateMonthlyReports(row.getSiteId(property), change));
            }
        }
        return batch;
    }

    private class RendererProvider implements PivotCellRendererProvider {

        @Override
        public PivotCellRenderer getRendererForColumn(Axis column) {
            int indicatorId = -1;
            if (element.getFilter().isRestricted(DimensionType.Indicator)) {
                indicatorId = element.getFilter().getRestrictions(DimensionType.Indicator).iterator().next();
            } else {
                indicatorId = findIndicatorId(column);
            }
            if (indicatorId == -1) {
                return new MixedCellRenderer(indicators);
            } else {
                IndicatorDTO indicator = indicators.get(indicatorId);
                if (indicator.isQualitative()) {
                    return new QualitativeCellRenderer(indicator);
                } else {
                    return new QuantitativeCellRenderer(indicator);
                }
            }
        }

    }

    private class GridHeaderDecorator implements HeaderDecorator {

        @Override
        public String decorateHeader(Axis axis) {
            if (showAxisIcons && axis.isLeaf()) {
                StringBuilder sb = new StringBuilder();
                sb.append(IconUtil.iconHtml(PivotResources.INSTANCE.css().zoomIcon()));

                if (axis.getDimension() == null) {
                    return "";
                }

                switch (axis.getDimension().getType()) {
                case Indicator:
                    sb.append(IconUtil.iconHtml(PivotResources.INSTANCE.css().editIcon()));
                }
                sb.append("<span>");
                sb.append(axis.getLabel());
                sb.append("</span>");
                return sb.toString();
            } else {
                return axis.getLabel();
            }
        }

        @Override
        public String cornerCellHtml() {
            if (showSwapIcon) {
                return IconUtil.iconHtml(PivotResources.INSTANCE.css().swapIcon());
            } else {
                return "";
            }
        }
    }

    private HeaderDecorator HEADER_DECORATOR = new GridHeaderDecorator();

    public void setHEADER_DECORATORWithOutEdit() {
        HEADER_DECORATOR = new HeaderDecorator() {

            @Override
            public String decorateHeader(Axis axis) {
                if (showAxisIcons && axis.isLeaf()) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(IconUtil.iconHtml(PivotResources.INSTANCE.css().zoomIcon()));

                    if (axis.getDimension() == null) {
                        return "";
                    }

                    sb.append("<span>");
                    sb.append(axis.getLabel());
                    sb.append("</span>");
                    return sb.toString();
                } else {
                    return axis.getLabel();
                }
            }

            @Override
            public String cornerCellHtml() {
                if (showSwapIcon) {
                    return IconUtil.iconHtml(PivotResources.INSTANCE.css().swapIcon());
                } else {
                    return "";
                }
            }
        };
    }

    public EditorTreeGrid<PivotTableRow> getGrid() {
        return grid;
    }

    public boolean hasIndicatorsInStore() {
        return (indicators != null && indicators.size() != 0);
    }
}