summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/src/com/vaadin/data/DataGenerator.java4
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java289
-rw-r--r--server/src/com/vaadin/ui/Grid.java304
3 files changed, 272 insertions, 325 deletions
diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java
index 5e301d8151..fd64a389b8 100644
--- a/server/src/com/vaadin/data/DataGenerator.java
+++ b/server/src/com/vaadin/data/DataGenerator.java
@@ -47,11 +47,11 @@ public interface DataGenerator extends Serializable {
/**
* Informs the DataGenerator that an item id has been dropped and is no
- * longer needed.
+ * longer needed. This method should clean up any unneeded stored data
+ * related to the item.
*
* @param itemId
* removed item id
*/
public void destroyData(Object itemId);
-
}
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index 45caf01587..293940aec7 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -26,9 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
-import com.google.gwt.thirdparty.guava.common.collect.Maps;
-import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.Indexed.ItemAddEvent;
import com.vaadin.data.Container.Indexed.ItemRemoveEvent;
@@ -43,14 +40,10 @@ import com.vaadin.server.ClientConnector;
import com.vaadin.server.KeyMapper;
import com.vaadin.shared.data.DataProviderRpc;
import com.vaadin.shared.data.DataRequestRpc;
-import com.vaadin.shared.ui.grid.GridClientRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.Range;
-import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column;
-import com.vaadin.ui.Grid.DetailsGenerator;
-import com.vaadin.ui.Grid.RowReference;
import elemental.json.Json;
import elemental.json.JsonArray;
@@ -220,167 +213,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
}
- /**
- * A class that makes detail component related internal communication
- * possible between {@link RpcDataProviderExtension} and grid.
- *
- * @since 7.5.0
- * @author Vaadin Ltd
- */
- // TODO this should probably be a static nested class
- public final class DetailComponentManager implements DataGenerator {
- /**
- * This map represents all the components that have been requested for
- * each item id.
- * <p>
- * Normally this map is consistent with what is displayed in the
- * component hierarchy (and thus the DOM). The only time this map is out
- * of sync with the DOM is between the any calls to
- * {@link #createDetails(Object)} or {@link #destroyDetails(Object)},
- * and {@link GridClientRpc#setDetailsConnectorChanges(Set)}.
- * <p>
- * This is easily checked: if {@link #unattachedComponents} is
- * {@link Collection#isEmpty() empty}, then this field is consistent
- * with the connector hierarchy.
- */
- private final Map<Object, Component> visibleDetailsComponents = Maps
- .newHashMap();
-
- /**
- * Keeps tabs on all the details that did not get a component during
- * {@link #createDetails(Object)}.
- */
- private final Set<Object> emptyDetails = Sets.newHashSet();
-
- private Grid grid;
-
- /**
- * Creates a details component by the request of the client side, with
- * the help of the user-defined {@link DetailsGenerator}.
- * <p>
- * Also keeps internal bookkeeping up to date.
- *
- * @param itemId
- * the item id for which to create the details component.
- * Assumed not <code>null</code> and that a component is not
- * currently present for this item previously
- * @throws IllegalStateException
- * if the current details generator provides a component
- * that was manually attached, or if the same instance has
- * already been provided
- */
- public void createDetails(Object itemId) throws IllegalStateException {
- assert itemId != null : "itemId was null";
-
- if (visibleDetailsComponents.containsKey(itemId)
- || emptyDetails.contains(itemId)) {
- // Don't overwrite existing components
- return;
- }
-
- RowReference rowReference = new RowReference(grid);
- rowReference.set(itemId);
-
- DetailsGenerator detailsGenerator = grid.getDetailsGenerator();
- Component details = detailsGenerator.getDetails(rowReference);
- if (details != null) {
- if (details.getParent() != null) {
- String name = detailsGenerator.getClass().getName();
- throw new IllegalStateException(name
- + " generated a details component that already "
- + "was attached. (itemId: " + itemId
- + ", component: " + details + ")");
- }
-
- visibleDetailsComponents.put(itemId, details);
-
- details.setParent(grid);
- grid.markAsDirty();
-
- assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
- + "itemId is empty even though we just created a "
- + "component for it (" + itemId + ")";
- } else {
- emptyDetails.add(itemId);
- }
-
- }
-
- /**
- * Destroys correctly a details component, by the request of the client
- * side.
- * <p>
- * Also keeps internal bookkeeping up to date.
- *
- * @param itemId
- * 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) {
- return;
- }
-
- removedComponent.setParent(null);
- grid.markAsDirty();
- }
-
- /**
- * Gets all details components that are currently attached to the grid.
- * <p>
- * Used internally by the Grid object.
- *
- * @return all details components that are currently attached to the
- * grid
- */
- public Collection<Component> getComponents() {
- Set<Component> components = new HashSet<Component>(
- visibleDetailsComponents.values());
- return components;
- }
-
- public void refresh(Object itemId) {
- destroyDetails(itemId);
- createDetails(itemId);
- }
-
- void setGrid(Grid grid) {
- if (this.grid != null) {
- throw new IllegalStateException("Grid may injected only once.");
- }
- this.grid = grid;
- }
-
- /**
- * {@inheritDoc}
- *
- * @since 7.6
- */
- @Override
- public void generateData(Object itemId, Item item, JsonObject rowData) {
- if (visibleDetails.contains(itemId)) {
- // Double check to be sure details component exists.
- detailComponentManager.createDetails(itemId);
- Component detailsComponent = visibleDetailsComponents
- .get(itemId);
- rowData.put(
- GridState.JSONKEY_DETAILS_VISIBLE,
- (detailsComponent != null ? detailsComponent
- .getConnectorId() : ""));
- }
- }
-
- @Override
- public void destroyData(Object itemId) {
- if (visibleDetails.contains(itemId)) {
- destroyDetails(itemId);
- }
- }
- }
-
private final Indexed container;
private DataProviderRpc rpc;
@@ -404,49 +236,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
else {
-
- /*
- * Clear everything we have in view, and let the client
- * re-request for whatever it needs.
- *
- * Why this shortcut? Well, since anything could've happened, we
- * don't know what has happened. There are a lot of use-cases we
- * can cover at once with this carte blanche operation:
- *
- * 1) Grid is scrolled somewhere in the middle and all the
- * rows-inview are removed. We need a new pageful.
- *
- * 2) Grid is scrolled somewhere in the middle and none of the
- * visible rows are removed. We need no new rows.
- *
- * 3) Grid is scrolled all the way to the bottom, and the last
- * rows are being removed. Grid needs to scroll up and request
- * for more rows at the top.
- *
- * 4) Grid is scrolled pretty much to the bottom, and the last
- * rows are being removed. Grid needs to be aware that some
- * scrolling is needed, but not to compensate for all the
- * removed rows. And it also needs to request for some more rows
- * to the top.
- *
- * 5) Some ranges of rows are removed from view. We need to
- * collapse the gaps with existing rows and load the missing
- * rows.
- *
- * 6) The ultimate use case! Grid has 1.5 pages of rows and
- * scrolled a bit down. One page of rows is removed. We need to
- * make sure that new rows are loaded, but not all old slots are
- * occupied, since the page can't be filled with new row data.
- * It also needs to be scrolled to the top.
- *
- * So, it's easier (and safer) to do the simple thing instead of
- * taking all the corner cases into account.
- */
-
- for (Object itemId : visibleDetails) {
- detailComponentManager.destroyDetails(itemId);
- }
-
/* Mark as dirty to push changes in beforeClientResponse */
bareItemSetTriggeredSizeChange = true;
markAsDirty();
@@ -469,15 +258,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
/** Size possibly changed with a bare ItemSetChangeEvent */
private boolean bareItemSetTriggeredSizeChange = false;
- /**
- * This map represents all the details that are user-defined as visible.
- * This does not reflect the status in the DOM.
- */
- // TODO this should probably be inside DetailComponentManager
- private final Set<Object> visibleDetails = new HashSet<Object>();
-
- private final DetailComponentManager detailComponentManager = new DetailComponentManager();
-
private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
@@ -515,7 +295,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
addDataGenerator(activeItemHandler);
- addDataGenerator(detailComponentManager);
}
/**
@@ -612,7 +391,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
* the key mapper for columns
*/
public void extend(Grid component) {
- detailComponentManager.setGrid(component);
super.extend(component);
}
@@ -809,71 +587,4 @@ public class RpcDataProviderExtension extends AbstractExtension {
protected Grid getGrid() {
return (Grid) getParent();
}
-
- /**
- * Marks a row's details to be visible or hidden.
- * <p>
- * If that row is currently in the client side's cache, this information
- * will be sent over to the client.
- *
- * @since 7.5.0
- * @param itemId
- * the id of the item of which to change the details visibility
- * @param visible
- * <code>true</code> to show the details, <code>false</code> to
- * hide
- */
- public void setDetailsVisible(Object itemId, boolean visible) {
- if (visible) {
- visibleDetails.add(itemId);
-
- /*
- * This might be an issue with a huge number of open rows, but as of
- * now this works in most of the cases.
- */
- detailComponentManager.createDetails(itemId);
- } else {
- visibleDetails.remove(itemId);
-
- detailComponentManager.destroyDetails(itemId);
- }
-
- updateRowData(itemId);
- }
-
- /**
- * Checks whether the details for a row is marked as visible.
- *
- * @since 7.5.0
- * @param itemId
- * the id of the item of which to check the visibility
- * @return <code>true</code> iff the detials are visible for the item. This
- * might return <code>true</code> even if the row is not currently
- * visible in the DOM
- */
- public boolean isDetailsVisible(Object itemId) {
- return visibleDetails.contains(itemId);
- }
-
- /**
- * Refreshes all visible detail sections.
- *
- * @since 7.5.0
- */
- public void refreshDetails() {
- for (Object itemId : ImmutableSet.copyOf(visibleDetails)) {
- detailComponentManager.refresh(itemId);
- updateRowData(itemId);
- }
- }
-
- /**
- * Gets the detail component manager for this data provider
- *
- * @since 7.5.0
- * @return the detail component manager
- * */
- public DetailComponentManager getDetailComponentManager() {
- return detailComponentManager;
- }
}
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java
index 6e1520e028..f7832ece40 100644
--- a/server/src/com/vaadin/ui/Grid.java
+++ b/server/src/com/vaadin/ui/Grid.java
@@ -42,6 +42,7 @@ import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+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;
@@ -57,7 +58,6 @@ import com.vaadin.data.DataGenerator;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.RpcDataProviderExtension;
-import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory;
import com.vaadin.data.fieldgroup.FieldGroup;
@@ -281,15 +281,14 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
};
/**
- * This method is called for whenever a new details row needs to be
- * generated.
+ * This method is called for whenever a details row needs to be shown on
+ * the client. Grid removes all of its references to details components
+ * when they are no longer displayed on the client-side and will
+ * re-request once needed again.
* <p>
* <em>Note:</em> If a component gets generated, it may not be manually
- * attached anywhere, nor may it be a reused instance &ndash; each
- * invocation of this method should produce a unique and isolated
- * component instance. Essentially, this should mostly be a
- * self-contained fire-and-forget method, as external references to the
- * generated component might cause unexpected behavior.
+ * attached anywhere. The same details component can not be displayed
+ * for multiple different rows.
*
* @param rowReference
* the reference for the row for which to generate details
@@ -300,6 +299,225 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
}
/**
+ * A class that manages details components by calling
+ * {@link DetailsGenerator} as needed. Details components are attached by
+ * this class when the {@link RpcDataProviderExtension} is sending data to
+ * the client. Details components are detached and forgotten when client
+ * informs that it has dropped the corresponding item.
+ *
+ * @since
+ */
+ private final static class DetailComponentManager extends
+ AbstractGridExtension implements DataGenerator {
+
+ /**
+ * The user-defined details generator.
+ *
+ * @see #setDetailsGenerator(DetailsGenerator)
+ */
+ private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
+
+ /**
+ * This map represents all details that are currently visible on the
+ * client. Details components get destroyed once they scroll out of
+ * view.
+ */
+ private final Map<Object, Component> itemIdToDetailsComponent = Maps
+ .newHashMap();
+
+ /**
+ * Set of item ids that got <code>null</code> from DetailsGenerator when
+ * {@link DetailsGenerator#getDetails(RowReference)} was called.
+ */
+ private final Set<Object> emptyDetails = new HashSet<Object>();
+
+ /**
+ * Set of item IDs for all open details rows. Contains even the ones
+ * that are not currently visible on the client.
+ */
+ private final Set<Object> openDetails = new HashSet<Object>();
+
+ public DetailComponentManager(Grid grid) {
+ super(grid);
+ }
+
+ /**
+ * Creates a details component with the help of the user-defined
+ * {@link DetailsGenerator}.
+ * <p>
+ * This method attaches created components to the parent {@link Grid}.
+ *
+ * @param itemId
+ * the item id for which to create the details component.
+ * @throws IllegalStateException
+ * if the current details generator provides a component
+ * that was manually attached.
+ */
+ private void createDetails(Object itemId) throws IllegalStateException {
+ assert itemId != null : "itemId was null";
+
+ if (itemIdToDetailsComponent.containsKey(itemId)
+ || emptyDetails.contains(itemId)) {
+ // Don't overwrite existing components
+ return;
+ }
+
+ RowReference rowReference = new RowReference(getParentGrid());
+ rowReference.set(itemId);
+
+ DetailsGenerator detailsGenerator = getParentGrid()
+ .getDetailsGenerator();
+ Component details = detailsGenerator.getDetails(rowReference);
+ if (details != null) {
+ if (details.getParent() != null) {
+ String name = detailsGenerator.getClass().getName();
+ throw new IllegalStateException(name
+ + " generated a details component that already "
+ + "was attached. (itemId: " + itemId
+ + ", component: " + details + ")");
+ }
+
+ itemIdToDetailsComponent.put(itemId, details);
+
+ addComponentToGrid(details);
+
+ assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
+ + "itemId is empty even though we just created a "
+ + "component for it (" + itemId + ")";
+ } else {
+ emptyDetails.add(itemId);
+ }
+
+ }
+
+ /**
+ * Destroys a details component correctly.
+ * <p>
+ * This method will detach the component from parent {@link Grid}.
+ *
+ * @param itemId
+ * the item id for which to destroy the details component
+ */
+ private void destroyDetails(Object itemId) {
+ emptyDetails.remove(itemId);
+
+ Component removedComponent = itemIdToDetailsComponent
+ .remove(itemId);
+ if (removedComponent == null) {
+ return;
+ }
+
+ removeComponentFromGrid(removedComponent);
+ }
+
+ /**
+ * Recreates all visible details components.
+ */
+ public void refreshDetails() {
+ Set<Object> visibleItemIds = new HashSet<Object>(
+ itemIdToDetailsComponent.keySet());
+ for (Object itemId : visibleItemIds) {
+ destroyDetails(itemId);
+ createDetails(itemId);
+ refreshRow(itemId);
+ }
+ }
+
+ /**
+ * Sets details visiblity status of given item id.
+ *
+ * @param itemId
+ * item id to set
+ * @param visible
+ * <code>true</code> if visible; <code>false</code> if not
+ */
+ public void setDetailsVisible(Object itemId, boolean visible) {
+ if ((visible && openDetails.contains(itemId))
+ || (!visible && !openDetails.contains(itemId))) {
+ return;
+ }
+
+ if (visible) {
+ openDetails.add(itemId);
+ refreshRow(itemId);
+ } else {
+ openDetails.remove(itemId);
+ destroyDetails(itemId);
+ refreshRow(itemId);
+ }
+ }
+
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ // DetailComponentManager should not send anything if details
+ // generator is the default null version.
+ if (openDetails.contains(itemId)
+ && !detailsGenerator.equals(DetailsGenerator.NULL)) {
+ // Double check to be sure details component exists.
+ createDetails(itemId);
+
+ Component detailsComponent = itemIdToDetailsComponent
+ .get(itemId);
+ rowData.put(
+ GridState.JSONKEY_DETAILS_VISIBLE,
+ (detailsComponent != null ? detailsComponent
+ .getConnectorId() : ""));
+ }
+ }
+
+ @Override
+ public void destroyData(Object itemId) {
+ if (openDetails.contains(itemId)) {
+ destroyDetails(itemId);
+ }
+ }
+
+ /**
+ * Sets a new details generator for row details.
+ * <p>
+ * The currently opened row details will be re-rendered.
+ *
+ * @param detailsGenerator
+ * the details generator to set
+ * @throws IllegalArgumentException
+ * if detailsGenerator is <code>null</code>;
+ */
+ public void setDetailsGenerator(DetailsGenerator detailsGenerator)
+ throws IllegalArgumentException {
+ if (detailsGenerator == null) {
+ throw new IllegalArgumentException(
+ "Details generator may not be null");
+ } else if (detailsGenerator == this.detailsGenerator) {
+ return;
+ }
+
+ this.detailsGenerator = detailsGenerator;
+
+ refreshDetails();
+ }
+
+ /**
+ * Gets the current details generator for row details.
+ *
+ * @return the detailsGenerator the current details generator
+ */
+ public DetailsGenerator getDetailsGenerator() {
+ return detailsGenerator;
+ }
+
+ /**
+ * Checks whether details are visible for the given item.
+ *
+ * @param itemId
+ * the id of the item for which to check details visibility
+ * @return <code>true</code> iff the details are visible
+ */
+ public boolean isDetailsVisible(Object itemId) {
+ return openDetails.contains(itemId);
+ }
+ }
+
+ /**
* Custom field group that allows finding property types before an item has
* been bound.
*/
@@ -4119,6 +4337,30 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
protected void refreshRow(Object itemId) {
getParentGrid().datasourceExtension.updateRowData(itemId);
}
+
+ /**
+ * Informs the parent Grid that this Extension wants to add a child
+ * component to it.
+ *
+ * @since
+ * @param c
+ * component
+ */
+ protected void addComponentToGrid(Component c) {
+ getParentGrid().addComponent(c);
+ }
+
+ /**
+ * Informs the parent Grid that this Extension wants to remove a child
+ * component from it.
+ *
+ * @since
+ * @param c
+ * component
+ */
+ protected void removeComponentFromGrid(Component c) {
+ getParentGrid().removeComponent(c);
+ }
}
/**
@@ -4241,15 +4483,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler();
- /**
- * The user-defined details generator.
- *
- * @see #setDetailsGenerator(DetailsGenerator)
- */
- private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
-
private DetailComponentManager detailComponentManager = null;
+ private Set<Component> extensionComponents = new HashSet<Component>();
+
private static final Method SELECTION_CHANGE_METHOD = ReflectTools
.findMethod(SelectionListener.class, "select", SelectionEvent.class);
@@ -4637,8 +4874,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
datasourceExtension.extend(this);
datasourceExtension.addDataGenerator(new RowDataGenerator());
- detailComponentManager = datasourceExtension
- .getDetailComponentManager();
+ detailComponentManager = new DetailComponentManager(this);
/*
* selectionModel == null when the invocation comes from the
@@ -6093,6 +6329,18 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
return footer.isVisible();
}
+ private void addComponent(Component c) {
+ extensionComponents.add(c);
+ c.setParent(this);
+ markAsDirty();
+ }
+
+ private void removeComponent(Component c) {
+ extensionComponents.remove(c);
+ c.setParent(null);
+ markAsDirty();
+ }
+
@Override
public Iterator<Component> iterator() {
// This is a hash set to avoid adding header/footer components inside
@@ -6123,7 +6371,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
componentList.addAll(getEditorFields());
- componentList.addAll(detailComponentManager.getComponents());
+ componentList.addAll(extensionComponents);
return componentList.iterator();
}
@@ -6818,16 +7066,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
*/
public void setDetailsGenerator(DetailsGenerator detailsGenerator)
throws IllegalArgumentException {
- if (detailsGenerator == null) {
- throw new IllegalArgumentException(
- "Details generator may not be null");
- } else if (detailsGenerator == this.detailsGenerator) {
- return;
- }
-
- this.detailsGenerator = detailsGenerator;
-
- datasourceExtension.refreshDetails();
+ detailComponentManager.setDetailsGenerator(detailsGenerator);
}
/**
@@ -6837,7 +7076,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* @return the detailsGenerator the current details generator
*/
public DetailsGenerator getDetailsGenerator() {
- return detailsGenerator;
+ return detailComponentManager.getDetailsGenerator();
}
/**
@@ -6851,10 +7090,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* to hide them
*/
public void setDetailsVisible(Object itemId, boolean visible) {
- if (DetailsGenerator.NULL.equals(detailsGenerator)) {
- return;
- }
- datasourceExtension.setDetailsVisible(itemId, visible);
+ detailComponentManager.setDetailsVisible(itemId, visible);
}
/**
@@ -6866,7 +7102,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* @return <code>true</code> iff the details are visible
*/
public boolean isDetailsVisible(Object itemId) {
- return datasourceExtension.isDetailsVisible(itemId);
+ return detailComponentManager.isDetailsVisible(itemId);
}
private static SelectionMode getDefaultSelectionMode() {