From: Johannes Dahlström Date: Wed, 16 Jul 2014 08:16:38 +0000 (+0300) Subject: Client-side Grid header/footer rewrite: add default header support (#13334) X-Git-Tag: 7.4.0.beta1~9^2~189^2~54^2~45 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=cfad6d54de44fead836ec9355bbc77c06fa26a40;p=vaadin-framework.git Client-side Grid header/footer rewrite: add default header support (#13334) Currently supported: * Adding and removal of header and footer rows * Header is single-row by default * Footer is zero-row by default * Text captions * Showing and hiding the whole header or footer * Default header rows for sorting UI TODO: * Column spanning * HTML content * Widget content * Component content * Server side API * Shared state handling Change-Id: I3d6a2b75fad87780f83238ab792bbbcfe99a48fd --- diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index 403b0d1e3f..ff75417c12 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -45,6 +45,7 @@ import com.vaadin.client.Util; import com.vaadin.client.data.DataChangeHandler; import com.vaadin.client.data.DataSource; import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.grid.GridHeader.HeaderRow; import com.vaadin.client.ui.grid.renderers.ComplexRenderer; import com.vaadin.client.ui.grid.renderers.TextRenderer; import com.vaadin.client.ui.grid.renderers.WidgetRenderer; @@ -616,6 +617,13 @@ public class Grid extends Composite implements protected abstract SelectionModel createModel(); } + class SortableColumnHeaderRenderer extends + AbstractGridColumn.SortableColumnHeaderRenderer { + SortableColumnHeaderRenderer(Renderer cellRenderer) { + super(Grid.this, cellRenderer); + } + } + /** * Base class for grid columns internally used by the Grid. The user should * use {@link GridColumn} when creating new columns. @@ -633,9 +641,11 @@ public class Grid extends Composite implements * * FIXME Currently assumes multisorting */ - private class SortableColumnHeaderRenderer extends + static class SortableColumnHeaderRenderer extends ComplexRenderer { + private Grid grid; + /** * Delay before a long tap action is triggered. Number in * milliseconds. @@ -658,7 +668,8 @@ public class Grid extends Composite implements @Override public void run() { - SortOrder sortingOrder = getSortingOrder(); + SortOrder sortingOrder = getSortingOrder(grid + .getColumnFromVisibleIndex(cell.getColumn())); if (sortingOrder == null) { /* * No previous sorting, sort Ascending @@ -697,7 +708,9 @@ public class Grid extends Composite implements * @param cellRenderer * The actual cell renderer */ - public SortableColumnHeaderRenderer(Renderer cellRenderer) { + public SortableColumnHeaderRenderer(Grid grid, + Renderer cellRenderer) { + this.grid = grid; this.cellRenderer = cellRenderer; } @@ -715,9 +728,11 @@ public class Grid extends Composite implements * this is fixed. */ if (grid != null) { - SortOrder sortingOrder = getSortingOrder(); + GridColumn column = grid + .getColumnFromVisibleIndex(cell.getColumn()); + SortOrder sortingOrder = getSortingOrder(column); Element cellElement = cell.getElement(); - if (grid.getColumn(cell.getColumn()).isSortable()) { + if (column.isSortable()) { if (sortingOrder != null) { if (SortDirection.ASCENDING == sortingOrder .getDirection()) { @@ -836,6 +851,18 @@ public class Grid extends Composite implements } + protected void removeFromRow(HeaderRow row) { + row.setRenderer(new Renderer() { + @Override + public void render(FlyweightCell cell, String data) { + cleanup(cell); + } + }); + grid.refreshHeader(); + row.setRenderer(cellRenderer); + grid.refreshHeader(); + } + /** * Sorts the column in a direction */ @@ -844,13 +871,14 @@ public class Grid extends Composite implements TableCellElement th = TableCellElement.as(cell.getElement()); // Apply primary sorting on clicked column - GridColumn columnInstance = getColumnInstance(); + GridColumn columnInstance = grid + .getColumnFromVisibleIndex(cell.getColumn()); Sort sorting = Sort.by(columnInstance, direction); // Re-apply old sorting to the sort order if (multisort) { for (SortOrder order : grid.getSortOrder()) { - if (order.getColumn() != AbstractGridColumn.this) { + if (order.getColumn() != columnInstance) { sorting = sorting.then(order.getColumn(), order.getDirection()); } @@ -861,24 +889,12 @@ public class Grid extends Composite implements grid.sort(sorting); } - /** - * Resolves a GridColumn out of a AbstractGridColumn - */ - private GridColumn getColumnInstance() { - for (GridColumn column : grid.getColumns()) { - if (column == AbstractGridColumn.this) { - return (GridColumn) column; - } - } - return null; - } - /** * Finds the sorting order for this column */ - private SortOrder getSortingOrder() { + private SortOrder getSortingOrder(GridColumn column) { for (SortOrder order : grid.getSortOrder()) { - if (order.getColumn() == AbstractGridColumn.this) { + if (order.getColumn() == column) { return order; } } @@ -922,8 +938,7 @@ public class Grid extends Composite implements * Renderer for rendering the header cell value into the cell */ @Deprecated - private Renderer headerRenderer = new SortableColumnHeaderRenderer( - new TextRenderer()); + private Renderer headerRenderer = new TextRenderer(); /** * Renderer for rendering the footer cell value into the cell @@ -964,8 +979,7 @@ public class Grid extends Composite implements throw new IllegalArgumentException("Renderer cannot be null."); } - this.headerRenderer = new SortableColumnHeaderRenderer( - headerRenderer); + this.headerRenderer = headerRenderer; this.footerRenderer = footerRenderer; } @@ -1016,7 +1030,7 @@ public class Grid extends Composite implements if (renderer == null) { throw new IllegalArgumentException("Renderer cannot be null."); } - headerRenderer = new SortableColumnHeaderRenderer(headerRenderer); + this.headerRenderer = headerRenderer; if (grid != null) { grid.refreshHeader(); } @@ -1472,7 +1486,8 @@ public class Grid extends Composite implements escalator.getFooter().setEscalatorUpdater(createFooterUpdater()); header.setGrid(this); - header.appendRow(); + HeaderRow defaultRow = header.appendRow(); + header.setDefaultRow(defaultRow); footer.setGrid(this); @@ -2470,13 +2485,14 @@ public class Grid extends Composite implements if (container != null) { cell = container.getCell(e); if (cell != null) { + // FIXME getFromVisibleIndex??? GridColumn gridColumn = columns.get(cell.getColumn()); Renderer renderer; if (container == escalator.getHeader()) { - renderer = gridColumn.getHeaderRenderer(); + renderer = header.getRow(cell.getRow()).getRenderer(); } else if (container == escalator.getFooter()) { - renderer = gridColumn.getFooterRenderer(); + renderer = footer.getRow(cell.getRow()).getRenderer(); } else { renderer = gridColumn.getRenderer(); } diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java index 67f4dc4045..3c7629a60d 100644 --- a/client/src/com/vaadin/client/ui/grid/GridConnector.java +++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java @@ -36,6 +36,7 @@ import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.data.RpcDataSourceConnector.RpcDataSource; import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.grid.GridHeader.HeaderRow; import com.vaadin.client.ui.grid.GridStaticSection.StaticRow; import com.vaadin.client.ui.grid.renderers.AbstractRendererConnector; import com.vaadin.client.ui.grid.selection.AbstractRowHandleSelectionModel; @@ -368,6 +369,10 @@ public class GridConnector extends AbstractComponentConnector { for (CellState cellState : rowState.cells) { row.getCell(i++).setText(cellState.text); } + + if (section instanceof GridHeader && rowState.defaultRow) { + ((GridHeader) section).setDefaultRow((HeaderRow) row); + } } section.setVisible(state.visible); diff --git a/client/src/com/vaadin/client/ui/grid/GridHeader.java b/client/src/com/vaadin/client/ui/grid/GridHeader.java index a2207c49c7..c5b0febeca 100644 --- a/client/src/com/vaadin/client/ui/grid/GridHeader.java +++ b/client/src/com/vaadin/client/ui/grid/GridHeader.java @@ -15,6 +15,8 @@ */ package com.vaadin.client.ui.grid; +import com.vaadin.client.ui.grid.Grid.AbstractGridColumn.SortableColumnHeaderRenderer; + /** * Represents the header section of a Grid. A header consists of a single header * row containing a header cell for each column. Each cell has a simple textual @@ -41,6 +43,16 @@ public class GridHeader extends GridStaticSection { */ public class HeaderRow extends GridStaticSection.StaticRow { + private boolean isDefault = false; + + protected void setDefault(boolean isDefault) { + this.isDefault = isDefault; + } + + public boolean isDefault() { + return isDefault; + } + @Override protected HeaderCell createCell() { return new HeaderCell(); @@ -54,6 +66,67 @@ public class GridHeader extends GridStaticSection { public class HeaderCell extends GridStaticSection.StaticCell { } + private HeaderRow defaultRow; + + @Override + public void removeRow(int index) { + HeaderRow removedRow = getRow(index); + super.removeRow(index); + if (removedRow == defaultRow) { + setDefaultRow(null); + } + } + + /** + * Sets the default row of this header. The default row is a special header + * row providing a user interface for sorting columns. + * + * @param row + * the new default row, or null for no default row + * + * @throws IllegalArgumentException + * this header does not contain the row + */ + public void setDefaultRow(HeaderRow row) { + if (row == defaultRow) { + return; + } + if (row != null && !getRows().contains(row)) { + throw new IllegalArgumentException( + "Cannot set a default row that does not exist in the container"); + } + if (defaultRow != null) { + assert defaultRow.getRenderer() instanceof SortableColumnHeaderRenderer; + + // Eclipse is wrong about this warning - javac does not accept the + // parameterized version + ((Grid.SortableColumnHeaderRenderer) defaultRow.getRenderer()) + .removeFromRow(defaultRow); + + defaultRow.setDefault(false); + } + if (row != null) { + assert !(row.getRenderer() instanceof SortableColumnHeaderRenderer); + + row.setRenderer(getGrid().new SortableColumnHeaderRenderer(row + .getRenderer())); + + row.setDefault(true); + } + defaultRow = row; + refreshGrid(); + } + + /** + * Returns the current default row of this header. The default row is a + * special header row providing a user interface for sorting columns. + * + * @return the default row or null if no default row set + */ + public HeaderRow getDefaultRow() { + return defaultRow; + } + @Override protected HeaderRow createRow() { return new HeaderRow(); diff --git a/client/src/com/vaadin/client/ui/grid/GridStaticSection.java b/client/src/com/vaadin/client/ui/grid/GridStaticSection.java index f455acc92e..4318811ca2 100644 --- a/client/src/com/vaadin/client/ui/grid/GridStaticSection.java +++ b/client/src/com/vaadin/client/ui/grid/GridStaticSection.java @@ -111,6 +111,10 @@ abstract class GridStaticSection> cells.remove(index); } + protected void setRenderer(Renderer renderer) { + this.renderer = renderer; + } + protected Renderer getRenderer() { return renderer; } @@ -227,11 +231,12 @@ abstract class GridStaticSection> * if the row does not exist in this section */ public void removeRow(ROWTYPE row) { - if (!rows.remove(row)) { + try { + removeRow(rows.indexOf(row)); + } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException( "Section does not contain the given row"); } - refreshGrid(); } /** diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java index e4e6dcf4be..67a97c74b7 100644 --- a/server/src/com/vaadin/ui/components/grid/Grid.java +++ b/server/src/com/vaadin/ui/components/grid/Grid.java @@ -225,8 +225,13 @@ public class Grid extends AbstractComponent implements SelectionChangeNotifier { */ public Grid(final Container.Indexed datasource) { - getState().header.rows.add(new RowState()); + RowState headerDefaultRow = new RowState(); + headerDefaultRow.defaultRow = true; + getState().header.rows.add(headerDefaultRow); + + // FIXME By default there shouldn't be any footer row getState().footer.rows.add(new RowState()); + setColumnFootersVisible(false); setContainerDataSource(datasource); diff --git a/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java b/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java index 358c06d089..859e01f089 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java @@ -33,6 +33,8 @@ public class GridStaticSectionState implements Serializable { public static class RowState implements Serializable { public List cells = new ArrayList(); + + public boolean defaultRow = false; } public List rows = new ArrayList(); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java index 2ced9e16d4..1b27350f25 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java @@ -16,12 +16,16 @@ package com.vaadin.tests.components.grid.basicfeatures; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.Arrays; import java.util.List; import org.junit.Test; import com.vaadin.testbench.TestBenchElement; +import com.vaadin.tests.components.grid.GridElement.GridCellElement; public class GridHeaderTest extends GridStaticSectionTest { @@ -126,7 +130,41 @@ public class GridHeaderTest extends GridStaticSectionTest { assertHeaderTexts(0, 0); } + @Test + public void testDefaultRow() throws Exception { + openTestURL(); + + selectMenuPath("Component", "Columns", "Column 0", "Sortable"); + + GridCellElement headerCell = getGridElement().getHeaderCell(0, 0); + + headerCell.click(); + + assertTrue(hasClassName(headerCell, "sort-asc")); + + headerCell.click(); + + assertFalse(hasClassName(headerCell, "sort-asc")); + assertTrue(hasClassName(headerCell, "sort-desc")); + + selectMenuPath("Component", "Header", "Prepend row"); + selectMenuPath("Component", "Header", "Default row", "Top"); + + assertFalse(hasClassName(headerCell, "sort-desc")); + headerCell = getGridElement().getHeaderCell(0, 0); + assertTrue(hasClassName(headerCell, "sort-desc")); + + selectMenuPath("Component", "Header", "Default row", "Unset"); + + assertFalse(hasClassName(headerCell, "sort-desc")); + } + private void assertHeaderCount(int count) { assertEquals("header count", count, getGridElement().getHeaderCount()); } + + private boolean hasClassName(TestBenchElement element, String name) { + return Arrays.asList(element.getAttribute("class").split(" ")) + .contains(name); + } } diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java index 21d65d56ac..e013306dc0 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java @@ -31,6 +31,7 @@ import com.vaadin.client.ui.grid.GridHeader; import com.vaadin.client.ui.grid.GridHeader.HeaderRow; import com.vaadin.client.ui.grid.Renderer; import com.vaadin.client.ui.grid.datasources.ListDataSource; +import com.vaadin.client.ui.grid.datasources.ListSorter; import com.vaadin.client.ui.grid.renderers.DateRenderer; import com.vaadin.client.ui.grid.renderers.HtmlRenderer; import com.vaadin.client.ui.grid.renderers.NumberRenderer; @@ -57,6 +58,7 @@ public class GridBasicClientFeatures extends private final Grid> grid; private final List> data; private final ListDataSource> ds; + private final ListSorter> sorter; /** * Our basic data object @@ -124,6 +126,8 @@ public class GridBasicClientFeatures extends grid.setDataSource(ds); grid.setSelectionMode(SelectionMode.NONE); + sorter = new ListSorter>(grid); + // Create a bunch of grid columns // Data source layout: @@ -247,6 +251,13 @@ public class GridBasicClientFeatures extends !grid.getColumn(index).isVisible()); } }, "Component", "Columns", "Column " + i); + addMenuCommand("Sortable", new ScheduledCommand() { + @Override + public void execute() { + grid.getColumn(index).setSortable( + !grid.getColumn(index).isSortable()); + } + }, "Component", "Columns", "Column " + i); } } @@ -278,6 +289,25 @@ public class GridBasicClientFeatures extends } }, menuPath); + addMenuCommand("Top", new ScheduledCommand() { + @Override + public void execute() { + header.setDefaultRow(header.getRow(0)); + } + }, "Component", "Header", "Default row"); + addMenuCommand("Bottom", new ScheduledCommand() { + @Override + public void execute() { + header.setDefaultRow(header.getRow(header.getRowCount() - 1)); + } + }, "Component", "Header", "Default row"); + addMenuCommand("Unset", new ScheduledCommand() { + @Override + public void execute() { + header.setDefaultRow(null); + } + }, "Component", "Header", "Default row"); + addMenuCommand("Prepend row", new ScheduledCommand() { @Override public void execute() {