diff options
author | Leif Åstrand <legioth@gmail.com> | 2017-02-02 12:33:57 +0200 |
---|---|---|
committer | Denis <denis@vaadin.com> | 2017-02-02 12:33:57 +0200 |
commit | 700742c85872c018af017f757fde64d564050811 (patch) | |
tree | 601e638e6a46b9636a8d267a685832a222a8d53c | |
parent | 567a43038a64b1acef395cc248bcda31b0a72949 (diff) | |
download | vaadin-framework-700742c85872c018af017f757fde64d564050811.tar.gz vaadin-framework-700742c85872c018af017f757fde64d564050811.zip |
Add helpers for dealing with columns based on its id (#8411)
* Add helpers for dealing with columns based on its id
setColumns(Column...) is removed since it's not possible to re-add a
removed column instance.
Fixes #8361
8 files changed, 506 insertions, 40 deletions
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index daf1cd1c69..bab686ed7f 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.BinaryOperator; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -2175,6 +2176,19 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } /** + * Removes the column with the given column id. + * + * @see #removeColumn(Column) + * @see Column#setId(String) + * + * @param columnId + * the id of the column to remove, not <code>null</code> + */ + public void removeColumn(String columnId) { + removeColumn(getColumnOrThrow(columnId)); + } + + /** * Sets the details component generator. * * @param generator @@ -2235,6 +2249,16 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, return columnIds.get(columnId); } + private Column<T, ?> getColumnOrThrow(String columnId) { + Objects.requireNonNull(columnId, "Column id cannot be null"); + Column<T, ?> column = getColumn(columnId); + if (column == null) { + throw new IllegalStateException( + "There is no column with the id " + columnId); + } + return column; + } + @Override public Iterator<Component> iterator() { Set<Component> componentSet = new LinkedHashSet<>(extensionComponents); @@ -2804,26 +2828,35 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } /** - * Sets the columns and their order for the grid. Columns currently in this - * grid that are not present in columns are removed. Similarly, any new - * column in columns will be added to this grid. + * Sets the columns and their order based on their column ids. Columns + * currently in this grid that are not present in the list of column ids are + * removed. This includes any column that has no id. Similarly, any new + * column in columns will be added to this grid. New columns can only be + * added for a <code>Grid</code> created using {@link Grid#Grid(Class)} or + * {@link #withPropertySet(PropertySet)}. * - * @param columns - * the columns to set + * + * @param columnIds + * the column ids to set + * + * @see Column#setId(String) */ - public void setColumns(Column<T, ?>... columns) { - List<Column<T, ?>> currentColumns = getColumns(); - Set<Column<T, ?>> removeColumns = new HashSet<>(currentColumns); - Set<Column<T, ?>> addColumns = Arrays.stream(columns) - .collect(Collectors.toSet()); + public void setColumns(String... columnIds) { + // Must extract to an explicitly typed variable because otherwise javac + // cannot determine which overload of setColumnOrder to use + Column<T, ?>[] newColumnOrder = Stream.of(columnIds) + .map((Function<String, Column<T, ?>>) id -> { + Column<T, ?> column = getColumn(id); + if (column == null) { + column = addColumn(id); + } + return column; + }).toArray(Column[]::new); + setColumnOrder(newColumnOrder); - removeColumns.removeAll(addColumns); - removeColumns.stream().forEach(this::removeColumn); - - addColumns.removeAll(currentColumns); - addColumns.stream().forEach(c -> addColumn(getIdentifier(c), c)); - - setColumnOrder(columns); + // The columns to remove are now at the end of the column list + getColumns().stream().skip(columnIds.length) + .forEach(this::removeColumn); } private String getIdentifier(Column<T, ?> column) { @@ -2848,16 +2881,21 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, * the columns in the order they should be */ public void setColumnOrder(Column<T, ?>... columns) { + setColumnOrder(Stream.of(columns)); + } + + private void setColumnOrder(Stream<Column<T, ?>> columns) { List<String> columnOrder = new ArrayList<>(); - for (Column<T, ?> column : columns) { + columns.forEach(column -> { if (columnSet.contains(column)) { columnOrder.add(column.getInternalId()); } else { - throw new IllegalArgumentException( + throw new IllegalStateException( "setColumnOrder should not be called " + "with columns that are not in the grid."); } - } + + }); List<String> stateColumnOrder = getState().columnOrder; if (stateColumnOrder.size() != columnOrder.size()) { @@ -2870,6 +2908,20 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } /** + * Sets a new column order for the grid based on their column ids. All + * columns which are not ordered here will remain in the order they were + * before as the last columns of grid. + * + * @param columnIds + * the column ids in the order they should be + * + * @see Column#setId(String) + */ + public void setColumnOrder(String... columnIds) { + setColumnOrder(Stream.of(columnIds).map(this::getColumnOrThrow)); + } + + /** * Returns the selection model for this grid. * * @return the selection model, not null @@ -3029,7 +3081,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } /** - * Sort this Grid in user-specified {@link QuerySortOrder} by a column. + * Sort this Grid in user-specified direction by a column. * * @param column * a column to sort against @@ -3043,6 +3095,32 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } /** + * Sort this Grid in ascending order by a specified column defined by id. + * + * @param columnId + * the id of the column to sort against + * + * @see Column#setId(String) + */ + public void sort(String columnId) { + sort(columnId, SortDirection.ASCENDING); + } + + /** + * Sort this Grid in a user-specified direction by a column defined by id. + * + * @param columnId + * the id of the column to sort against + * @param direction + * a sort order value (ascending/descending) + * + * @see Column#setId(String) + */ + public void sort(String columnId, SortDirection direction) { + sort(getColumnOrThrow(columnId), direction); + } + + /** * Clear the current sort order, and re-sort the grid. */ public void clearSortOrder() { diff --git a/server/src/main/java/com/vaadin/ui/components/grid/Footer.java b/server/src/main/java/com/vaadin/ui/components/grid/Footer.java index ddc36c1efa..807efd4a5f 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/Footer.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/Footer.java @@ -15,9 +15,13 @@ */ package com.vaadin.ui.components.grid; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; /** * Represents the footer section of a Grid. @@ -112,9 +116,29 @@ public abstract class Footer extends StaticSection<Footer.Row> { */ @Override public FooterCell join(FooterCell... cellsToMerge) { - Set<FooterCell> footerCells = new HashSet<>( - Arrays.asList(cellsToMerge)); - return join(footerCells); + return join(Stream.of(cellsToMerge)); + } + + private FooterCell join(Stream<FooterCell> cellStream) { + return join(cellStream.collect(Collectors.toSet())); + } + + @Override + public FooterCell join(Column<?, ?>... columnsToMerge) { + return join(Stream.of(columnsToMerge).map(this::getCell)); + } + + @Override + public FooterCell join(String... columnIdsToMerge) { + Grid<?> grid = getGrid(); + return join(Stream.of(columnIdsToMerge).map(columnId -> { + Column<?, ?> column = grid.getColumn(columnId); + if (column == null) { + throw new IllegalStateException( + "There is no column with the id " + columnId); + } + return getCell(column); + })); } } diff --git a/server/src/main/java/com/vaadin/ui/components/grid/FooterRow.java b/server/src/main/java/com/vaadin/ui/components/grid/FooterRow.java index 23971c3b36..1234c270e3 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/FooterRow.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/FooterRow.java @@ -82,4 +82,37 @@ public interface FooterRow extends Serializable { * @see com.vaadin.ui.AbstractComponent#setCaption(String) setCaption */ FooterCell join(FooterCell... cellsToMerge); + + /** + * Merges cells corresponding to the given columns in the row. Original + * cells are hidden, and new merged cell is shown instead. The cell has a + * width of all merged cells together, inherits styles of the first merged + * cell but has empty caption. + * + * @param columnsToMerge + * the columns of the cells that should be merged. The cells + * should not be merged to any other cell set. + * @return the remaining visible cell after the merge + * + * @see #join(Set) + * @see com.vaadin.ui.AbstractComponent#setCaption(String) setCaption + */ + FooterCell join(Grid.Column<?, ?>... columnsToMerge); + + /** + * Merges cells corresponding to the given column ids in the row. Original + * cells are hidden, and new merged cell is shown instead. The cell has a + * width of all merged cells together, inherits styles of the first merged + * cell but has empty caption. + * + * @param columnIdsToMerge + * the ids of the columns of the cells that should be merged. The + * cells should not be merged to any other cell set. + * @return the remaining visible cell after the merge + * + * @see #join(Set) + * @see com.vaadin.ui.AbstractComponent#setCaption(String) setCaption + * @see Column#setId(String) + */ + FooterCell join(String... columnIdsToMerge); } diff --git a/server/src/main/java/com/vaadin/ui/components/grid/Header.java b/server/src/main/java/com/vaadin/ui/components/grid/Header.java index 69c23c6818..421d616387 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/Header.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/Header.java @@ -15,12 +15,15 @@ */ package com.vaadin.ui.components.grid; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jsoup.nodes.Element; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; @@ -137,9 +140,29 @@ public abstract class Header extends StaticSection<Header.Row> { */ @Override public HeaderCell join(HeaderCell... cellsToMerge) { - Set<HeaderCell> headerCells = new HashSet<>( - Arrays.asList(cellsToMerge)); - return join(headerCells); + return join(Stream.of(cellsToMerge)); + } + + private HeaderCell join(Stream<HeaderCell> cellStream) { + return join(cellStream.collect(Collectors.toSet())); + } + + @Override + public HeaderCell join(Column<?, ?>... columnsToMerge) { + return join(Stream.of(columnsToMerge).map(this::getCell)); + } + + @Override + public HeaderCell join(String... columnIdsToMerge) { + Grid<?> grid = getGrid(); + return join(Stream.of(columnIdsToMerge).map(columnId -> { + Column<?, ?> column = grid.getColumn(columnId); + if (column == null) { + throw new IllegalStateException( + "There is no column with the id " + columnId); + } + return getCell(column); + })); } @Override diff --git a/server/src/main/java/com/vaadin/ui/components/grid/HeaderRow.java b/server/src/main/java/com/vaadin/ui/components/grid/HeaderRow.java index e07c44ba0e..a25d6f2595 100644 --- a/server/src/main/java/com/vaadin/ui/components/grid/HeaderRow.java +++ b/server/src/main/java/com/vaadin/ui/components/grid/HeaderRow.java @@ -83,4 +83,36 @@ public interface HeaderRow extends Serializable { */ HeaderCell join(HeaderCell... cellsToMerge); + /** + * Merges cells corresponding to the given columns in the row. Original + * cells are hidden, and new merged cell is shown instead. The cell has a + * width of all merged cells together, inherits styles of the first merged + * cell but has empty caption. + * + * @param columnsToMerge + * the columns of the cells that should be merged. The cells + * should not be merged to any other cell set. + * @return the remaining visible cell after the merge + * + * @see #join(Set) + * @see com.vaadin.ui.AbstractComponent#setCaption(String) setCaption + */ + HeaderCell join(Grid.Column<?, ?>... columnsToMerge); + + /** + * Merges cells corresponding to the given column ids in the row. Original + * cells are hidden, and new merged cell is shown instead. The cell has a + * width of all merged cells together, inherits styles of the first merged + * cell but has empty caption. + * + * @param columnIdsToMerge + * the ids of the columns of the cells that should be merged. The + * cells should not be merged to any other cell set. + * @return the remaining visible cell after the merge + * + * @see #join(Set) + * @see com.vaadin.ui.AbstractComponent#setCaption(String) setCaption + * @see Column#setId(String) + */ + HeaderCell join(String... columnIdsToMerge); } diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridNullValueSort.java b/server/src/test/java/com/vaadin/tests/components/grid/GridNullValueSort.java index 57cc19fa1b..c68cb2901c 100644 --- a/server/src/test/java/com/vaadin/tests/components/grid/GridNullValueSort.java +++ b/server/src/test/java/com/vaadin/tests/components/grid/GridNullValueSort.java @@ -1,18 +1,19 @@ package com.vaadin.tests.components.grid; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + import com.vaadin.server.SerializableComparator; import com.vaadin.server.VaadinSession; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.ui.Grid; import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.NumberRenderer; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; public class GridNullValueSort { @@ -49,6 +50,28 @@ public class GridNullValueSort { } @Test + public void testSortByColumnId() { + grid.sort("int"); + performSort(Arrays.asList(2, 1, 3), Arrays.asList(1, 2, 3)); + } + + @Test + public void testSortByColumnIdAndDirection() { + grid.sort("int", SortDirection.DESCENDING); + performSort(Arrays.asList(2, 1, 3), Arrays.asList(3, 2, 1)); + } + + @Test(expected = IllegalStateException.class) + public void testSortByMissingColumnId() { + grid.sort("notHere"); + } + + @Test(expected = IllegalStateException.class) + public void testSortByMissingColumnIdAndDirection() { + grid.sort("notHere", SortDirection.DESCENDING); + } + + @Test public void testNumbers() { grid.sort(grid.getColumn("int"), SortDirection.ASCENDING); performSort(Arrays.asList(1, 2, null, 3, null, null), diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java index fc644761bf..f216593c2f 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java @@ -40,15 +40,21 @@ import elemental.json.JsonObject; public class GridTest { private Grid<String> grid; + private Column<String, String> fooColumn; + private Column<String, Number> lengthColumn; + private Column<String, String> objectColumn; + private Column<String, String> randomColumn; @Before public void setUp() { grid = new Grid<>(); - grid.addColumn(ValueProvider.identity()).setId("foo"); - grid.addColumn(String::length, new NumberRenderer()); - grid.addColumn(string -> new Object()); - grid.addColumn(ValueProvider.identity()).setId("randomColumnId"); + fooColumn = grid.addColumn(ValueProvider.identity()).setId("foo"); + lengthColumn = grid.addColumn(String::length, new NumberRenderer()) + .setId("length"); + objectColumn = grid.addColumn(string -> new Object()); + randomColumn = grid.addColumn(ValueProvider.identity()) + .setId("randomColumnId"); } @Test @@ -337,6 +343,116 @@ public class GridTest { grid.addColumn("name"); } + @Test + public void removeByColumn_readdById() { + Grid<Person> grid = new Grid<>(Person.class); + grid.removeColumn(grid.getColumn("name")); + + grid.addColumn("name"); + + List<Column<Person, ?>> columns = grid.getColumns(); + Assert.assertEquals(2, columns.size()); + Assert.assertEquals("born", columns.get(0).getId()); + Assert.assertEquals("name", columns.get(1).getId()); + } + + @Test + public void removeColumnByColumn() { + grid.removeColumn(fooColumn); + + Assert.assertEquals( + Arrays.asList(lengthColumn, objectColumn, randomColumn), + grid.getColumns()); + } + + @Test + public void removeColumnByColumn_alreadyRemoved() { + grid.removeColumn(fooColumn); + // Questionable that this doesn't throw, but that's a separate ticket... + grid.removeColumn(fooColumn); + + Assert.assertEquals( + Arrays.asList(lengthColumn, objectColumn, randomColumn), + grid.getColumns()); + } + + @Test(expected = IllegalStateException.class) + public void removeColumnById_alreadyRemoved() { + grid.removeColumn("foo"); + grid.removeColumn("foo"); + } + + @Test + public void removeColumnById() { + grid.removeColumn("foo"); + + Assert.assertEquals( + Arrays.asList(lengthColumn, objectColumn, randomColumn), + grid.getColumns()); + } + + @Test + public void setColumns_reorder() { + // Will remove other columns + grid.setColumns("length", "foo"); + + List<Column<String, ?>> columns = grid.getColumns(); + + Assert.assertEquals(2, columns.size()); + Assert.assertEquals("length", columns.get(0).getId()); + Assert.assertEquals("foo", columns.get(1).getId()); + } + + @Test(expected = IllegalStateException.class) + public void setColumns_addColumn_notBeangrid() { + // Not possible to add a column in a grid that cannot add columns based + // on a string + grid.setColumns("notHere"); + } + + @Test + public void setColumns_addColumns_beangrid() { + Grid<Person> grid = new Grid<>(Person.class); + + // Remove so we can add it back + grid.removeColumn("name"); + + grid.setColumns("born", "name"); + + List<Column<Person, ?>> columns = grid.getColumns(); + Assert.assertEquals(2, columns.size()); + Assert.assertEquals("born", columns.get(0).getId()); + Assert.assertEquals("name", columns.get(1).getId()); + } + + @Test + public void setColumnOrder_byColumn() { + grid.setColumnOrder(randomColumn, lengthColumn); + + Assert.assertEquals(Arrays.asList(randomColumn, lengthColumn, fooColumn, + objectColumn), grid.getColumns()); + } + + @Test(expected = IllegalStateException.class) + public void setColumnOrder_byColumn_removedColumn() { + grid.removeColumn(randomColumn); + grid.setColumnOrder(randomColumn, lengthColumn); + } + + @Test + public void setColumnOrder_byString() { + grid.setColumnOrder("randomColumnId", "length"); + + Assert.assertEquals(Arrays.asList(randomColumn, lengthColumn, fooColumn, + objectColumn), grid.getColumns()); + } + + @Test(expected = IllegalStateException.class) + public void setColumnOrder_byString_removedColumn() { + grid.removeColumn("randomColumnId"); + grid.setColumnOrder("randomColumnId", "length"); + } + private static <T> JsonObject getRowData(Grid<T> grid, T row) { JsonObject json = Json.createObject(); if (grid.getColumns().isEmpty()) { diff --git a/server/src/test/java/com/vaadin/ui/components/grid/StaticSectionTest.java b/server/src/test/java/com/vaadin/ui/components/grid/StaticSectionTest.java new file mode 100644 index 0000000000..1223cfaef8 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/components/grid/StaticSectionTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui.components.grid; + +import java.util.Arrays; +import java.util.HashSet; + +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.data.ValueProvider; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; + +public class StaticSectionTest { + private final Grid<String> grid = new Grid<>(); + private final Column<String, String> col1 = grid + .addColumn(ValueProvider.identity()).setId("col1"); + private final Column<String, String> col2 = grid + .addColumn(ValueProvider.identity()).setId("col2"); + private final Column<String, String> col3 = grid + .addColumn(ValueProvider.identity()).setId("col3"); + + private HeaderRow headerRow; + private FooterRow footerRow; + + @Before + public void setUp() { + footerRow = grid.addFooterRowAt(0); + headerRow = grid.addHeaderRowAt(0); + } + + @Test + public void joinFootersBySet() { + footerRow.join(new HashSet<>(Arrays.asList(footerRow.getCell(col1), + footerRow.getCell(col2)))); + + assertFootersJoined(); + } + + @Test + public void joinFootersByCells() { + footerRow.join(footerRow.getCell(col1), footerRow.getCell(col2)); + + assertFootersJoined(); + } + + @Test + public void joinFootersByColumns() { + footerRow.join(col1, col2); + + assertFootersJoined(); + } + + @Test + public void joinFootersByIds() { + footerRow.join("col1", "col2"); + + assertFootersJoined(); + } + + @Test + public void joinHeadersBySet() { + headerRow.join(new HashSet<>(Arrays.asList(headerRow.getCell(col1), + headerRow.getCell(col2)))); + + assertHeadersJoined(); + } + + @Test + public void joinHeadersByCells() { + headerRow.join(headerRow.getCell(col1), headerRow.getCell(col2)); + + assertHeadersJoined(); + } + + @Test + public void joinHeadersByColumns() { + headerRow.join(col1, col2); + + assertHeadersJoined(); + } + + @Test + public void joinHeadersByIds() { + headerRow.join("col1", "col2"); + + assertHeadersJoined(); + } + + @Test(expected = IllegalStateException.class) + public void joinHeadersByMissingIds() { + headerRow.join("col1", "col4"); + } + + @Test(expected = IllegalStateException.class) + public void joinFootersByMissingIds() { + headerRow.join("col1", "col4"); + } + + private void assertFootersJoined() { + assertJoined((StaticSection.StaticRow<?>) footerRow); + } + + private void assertHeadersJoined() { + assertJoined((StaticSection.StaticRow<?>) headerRow); + } + + private static void assertJoined(StaticSection.StaticRow<?> staticRow) { + // There doesn't seem to be any direct API for checking what's joined, + // so verifying the merging by checking the declarative output + Element container = new Element(Tag.valueOf("container"), ""); + + staticRow.writeDesign(container, null); + + Assert.assertEquals(2, container.children().size()); + Assert.assertEquals("col1,col2", container.child(0).attr("column-ids")); + Assert.assertEquals("col3", container.child(1).attr("column-ids")); + } + +} |