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 51KB

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