diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2015-09-29 19:13:36 +0300 |
---|---|---|
committer | Teemu Suo-Anttila <teemusa@vaadin.com> | 2015-10-12 07:18:12 +0000 |
commit | 8a66300779e9dcc949d68ab1a66d445921e0d40b (patch) | |
tree | f08f6b36213e289bcc11595b7ba602cd5b462f5c /server/src/com/vaadin/ui | |
parent | e5634deaa77cdcebc577a1f04d146b960b3b54d1 (diff) | |
download | vaadin-framework-8a66300779e9dcc949d68ab1a66d445921e0d40b.tar.gz vaadin-framework-8a66300779e9dcc949d68ab1a66d445921e0d40b.zip |
Refactor DetailComponentManager to be a static nested class of Grid
While refactoring any special cases are removed. This needs Grid
extensions to have a way for adding and removing components from Grid.
Removing any and all parts of RpcDataProvider work towards having it
separate from Grid and maybe usable for other components as well.
Change-Id: Ia4e25d5f0acaf2085478346b0ff6e23c8334e1b9
Diffstat (limited to 'server/src/com/vaadin/ui')
-rw-r--r-- | server/src/com/vaadin/ui/Grid.java | 304 |
1 files changed, 270 insertions, 34 deletions
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 – 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() { |