com.extjs.gxt.ui.client.widget.grid.GroupingView.java Source code

Java tutorial

Introduction

Here is the source code for com.extjs.gxt.ui.client.widget.grid.GroupingView.java

Source

/*
 * Sencha GXT 2.3.1 - Sencha for GWT
 * Copyright(c) 2007-2013, Sencha, Inc.
 * licensing@sencha.com
 * 
 * http://www.sencha.com/products/gxt/license/
 */
package com.extjs.gxt.ui.client.widget.grid;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.FastMap;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.data.ModelData;
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.event.MenuEvent;
import com.extjs.gxt.ui.client.event.SelectionEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.js.JsArray;
import com.extjs.gxt.ui.client.store.GroupingStore;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.Point;
import com.extjs.gxt.ui.client.widget.menu.CheckMenuItem;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.extjs.gxt.ui.client.widget.menu.SeparatorMenuItem;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.ui.AbstractImagePrototype;

/**
 * <code>GridView</code> that groups data based on a <code>GroupingStore</code>.
 */
public class GroupingView extends GridView {

    public class GroupingViewImages extends GridViewImages {
        private AbstractImagePrototype groupBy = GXT.IMAGES.grid_groupBy();

        public AbstractImagePrototype getGroupBy() {
            return groupBy;
        }

        public void setGroupBy(AbstractImagePrototype groupBy) {
            this.groupBy = groupBy;
        }
    }

    protected boolean enableGrouping;
    protected boolean isUpdating;

    protected Map<String, String> map = new FastMap<String>();
    private int counter = 0;
    private boolean enableGroupingMenu = true;
    private boolean enableNoGroups = true;
    private GroupingStore<ModelData> groupingStore;
    private GridGroupRenderer groupRenderer;
    private String lastGroupField;
    private boolean showGroupedColumn = true;
    private boolean showGroupName;
    private boolean startCollapsed;
    private Map<String, Boolean> state = new FastMap<Boolean>();

    /**
     * Collapses all groups.
     */
    public void collapseAllGroups() {
        toggleAllGroups(false);
    }

    /**
     * Expands all groups.
     */
    public void expandAllGroups() {
        toggleAllGroups(true);
    }

    /**
     * Returns the group renderer.
     * 
     * @return the group renderer
     */
    public GridGroupRenderer getGroupRenderer() {
        return groupRenderer;
    }

    /**
     * Returns the group elements.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public NodeList<Element> getGroups() {
        if (!enableGrouping) {
            return new JsArray().getJsObject().cast();
        }
        return (NodeList) mainBody.dom.getChildNodes();
    }

    @Override
    public GroupingViewImages getImages() {
        if (images == null) {
            images = new GroupingViewImages();
        }
        return (GroupingViewImages) images;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void initData(ListStore ds, ColumnModel cm) {
        super.initData(ds, cm);
        groupingStore = (GroupingStore) ds;
    }

    /**
     * Returns true if the grouping menu is enabled.
     * 
     * @return the enable grouping state
     */
    public boolean isEnableGroupingMenu() {
        return enableGroupingMenu;
    }

    /**
     * Returns true if the user can turn off grouping.
     * 
     * @return the enable no groups state
     */
    public boolean isEnableNoGroups() {
        return enableNoGroups;
    }

    /**
     * Returns true if the group is expanded.
     * 
     * @param group the group
     * @return true if expanded
     */
    public boolean isExpanded(Element group) {
        return group.getClassName().indexOf("x-grid-group-collapsed") == -1;
    }

    /**
     * Returns true if the grouped column is visible.
     * 
     * @return the show grouped column
     */
    public boolean isShowGroupedColumn() {
        return showGroupedColumn;
    }

    /**
     * Returns true if start collapsed is enabled.
     * 
     * @return the start collapsed state
     */
    public boolean isStartCollapsed() {
        return startCollapsed;
    }

    /**
     * True to enable the grouping entry in the header context menu (defaults to
     * true).
     * 
     * @param enableGroupingMenu true to enable
     */
    public void setEnableGroupingMenu(boolean enableGroupingMenu) {
        this.enableGroupingMenu = enableGroupingMenu;
    }

    /**
     * True to allow the user to turn off grouping by adding a check item to the
     * header context menu (defaults to true).
     * 
     * @param enableNoGroups true to enable turning off grouping
     */
    public void setEnableNoGroups(boolean enableNoGroups) {
        this.enableNoGroups = enableNoGroups;
    }

    /**
     * Sets the group renderer.
     * 
     * @param groupRenderer the group renderer
     */
    public void setGroupRenderer(GridGroupRenderer groupRenderer) {
        this.groupRenderer = groupRenderer;
    }

    /**
     * Sets whether the grouped column is visible (defaults to true).
     * 
     * @param showGroupedColumn true to show the grouped column
     */
    public void setShowGroupedColumn(boolean showGroupedColumn) {
        this.showGroupedColumn = showGroupedColumn;
    }

    /**
     * Sets whether the groups should start collapsed (defaults to false).
     * 
     * @param startCollapsed true to start collapsed
     */
    public void setStartCollapsed(boolean startCollapsed) {
        this.startCollapsed = startCollapsed;
    }

    /**
     * Toggles all groups.
     * 
     * @param expanded true to expand
     */
    public void toggleAllGroups(boolean expanded) {
        NodeList<Element> groups = getGroups();
        for (int i = 0, len = groups.getLength(); i < len; i++) {
            toggleGroup(groups.getItem(i), expanded);
        }
    }

    @Override
    protected Menu createContextMenu(final int colIndex) {
        Menu menu = super.createContextMenu(colIndex);

        if (menu != null && enableGroupingMenu && cm.isGroupable(colIndex)) {
            MenuItem groupBy = new MenuItem(GXT.MESSAGES.groupingView_groupByText());
            groupBy.setIcon(getImages().getGroupBy());
            groupBy.addSelectionListener(new SelectionListener<MenuEvent>() {

                @Override
                public void componentSelected(MenuEvent ce) {
                    onGroupByClick(ce, colIndex);
                }

            });
            menu.add(new SeparatorMenuItem());
            menu.add(groupBy);
        }

        if (menu != null && enableGroupingMenu && enableGrouping && enableNoGroups) {
            final CheckMenuItem showInGroups = new CheckMenuItem(GXT.MESSAGES.groupingView_showGroupsText());
            showInGroups.setChecked(true);
            showInGroups.addSelectionListener(new SelectionListener<MenuEvent>() {

                @Override
                public void componentSelected(MenuEvent ce) {
                    onShowGroupsClick(ce, showInGroups.isChecked());
                }
            });
            menu.add(showInGroups);
        }
        return menu;
    }

    protected void doGroupEnd(StringBuilder buf, GroupColumnData g, List<ColumnData> cs, int colCount) {
        buf.append(templates.endGroup());
    }

    protected void doGroupStart(StringBuilder buf, GroupColumnData g, List<ColumnData> cs, int colCount) {
        buf.append(templates.startGroup(g.groupId, g.css, g.style, g.group.toString()));
    }

    @Override
    protected String doRender(List<ColumnData> cs, List<ModelData> rows, int startRow, int colCount,
            boolean stripe) {
        if (rows.size() < 1) {
            return "";
        }

        String groupField = getGroupField();
        int colIndex = cm.findColumnIndex(groupField);

        enableGrouping = groupField != null;

        if (!enableGrouping || isUpdating) {
            return super.doRender(cs, rows, startRow, colCount, stripe);
        }

        String gstyle = "width:" + getTotalWidth() + "px;";
        String gidPrefix = grid.getId();

        ColumnConfig cfg = cm.getColumn(colIndex);

        String prefix = showGroupName ? cfg.getHeaderHtml() + ": " : "";

        GroupColumnData curGroup = null;
        String gid = null;

        List<GroupColumnData> groups = new ArrayList<GroupColumnData>();

        for (int j = 0; j < rows.size(); j++) {
            ModelData model = (ModelData) rows.get(j);

            int rowIndex = (j + startRow);

            // the value for the group field
            Object gvalue = model.get(groupField);

            // the rendered group value
            String g = getGroup(gvalue, model, rowIndex, colIndex, ds);

            if (curGroup == null || !curGroup.group.equals(g)) {
                gid = getGroupId(gidPrefix, groupField, g);

                boolean isCollapsed = state.get(gid) != null ? !state.get(gid) : startCollapsed;
                String gcls = isCollapsed ? "x-grid-group-collapsed" : "";

                curGroup = new GroupColumnData();
                curGroup.group = g;
                curGroup.field = groupField;
                curGroup.gvalue = gvalue;
                curGroup.text = prefix + g;
                curGroup.groupId = gid;
                curGroup.startRow = rowIndex;
                curGroup.style = gstyle;
                curGroup.css = gcls;
                curGroup.models.add(model);
                groups.add(curGroup);

            } else {
                curGroup.models.add(model);
            }
            // model.set("_groupId", gid);

        }

        for (GroupColumnData group : groups) {
            if (groupRenderer != null) {
                String g = groupRenderer.render(group);
                if (g == null || g.equals("")) {
                    g = "&nbsp;";
                }
                group.group = g;
            }
        }

        StringBuilder buf = new StringBuilder();

        for (int i = 0, len = groups.size(); i < len; i++) {
            GroupColumnData g = groups.get(i);
            doGroupStart(buf, g, cs, colCount);
            buf.append(super.doRender(cs, g.models, g.startRow, colCount, stripe));
            doGroupEnd(buf, g, cs, colCount);
        }

        return buf.toString();
    }

    protected Element findGroup(Element el) {
        return fly(el).findParentElement(".x-grid-group", 10);
    }

    protected String getGroup(Object value, ModelData m, int rowIndex, int colIndex, ListStore<ModelData> ds) {
        return value == null ? "" : value.toString();
    }

    protected String getGroupField() {
        return groupingStore.getGroupState();
    }

    protected String getGroupId(String gidPrefix, String groupField, String group) {
        String s = gidPrefix + "-gp-" + groupField + "-" + group;
        String r = map.get(s);
        if (r == null) {
            r = gidPrefix + "-gp-groupid-" + String.valueOf(counter++);
            map.put(s, r);
        }

        return r;
    }

    protected Element getGroupRow(Element group, int rowIndex) {
        return getGroupRows(group).getItem(rowIndex);
    }

    protected int getGroupRowCount(Element group) {
        return group.getChildNodes().getItem(1).getChildNodes().getLength();
    }

    protected NodeList<Element> getGroupRows(Element group) {
        return group.getChildNodes().getItem(1).getChildNodes().cast();
    }

    @Override
    protected NodeList<Element> getRows() {
        if (!enableGrouping) {
            return super.getRows();
        }
        if (!hasRows()) {
            return new JsArray().getJsObject().cast();
        }

        NodeList<Element> gs = getGroups();
        JsArray rows = new JsArray();
        for (int i = 0, len = gs.getLength(); i < len; i++) {
            NodeList<Element> g = gs.getItem(i).getChildNodes().getItem(1).getChildNodes().cast();
            for (int j = 0, len2 = g.getLength(); j < len2; j++) {
                rows.add(g.getItem(j));
            }
        }
        return rows.getJsObject().cast();
    }

    @SuppressWarnings("rawtypes")
    @Override
    protected void init(Grid grid) {
        super.init(grid);
        grid.getAriaSupport().setRole("treegrid");
    }

    @Override
    protected void initTemplates() {
        super.initTemplates();

        GridSelectionModel<ModelData> sm = grid.getSelectionModel();
        sm.addListener(Events.BeforeSelect, new Listener<SelectionEvent<ModelData>>() {
            public void handleEvent(SelectionEvent<ModelData> be) {
                onBeforeRowSelect(be);
            }
        });
    }

    @Override
    protected void onAdd(ListStore<ModelData> store, List<ModelData> models, int index) {
        if (enableGrouping) {
            Point ss = getScrollState();
            refresh(false);
            restoreScroll(ss);
        } else {
            super.onAdd(store, models, index);
        }
    }

    protected void onGroupSelect(Element group, boolean select) {
        El.fly(group).firstChild().setStyleName("x-grid3-group-selected", select);
        grid.getAriaSupport().setState("aria-activedescendant", group.getFirstChildElement().getId());
    }

    @Override
    protected void onMouseDown(GridEvent<ModelData> ge) {
        super.onMouseDown(ge);
        El hd = ge.getTarget(".x-grid-group-hd", 10);
        if (hd != null) {
            ge.stopEvent();
            toggleGroup(hd.dom.getParentElement(), isGroupExpanded(hd.dom.getParentElement()));
        }
    }

    @Override
    protected void onRemove(ListStore<ModelData> ds, ModelData m, int index, boolean isUpdate) {
        super.onRemove(ds, m, index, isUpdate);
        String groupField = getGroupField();
        if (enableGrouping) {
            String id = getGroupId(grid.getId(), groupField,
                    getGroup(m.get(groupField), m, index, cm.findColumnIndex(groupField), ds));
            Element g = XDOM.getElementById(id);
            if (g != null && !g.getChildNodes().getItem(1).hasChildNodes()) {
                fly(g).removeFromParent();
                removeGroupId(id);
            }
        }
        // apply empty text
    }

    protected void onShowGroupsClick(MenuEvent be, boolean checked) {
        if (checked) {
            onGroupByClick(be, activeHdIndex);
        } else {
            groupingStore.clearGrouping();
        }
    }

    @Override
    protected void refreshRow(int row) {
        isUpdating = true;
        super.refreshRow(row);
        isUpdating = false;
    }

    @Override
    protected SafeHtml renderRows(int startRow, int endRow) {
        String groupField = getGroupField();
        boolean eg = groupField != null;
        if (!showGroupedColumn) {
            int colIndex = cm.findColumnIndex(groupField);
            if (!eg && lastGroupField != null) {
                mainBody.update(SafeHtmlUtils.EMPTY_SAFE_HTML);
                cm.setHidden(cm.findColumnIndex(lastGroupField), false);
                lastGroupField = null;
            } else if (eg && (lastGroupField == null || lastGroupField == groupField)) {
                lastGroupField = groupField;
                cm.setHidden(colIndex, true);
            } else if (eg && lastGroupField != null && !groupField.equals(lastGroupField)) {
                mainBody.update(SafeHtmlUtils.EMPTY_SAFE_HTML);
                int oldIndex = cm.findColumnIndex(lastGroupField);
                cm.setHidden(oldIndex, false);
                lastGroupField = groupField;
                cm.setHidden(colIndex, true);
            }
        }
        return super.renderRows(startRow, endRow);
    }

    @Override
    protected void templateOnAllColumnWidthsUpdated(List<Integer> ws, int tw) {
        super.templateOnAllColumnWidthsUpdated(ws, tw);
        updateGroupWidths();
    }

    @Override
    protected void templateOnColumnHiddenUpdated(int col, boolean hidden, int tw) {
        super.templateOnColumnHiddenUpdated(col, hidden, tw);
        updateGroupWidths();
    }

    @Override
    protected void templateOnColumnWidthUpdated(int col, int w, int tw) {
        super.templateOnColumnWidthUpdated(col, w, tw);
        updateGroupWidths();
    }

    protected void toggleGroup(Element g, boolean expanded) {
        if (grid.editSupport != null) {
            grid.editSupport.stopEditing();
        }
        state.put(fly(g).getId(), expanded);
        fly(g).setStyleName("x-grid-group-collapsed", !expanded);
        g.getFirstChildElement().setAttribute("aria-expanded", expanded ? "true" : "false");
        calculateVBar(false);
    }

    private boolean isGroupExpanded(Element g) {
        return fly(g).hasStyleName("x-grid-group-collapsed");
    }

    private void onBeforeRowSelect(SelectionEvent<ModelData> se) {
        if (!enableGrouping) {
            return;
        }
        Element row = getRow(se.getIndex());
        if (row != null) {
            Element g = findGroup(row);
            toggleGroup(g, true);
        }
    }

    private void onGroupByClick(MenuEvent me, int colIndex) {
        groupingStore.groupBy(cm.getDataIndex(colIndex));
    }

    private void removeGroupId(String id) {
        for (Entry<String, String> e : map.entrySet()) {
            if (e.getValue().equals(id)) {
                map.remove(e.getKey());
                return;
            }
        }
    }

    private void updateGroupWidths() {
        if (!enableGrouping || ds.getCount() < 1) {
            return;
        }
        String tw = Math.max(cm.getTotalWidth(), el.dom.getOffsetWidth() - getScrollAdjust()) + "px";
        NodeList<Element> gs = getGroups();
        for (int i = 0, len = gs.getLength(); i < len; i++) {
            Element e = gs.getItem(i).getFirstChild().cast();
            e.getStyle().setProperty("width", tw);
        }
    }

}