aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java238
-rw-r--r--server/src/test/java/com/vaadin/tests/components/grid/GridDetailsTest.java90
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());
+ }
+}