]> source.dussan.org Git - vaadin-framework.git/commitdiff
Client-side Grid header/footer rewrite: add default header support (#13334)
authorJohannes Dahlström <johannesd@vaadin.com>
Wed, 16 Jul 2014 08:16:38 +0000 (11:16 +0300)
committerJohn Ahlroos <john@vaadin.com>
Tue, 29 Jul 2014 06:53:00 +0000 (09:53 +0300)
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

client/src/com/vaadin/client/ui/grid/Grid.java
client/src/com/vaadin/client/ui/grid/GridConnector.java
client/src/com/vaadin/client/ui/grid/GridHeader.java
client/src/com/vaadin/client/ui/grid/GridStaticSection.java
server/src/com/vaadin/ui/components/grid/Grid.java
shared/src/com/vaadin/shared/ui/grid/GridStaticSectionState.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java
uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java

index 403b0d1e3f020b6c09653e2ba0cc5a3e552b31fc..ff75417c12bf98c5366a73a015dba5394a22a3d9 100644 (file)
@@ -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<T> extends Composite implements
         protected abstract <T> SelectionModel<T> createModel();
     }
 
+    class SortableColumnHeaderRenderer extends
+            AbstractGridColumn.SortableColumnHeaderRenderer {
+        SortableColumnHeaderRenderer(Renderer<String> 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<T> extends Composite implements
          * 
          * FIXME Currently assumes multisorting
          */
-        private class SortableColumnHeaderRenderer extends
+        static class SortableColumnHeaderRenderer extends
                 ComplexRenderer<String> {
 
+            private Grid<?> grid;
+
             /**
              * Delay before a long tap action is triggered. Number in
              * milliseconds.
@@ -658,7 +668,8 @@ public class Grid<T> 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<T> extends Composite implements
              * @param cellRenderer
              *            The actual cell renderer
              */
-            public SortableColumnHeaderRenderer(Renderer<String> cellRenderer) {
+            public SortableColumnHeaderRenderer(Grid<?> grid,
+                    Renderer<String> cellRenderer) {
+                this.grid = grid;
                 this.cellRenderer = cellRenderer;
             }
 
@@ -715,9 +728,11 @@ public class Grid<T> 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<T> extends Composite implements
 
             }
 
+            protected void removeFromRow(HeaderRow row) {
+                row.setRenderer(new Renderer<String>() {
+                    @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<T> extends Composite implements
                 TableCellElement th = TableCellElement.as(cell.getElement());
 
                 // Apply primary sorting on clicked column
-                GridColumn<C, T> 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<T> extends Composite implements
                 grid.sort(sorting);
             }
 
-            /**
-             * Resolves a GridColumn out of a AbstractGridColumn
-             */
-            private GridColumn<C, T> getColumnInstance() {
-                for (GridColumn<?, T> column : grid.getColumns()) {
-                    if (column == AbstractGridColumn.this) {
-                        return (GridColumn<C, T>) 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<T> extends Composite implements
          * Renderer for rendering the header cell value into the cell
          */
         @Deprecated
-        private Renderer<String> headerRenderer = new SortableColumnHeaderRenderer(
-                new TextRenderer());
+        private Renderer<String> headerRenderer = new TextRenderer();
 
         /**
          * Renderer for rendering the footer cell value into the cell
@@ -964,8 +979,7 @@ public class Grid<T> 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<T> 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<T> 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<T> extends Composite implements
             if (container != null) {
                 cell = container.getCell(e);
                 if (cell != null) {
+                    // FIXME getFromVisibleIndex???
                     GridColumn<?, T> 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();
                     }
index 67f4dc404557d45cf25c60f2d0e2160d28963a4f..3c7629a60d2b41f5f85b62f64b3a1586d0f0d678 100644 (file)
@@ -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);
index a2207c49c7dbc6dbdde5ced4947a9f5d9e9bbdfa..c5b0febeca970f0cbbf53fc26f71d7a486d33a71 100644 (file)
@@ -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<GridHeader.HeaderRow> {
      */
     public class HeaderRow extends GridStaticSection.StaticRow<HeaderCell> {
 
+        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<GridHeader.HeaderRow> {
     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();
index f455acc92ed76f74c7294536068b0458b8280a1b..4318811ca2d8b7cf88e03fb599f834a3834b2dfb 100644 (file)
@@ -111,6 +111,10 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>>
             cells.remove(index);
         }
 
+        protected void setRenderer(Renderer<String> renderer) {
+            this.renderer = renderer;
+        }
+
         protected Renderer<String> getRenderer() {
             return renderer;
         }
@@ -227,11 +231,12 @@ abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>>
      *             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();
     }
 
     /**
index e4e6dcf4bec798b03199b6100108e5875f3b25ab..67a97c74b7b8930a14077f5bc7d03803f6e3036e 100644 (file)
@@ -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);
index 358c06d089c9c86f720e68301c52689502786cf3..859e01f08953a5fedfcfc24acec1e2e8da8d7276 100644 (file)
@@ -33,6 +33,8 @@ public class GridStaticSectionState implements Serializable {
 
     public static class RowState implements Serializable {
         public List<CellState> cells = new ArrayList<CellState>();
+
+        public boolean defaultRow = false;
     }
 
     public List<RowState> rows = new ArrayList<RowState>();
index 2ced9e16d4c6ffb26be00dd38d4c9ff3f610534f..1b27350f258e40a311355f829d50c99539a32aff 100644 (file)
 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);
+    }
 }
index 21d65d56ac3562d4ea6441b427927020ba490c4d..e013306dc0aae01eb6cb63e6de3119358e40c8df 100644 (file)
@@ -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<List<Data>> grid;
     private final List<List<Data>> data;
     private final ListDataSource<List<Data>> ds;
+    private final ListSorter<List<Data>> sorter;
 
     /**
      * Our basic data object
@@ -124,6 +126,8 @@ public class GridBasicClientFeatures extends
         grid.setDataSource(ds);
         grid.setSelectionMode(SelectionMode.NONE);
 
+        sorter = new ListSorter<List<Data>>(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() {