You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DetailsManagerConnector.java 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.connectors.grid;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. import com.google.gwt.core.client.Scheduler;
  20. import com.google.gwt.user.client.ui.Widget;
  21. import com.vaadin.client.ComponentConnector;
  22. import com.vaadin.client.ConnectorMap;
  23. import com.vaadin.client.LayoutManager;
  24. import com.vaadin.client.ServerConnector;
  25. import com.vaadin.client.data.DataChangeHandler;
  26. import com.vaadin.client.extensions.AbstractExtensionConnector;
  27. import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator;
  28. import com.vaadin.client.widgets.Grid;
  29. import com.vaadin.shared.Registration;
  30. import com.vaadin.shared.ui.Connect;
  31. import com.vaadin.shared.ui.grid.DetailsManagerState;
  32. import com.vaadin.shared.ui.grid.GridState;
  33. import com.vaadin.ui.Grid.DetailsManager;
  34. import elemental.json.JsonObject;
  35. /**
  36. * Connector class for {@link DetailsManager} of the Grid component.
  37. *
  38. * @author Vaadin Ltd
  39. * @since 8.0
  40. */
  41. @Connect(DetailsManager.class)
  42. public class DetailsManagerConnector extends AbstractExtensionConnector {
  43. /* Map for tracking which details are open on which row */
  44. private Map<Integer, String> indexToDetailConnectorId = new HashMap<>();
  45. /* Boolean flag to avoid multiple refreshes */
  46. private boolean refreshing;
  47. /* Registration for data change handler. */
  48. private Registration dataChangeRegistration;
  49. /**
  50. * DataChangeHandler for updating the visibility of detail widgets.
  51. */
  52. private final class DetailsChangeHandler implements DataChangeHandler {
  53. @Override
  54. public void resetDataAndSize(int estimatedNewDataSize) {
  55. // Full clean up
  56. indexToDetailConnectorId.clear();
  57. }
  58. @Override
  59. public void dataUpdated(int firstRowIndex, int numberOfRows) {
  60. for (int i = 0; i < numberOfRows; ++i) {
  61. int index = firstRowIndex + i;
  62. detachIfNeeded(index, getDetailsComponentConnectorId(index));
  63. }
  64. // Deferred opening of new ones.
  65. refreshDetails();
  66. }
  67. /* The remaining methods will do a full refresh for now */
  68. @Override
  69. public void dataRemoved(int firstRowIndex, int numberOfRows) {
  70. refreshDetails();
  71. }
  72. @Override
  73. public void dataAvailable(int firstRowIndex, int numberOfRows) {
  74. refreshDetails();
  75. }
  76. @Override
  77. public void dataAdded(int firstRowIndex, int numberOfRows) {
  78. refreshDetails();
  79. }
  80. }
  81. /**
  82. * Height aware details generator for client-side Grid.
  83. */
  84. private class CustomDetailsGenerator
  85. implements HeightAwareDetailsGenerator {
  86. @Override
  87. public Widget getDetails(int rowIndex) {
  88. String id = getDetailsComponentConnectorId(rowIndex);
  89. if (id == null) {
  90. return null;
  91. }
  92. return getConnector(id).getWidget();
  93. }
  94. @Override
  95. public double getDetailsHeight(int rowIndex) {
  96. // Case of null is handled in the getDetails method and this method
  97. // will not called if it returns null.
  98. String id = getDetailsComponentConnectorId(rowIndex);
  99. ComponentConnector componentConnector = getConnector(id);
  100. getLayoutManager().setNeedsMeasureRecursively(componentConnector);
  101. getLayoutManager().layoutNow();
  102. return getLayoutManager().getOuterHeightDouble(
  103. componentConnector.getWidget().getElement());
  104. }
  105. private ComponentConnector getConnector(String id) {
  106. return (ComponentConnector) ConnectorMap.get(getConnection())
  107. .getConnector(id);
  108. }
  109. }
  110. @Override
  111. protected void extend(ServerConnector target) {
  112. getWidget().setDetailsGenerator(new CustomDetailsGenerator());
  113. dataChangeRegistration = getWidget().getDataSource()
  114. .addDataChangeHandler(new DetailsChangeHandler());
  115. }
  116. private void detachIfNeeded(int rowIndex, String id) {
  117. if (indexToDetailConnectorId.containsKey(rowIndex)) {
  118. if (indexToDetailConnectorId.get(rowIndex).equals(id)) {
  119. return;
  120. }
  121. // New Details component, hide old one
  122. getWidget().setDetailsVisible(rowIndex, false);
  123. indexToDetailConnectorId.remove(rowIndex);
  124. }
  125. }
  126. @Override
  127. public void onUnregister() {
  128. super.onUnregister();
  129. dataChangeRegistration.remove();
  130. dataChangeRegistration = null;
  131. indexToDetailConnectorId.clear();
  132. }
  133. @Override
  134. public GridConnector getParent() {
  135. return (GridConnector) super.getParent();
  136. }
  137. @Override
  138. public DetailsManagerState getState() {
  139. return (DetailsManagerState) super.getState();
  140. }
  141. private Grid<JsonObject> getWidget() {
  142. return getParent().getWidget();
  143. }
  144. /**
  145. * Returns the connector id for a details component.
  146. *
  147. * @param rowIndex
  148. * the row index of details component
  149. * @return connector id; {@code null} if row or id is not found
  150. */
  151. private String getDetailsComponentConnectorId(int rowIndex) {
  152. JsonObject row = getParent().getWidget().getDataSource()
  153. .getRow(rowIndex);
  154. if (row == null || !row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
  155. || row.getString(GridState.JSONKEY_DETAILS_VISIBLE).isEmpty()) {
  156. return null;
  157. }
  158. return row.getString(GridState.JSONKEY_DETAILS_VISIBLE);
  159. }
  160. private LayoutManager getLayoutManager() {
  161. return LayoutManager.get(getConnection());
  162. }
  163. /**
  164. * Schedules a deferred opening for new details components.
  165. */
  166. private void refreshDetails() {
  167. if (refreshing) {
  168. return;
  169. }
  170. refreshing = true;
  171. Scheduler.get().scheduleFinally(this::refreshDetailsVisibility);
  172. }
  173. private void refreshDetailsVisibility() {
  174. for (int i = 0; i < getWidget().getDataSource().size(); ++i) {
  175. String id = getDetailsComponentConnectorId(i);
  176. detachIfNeeded(i, id);
  177. if (id == null) {
  178. continue;
  179. }
  180. indexToDetailConnectorId.put(i, id);
  181. getWidget().setDetailsVisible(i, true);
  182. }
  183. refreshing = false;
  184. }
  185. }