]> source.dussan.org Git - vaadin-framework.git/commitdiff
Adds details generator swap support for Grid (#16644)
authorHenrik Paul <henrik@vaadin.com>
Mon, 16 Mar 2015 09:45:22 +0000 (11:45 +0200)
committerHenrik Paul <henrik@vaadin.com>
Wed, 18 Mar 2015 11:58:14 +0000 (13:58 +0200)
Change-Id: I741970a7bcebd27d3aa28d608d767b4b4f063ae8

client/src/com/vaadin/client/connectors/GridConnector.java
client/src/com/vaadin/client/widgets/Grid.java
server/src/com/vaadin/data/RpcDataProviderExtension.java
server/src/com/vaadin/ui/Grid.java
shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java [deleted file]
shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java

index 1787dc5c97b2758ea4ceb4b919d6fdbb3cd53703..e6b9c894836fb0b6e8303ed4762f9774a3e46032 100644 (file)
@@ -77,7 +77,7 @@ import com.vaadin.client.widgets.Grid.HeaderRow;
 import com.vaadin.shared.Connector;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.Connect;
-import com.vaadin.shared.ui.grid.ConnectorIndexChange;
+import com.vaadin.shared.ui.grid.DetailsConnectorChange;
 import com.vaadin.shared.ui.grid.EditorClientRpc;
 import com.vaadin.shared.ui.grid.EditorServerRpc;
 import com.vaadin.shared.ui.grid.GridClientRpc;
@@ -382,7 +382,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements
             }
         }
 
-        public void setDetailsConnectorChanges(Set<ConnectorIndexChange> changes) {
+        public void setDetailsConnectorChanges(
+                Set<DetailsConnectorChange> changes) {
             /*
              * To avoid overwriting connectors while moving them about, we'll
              * take all the affected connectors, first all remove those that are
@@ -390,7 +391,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
              */
 
             /* Remove moved/removed connectors from bookkeeping */
-            for (ConnectorIndexChange change : changes) {
+            for (DetailsConnectorChange change : changes) {
                 Integer oldIndex = change.getOldIndex();
                 Connector removedConnector = indexToDetailsMap.remove(oldIndex);
 
@@ -402,7 +403,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
             }
 
             /* Add moved/added connectors to bookkeeping */
-            for (ConnectorIndexChange change : changes) {
+            for (DetailsConnectorChange change : changes) {
                 Integer newIndex = change.getNewIndex();
                 ComponentConnector connector = (ComponentConnector) change
                         .getConnector();
@@ -456,8 +457,11 @@ public class GridConnector extends AbstractHasComponentsConnector implements
         }
 
         public void responseReceived(int fetchId) {
-            boolean success = pendingFetches.remove(fetchId);
-            assert success : "Received a response with an unidentified fetch id";
+            /* Ignore negative fetchIds (they're pushed, not fetched) */
+            if (fetchId >= 0) {
+                boolean success = pendingFetches.remove(fetchId);
+                assert success : "Received a response with an unidentified fetch id";
+            }
         }
 
         @Override
@@ -607,18 +611,20 @@ public class GridConnector extends AbstractHasComponentsConnector implements
 
             @Override
             public void setDetailsConnectorChanges(
-                    Set<ConnectorIndexChange> connectorChanges, int fetchId) {
+                    Set<DetailsConnectorChange> connectorChanges, int fetchId) {
                 customDetailsGenerator
                         .setDetailsConnectorChanges(connectorChanges);
 
                 // refresh moved/added details rows
-                for (ConnectorIndexChange change : connectorChanges) {
-                    Integer newIndex = change.getNewIndex();
-                    if (newIndex != null) {
-                        int index = newIndex.intValue();
-                        getWidget().setDetailsVisible(index, false);
-                        getWidget().setDetailsVisible(index, true);
+                for (DetailsConnectorChange change : connectorChanges) {
+                    Integer index = change.getNewIndex();
+                    if (index == null) {
+                        index = change.getOldIndex();
                     }
+
+                    int i = index.intValue();
+                    getWidget().setDetailsVisible(i, false);
+                    getWidget().setDetailsVisible(i, true);
                 }
                 detailsConnectorFetcher.responseReceived(fetchId);
             }
index f4aaf798b7b6c336e23fd9afd21931da7957fcaa..8243782c4e7f63415ffde58f62c9e44ab7b87ca7 100644 (file)
@@ -6387,12 +6387,13 @@ public class Grid<T> extends ResizeComposite implements
          * see GridSpacerUpdater.init for implementation details.
          */
 
-        if (visible && !isDetailsVisible(rowIndex)) {
+        boolean isVisible = isDetailsVisible(rowIndex);
+        if (visible && !isVisible) {
             escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT);
             visibleDetails.add(rowIndexInteger);
         }
 
-        else if (!visible && isDetailsVisible(rowIndex)) {
+        else if (!visible && isVisible) {
             escalator.getBody().setSpacer(rowIndex, -1);
             visibleDetails.remove(rowIndexInteger);
         }
index 620933c3791f269cadbba777b2bbfb222c1632fd..97d141cd6e38469c4d1150d007c8714f4230f3af 100644 (file)
@@ -30,6 +30,7 @@ import java.util.logging.Logger;
 
 import com.google.gwt.thirdparty.guava.common.collect.BiMap;
 import com.google.gwt.thirdparty.guava.common.collect.HashBiMap;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.vaadin.data.Container.Indexed;
 import com.vaadin.data.Container.Indexed.ItemAddEvent;
 import com.vaadin.data.Container.Indexed.ItemRemoveEvent;
@@ -1214,4 +1215,10 @@ public class RpcDataProviderExtension extends AbstractExtension {
     public boolean isDetailsVisible(Object itemId) {
         return visibleDetails.contains(itemId);
     }
+
+    public void refreshDetails() {
+        for (Object itemId : ImmutableSet.copyOf(visibleDetails)) {
+            detailComponentManager.refresh(itemId);
+        }
+    }
 }
index da5cedd999ab70556022379c4902248a4b85b441..ec1dd45536f4383dca2775e7d4d1a7d3dc001819 100644 (file)
@@ -38,6 +38,7 @@ import java.util.logging.Logger;
 
 import com.google.gwt.thirdparty.guava.common.collect.BiMap;
 import com.google.gwt.thirdparty.guava.common.collect.HashBiMap;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView;
 import com.vaadin.data.Container;
@@ -77,7 +78,7 @@ import com.vaadin.server.KeyMapper;
 import com.vaadin.server.VaadinSession;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.data.sort.SortDirection;
-import com.vaadin.shared.ui.grid.ConnectorIndexChange;
+import com.vaadin.shared.ui.grid.DetailsConnectorChange;
 import com.vaadin.shared.ui.grid.EditorClientRpc;
 import com.vaadin.shared.ui.grid.EditorServerRpc;
 import com.vaadin.shared.ui.grid.GridClientRpc;
@@ -2840,7 +2841,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
          * {@link Collection#isEmpty() empty}, then this field is consistent
          * with the connector hierarchy.
          */
-        private final Map<Object, Component> visibleDetailsComponents = new HashMap<Object, Component>();
+        private final Map<Object, Component> visibleDetailsComponents = Maps
+                .newHashMap();
 
         /** A lookup map for which row contains which details component. */
         private BiMap<Integer, Component> rowIndexToDetails = HashBiMap
@@ -2863,7 +2865,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
          * could find out the same thing by taking out all the other components
          * and checking whether Grid is their parent or not.
          */
-        private final Set<Component> unattachedComponents = new HashSet<Component>();
+        private final Set<Component> unattachedComponents = Sets.newHashSet();
+
+        /**
+         * Keeps tabs on all the details that did not get a component during
+         * {@link #createDetails(Object, int)}.
+         */
+        private final Map<Object, Integer> emptyDetails = Maps.newHashMap();
 
         /**
          * Creates a details component by the request of the client side, with
@@ -2887,14 +2895,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
             assert itemId != null : "itemId was null";
             Integer newRowIndex = Integer.valueOf(rowIndex);
 
-            assert !visibleDetailsComponents.containsKey(itemId) : "itemId already has a component. Should be destroyed first.";
+            assert !visibleDetailsComponents.containsKey(itemId) : "itemId "
+                    + "already has a component. Should be destroyed first.";
 
             RowReference rowReference = new RowReference(Grid.this);
             rowReference.set(itemId);
 
             Component details = getDetailsGenerator().getDetails(rowReference);
             if (details != null) {
-
                 if (details.getParent() != null) {
                     String generatorName = getDetailsGenerator().getClass()
                             .getName();
@@ -2916,6 +2924,20 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
                 visibleDetailsComponents.put(itemId, details);
                 rowIndexToDetails.put(newRowIndex, details);
                 unattachedComponents.add(details);
+
+                assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks "
+                        + "itemId is empty even though we just created a "
+                        + "component for it (" + itemId + ")";
+            } else {
+                assert !emptyDetails.containsKey(itemId) : "Bookkeeping has "
+                        + "already itemId marked as empty (itemId: " + itemId
+                        + ", old index: " + emptyDetails.get(itemId)
+                        + ", new index: " + newRowIndex + ")";
+                assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping"
+                        + " already had another itemId for this empty index "
+                        + "(index: " + newRowIndex + ", new itemId: " + itemId
+                        + ")";
+                emptyDetails.put(itemId, newRowIndex);
             }
 
             /*
@@ -2934,6 +2956,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
          *            the item id for which to destroy the details component
          */
         public void destroyDetails(Object itemId) {
+            emptyDetails.remove(itemId);
+
             Component removedComponent = visibleDetailsComponents
                     .remove(itemId);
             if (removedComponent == null) {
@@ -2972,8 +2996,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
          * 
          * @return information on how the connectors have changed
          */
-        Set<ConnectorIndexChange> getAndResetConnectorChanges() {
-            Set<ConnectorIndexChange> changes = new HashSet<ConnectorIndexChange>();
+        Set<DetailsConnectorChange> getAndResetConnectorChanges() {
+            Set<DetailsConnectorChange> changes = new HashSet<DetailsConnectorChange>();
 
             // populate diff with added/changed
             for (Entry<Integer, Component> entry : rowIndexToDetails.entrySet()) {
@@ -2996,7 +3020,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
                 }
 
                 if (!SharedUtil.equals(oldIndex, newIndex)) {
-                    changes.add(new ConnectorIndexChange(component, oldIndex,
+                    changes.add(new DetailsConnectorChange(component, oldIndex,
                             newIndex));
                 }
             }
@@ -3008,7 +3032,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
                 Component component = entry.getValue();
                 Integer newIndex = rowIndexToDetails.inverse().get(component);
                 if (newIndex == null) {
-                    changes.add(new ConnectorIndexChange(null, oldIndex, null));
+                    changes.add(new DetailsConnectorChange(null, oldIndex, null));
                 }
             }
 
@@ -3017,6 +3041,21 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
 
             return changes;
         }
+
+        public void refresh(Object itemId) {
+            Component component = visibleDetailsComponents.get(itemId);
+            Integer rowIndex = null;
+            if (component != null) {
+                rowIndex = rowIndexToDetails.inverse().get(component);
+                destroyDetails(itemId);
+            } else {
+                rowIndex = emptyDetails.remove(itemId);
+            }
+
+            assert rowIndex != null : "Given itemId does not map to an existing detail row ("
+                    + itemId + ")";
+            createDetails(itemId, rowIndex.intValue());
+        }
     }
 
     /**
@@ -5374,7 +5413,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
 
         this.detailsGenerator = detailsGenerator;
 
-        getLogger().warning("[[details]] update details on generator swap");
+        datasourceExtension.refreshDetails();
+        getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges(
+                detailComponentManager.getAndResetConnectorChanges(), -1);
     }
 
     /**
diff --git a/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java b/shared/src/com/vaadin/shared/ui/grid/ConnectorIndexChange.java
deleted file mode 100644 (file)
index 16be920..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2000-2014 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.shared.ui.grid;
-
-import java.io.Serializable;
-
-import com.vaadin.shared.Connector;
-
-/**
- * A description of an indexing modification for a connector. This is used by
- * Grid by internal bookkeeping updates.
- * 
- * @since
- * @author Vaadin Ltd
- */
-public class ConnectorIndexChange implements Serializable {
-
-    private Connector connector;
-    private Integer oldIndex;
-    private Integer newIndex;
-
-    /** Create a new connector index change */
-    public ConnectorIndexChange() {
-    }
-
-    /**
-     * Convenience constructor for setting all the fields in one line.
-     * <p>
-     * Calling this constructor will also assert that the state of the pojo is
-     * consistent by internal assumptions.
-     * 
-     * @param connector
-     *            the changed connector
-     * @param oldIndex
-     *            the old index
-     * @param newIndex
-     *            the new index
-     */
-    public ConnectorIndexChange(Connector connector, Integer oldIndex,
-            Integer newIndex) {
-        this.connector = connector;
-        this.oldIndex = oldIndex;
-        this.newIndex = newIndex;
-
-        assert assertStateIsOk();
-    }
-
-    private boolean assertStateIsOk() {
-        assert (connector != null && newIndex != null)
-                || (connector == null && oldIndex != null && newIndex == null) : "connector: "
-                + nullityString(connector)
-                + ", oldIndex: "
-                + nullityString(oldIndex)
-                + ", newIndex: "
-                + nullityString(newIndex);
-        return true;
-    }
-
-    private static String nullityString(Object object) {
-        return object == null ? "null" : "non-null";
-    }
-
-    /**
-     * Gets the old index for the connector.
-     * <p>
-     * If <code>null</code>, the connector is recently added. This means that
-     * {@link #getConnector()} is expected not to return <code>null</code>.
-     * 
-     * @return the old index for the connector
-     */
-    public Integer getOldIndex() {
-        assert assertStateIsOk();
-        return oldIndex;
-    }
-
-    /**
-     * Gets the new index for the connector.
-     * <p>
-     * If <code>null</code>, the connector should be removed. This means that
-     * {@link #getConnector()} is expected to return <code>null</code> as well.
-     * 
-     * @return the new index for the connector
-     */
-    public Integer getNewIndex() {
-        assert assertStateIsOk();
-        return newIndex;
-    }
-
-    /**
-     * Gets the changed connector.
-     * 
-     * @return the changed connector. Might be <code>null</code>
-     */
-    public Connector getConnector() {
-        assert assertStateIsOk();
-        return connector;
-    }
-
-    /**
-     * Sets the changed connector.
-     * 
-     * @param connector
-     *            the changed connector. May be <code>null</code>
-     */
-    public void setConnector(Connector connector) {
-        this.connector = connector;
-    }
-
-    /**
-     * Sets the old index
-     * 
-     * @param oldIndex
-     *            the old index. May be <code>null</code> if a new connector is
-     *            being inserted
-     */
-    public void setOldIndex(Integer oldIndex) {
-        this.oldIndex = oldIndex;
-    }
-
-    /**
-     * Sets the new index
-     * 
-     * @param newIndex
-     *            the new index. May be <code>null</code> if a connector is
-     *            being removed
-     */
-    public void setNewIndex(Integer newIndex) {
-        this.newIndex = newIndex;
-    }
-}
diff --git a/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java b/shared/src/com/vaadin/shared/ui/grid/DetailsConnectorChange.java
new file mode 100644 (file)
index 0000000..40f4541
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2000-2014 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.shared.ui.grid;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.Connector;
+
+/**
+ * A description of an indexing modification for a connector. This is used by
+ * Grid by internal bookkeeping updates.
+ * 
+ * @since
+ * @author Vaadin Ltd
+ */
+public class DetailsConnectorChange implements Serializable {
+
+    private Connector connector;
+    private Integer oldIndex;
+    private Integer newIndex;
+
+    /** Create a new connector index change */
+    public DetailsConnectorChange() {
+    }
+
+    /**
+     * Convenience constructor for setting all the fields in one line.
+     * <p>
+     * Calling this constructor will also assert that the state of the pojo is
+     * consistent by internal assumptions.
+     * 
+     * @param connector
+     *            the changed connector
+     * @param oldIndex
+     *            the old index
+     * @param newIndex
+     *            the new index
+     */
+    public DetailsConnectorChange(Connector connector, Integer oldIndex,
+            Integer newIndex) {
+        this.connector = connector;
+        this.oldIndex = oldIndex;
+        this.newIndex = newIndex;
+
+        assert assertStateIsOk();
+    }
+
+    private boolean assertStateIsOk() {
+        boolean connectorAndNewIndexIsNotNull = connector != null
+                && newIndex != null;
+        boolean connectorAndNewIndexIsNullThenOldIndexIsSet = connector == null
+                && newIndex == null && oldIndex != null;
+
+        assert (connectorAndNewIndexIsNotNull || connectorAndNewIndexIsNullThenOldIndexIsSet) : "connector: "
+                + nullityString(connector)
+                + ", oldIndex: "
+                + nullityString(oldIndex)
+                + ", newIndex: "
+                + nullityString(newIndex);
+        return true;
+    }
+
+    private static String nullityString(Object object) {
+        return object == null ? "null" : "non-null";
+    }
+
+    /**
+     * Gets the old index for the connector.
+     * <p>
+     * If <code>null</code>, the connector is recently added. This means that
+     * {@link #getConnector()} is expected not to return <code>null</code>.
+     * 
+     * @return the old index for the connector
+     */
+    public Integer getOldIndex() {
+        assert assertStateIsOk();
+        return oldIndex;
+    }
+
+    /**
+     * Gets the new index for the connector.
+     * <p>
+     * If <code>null</code>, the connector should be removed. This means that
+     * {@link #getConnector()} is expected to return <code>null</code> as well.
+     * 
+     * @return the new index for the connector
+     */
+    public Integer getNewIndex() {
+        assert assertStateIsOk();
+        return newIndex;
+    }
+
+    /**
+     * Gets the changed connector.
+     * 
+     * @return the changed connector. Might be <code>null</code>
+     */
+    public Connector getConnector() {
+        assert assertStateIsOk();
+        return connector;
+    }
+
+    /**
+     * Sets the changed connector.
+     * 
+     * @param connector
+     *            the changed connector. May be <code>null</code>
+     */
+    public void setConnector(Connector connector) {
+        this.connector = connector;
+    }
+
+    /**
+     * Sets the old index
+     * 
+     * @param oldIndex
+     *            the old index. May be <code>null</code> if a new connector is
+     *            being inserted
+     */
+    public void setOldIndex(Integer oldIndex) {
+        this.oldIndex = oldIndex;
+    }
+
+    /**
+     * Sets the new index
+     * 
+     * @param newIndex
+     *            the new index. May be <code>null</code> if a connector is
+     *            being removed
+     */
+    public void setNewIndex(Integer newIndex) {
+        this.newIndex = newIndex;
+    }
+}
index 672c83ff539b05d53ba0096dc35b4b55fe98a0e5..98e7fac5674379b8e2531c64484633ebdf3d2fe9 100644 (file)
@@ -65,9 +65,10 @@ public interface GridClientRpc extends ClientRpc {
      * @param connectorChanges
      *            the indexing changes of details connectors
      * @param fetchId
-     *            the id of the request for fetching the changes
+     *            the id of the request for fetching the changes. A negative
+     *            number indicates a push (not requested by the client side)
      */
     public void setDetailsConnectorChanges(
-            Set<ConnectorIndexChange> connectorChanges, int fetchId);
+            Set<DetailsConnectorChange> connectorChanges, int fetchId);
 
 }
index 08f0d7d5d22523da7109855f1de2285b780810c1..63fb903f53f6b7e4241ff4ac0e8c7b06a832edb6 100644 (file)
@@ -47,10 +47,12 @@ import com.vaadin.ui.Button;
 import com.vaadin.ui.Button.ClickEvent;
 import com.vaadin.ui.Button.ClickListener;
 import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
 import com.vaadin.ui.Grid;
 import com.vaadin.ui.Grid.CellReference;
 import com.vaadin.ui.Grid.CellStyleGenerator;
 import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Grid.DetailsGenerator;
 import com.vaadin.ui.Grid.FooterCell;
 import com.vaadin.ui.Grid.HeaderCell;
 import com.vaadin.ui.Grid.HeaderRow;
@@ -60,6 +62,7 @@ import com.vaadin.ui.Grid.RowStyleGenerator;
 import com.vaadin.ui.Grid.SelectionMode;
 import com.vaadin.ui.Grid.SelectionModel;
 import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
 import com.vaadin.ui.Panel;
 import com.vaadin.ui.renderers.DateRenderer;
 import com.vaadin.ui.renderers.HtmlRenderer;
@@ -114,6 +117,53 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
 
     private Panel detailsPanel;
 
+    private final DetailsGenerator detailedDetailsGenerator = new DetailsGenerator() {
+        @Override
+        public Component getDetails(final RowReference rowReference) {
+            CssLayout cssLayout = new CssLayout();
+            cssLayout.setHeight("200px");
+            cssLayout.setWidth("100%");
+
+            Item item = rowReference.getItem();
+            for (Object propertyId : item.getItemPropertyIds()) {
+                Property<?> prop = item.getItemProperty(propertyId);
+                String string = prop.getValue().toString();
+                cssLayout.addComponent(new Label(string));
+            }
+
+            final int rowIndex = grid.getContainerDataSource().indexOfId(
+                    rowReference.getItemId());
+            ClickListener clickListener = new ClickListener() {
+                @Override
+                public void buttonClick(ClickEvent event) {
+                    Notification.show("You clicked on the "
+                            + "button in the details for " + "row " + rowIndex);
+                }
+            };
+            cssLayout.addComponent(new Button("Press me", clickListener));
+            return cssLayout;
+        }
+    };
+
+    private final DetailsGenerator watchingDetailsGenerator = new DetailsGenerator() {
+        private int id = 0;
+
+        @Override
+        public Component getDetails(RowReference rowReference) {
+            return new Label("You are watching item id "
+                    + rowReference.getItemId() + " (" + (id++) + ")");
+        }
+    };
+
+    private final DetailsGenerator hierarchicalDetailsGenerator = new DetailsGenerator() {
+        @Override
+        public Component getDetails(RowReference rowReference) {
+            detailsPanel = new Panel();
+            detailsPanel.setContent(new Label("One"));
+            return detailsPanel;
+        }
+    };
+
     @Override
     @SuppressWarnings("unchecked")
     protected Grid constructComponent() {
@@ -1059,40 +1109,32 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
     }
 
     private void createDetailsActions() {
-        createClickAction("custom details generator", "Details",
-                new Command<Grid, Void>() {
-                    @Override
-                    public void execute(Grid c, Void value, Object data) {
-                        grid.setDetailsGenerator(new Grid.DetailsGenerator() {
-                            private int seq = 0;
+        Command<Grid, DetailsGenerator> swapDetailsGenerator = new Command<Grid, DetailsGenerator>() {
+            @Override
+            public void execute(Grid c, DetailsGenerator generator, Object data) {
+                grid.setDetailsGenerator(generator);
+            }
+        };
 
-                            @Override
-                            public Component getDetails(
-                                    RowReference rowReference) {
-                                return new Label("You are watching item id "
-                                        + rowReference.getItemId() + " ("
-                                        + (seq++) + ")");
-                            }
-                        });
-                    }
-                }, null);
-        createClickAction("hierarchy details generator", "Details",
-                new Command<Grid, Void>() {
-                    @Override
-                    public void execute(Grid c, Void value, Object data) {
-                        grid.setDetailsGenerator(new Grid.DetailsGenerator() {
-                            @Override
-                            public Component getDetails(
-                                    RowReference rowReference) {
-                                detailsPanel = new Panel();
-                                detailsPanel.setContent(new Label("One"));
-                                return detailsPanel;
-                            }
-                        });
-                    }
-                }, null);
+        Command<Grid, Boolean> openOrCloseItemId = new Command<Grid, Boolean>() {
+            @Override
+            @SuppressWarnings("boxing")
+            public void execute(Grid g, Boolean visible, Object itemId) {
+                g.setDetailsVisible(itemId, visible);
+            }
+        };
 
-        createClickAction("change hierarchy in generator", "Details",
+        createCategory("Generators", "Details");
+        createClickAction("NULL", "Generators", swapDetailsGenerator,
+                DetailsGenerator.NULL);
+        createClickAction("\"Watching\"", "Generators", swapDetailsGenerator,
+                watchingDetailsGenerator);
+        createClickAction("Detailed", "Generators", swapDetailsGenerator,
+                detailedDetailsGenerator);
+        createClickAction("Hierarchical", "Generators", swapDetailsGenerator,
+                hierarchicalDetailsGenerator);
+
+        createClickAction("- Change Component", "Generators",
                 new Command<Grid, Void>() {
                     @Override
                     public void execute(Grid c, Void value, Object data) {
@@ -1105,7 +1147,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                     }
                 }, null);
 
-        createClickAction("toggle firstItemId", "Details",
+        createClickAction("Toggle firstItemId", "Details",
                 new Command<Grid, Void>() {
                     @Override
                     public void execute(Grid g, Void value, Object data) {
@@ -1117,26 +1159,11 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                     }
                 }, null);
 
-        createBooleanAction("firstItemId", "Details", false,
-                new Command<Grid, Boolean>() {
-                    @Override
-                    @SuppressWarnings("boxing")
-                    public void execute(Grid g, Boolean visible, Object data) {
-                        g.setDetailsVisible(g.getContainerDataSource()
-                                .firstItemId(), visible);
-                    }
-                });
+        createBooleanAction("Open firstItemId", "Details", false,
+                openOrCloseItemId, ds.firstItemId());
 
-        createBooleanAction("lastItemId-5", "Details", false,
-                new Command<Grid, Boolean>() {
-                    @Override
-                    @SuppressWarnings("boxing")
-                    public void execute(Grid g, Boolean visible, Object data) {
-                        Object fifthLastItemId = g.getContainerDataSource()
-                                .getItemIds(ROWS - 6, 1).get(0);
-                        g.setDetailsVisible(fifthLastItemId, visible);
-                    }
-                });
+        createBooleanAction("Open 995", "Details", false, openOrCloseItemId,
+                ds.getIdByIndex(995));
     }
 
     @Override
index e9b5b688d1ec5fc1a781cc65d20478ce1ae0f2be..f3f58b002e3542ef2f93f7a9682a537e13ba2e31 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.tests.components.grid.basicfeatures.server;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -27,7 +28,7 @@ import org.openqa.selenium.By;
 import org.openqa.selenium.NoSuchElementException;
 
 import com.vaadin.testbench.TestBenchElement;
-import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures;
+import com.vaadin.testbench.elements.NotificationElement;
 import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest;
 
 public class GridDetailsServerTest extends GridBasicFeaturesTest {
@@ -37,20 +38,21 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
      * able to scroll that particular details row into view, making tests
      * awkward with two scroll commands back to back.
      */
-    private static final int ALMOST_LAST_ITEM_INDEX = GridBasicFeatures.ROWS - 5;
-    private static final String[] ALMOST_LAST_ITEM_DETAILS = new String[] {
-            "Component", "Details", "lastItemId-5" };
-
-    private static final String[] FIRST_ITEM_DETAILS = new String[] {
-            "Component", "Details", "firstItemId" };
+    private static final int ALMOST_LAST_INDEX = 995;
+    private static final String[] OPEN_ALMOST_LAST_ITEM_DETAILS = new String[] {
+            "Component", "Details", "Open " + ALMOST_LAST_INDEX };
+    private static final String[] OPEN_FIRST_ITEM_DETAILS = new String[] {
+            "Component", "Details", "Open firstItemId" };
     private static final String[] TOGGLE_FIRST_ITEM_DETAILS = new String[] {
-            "Component", "Details", "toggle firstItemId" };
-    private static final String[] CUSTOM_DETAILS_GENERATOR = new String[] {
-            "Component", "Details", "custom details generator" };
-    private static final String[] HIERARCHY_DETAILS_GENERATOR = new String[] {
-            "Component", "Details", "hierarchy details generator" };
+            "Component", "Details", "Toggle firstItemId" };
+    private static final String[] DETAILS_GENERATOR_NULL = new String[] {
+            "Component", "Details", "Generators", "NULL" };
+    private static final String[] DETAILS_GENERATOR_WATCHING = new String[] {
+            "Component", "Details", "Generators", "\"Watching\"" };
+    private static final String[] DETAILS_GENERATOR_HIERARCHICAL = new String[] {
+            "Component", "Details", "Generators", "Hierarchical" };
     private static final String[] CHANGE_HIERARCHY = new String[] {
-            "Component", "Details", "change hierarchy in generator" };
+            "Component", "Details", "Generators", "- Change Component" };
 
     @Before
     public void setUp() {
@@ -65,41 +67,42 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
         } catch (NoSuchElementException ignore) {
             // expected
         }
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         assertNotNull("details should've opened", getGridElement()
                 .getDetails(0));
     }
 
     @Test(expected = NoSuchElementException.class)
     public void closeVisibleDetails() {
-        selectMenuPath(FIRST_ITEM_DETAILS);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
 
         getGridElement().getDetails(0);
     }
 
     @Test
-    public void openDetailsOutsideOfActiveRange() {
+    public void openDetailsOutsideOfActiveRange() throws InterruptedException {
         getGridElement().scroll(10000);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         getGridElement().scroll(0);
+        Thread.sleep(50);
         assertNotNull("details should've been opened", getGridElement()
                 .getDetails(0));
     }
 
     @Test(expected = NoSuchElementException.class)
     public void closeDetailsOutsideOfActiveRange() {
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         getGridElement().scroll(10000);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         getGridElement().scroll(0);
         getGridElement().getDetails(0);
     }
 
     @Test
     public void componentIsVisibleClientSide() {
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
 
         TestBenchElement details = getGridElement().getDetails(0);
         assertNotNull("No widget detected inside details",
@@ -107,11 +110,11 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
     }
 
     @Test
-    public void togglingAVisibleDetailsRowWithSeparateRoundtrips() {
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS); // open
-        selectMenuPath(FIRST_ITEM_DETAILS); // close
-        selectMenuPath(FIRST_ITEM_DETAILS); // open
+    public void openingDetailsTwice() {
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // close
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open
 
         TestBenchElement details = getGridElement().getDetails(0);
         assertNotNull("No widget detected inside details",
@@ -120,7 +123,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
 
     @Test(expected = NoSuchElementException.class)
     public void scrollingDoesNotCreateAFloodOfDetailsRows() {
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
 
         // scroll somewhere to hit uncached rows
         getGridElement().scrollToRow(101);
@@ -133,8 +136,8 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
     public void openingDetailsOutOfView() {
         getGridElement().scrollToRow(500);
 
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
 
         getGridElement().scrollToRow(0);
 
@@ -145,8 +148,8 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
 
     @Test
     public void togglingAVisibleDetailsRowWithOneRoundtrip() {
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS); // open
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS); // open
 
         assertTrue("Unexpected generator content",
                 getGridElement().getDetails(0).getText().endsWith("(0)"));
@@ -156,36 +159,111 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest {
     }
 
     @Test
-    @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/")
     public void almosLastItemIdIsRendered() {
-        selectMenuPath(CUSTOM_DETAILS_GENERATOR);
-        selectMenuPath(ALMOST_LAST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(OPEN_ALMOST_LAST_ITEM_DETAILS);
         scrollGridVerticallyTo(100000);
 
         TestBenchElement details = getGridElement().getDetails(
-                ALMOST_LAST_ITEM_INDEX);
+                ALMOST_LAST_INDEX);
         assertNotNull(details);
         assertTrue("Unexpected details content",
-                details.getText().endsWith(ALMOST_LAST_ITEM_INDEX + " (0)"));
+                details.getText().endsWith(ALMOST_LAST_INDEX + " (0)"));
     }
 
     @Test
     public void hierarchyChangesWorkInDetails() {
-        selectMenuPath(HIERARCHY_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         assertEquals("One", getGridElement().getDetails(0).getText());
         selectMenuPath(CHANGE_HIERARCHY);
         assertEquals("Two", getGridElement().getDetails(0).getText());
     }
 
+    @Ignore("This use case is not currently supported by Grid. If the detail "
+            + "is out of view, the component is detached from the UI and a "
+            + "new instance is generated when scrolled back. Support will "
+            + "maybe be incorporated at a later time")
     @Test
-    @Ignore("This will be patched with https://dev.vaadin.com/review/#/c/7917/")
     public void hierarchyChangesWorkInDetailsWhileOutOfView() {
-        selectMenuPath(HIERARCHY_DETAILS_GENERATOR);
-        selectMenuPath(FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
         scrollGridVerticallyTo(10000);
         selectMenuPath(CHANGE_HIERARCHY);
         scrollGridVerticallyTo(0);
         assertEquals("Two", getGridElement().getDetails(0).getText());
     }
+
+    @Test
+    public void swappingDetailsGenerators_noDetailsShown() {
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(DETAILS_GENERATOR_NULL);
+        assertFalse("Got some errors", $(NotificationElement.class).exists());
+    }
+
+    @Test
+    public void swappingDetailsGenerators_shownDetails() {
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        assertTrue("Details should be empty at the start", getGridElement()
+                .getDetails(0).getText().isEmpty());
+
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        assertFalse("Details should not be empty after swapping generator",
+                getGridElement().getDetails(0).getText().isEmpty());
+    }
+
+    @Test
+    public void swappingDetailsGenerators_whileDetailsScrolledOut_showNever() {
+        scrollGridVerticallyTo(1000);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        assertFalse("Got some errors", $(NotificationElement.class).exists());
+    }
+
+    @Test
+    public void swappingDetailsGenerators_whileDetailsScrolledOut_showAfter() {
+        scrollGridVerticallyTo(1000);
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        scrollGridVerticallyTo(0);
+
+        assertFalse("Got some errors", $(NotificationElement.class).exists());
+        assertNotNull("Could not find a details", getGridElement()
+                .getDetails(0));
+    }
+
+    @Test
+    public void swappingDetailsGenerators_whileDetailsScrolledOut_showBefore() {
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        scrollGridVerticallyTo(1000);
+
+        assertFalse("Got some errors", $(NotificationElement.class).exists());
+        assertNotNull("Could not find a details", getGridElement()
+                .getDetails(0));
+    }
+
+    @Test
+    public void swappingDetailsGenerators_whileDetailsScrolledOut_showBeforeAndAfter() {
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        scrollGridVerticallyTo(1000);
+        scrollGridVerticallyTo(0);
+
+        assertFalse("Got some errors", $(NotificationElement.class).exists());
+        assertNotNull("Could not find a details", getGridElement()
+                .getDetails(0));
+    }
+
+    @Test
+    public void nullDetailComponentToggling() {
+        selectMenuPath(OPEN_FIRST_ITEM_DETAILS);
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        selectMenuPath(DETAILS_GENERATOR_NULL);
+        assertTrue("Details should be empty with null component",
+                getGridElement().getDetails(0).getText().isEmpty());
+        selectMenuPath(DETAILS_GENERATOR_WATCHING);
+        assertFalse("Details should be not empty with details component",
+                getGridElement().getDetails(0).getText().isEmpty());
+    }
 }