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.

GridConnector.java 50KB

Migrate 7.7.5 branch patches to v8. (#7969) * Prevent adding several scrollbar handlers (#19189). Change-Id: Ib0cc6c6835aab6d263f153362a328bcf2be7bc5c * Prevent adding several scrollbar handlers (#19189). * Keep expand ratio for last row/column when reducing grid layout size (#20297) Change-Id: Iff53a803596f4fc1eae8e4bfa307b9c1f4df961a * Fixed drag and drop failure when message dragged from email client (#20451) When dragging message form email client on Windows, item.webkitGetAsEntry() might return null creating NPE on the client side. Added additional checks for this situation. Change-Id: I569f7e6d0d7b137f24be53d1fbce384695ae8c73 * Change expected pre-release version number pattern in publish report Change-Id: Icdacecc490d2490ea9e262f5c5736c1dece2a89d * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/main/java/com/vaadin/tests/components/textfield/TextChangeEvents.java * Fixed touch scrolling issue in Surface and WP devices (#18737) Fixed by using pointerevents instead of touchevents when the browser is IE11, or Edge. Also added touch-action: none; css rules into escalator.css to prevent default touch behaviour on IE11 and Edge. Does not affect IE8 to IE10 browsers, behaviour on those will stay the same as before the fix. No new unit tests since we do not have automatic touch testing possibilities yet. Please test manually with Surface: IE11 and Edge, use for example uitest: com.vaadin.tests.components.grid.basics.GridBasicsomponents.grid.basics.GridBasics Change-Id: Iddbf1852e6ffafc855f749d6f4ebb235ed0f5703 * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 # Conflicts: # client/src/main/java/com/vaadin/client/connectors/GridConnector.java # client/src/main/java/com/vaadin/client/widgets/Grid.java # server/src/main/java/com/vaadin/ui/Grid.java # shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java # themes/src/main/themes/VAADIN/themes/base/grid/grid.scss # uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java Change-Id: Ieca56121875198ed559a41c143b28926e2695433 * Fix NPE in case some items don't contain all properties of Grid. This could occur in when parent is a different entity than its children in hierarchical data. Change-Id: Icd53b5b5e5544a3680d0cd99702ab78224b2dc08 # Conflicts: # server/src/main/java/com/vaadin/data/fieldgroup/FieldGroup.java # server/src/main/java/com/vaadin/ui/Grid.java * Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50 # Conflicts: # client/src/main/java/com/vaadin/client/ui/VTextField.java # uitest/src/test/java/com/vaadin/tests/components/textfield/TextChangeEventsTest.java * Add lazy/simple resize mode to Grid (#20108) Change-Id: I47427efc28c350382dba8c1f50fd332a3f4585e4 * Removed V8 VTextField unused import, forgotten @RunLocally. * Don't rely on selenium "sendKeys" behavior. * Revert "Change expected pre-release version number pattern in publish report" This reverts commit 8df27b952dddb691aead6a633c5b3724c98bf343. * Migrate TextField/TextArea patch from 7.7 to master (modern components) Mark TextField/TextArea as busy when a text change event is pending (#20469) Change-Id: I404985ae0be1e7dc65171b610032f8649e700f50
7 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  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.v7.client.connectors;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collection;
  20. import java.util.HashMap;
  21. import java.util.HashSet;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Map.Entry;
  26. import java.util.Set;
  27. import java.util.logging.Logger;
  28. import com.google.gwt.core.client.Scheduler;
  29. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  30. import com.google.gwt.dom.client.Element;
  31. import com.google.gwt.dom.client.EventTarget;
  32. import com.google.gwt.dom.client.NativeEvent;
  33. import com.google.gwt.event.shared.HandlerRegistration;
  34. import com.google.gwt.user.client.DOM;
  35. import com.google.gwt.user.client.Timer;
  36. import com.google.gwt.user.client.ui.Widget;
  37. import com.vaadin.client.ComponentConnector;
  38. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  39. import com.vaadin.client.DeferredWorker;
  40. import com.vaadin.client.MouseEventDetailsBuilder;
  41. import com.vaadin.client.ServerConnector;
  42. import com.vaadin.client.TooltipInfo;
  43. import com.vaadin.client.WidgetUtil;
  44. import com.vaadin.client.communication.StateChangeEvent;
  45. import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
  46. import com.vaadin.client.ui.AbstractComponentConnector;
  47. import com.vaadin.client.ui.AbstractHasComponentsConnector;
  48. import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
  49. import com.vaadin.client.ui.SimpleManagedLayout;
  50. import com.vaadin.client.ui.layout.ElementResizeEvent;
  51. import com.vaadin.client.ui.layout.ElementResizeListener;
  52. import com.vaadin.shared.MouseEventDetails;
  53. import com.vaadin.shared.data.sort.SortDirection;
  54. import com.vaadin.shared.ui.Connect;
  55. import com.vaadin.v7.client.connectors.RpcDataSourceConnector.DetailsListener;
  56. import com.vaadin.v7.client.connectors.RpcDataSourceConnector.RpcDataSource;
  57. import com.vaadin.v7.client.widget.escalator.events.RowHeightChangedEvent;
  58. import com.vaadin.v7.client.widget.escalator.events.RowHeightChangedHandler;
  59. import com.vaadin.v7.client.widget.grid.CellReference;
  60. import com.vaadin.v7.client.widget.grid.CellStyleGenerator;
  61. import com.vaadin.v7.client.widget.grid.EditorHandler;
  62. import com.vaadin.v7.client.widget.grid.EventCellReference;
  63. import com.vaadin.v7.client.widget.grid.HeightAwareDetailsGenerator;
  64. import com.vaadin.v7.client.widget.grid.RowReference;
  65. import com.vaadin.v7.client.widget.grid.RowStyleGenerator;
  66. import com.vaadin.v7.client.widget.grid.events.BodyClickHandler;
  67. import com.vaadin.v7.client.widget.grid.events.BodyDoubleClickHandler;
  68. import com.vaadin.v7.client.widget.grid.events.ColumnReorderEvent;
  69. import com.vaadin.v7.client.widget.grid.events.ColumnReorderHandler;
  70. import com.vaadin.v7.client.widget.grid.events.ColumnResizeEvent;
  71. import com.vaadin.v7.client.widget.grid.events.ColumnResizeHandler;
  72. import com.vaadin.v7.client.widget.grid.events.ColumnVisibilityChangeEvent;
  73. import com.vaadin.v7.client.widget.grid.events.ColumnVisibilityChangeHandler;
  74. import com.vaadin.v7.client.widget.grid.events.GridClickEvent;
  75. import com.vaadin.v7.client.widget.grid.events.GridDoubleClickEvent;
  76. import com.vaadin.v7.client.widget.grid.sort.SortEvent;
  77. import com.vaadin.v7.client.widget.grid.sort.SortHandler;
  78. import com.vaadin.v7.client.widget.grid.sort.SortOrder;
  79. import com.vaadin.v7.client.widgets.Grid;
  80. import com.vaadin.v7.client.widgets.Grid.Column;
  81. import com.vaadin.v7.client.widgets.Grid.FooterCell;
  82. import com.vaadin.v7.client.widgets.Grid.FooterRow;
  83. import com.vaadin.v7.client.widgets.Grid.HeaderCell;
  84. import com.vaadin.v7.client.widgets.Grid.HeaderRow;
  85. import com.vaadin.v7.shared.ui.grid.EditorClientRpc;
  86. import com.vaadin.v7.shared.ui.grid.EditorServerRpc;
  87. import com.vaadin.v7.shared.ui.grid.GridClientRpc;
  88. import com.vaadin.v7.shared.ui.grid.GridColumnState;
  89. import com.vaadin.v7.shared.ui.grid.GridConstants;
  90. import com.vaadin.v7.shared.ui.grid.GridConstants.Section;
  91. import com.vaadin.v7.shared.ui.grid.GridServerRpc;
  92. import com.vaadin.v7.shared.ui.grid.GridState;
  93. import com.vaadin.v7.shared.ui.grid.GridStaticSectionState;
  94. import com.vaadin.v7.shared.ui.grid.GridStaticSectionState.CellState;
  95. import com.vaadin.v7.shared.ui.grid.GridStaticSectionState.RowState;
  96. import com.vaadin.v7.shared.ui.grid.ScrollDestination;
  97. import elemental.json.JsonObject;
  98. import elemental.json.JsonValue;
  99. /**
  100. * Connects the client side {@link Grid} widget with the server side
  101. * <code>Grid</code> component.
  102. * <p>
  103. * The Grid is typed to JSONObject. The structure of the JSONObject is described
  104. * at {@link com.vaadin.shared.data.DataProviderRpc#setRowData(int, List)
  105. * DataProviderRpc.setRowData(int, List)}.
  106. *
  107. * @since 7.4
  108. * @author Vaadin Ltd
  109. */
  110. @Connect(com.vaadin.v7.ui.Grid.class)
  111. public class GridConnector extends AbstractHasComponentsConnector
  112. implements SimpleManagedLayout, DeferredWorker {
  113. private static final class CustomStyleGenerator implements
  114. CellStyleGenerator<JsonObject>, RowStyleGenerator<JsonObject> {
  115. @Override
  116. public String getStyle(CellReference<JsonObject> cellReference) {
  117. JsonObject row = cellReference.getRow();
  118. if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
  119. return null;
  120. }
  121. Column<?, JsonObject> column = cellReference.getColumn();
  122. if (!(column instanceof CustomGridColumn)) {
  123. // Selection checkbox column
  124. return null;
  125. }
  126. CustomGridColumn c = (CustomGridColumn) column;
  127. JsonObject cellStylesObject = row
  128. .getObject(GridState.JSONKEY_CELLSTYLES);
  129. assert cellStylesObject != null;
  130. if (cellStylesObject.hasKey(c.id)) {
  131. return cellStylesObject.getString(c.id);
  132. } else {
  133. return null;
  134. }
  135. }
  136. @Override
  137. public String getStyle(RowReference<JsonObject> rowReference) {
  138. JsonObject row = rowReference.getRow();
  139. if (row.hasKey(GridState.JSONKEY_ROWSTYLE)) {
  140. return row.getString(GridState.JSONKEY_ROWSTYLE);
  141. } else {
  142. return null;
  143. }
  144. }
  145. }
  146. /**
  147. * Custom implementation of the custom grid column using a JSONObject to
  148. * represent the cell value and String as a column type.
  149. */
  150. private class CustomGridColumn extends Grid.Column<Object, JsonObject> {
  151. private final String id;
  152. private AbstractGridRendererConnector<Object> rendererConnector;
  153. private AbstractComponentConnector editorConnector;
  154. private HandlerRegistration errorStateHandler;
  155. public CustomGridColumn(String id,
  156. AbstractGridRendererConnector<Object> rendererConnector) {
  157. super(rendererConnector.getRenderer());
  158. this.rendererConnector = rendererConnector;
  159. this.id = id;
  160. }
  161. /**
  162. * Sets a new renderer for this column object
  163. *
  164. * @param rendererConnector
  165. * a renderer connector object
  166. */
  167. public void setRenderer(
  168. AbstractGridRendererConnector<Object> rendererConnector) {
  169. setRenderer(rendererConnector.getRenderer());
  170. this.rendererConnector = rendererConnector;
  171. }
  172. @Override
  173. public Object getValue(final JsonObject obj) {
  174. final JsonObject rowData = obj.getObject(GridState.JSONKEY_DATA);
  175. if (rowData.hasKey(id)) {
  176. final JsonValue columnValue = rowData.get(id);
  177. return rendererConnector.decode(columnValue);
  178. }
  179. return null;
  180. }
  181. private AbstractComponentConnector getEditorConnector() {
  182. return editorConnector;
  183. }
  184. private void setEditorConnector(
  185. final AbstractComponentConnector editorConnector) {
  186. this.editorConnector = editorConnector;
  187. if (errorStateHandler != null) {
  188. errorStateHandler.removeHandler();
  189. errorStateHandler = null;
  190. }
  191. // Avoid nesting too deep
  192. if (editorConnector == null) {
  193. return;
  194. }
  195. errorStateHandler = editorConnector.addStateChangeHandler(
  196. "errorMessage", new StateChangeHandler() {
  197. @Override
  198. public void onStateChanged(
  199. StateChangeEvent stateChangeEvent) {
  200. String error = editorConnector
  201. .getState().errorMessage;
  202. if (error == null) {
  203. columnToErrorMessage
  204. .remove(CustomGridColumn.this);
  205. } else {
  206. // The error message is formatted as HTML;
  207. // therefore, we use this hack to make the
  208. // string human-readable.
  209. Element e = DOM.createElement("div");
  210. e.setInnerHTML(editorConnector
  211. .getState().errorMessage);
  212. error = getHeaderCaption() + ": "
  213. + e.getInnerText();
  214. columnToErrorMessage.put(CustomGridColumn.this,
  215. error);
  216. }
  217. // Handle Editor RPC before updating error status
  218. Scheduler.get()
  219. .scheduleFinally(new ScheduledCommand() {
  220. @Override
  221. public void execute() {
  222. updateErrorColumns();
  223. }
  224. });
  225. }
  226. public void updateErrorColumns() {
  227. getWidget().getEditor().setEditorError(
  228. getColumnErrors(),
  229. columnToErrorMessage.keySet());
  230. }
  231. });
  232. }
  233. }
  234. /*
  235. * An editor handler using Vaadin RPC to manage the editor state.
  236. */
  237. private class CustomEditorHandler implements EditorHandler<JsonObject> {
  238. private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
  239. private EditorRequest<JsonObject> currentRequest = null;
  240. private boolean serverInitiated = false;
  241. public CustomEditorHandler() {
  242. registerRpc(EditorClientRpc.class, new EditorClientRpc() {
  243. @Override
  244. public void bind(final int rowIndex) {
  245. // call this deferred to avoid issues with editing on init
  246. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  247. @Override
  248. public void execute() {
  249. GridConnector.this.getWidget().editRow(rowIndex);
  250. }
  251. });
  252. }
  253. @Override
  254. public void cancel(int rowIndex) {
  255. serverInitiated = true;
  256. GridConnector.this.getWidget().cancelEditor();
  257. }
  258. @Override
  259. public void confirmBind(final boolean bindSucceeded) {
  260. endRequest(bindSucceeded, null, null);
  261. }
  262. @Override
  263. public void confirmSave(boolean saveSucceeded,
  264. String errorMessage, List<String> errorColumnsIds) {
  265. endRequest(saveSucceeded, errorMessage, errorColumnsIds);
  266. }
  267. });
  268. }
  269. @Override
  270. public void bind(EditorRequest<JsonObject> request) {
  271. startRequest(request);
  272. rpc.bind(request.getRowIndex());
  273. }
  274. @Override
  275. public void save(EditorRequest<JsonObject> request) {
  276. startRequest(request);
  277. rpc.save(request.getRowIndex());
  278. }
  279. @Override
  280. public void cancel(EditorRequest<JsonObject> request) {
  281. if (!handleServerInitiated(request)) {
  282. // No startRequest as we don't get (or need)
  283. // a confirmation from the server
  284. rpc.cancel(request.getRowIndex());
  285. }
  286. }
  287. @Override
  288. public Widget getWidget(Grid.Column<?, JsonObject> column) {
  289. assert column != null;
  290. if (column instanceof CustomGridColumn) {
  291. AbstractComponentConnector c = ((CustomGridColumn) column)
  292. .getEditorConnector();
  293. if (c == null) {
  294. return null;
  295. }
  296. return c.getWidget();
  297. } else {
  298. throw new IllegalStateException("Unexpected column type: "
  299. + column.getClass().getName());
  300. }
  301. }
  302. /**
  303. * Used to handle the case where the editor calls us because it was
  304. * invoked by the server via RPC and not by the client. In that case,
  305. * the request can be simply synchronously completed.
  306. *
  307. * @param request
  308. * the request object
  309. * @return true if the request was originally triggered by the server,
  310. * false otherwise
  311. */
  312. private boolean handleServerInitiated(EditorRequest<?> request) {
  313. assert request != null : "Cannot handle null request";
  314. assert currentRequest == null : "Earlier request not yet finished";
  315. if (serverInitiated) {
  316. serverInitiated = false;
  317. request.success();
  318. return true;
  319. } else {
  320. return false;
  321. }
  322. }
  323. private void startRequest(EditorRequest<JsonObject> request) {
  324. assert currentRequest == null : "Earlier request not yet finished";
  325. currentRequest = request;
  326. }
  327. private void endRequest(boolean succeeded, String errorMessage,
  328. List<String> errorColumnsIds) {
  329. assert currentRequest != null : "Current request was null";
  330. /*
  331. * Clear current request first to ensure the state is valid if
  332. * another request is made in the callback.
  333. */
  334. EditorRequest<JsonObject> request = currentRequest;
  335. currentRequest = null;
  336. if (succeeded) {
  337. request.success();
  338. } else {
  339. Collection<Column<?, JsonObject>> errorColumns;
  340. if (errorColumnsIds != null) {
  341. errorColumns = new ArrayList<Column<?, JsonObject>>();
  342. for (String colId : errorColumnsIds) {
  343. errorColumns.add(columnIdToColumn.get(colId));
  344. }
  345. } else {
  346. errorColumns = null;
  347. }
  348. request.failure(errorMessage, errorColumns);
  349. }
  350. }
  351. }
  352. private class ItemClickHandler
  353. implements BodyClickHandler, BodyDoubleClickHandler {
  354. @Override
  355. public void onClick(GridClickEvent event) {
  356. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  357. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  358. }
  359. }
  360. @Override
  361. public void onDoubleClick(GridDoubleClickEvent event) {
  362. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  363. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  364. }
  365. }
  366. private void fireItemClick(CellReference<?> cell,
  367. NativeEvent mouseEvent) {
  368. String rowKey = getRowKey((JsonObject) cell.getRow());
  369. String columnId = getColumnId(cell.getColumn());
  370. getRpcProxy(GridServerRpc.class).itemClick(rowKey, columnId,
  371. MouseEventDetailsBuilder
  372. .buildMouseEventDetails(mouseEvent));
  373. }
  374. }
  375. private ColumnReorderHandler<JsonObject> columnReorderHandler = new ColumnReorderHandler<JsonObject>() {
  376. @Override
  377. public void onColumnReorder(ColumnReorderEvent<JsonObject> event) {
  378. if (!columnsUpdatedFromState) {
  379. List<Column<?, JsonObject>> columns = getWidget().getColumns();
  380. final List<String> newColumnOrder = new ArrayList<String>();
  381. for (Column<?, JsonObject> column : columns) {
  382. if (column instanceof CustomGridColumn) {
  383. newColumnOrder.add(((CustomGridColumn) column).id);
  384. } // the other case would be the multi selection column
  385. }
  386. getRpcProxy(GridServerRpc.class)
  387. .columnsReordered(newColumnOrder, columnOrder);
  388. columnOrder = newColumnOrder;
  389. getState().columnOrder = newColumnOrder;
  390. }
  391. }
  392. };
  393. private ColumnVisibilityChangeHandler<JsonObject> columnVisibilityChangeHandler = new ColumnVisibilityChangeHandler<JsonObject>() {
  394. @Override
  395. public void onVisibilityChange(
  396. ColumnVisibilityChangeEvent<JsonObject> event) {
  397. if (!columnsUpdatedFromState) {
  398. Column<?, JsonObject> column = event.getColumn();
  399. if (column instanceof CustomGridColumn) {
  400. getRpcProxy(GridServerRpc.class).columnVisibilityChanged(
  401. ((CustomGridColumn) column).id, column.isHidden(),
  402. event.isUserOriginated());
  403. for (GridColumnState state : getState().columns) {
  404. if (state.id.equals(((CustomGridColumn) column).id)) {
  405. state.hidden = event.isHidden();
  406. break;
  407. }
  408. }
  409. } else {
  410. getLogger().warning(
  411. "Visibility changed for a unknown column type in Grid: "
  412. + column + ", type " + column.getClass());
  413. }
  414. }
  415. }
  416. };
  417. private ColumnResizeHandler<JsonObject> columnResizeHandler = new ColumnResizeHandler<JsonObject>() {
  418. @Override
  419. public void onColumnResize(ColumnResizeEvent<JsonObject> event) {
  420. if (!columnsUpdatedFromState) {
  421. Column<?, JsonObject> column = event.getColumn();
  422. if (column instanceof CustomGridColumn) {
  423. getRpcProxy(GridServerRpc.class).columnResized(
  424. ((CustomGridColumn) column).id,
  425. column.getWidthActual());
  426. }
  427. }
  428. }
  429. };
  430. private class CustomDetailsGenerator
  431. implements HeightAwareDetailsGenerator {
  432. private final Map<String, ComponentConnector> idToDetailsMap = new HashMap<String, ComponentConnector>();
  433. private final Map<String, Integer> idToRowIndex = new HashMap<String, Integer>();
  434. private final Map<Element, ScheduledCommand> elementToResizeCommand = new HashMap<Element, Scheduler.ScheduledCommand>();
  435. private final ElementResizeListener detailsRowResizeListener = new ElementResizeListener() {
  436. @Override
  437. public void onElementResize(ElementResizeEvent e) {
  438. if (elementToResizeCommand.containsKey(e.getElement())) {
  439. Scheduler.get().scheduleFinally(
  440. elementToResizeCommand.get(e.getElement()));
  441. }
  442. }
  443. };
  444. /* calculated when the first details row is opened */
  445. private Double spacerCellBorderHeights = null;
  446. @Override
  447. public Widget getDetails(int rowIndex) {
  448. String id = getId(rowIndex);
  449. if (id == null || !hasDetailsOpen(rowIndex)) {
  450. return null;
  451. }
  452. ComponentConnector componentConnector = idToDetailsMap.get(id);
  453. idToRowIndex.put(id, rowIndex);
  454. Widget widget = componentConnector.getWidget();
  455. getLayoutManager().addElementResizeListener(widget.getElement(),
  456. detailsRowResizeListener);
  457. elementToResizeCommand.put(widget.getElement(),
  458. createResizeCommand(rowIndex, widget.getElement()));
  459. return widget;
  460. }
  461. private ScheduledCommand createResizeCommand(final int rowIndex,
  462. final Element element) {
  463. return new ScheduledCommand() {
  464. @Override
  465. public void execute() {
  466. // It should not be possible to get here without calculating
  467. // the spacerCellBorderHeights or without having the details
  468. // row open, nor for this command to be triggered while
  469. // layout is running, but it's safer to check anyway.
  470. if (spacerCellBorderHeights != null
  471. && !getLayoutManager().isLayoutRunning()
  472. && hasDetailsOpen(rowIndex)) {
  473. double height = getLayoutManager().getOuterHeightDouble(
  474. element) + spacerCellBorderHeights;
  475. getWidget().setDetailsHeight(rowIndex, height);
  476. }
  477. }
  478. };
  479. }
  480. private boolean hasDetailsOpen(int rowIndex) {
  481. JsonObject row = getWidget().getDataSource().getRow(rowIndex);
  482. if (row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)) {
  483. String id = row.getString(GridState.JSONKEY_DETAILS_VISIBLE);
  484. return id != null && !id.isEmpty();
  485. }
  486. return false;
  487. }
  488. @Override
  489. public double getDetailsHeight(int rowIndex) {
  490. // Case of null is handled in the getDetails method and this method
  491. // will not called if it returns null.
  492. String id = getId(rowIndex);
  493. ComponentConnector componentConnector = idToDetailsMap.get(id);
  494. getLayoutManager().setNeedsMeasureRecursively(componentConnector);
  495. getLayoutManager().layoutNow();
  496. Element element = componentConnector.getWidget().getElement();
  497. if (spacerCellBorderHeights == null) {
  498. // If theme is changed, new details generator is created from
  499. // scratch, so this value doesn't need to be updated elsewhere.
  500. spacerCellBorderHeights = WidgetUtil
  501. .getBorderTopAndBottomThickness(
  502. element.getParentElement());
  503. }
  504. return getLayoutManager().getOuterHeightDouble(element);
  505. }
  506. /**
  507. * Fetches id from the row object that corresponds with the given
  508. * rowIndex.
  509. *
  510. * @since 7.6.1
  511. * @param rowIndex
  512. * the index of the row for which to fetch the id
  513. * @return id of the row if such id exists, {@code null} otherwise
  514. */
  515. private String getId(int rowIndex) {
  516. JsonObject row = getWidget().getDataSource().getRow(rowIndex);
  517. if (!row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) || row
  518. .getString(GridState.JSONKEY_DETAILS_VISIBLE).isEmpty()) {
  519. return null;
  520. }
  521. return row.getString(GridState.JSONKEY_DETAILS_VISIBLE);
  522. }
  523. public void updateConnectorHierarchy(List<ServerConnector> children) {
  524. Set<String> connectorIds = new HashSet<String>();
  525. for (ServerConnector child : children) {
  526. if (child instanceof ComponentConnector) {
  527. connectorIds.add(child.getConnectorId());
  528. idToDetailsMap.put(child.getConnectorId(),
  529. (ComponentConnector) child);
  530. }
  531. }
  532. Set<String> removedDetails = new HashSet<String>();
  533. for (Entry<String, ComponentConnector> entry : idToDetailsMap
  534. .entrySet()) {
  535. ComponentConnector connector = entry.getValue();
  536. String id = connector.getConnectorId();
  537. if (!connectorIds.contains(id)) {
  538. removedDetails.add(entry.getKey());
  539. if (idToRowIndex.containsKey(id)) {
  540. getWidget().setDetailsVisible(idToRowIndex.get(id),
  541. false);
  542. }
  543. }
  544. }
  545. for (String id : removedDetails) {
  546. Element element = idToDetailsMap.get(id).getWidget()
  547. .getElement();
  548. elementToResizeCommand.remove(element);
  549. getLayoutManager().removeElementResizeListener(element,
  550. detailsRowResizeListener);
  551. idToDetailsMap.remove(id);
  552. idToRowIndex.remove(id);
  553. }
  554. }
  555. }
  556. /**
  557. * Class for handling scrolling issues with open details.
  558. *
  559. * @since 7.5.2
  560. */
  561. private class LazyDetailsScroller implements DeferredWorker {
  562. /* Timer value tested to work in our test cluster with slow IE8s. */
  563. private static final int DISABLE_LAZY_SCROLL_TIMEOUT = 1500;
  564. /*
  565. * Cancels details opening scroll after timeout. Avoids any unexpected
  566. * scrolls via details opening.
  567. */
  568. private Timer disableScroller = new Timer() {
  569. @Override
  570. public void run() {
  571. targetRow = -1;
  572. }
  573. };
  574. private Integer targetRow = -1;
  575. private ScrollDestination destination = null;
  576. public void scrollToRow(Integer row, ScrollDestination dest) {
  577. targetRow = row;
  578. destination = dest;
  579. disableScroller.schedule(DISABLE_LAZY_SCROLL_TIMEOUT);
  580. }
  581. /**
  582. * Inform LazyDetailsScroller that a details row has opened on a row.
  583. *
  584. * @param rowIndex
  585. * index of row with details now open
  586. */
  587. public void detailsOpened(int rowIndex) {
  588. if (targetRow == rowIndex) {
  589. getWidget().scrollToRow(targetRow, destination);
  590. disableScroller.run();
  591. }
  592. }
  593. @Override
  594. public boolean isWorkPending() {
  595. return disableScroller.isRunning();
  596. }
  597. }
  598. /**
  599. * Maps a generated column id to a grid column instance
  600. */
  601. private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
  602. private List<String> columnOrder = new ArrayList<String>();
  603. /**
  604. * {@link #columnsUpdatedFromState} is set to true when
  605. * {@link #updateColumnOrderFromState(List)} is updating the column order
  606. * for the widget. This flag tells the {@link #columnReorderHandler} to not
  607. * send same data straight back to server. After updates, listener sets the
  608. * value back to false.
  609. */
  610. private boolean columnsUpdatedFromState;
  611. private RpcDataSource dataSource;
  612. /* Used to track Grid editor columns with validation errors */
  613. private final Map<Column<?, JsonObject>, String> columnToErrorMessage = new HashMap<Column<?, JsonObject>, String>();
  614. private ItemClickHandler itemClickHandler = new ItemClickHandler();
  615. private String lastKnownTheme = null;
  616. private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator();
  617. private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator();
  618. private final DetailsListener detailsListener = new DetailsListener() {
  619. @Override
  620. public void reapplyDetailsVisibility(final int rowIndex,
  621. final JsonObject row) {
  622. if (hasDetailsOpen(row)) {
  623. // Command for opening details row.
  624. ScheduledCommand openDetails = new ScheduledCommand() {
  625. @Override
  626. public void execute() {
  627. // Re-apply to force redraw.
  628. getWidget().setDetailsVisible(rowIndex, false);
  629. getWidget().setDetailsVisible(rowIndex, true);
  630. lazyDetailsScroller.detailsOpened(rowIndex);
  631. }
  632. };
  633. if (initialChange) {
  634. Scheduler.get().scheduleDeferred(openDetails);
  635. } else {
  636. Scheduler.get().scheduleFinally(openDetails);
  637. }
  638. } else {
  639. getWidget().setDetailsVisible(rowIndex, false);
  640. }
  641. }
  642. private boolean hasDetailsOpen(JsonObject row) {
  643. return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
  644. && row.getString(GridState.JSONKEY_DETAILS_VISIBLE) != null;
  645. }
  646. };
  647. private final LazyDetailsScroller lazyDetailsScroller = new LazyDetailsScroller();
  648. private final CustomEditorHandler editorHandler = new CustomEditorHandler();
  649. /*
  650. * Initially details need to behave a bit differently to allow some
  651. * escalator magic.
  652. */
  653. private boolean initialChange;
  654. @Override
  655. @SuppressWarnings("unchecked")
  656. public Grid<JsonObject> getWidget() {
  657. return (Grid<JsonObject>) super.getWidget();
  658. }
  659. @Override
  660. public GridState getState() {
  661. return (GridState) super.getState();
  662. }
  663. @Override
  664. protected void init() {
  665. super.init();
  666. Grid<JsonObject> grid = getWidget();
  667. // All scroll RPC calls are executed finally to avoid issues on init
  668. registerRpc(GridClientRpc.class, new GridClientRpc() {
  669. @Override
  670. public void scrollToStart() {
  671. /*
  672. * no need for lazyDetailsScrollAdjuster, because the start is
  673. * always 0, won't change a bit.
  674. */
  675. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  676. @Override
  677. public void execute() {
  678. grid.scrollToStart();
  679. }
  680. });
  681. }
  682. @Override
  683. public void scrollToEnd() {
  684. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  685. @Override
  686. public void execute() {
  687. grid.scrollToEnd();
  688. // Scrolls further if details opens.
  689. lazyDetailsScroller.scrollToRow(dataSource.size() - 1,
  690. ScrollDestination.END);
  691. }
  692. });
  693. }
  694. @Override
  695. public void scrollToRow(final int row,
  696. final ScrollDestination destination) {
  697. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  698. @Override
  699. public void execute() {
  700. grid.scrollToRow(row, destination);
  701. // Scrolls a bit further if details opens.
  702. lazyDetailsScroller.scrollToRow(row, destination);
  703. }
  704. });
  705. }
  706. @Override
  707. public void recalculateColumnWidths() {
  708. grid.recalculateColumnWidths();
  709. }
  710. });
  711. /* Item click events */
  712. grid.addBodyClickHandler(itemClickHandler);
  713. grid.addBodyDoubleClickHandler(itemClickHandler);
  714. /* Style Generators */
  715. grid.setCellStyleGenerator(styleGenerator);
  716. grid.setRowStyleGenerator(styleGenerator);
  717. grid.addSortHandler(new SortHandler<JsonObject>() {
  718. @Override
  719. public void sort(SortEvent<JsonObject> event) {
  720. List<SortOrder> order = event.getOrder();
  721. String[] columnIds = new String[order.size()];
  722. SortDirection[] directions = new SortDirection[order.size()];
  723. for (int i = 0; i < order.size(); i++) {
  724. SortOrder sortOrder = order.get(i);
  725. CustomGridColumn column = (CustomGridColumn) sortOrder
  726. .getColumn();
  727. columnIds[i] = column.id;
  728. directions[i] = sortOrder.getDirection();
  729. }
  730. if (!Arrays.equals(columnIds, getState().sortColumns)
  731. || !Arrays.equals(directions, getState().sortDirs)) {
  732. // Report back to server if changed
  733. getRpcProxy(GridServerRpc.class).sort(columnIds, directions,
  734. event.isUserOriginated());
  735. }
  736. }
  737. });
  738. grid.setEditorHandler(editorHandler);
  739. grid.addColumnReorderHandler(columnReorderHandler);
  740. grid.addColumnVisibilityChangeHandler(
  741. columnVisibilityChangeHandler);
  742. grid.addColumnResizeHandler(columnResizeHandler);
  743. ConnectorFocusAndBlurHandler.addHandlers(this);
  744. grid.setDetailsGenerator(customDetailsGenerator);
  745. getLayoutManager().registerDependency(this, grid.getElement());
  746. // Handling row height changes
  747. grid.addRowHeightChangedHandler(new RowHeightChangedHandler() {
  748. @Override
  749. public void onRowHeightChanged(RowHeightChangedEvent event) {
  750. getLayoutManager()
  751. .setNeedsMeasureRecursively(GridConnector.this);
  752. getLayoutManager().layoutNow();
  753. }
  754. });
  755. layout();
  756. }
  757. @Override
  758. public void onStateChanged(final StateChangeEvent stateChangeEvent) {
  759. super.onStateChanged(stateChangeEvent);
  760. initialChange = stateChangeEvent.isInitialStateChange();
  761. // Column updates
  762. if (stateChangeEvent.hasPropertyChanged("columns")) {
  763. // Remove old columns
  764. purgeRemovedColumns();
  765. // Add new columns
  766. for (GridColumnState state : getState().columns) {
  767. if (!columnIdToColumn.containsKey(state.id)) {
  768. addColumnFromStateChangeEvent(state);
  769. }
  770. updateColumnFromStateChangeEvent(state);
  771. }
  772. }
  773. if (stateChangeEvent.hasPropertyChanged("columnOrder")) {
  774. if (orderNeedsUpdate(getState().columnOrder)) {
  775. updateColumnOrderFromState(getState().columnOrder);
  776. }
  777. }
  778. // Column resize mode
  779. if (stateChangeEvent.hasPropertyChanged("columnResizeMode")) {
  780. getWidget().setColumnResizeMode(getState().columnResizeMode);
  781. }
  782. // Header and footer
  783. if (stateChangeEvent.hasPropertyChanged("header")) {
  784. updateHeaderFromState(getState().header);
  785. }
  786. if (stateChangeEvent.hasPropertyChanged("footer")) {
  787. updateFooterFromState(getState().footer);
  788. }
  789. // Sorting
  790. if (stateChangeEvent.hasPropertyChanged("sortColumns")
  791. || stateChangeEvent.hasPropertyChanged("sortDirs")) {
  792. onSortStateChange();
  793. }
  794. // Editor
  795. if (stateChangeEvent.hasPropertyChanged("editorEnabled")) {
  796. getWidget().setEditorEnabled(getState().editorEnabled);
  797. }
  798. // Frozen columns
  799. if (stateChangeEvent.hasPropertyChanged("frozenColumnCount")) {
  800. getWidget().setFrozenColumnCount(getState().frozenColumnCount);
  801. }
  802. // Theme features
  803. String activeTheme = getConnection().getUIConnector().getActiveTheme();
  804. if (lastKnownTheme == null) {
  805. lastKnownTheme = activeTheme;
  806. } else if (!lastKnownTheme.equals(activeTheme)) {
  807. getWidget().resetSizesFromDom();
  808. lastKnownTheme = activeTheme;
  809. }
  810. }
  811. private void updateColumnOrderFromState(List<String> stateColumnOrder) {
  812. CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
  813. .size()];
  814. int i = 0;
  815. for (String id : stateColumnOrder) {
  816. columns[i] = columnIdToColumn.get(id);
  817. i++;
  818. }
  819. columnsUpdatedFromState = true;
  820. getWidget().setColumnOrder(columns);
  821. columnsUpdatedFromState = false;
  822. columnOrder = stateColumnOrder;
  823. }
  824. private boolean orderNeedsUpdate(List<String> stateColumnOrder) {
  825. if (stateColumnOrder.size() == columnOrder.size()) {
  826. for (int i = 0; i < columnOrder.size(); ++i) {
  827. if (!stateColumnOrder.get(i).equals(columnOrder.get(i))) {
  828. return true;
  829. }
  830. }
  831. return false;
  832. }
  833. return true;
  834. }
  835. private void updateHeaderFromState(GridStaticSectionState state) {
  836. Grid<JsonObject> grid = getWidget();
  837. grid.setHeaderVisible(state.visible);
  838. while (grid.getHeaderRowCount() > 0) {
  839. grid.removeHeaderRow(0);
  840. }
  841. for (RowState rowState : state.rows) {
  842. HeaderRow row = grid.appendHeaderRow();
  843. if (rowState.defaultRow) {
  844. grid.setDefaultHeaderRow(row);
  845. }
  846. for (CellState cellState : rowState.cells) {
  847. CustomGridColumn column = columnIdToColumn
  848. .get(cellState.columnId);
  849. updateHeaderCellFromState(row.getCell(column), cellState);
  850. }
  851. for (Set<String> group : rowState.cellGroups.keySet()) {
  852. Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
  853. .size()];
  854. CellState cellState = rowState.cellGroups.get(group);
  855. int i = 0;
  856. for (String columnId : group) {
  857. columns[i] = columnIdToColumn.get(columnId);
  858. i++;
  859. }
  860. // Set state to be the same as first in group.
  861. updateHeaderCellFromState(row.join(columns), cellState);
  862. }
  863. row.setStyleName(rowState.styleName);
  864. }
  865. }
  866. private void updateHeaderCellFromState(HeaderCell cell,
  867. CellState cellState) {
  868. switch (cellState.type) {
  869. case TEXT:
  870. cell.setText(cellState.text);
  871. break;
  872. case HTML:
  873. cell.setHtml(cellState.html);
  874. break;
  875. case WIDGET:
  876. ComponentConnector connector = (ComponentConnector) cellState.connector;
  877. if (connector != null) {
  878. cell.setWidget(connector.getWidget());
  879. } else {
  880. // This happens if you do setVisible(false) on the component on
  881. // the server side
  882. cell.setWidget(null);
  883. }
  884. break;
  885. default:
  886. throw new IllegalStateException(
  887. "unexpected cell type: " + cellState.type);
  888. }
  889. cell.setStyleName(cellState.styleName);
  890. }
  891. private void updateFooterFromState(GridStaticSectionState state) {
  892. Grid<JsonObject> grid = getWidget();
  893. grid.setFooterVisible(state.visible);
  894. while (grid.getFooterRowCount() > 0) {
  895. grid.removeFooterRow(0);
  896. }
  897. for (RowState rowState : state.rows) {
  898. FooterRow row = grid.appendFooterRow();
  899. for (CellState cellState : rowState.cells) {
  900. CustomGridColumn column = columnIdToColumn
  901. .get(cellState.columnId);
  902. updateFooterCellFromState(row.getCell(column), cellState);
  903. }
  904. for (Set<String> group : rowState.cellGroups.keySet()) {
  905. Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
  906. .size()];
  907. CellState cellState = rowState.cellGroups.get(group);
  908. int i = 0;
  909. for (String columnId : group) {
  910. columns[i] = columnIdToColumn.get(columnId);
  911. i++;
  912. }
  913. // Set state to be the same as first in group.
  914. updateFooterCellFromState(row.join(columns), cellState);
  915. }
  916. row.setStyleName(rowState.styleName);
  917. }
  918. }
  919. private void updateFooterCellFromState(FooterCell cell,
  920. CellState cellState) {
  921. switch (cellState.type) {
  922. case TEXT:
  923. cell.setText(cellState.text);
  924. break;
  925. case HTML:
  926. cell.setHtml(cellState.html);
  927. break;
  928. case WIDGET:
  929. ComponentConnector connector = (ComponentConnector) cellState.connector;
  930. if (connector != null) {
  931. cell.setWidget(connector.getWidget());
  932. } else {
  933. // This happens if you do setVisible(false) on the component on
  934. // the server side
  935. cell.setWidget(null);
  936. }
  937. break;
  938. default:
  939. throw new IllegalStateException(
  940. "unexpected cell type: " + cellState.type);
  941. }
  942. cell.setStyleName(cellState.styleName);
  943. }
  944. /**
  945. * Updates a column from a state change event.
  946. *
  947. * @param columnIndex
  948. * The index of the column to update
  949. */
  950. private void updateColumnFromStateChangeEvent(GridColumnState columnState) {
  951. CustomGridColumn column = columnIdToColumn.get(columnState.id);
  952. columnsUpdatedFromState = true;
  953. updateColumnFromState(column, columnState);
  954. columnsUpdatedFromState = false;
  955. }
  956. /**
  957. * Adds a new column to the grid widget from a state change event
  958. *
  959. * @param columnIndex
  960. * The index of the column, according to how it
  961. */
  962. private void addColumnFromStateChangeEvent(GridColumnState state) {
  963. @SuppressWarnings("unchecked")
  964. CustomGridColumn column = new CustomGridColumn(state.id,
  965. ((AbstractGridRendererConnector<Object>) state.rendererConnector));
  966. columnIdToColumn.put(state.id, column);
  967. /*
  968. * Add column to grid. Reordering is handled as a separate problem.
  969. */
  970. getWidget().addColumn(column);
  971. columnOrder.add(state.id);
  972. }
  973. /**
  974. * Updates the column values from a state
  975. *
  976. * @param column
  977. * The column to update
  978. * @param state
  979. * The state to get the data from
  980. */
  981. @SuppressWarnings("unchecked")
  982. private static void updateColumnFromState(CustomGridColumn column,
  983. GridColumnState state) {
  984. column.setWidth(state.width);
  985. column.setMinimumWidth(state.minWidth);
  986. column.setMaximumWidth(state.maxWidth);
  987. column.setExpandRatio(state.expandRatio);
  988. assert state.rendererConnector instanceof AbstractGridRendererConnector : "GridColumnState.rendererConnector is invalid (not subclass of AbstractGridRendererConnector)";
  989. column.setRenderer(
  990. (AbstractGridRendererConnector<Object>) state.rendererConnector);
  991. column.setSortable(state.sortable);
  992. column.setResizable(state.resizable);
  993. column.setHeaderCaption(state.headerCaption);
  994. column.setHidden(state.hidden);
  995. column.setHidable(state.hidable);
  996. column.setHidingToggleCaption(state.hidingToggleCaption);
  997. column.setEditable(state.editable);
  998. column.setEditorConnector(
  999. (AbstractComponentConnector) state.editorConnector);
  1000. }
  1001. /**
  1002. * Removes any orphan columns that has been removed from the state from the
  1003. * grid
  1004. */
  1005. private void purgeRemovedColumns() {
  1006. // Get columns still registered in the state
  1007. Set<String> columnsInState = new HashSet<String>();
  1008. for (GridColumnState columnState : getState().columns) {
  1009. columnsInState.add(columnState.id);
  1010. }
  1011. // Remove column no longer in state
  1012. Iterator<String> columnIdIterator = columnIdToColumn.keySet()
  1013. .iterator();
  1014. while (columnIdIterator.hasNext()) {
  1015. String id = columnIdIterator.next();
  1016. if (!columnsInState.contains(id)) {
  1017. CustomGridColumn column = columnIdToColumn.get(id);
  1018. columnIdIterator.remove();
  1019. getWidget().removeColumn(column);
  1020. columnOrder.remove(id);
  1021. }
  1022. }
  1023. }
  1024. public void setDataSource(RpcDataSource dataSource) {
  1025. this.dataSource = dataSource;
  1026. getWidget().setDataSource(this.dataSource);
  1027. }
  1028. private void onSortStateChange() {
  1029. List<SortOrder> sortOrder = new ArrayList<SortOrder>();
  1030. String[] sortColumns = getState().sortColumns;
  1031. SortDirection[] sortDirs = getState().sortDirs;
  1032. for (int i = 0; i < sortColumns.length; i++) {
  1033. sortOrder.add(new SortOrder(columnIdToColumn.get(sortColumns[i]),
  1034. sortDirs[i]));
  1035. }
  1036. getWidget().setSortOrder(sortOrder);
  1037. }
  1038. private Logger getLogger() {
  1039. return Logger.getLogger(getClass().getName());
  1040. }
  1041. /**
  1042. * Gets the row key for a row object.
  1043. *
  1044. * @param row
  1045. * the row object
  1046. * @return the key for the given row
  1047. */
  1048. public String getRowKey(JsonObject row) {
  1049. final Object key = dataSource.getRowKey(row);
  1050. assert key instanceof String : "Internal key was not a String but a "
  1051. + key.getClass().getSimpleName() + " (" + key + ")";
  1052. return (String) key;
  1053. }
  1054. /*
  1055. * (non-Javadoc)
  1056. *
  1057. * @see
  1058. * com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin.client
  1059. * .ComponentConnector)
  1060. */
  1061. @Override
  1062. public void updateCaption(ComponentConnector connector) {
  1063. // TODO Auto-generated method stub
  1064. }
  1065. @Override
  1066. public void onConnectorHierarchyChange(
  1067. ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
  1068. customDetailsGenerator.updateConnectorHierarchy(getChildren());
  1069. }
  1070. public String getColumnId(Grid.Column<?, ?> column) {
  1071. if (column instanceof CustomGridColumn) {
  1072. return ((CustomGridColumn) column).id;
  1073. }
  1074. return null;
  1075. }
  1076. @Override
  1077. public void layout() {
  1078. getWidget().onResize();
  1079. }
  1080. @Override
  1081. public boolean isWorkPending() {
  1082. return lazyDetailsScroller.isWorkPending();
  1083. }
  1084. /**
  1085. * Gets the listener used by this connector for tracking when row detail
  1086. * visibility changes.
  1087. *
  1088. * @since 7.5.0
  1089. * @return the used details listener
  1090. */
  1091. public DetailsListener getDetailsListener() {
  1092. return detailsListener;
  1093. }
  1094. @Override
  1095. public boolean hasTooltip() {
  1096. return getState().hasDescriptions || super.hasTooltip();
  1097. }
  1098. @Override
  1099. public TooltipInfo getTooltipInfo(Element element) {
  1100. CellReference<JsonObject> cell = getWidget().getCellReference(element);
  1101. if (cell != null) {
  1102. JsonObject row = cell.getRow();
  1103. if (row == null) {
  1104. return null;
  1105. }
  1106. Column<?, JsonObject> column = cell.getColumn();
  1107. if (!(column instanceof CustomGridColumn)) {
  1108. // Selection checkbox column
  1109. return null;
  1110. }
  1111. CustomGridColumn c = (CustomGridColumn) column;
  1112. JsonObject cellDescriptions = row
  1113. .getObject(GridState.JSONKEY_CELLDESCRIPTION);
  1114. if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) {
  1115. return new TooltipInfo(cellDescriptions.getString(c.id));
  1116. } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
  1117. return new TooltipInfo(
  1118. row.getString(GridState.JSONKEY_ROWDESCRIPTION));
  1119. } else {
  1120. return null;
  1121. }
  1122. }
  1123. return super.getTooltipInfo(element);
  1124. }
  1125. @Override
  1126. protected void sendContextClickEvent(MouseEventDetails details,
  1127. EventTarget eventTarget) {
  1128. // if element is the resize indicator, ignore the event
  1129. if (isResizeHandle(eventTarget)) {
  1130. WidgetUtil.clearTextSelection();
  1131. return;
  1132. }
  1133. EventCellReference<JsonObject> eventCell = getWidget().getEventCell();
  1134. Section section = eventCell.getSection();
  1135. String rowKey = null;
  1136. if (eventCell.isBody() && eventCell.getRow() != null) {
  1137. rowKey = getRowKey(eventCell.getRow());
  1138. }
  1139. String columnId = getColumnId(eventCell.getColumn());
  1140. getRpcProxy(GridServerRpc.class).contextClick(eventCell.getRowIndex(),
  1141. rowKey, columnId, section, details);
  1142. WidgetUtil.clearTextSelection();
  1143. }
  1144. private boolean isResizeHandle(EventTarget eventTarget) {
  1145. if (Element.is(eventTarget)) {
  1146. Element e = Element.as(eventTarget);
  1147. if (e.getClassName().contains("-column-resize-handle")) {
  1148. return true;
  1149. }
  1150. }
  1151. return false;
  1152. }
  1153. /**
  1154. * Creates a concatenation of all columns errors for Editor.
  1155. *
  1156. * @since 7.6
  1157. * @return displayed error string
  1158. */
  1159. private String getColumnErrors() {
  1160. List<String> errors = new ArrayList<String>();
  1161. for (Grid.Column<?, JsonObject> c : getWidget().getColumns()) {
  1162. if (!(c instanceof CustomGridColumn)) {
  1163. continue;
  1164. }
  1165. String error = columnToErrorMessage.get(c);
  1166. if (error != null) {
  1167. errors.add(error);
  1168. }
  1169. }
  1170. String result = "";
  1171. Iterator<String> i = errors.iterator();
  1172. while (i.hasNext()) {
  1173. result += i.next();
  1174. if (i.hasNext()) {
  1175. result += ", ";
  1176. }
  1177. }
  1178. return result.isEmpty() ? null : result;
  1179. }
  1180. }