diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2016-08-24 14:05:02 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-08-29 08:56:56 +0000 |
commit | b4861eda6bbda053277e3fb6858b8a94e8c3243b (patch) | |
tree | 5b120ff177526702a4586eeb4afdd46aef341ea1 /server | |
parent | 010954022ddf6d0bcac3df9a098ce67603d3322b (diff) | |
download | vaadin-framework-b4861eda6bbda053277e3fb6858b8a94e8c3243b.tar.gz vaadin-framework-b4861eda6bbda053277e3fb6858b8a94e8c3243b.zip |
Implement DetailsGenerators for Grid
Change-Id: I09057b990f10bde6cf72a16677e58cb2bc9a7029
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main/java/com/vaadin/ui/Grid.java | 238 | ||||
-rw-r--r-- | server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java | 90 |
2 files changed, 325 insertions, 3 deletions
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 2bd4158023..1a30b0f3fc 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -15,13 +15,17 @@ */ package com.vaadin.ui; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -39,6 +43,7 @@ import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.grid.ColumnState; import com.vaadin.shared.ui.grid.GridConstants.Section; import com.vaadin.shared.ui.grid.GridServerRpc; +import com.vaadin.shared.ui.grid.GridState; import elemental.json.Json; import elemental.json.JsonObject; @@ -52,7 +57,62 @@ import elemental.json.JsonObject; * @param <T> * the grid bean type */ -public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { +public class Grid<T> extends AbstractListing<T, SelectionModel<T>> + implements HasComponents { + + /** + * A callback interface for generating details for a particular row in Grid. + * + * @param <T> + * the grid bean type + */ + @FunctionalInterface + public interface DetailsGenerator<T> + extends Function<T, Component>, Serializable { + } + + /** + * A helper base class for creating extensions for the Grid component. + * + * @param <T> + */ + public static abstract class AbstractGridExtension<T> + extends AbstractListingExtension<T> { + + @Override + public void extend(AbstractListing<T, ?> grid) { + if (!(grid instanceof Grid)) { + throw new IllegalArgumentException( + getClass().getSimpleName() + " can only extend Grid"); + } + super.extend(grid); + } + + /** + * Adds given component to the connector hierarchy of Grid. + * + * @param c + * the component to add + */ + protected void addComponentToGrid(Component c) { + getParent().addExtensionComponent(c); + } + + /** + * Removes given component from the connector hierarchy of Grid. + * + * @param c + * the component to remove + */ + protected void removeComponentFromGrid(Component c) { + getParent().removeExtensionComponent(c); + } + + @Override + public Grid<T> getParent() { + return (Grid<T>) super.getParent(); + } + } private final class GridServerRpcImpl implements GridServerRpc { @Override @@ -121,6 +181,117 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { } /** + * Class for managing visible details rows. + * + * @param <T> + * the grid bean type + */ + public static class DetailsManager<T> extends AbstractGridExtension<T> { + + private Set<T> visibleDetails = new HashSet<>(); + private Map<T, Component> components = new HashMap<>(); + private DetailsGenerator<T> generator; + + /** + * Sets the details component generator. + * + * @param generator + * the generator for details components + */ + public void setDetailsGenerator(DetailsGenerator<T> generator) { + if (this.generator != generator) { + removeAllComponents(); + } + this.generator = generator; + visibleDetails.forEach(this::refresh); + } + + @Override + public void remove() { + removeAllComponents(); + + super.remove(); + } + + private void removeAllComponents() { + // Clean up old components + components.values().forEach(this::removeComponentFromGrid); + components.clear(); + } + + @Override + public void generateData(T data, JsonObject jsonObject) { + if (generator == null || !visibleDetails.contains(data)) { + return; + } + + if (!components.containsKey(data)) { + Component detailsComponent = generator.apply(data); + Objects.requireNonNull(detailsComponent, + "Details generator can't create null components"); + if (detailsComponent.getParent() != null) { + throw new IllegalStateException( + "Details component was already attached"); + } + addComponentToGrid(detailsComponent); + components.put(data, detailsComponent); + } + + jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE, + components.get(data).getConnectorId()); + } + + @Override + public void destroyData(T data) { + // No clean up needed. Components are removed when hiding details + // and/or changing details generator + } + + /** + * Sets the visibility of details component for given item. + * + * @param data + * the item to show details for + * @param visible + * {@code true} if details component should be visible; + * {@code false} if it should be hidden + */ + public void setDetailsVisible(T data, boolean visible) { + boolean refresh = false; + if (!visible) { + refresh = visibleDetails.remove(data); + if (components.containsKey(data)) { + removeComponentFromGrid(components.remove(data)); + } + } else { + refresh = visibleDetails.add(data); + } + + if (refresh) { + refresh(data); + } + } + + /** + * Returns the visibility of details component for given item. + * + * @param data + * the item to show details for + * + * @return {@code true} if details component should be visible; + * {@code false} if it should be hidden + */ + public boolean isDetailsVisible(T data) { + return visibleDetails.contains(data); + } + + @Override + public Grid<T> getParent() { + return super.getParent(); + } + } + + /** * This extension manages the configuration and data communication for a * Column inside of a Grid component. * @@ -145,7 +316,7 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { * @param valueType * the type of value * @param valueProvider - * the function to get values from data objects + * the function to get values from items */ protected Column(String caption, Class<V> valueType, Function<T, V> valueProvider) { @@ -182,7 +353,8 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { } JsonObject obj = jsonObject .getObject(DataCommunicatorConstants.DATA); - // Since we dont' have renderers yet, use a dummy toString for data. + // Since we dont' have renderers yet, use a dummy toString for + // data. obj.put(getState(false).id, valueProvider.apply(data).toString()); } @@ -348,6 +520,8 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { private KeyMapper<Column<T, ?>> columnKeys = new KeyMapper<>(); private Set<Column<T, ?>> columnSet = new HashSet<>(); private List<SortOrder<Column<T, ?>>> sortOrder = new ArrayList<>(); + private DetailsManager<T> detailsManager; + private Set<Component> extensionComponents = new HashSet<>(); /** * Constructor for the {@link Grid} component. @@ -370,6 +544,9 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { }); setDataSource(DataSource.create()); registerRpc(new GridServerRpcImpl()); + detailsManager = new DetailsManager<>(); + addExtension(detailsManager); + addDataGenerator(detailsManager); } /** @@ -414,6 +591,42 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { } /** + * Sets the details component generator. + * + * @param generator + * the generator for details components + */ + public void setDetailsGenerator(DetailsGenerator<T> generator) { + this.detailsManager.setDetailsGenerator(generator); + } + + /** + * Sets the visibility of details component for given item. + * + * @param data + * the item to show details for + * @param visible + * {@code true} if details component should be visible; + * {@code false} if it should be hidden + */ + public void setDetailsVisible(T data, boolean visible) { + detailsManager.setDetailsVisible(data, visible); + } + + /** + * Returns the visibility of details component for given item. + * + * @param data + * the item to show details for + * + * @return {@code true} if details component should be visible; + * {@code false} if it should be hidden + */ + public boolean isDetailsVisible(T data) { + return detailsManager.isDetailsVisible(data); + } + + /** * Gets an unmodifiable collection of all columns currently in this * {@link Grid}. * @@ -422,4 +635,23 @@ public class Grid<T> extends AbstractListing<T, SelectionModel<T>> { public Collection<Column<T, ?>> getColumns() { return Collections.unmodifiableSet(columnSet); } + + @Override + public Iterator<Component> iterator() { + return Collections.unmodifiableSet(extensionComponents).iterator(); + } + + private void addExtensionComponent(Component c) { + if (extensionComponents.add(c)) { + c.setParent(this); + markAsDirty(); + } + } + + private void removeExtensionComponent(Component c) { + if (extensionComponents.remove(c)) { + c.setParent(null); + markAsDirty(); + } + } } diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java new file mode 100644 index 0000000000..7590efccbb --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java @@ -0,0 +1,90 @@ +package com.vaadin.tests.components.grid; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Label; + +public class GridDetailsTest { + + private final class DummyLabel extends Label { + private DummyLabel(String content) { + super(content); + } + + @Override + public String getConnectorId() { + return ""; + } + } + + public static class TestGrid extends Grid<String> { + + /** + * Used to execute data generation + */ + public void runDataGeneration() { + super.getDataCommunicator().beforeClientResponse(true); + } + } + + private TestGrid grid; + private List<String> data; + + @Before + public void setUp() { + grid = new TestGrid(); + // Setup Grid and generate some details + data = new ArrayList<>(Arrays.asList("Foo", "Bar")); + grid.setItems(data); + grid.setDetailsGenerator(s -> new DummyLabel(s)); + + data.forEach(s -> grid.setDetailsVisible(s, true)); + + grid.runDataGeneration(); + } + + @Test + public void testGridComponentIteratorContainsDetailsComponents() { + Iterator<Component> i = grid.iterator(); + + while (i.hasNext()) { + Component c = i.next(); + if (c instanceof Label) { + String value = ((Label) c).getValue(); + Assert.assertTrue( + "Unexpected label in component iterator with value " + + value, + data.remove(value)); + } else { + Assert.fail( + "Iterator contained a component that is not a label."); + } + } + } + + @Test(expected = UnsupportedOperationException.class) + public void testGridComponentIteratorNotModifiable() { + Iterator<Component> iterator = grid.iterator(); + iterator.next(); + // This should fail + iterator.remove(); + } + + @Test + public void testGridComponentIteratorIsEmptyAfterHidingDetails() { + Assert.assertTrue("Component iterator should have components.", + grid.iterator().hasNext()); + data.forEach(s -> grid.setDetailsVisible(s, false)); + Assert.assertFalse("Component iterator should not have components.", + grid.iterator().hasNext()); + } +} |