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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.connectors.grid;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.HashSet;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Objects;
  25. import java.util.Set;
  26. import java.util.stream.Collectors;
  27. import com.google.gwt.core.client.Scheduler;
  28. import com.google.gwt.dom.client.Element;
  29. import com.google.gwt.dom.client.EventTarget;
  30. import com.google.gwt.dom.client.NativeEvent;
  31. import com.google.gwt.event.shared.HandlerRegistration;
  32. import com.vaadin.client.ComponentConnector;
  33. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  34. import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
  35. import com.vaadin.client.DeferredWorker;
  36. import com.vaadin.client.HasComponentsConnector;
  37. import com.vaadin.client.MouseEventDetailsBuilder;
  38. import com.vaadin.client.TooltipInfo;
  39. import com.vaadin.client.WidgetUtil;
  40. import com.vaadin.client.annotations.OnStateChange;
  41. import com.vaadin.client.communication.StateChangeEvent;
  42. import com.vaadin.client.connectors.AbstractListingConnector;
  43. import com.vaadin.client.connectors.grid.ColumnConnector.CustomColumn;
  44. import com.vaadin.client.data.AbstractRemoteDataSource;
  45. import com.vaadin.client.data.DataSource;
  46. import com.vaadin.client.ui.SimpleManagedLayout;
  47. import com.vaadin.client.widget.escalator.RowContainer;
  48. import com.vaadin.client.widget.grid.CellReference;
  49. import com.vaadin.client.widget.grid.DataAvailableEvent;
  50. import com.vaadin.client.widget.grid.DataAvailableHandler;
  51. import com.vaadin.client.widget.grid.EventCellReference;
  52. import com.vaadin.client.widget.grid.events.BodyClickHandler;
  53. import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
  54. import com.vaadin.client.widget.grid.events.GridClickEvent;
  55. import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
  56. import com.vaadin.client.widget.grid.sort.SortEvent;
  57. import com.vaadin.client.widget.grid.sort.SortOrder;
  58. import com.vaadin.client.widgets.Grid;
  59. import com.vaadin.client.widgets.Grid.Column;
  60. import com.vaadin.client.widgets.Grid.FooterRow;
  61. import com.vaadin.client.widgets.Grid.HeaderRow;
  62. import com.vaadin.client.widgets.Grid.SelectionColumn;
  63. import com.vaadin.client.widgets.Grid.StaticSection.StaticCell;
  64. import com.vaadin.shared.MouseEventDetails;
  65. import com.vaadin.shared.data.sort.SortDirection;
  66. import com.vaadin.shared.ui.Connect;
  67. import com.vaadin.shared.ui.grid.GridClientRpc;
  68. import com.vaadin.shared.ui.grid.GridConstants;
  69. import com.vaadin.shared.ui.grid.GridConstants.Section;
  70. import com.vaadin.shared.ui.grid.GridServerRpc;
  71. import com.vaadin.shared.ui.grid.GridState;
  72. import com.vaadin.shared.ui.grid.ScrollDestination;
  73. import com.vaadin.shared.ui.grid.SectionState;
  74. import com.vaadin.shared.ui.grid.SectionState.CellState;
  75. import com.vaadin.shared.ui.grid.SectionState.RowState;
  76. import elemental.json.JsonObject;
  77. /**
  78. * A connector class for the typed Grid component.
  79. *
  80. * @author Vaadin Ltd
  81. * @since 8.0
  82. */
  83. @Connect(com.vaadin.ui.Grid.class)
  84. public class GridConnector extends AbstractListingConnector
  85. implements HasComponentsConnector, SimpleManagedLayout, DeferredWorker {
  86. private Set<Runnable> refreshDetailsCallbacks = new HashSet<>();
  87. /**
  88. * Server-to-client RPC implementation for GridConnector.
  89. * <p>
  90. * The scrolling methods must trigger the scrolling only after any potential
  91. * resizing or other similar action triggered from the server side within
  92. * the same round trip has had a chance to happen, so there needs to be a
  93. * delay. The delay is done with <code>scheduleDeferred</code> rather than
  94. * <code>scheduleFinally</code> because otherwise the order of the
  95. * operations isn't guaranteed.
  96. *
  97. */
  98. private class GridConnectorClientRpc implements GridClientRpc {
  99. private final Grid<JsonObject> grid;
  100. private HandlerRegistration dataAvailableHandlerRegistration = null;
  101. private boolean recalculateScheduled = false;
  102. private GridConnectorClientRpc(Grid<JsonObject> grid) {
  103. this.grid = grid;
  104. }
  105. @Override
  106. public void scrollToRow(int row, ScrollDestination destination) {
  107. Scheduler.get().scheduleDeferred(() -> {
  108. grid.scrollToRow(row, destination);
  109. // Add details refresh listener and handle possible detail
  110. // for scrolled row.
  111. addDetailsRefreshCallback(() -> {
  112. if (rowHasDetails(row)) {
  113. grid.scrollToRow(row, destination);
  114. }
  115. });
  116. });
  117. }
  118. @Override
  119. public void scrollToStart() {
  120. Scheduler.get().scheduleDeferred(() -> grid.scrollToStart());
  121. }
  122. @Override
  123. public void scrollToEnd() {
  124. Scheduler.get().scheduleDeferred(() -> {
  125. grid.scrollToEnd();
  126. addDetailsRefreshCallback(() -> {
  127. if (rowHasDetails(grid.getDataSource().size() - 1)) {
  128. grid.scrollToEnd();
  129. }
  130. });
  131. });
  132. }
  133. @Override
  134. public void recalculateColumnWidths() {
  135. if (recalculateScheduled) {
  136. return;
  137. }
  138. // Must be scheduled so that possible refreshAll has time to clear
  139. // the cache.
  140. recalculateScheduled = true;
  141. Scheduler.get().scheduleFinally(() -> {
  142. // If cache has been cleared, wait for data to become available.
  143. // Don't trigger another attempt if there is already a handler
  144. // waiting, that one will trigger the call when calculations are
  145. // possible and clear out the registration afterwards.
  146. if (((AbstractRemoteDataSource<JsonObject>) getDataSource())
  147. .getCachedRange().length() == 0
  148. && getDataSource().size() > 0) {
  149. if (dataAvailableHandlerRegistration == null) {
  150. dataAvailableHandlerRegistration = grid
  151. .addDataAvailableHandler(
  152. new DataAvailableHandler() {
  153. @Override
  154. public void onDataAvailable(
  155. DataAvailableEvent event) {
  156. if (event.getAvailableRows()
  157. .length() == 0
  158. && getDataSource()
  159. .size() > 0) {
  160. // Cache not populated yet,
  161. // wait for next call.
  162. return;
  163. }
  164. grid.recalculateColumnWidths();
  165. if (dataAvailableHandlerRegistration != null) {
  166. dataAvailableHandlerRegistration
  167. .removeHandler();
  168. dataAvailableHandlerRegistration = null;
  169. }
  170. }
  171. });
  172. }
  173. } else if (dataAvailableHandlerRegistration == null) {
  174. grid.recalculateColumnWidths();
  175. }
  176. recalculateScheduled = false;
  177. });
  178. }
  179. }
  180. private class ItemClickHandler
  181. implements BodyClickHandler, BodyDoubleClickHandler {
  182. @Override
  183. public void onClick(GridClickEvent event) {
  184. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  185. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  186. }
  187. }
  188. @Override
  189. public void onDoubleClick(GridDoubleClickEvent event) {
  190. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  191. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  192. }
  193. }
  194. private void fireItemClick(CellReference<?> cell,
  195. NativeEvent mouseEvent) {
  196. String rowKey = getRowKey((JsonObject) cell.getRow());
  197. String columnId = columnToIdMap.get(cell.getColumn());
  198. int rowIndex = cell.getRowIndex();
  199. getRpcProxy(GridServerRpc.class).itemClick(rowKey, columnId,
  200. MouseEventDetailsBuilder.buildMouseEventDetails(mouseEvent),
  201. rowIndex);
  202. }
  203. }
  204. /* Map to keep track of all added columns */
  205. private Map<CustomColumn, String> columnToIdMap = new HashMap<>();
  206. private Map<String, CustomColumn> idToColumn = new HashMap<>();
  207. /* Child component list for HasComponentsConnector */
  208. private List<ComponentConnector> childComponents;
  209. private ItemClickHandler itemClickHandler = new ItemClickHandler();
  210. private boolean rowHeightScheduled = false;
  211. /**
  212. * Gets the string identifier of the given column in this grid.
  213. *
  214. * @param column
  215. * the column whose id to get
  216. * @return the string id of the column
  217. */
  218. public String getColumnId(Column<?, ?> column) {
  219. return columnToIdMap.get(column);
  220. }
  221. /**
  222. * Gets the column corresponding to the given string identifier.
  223. *
  224. * @param columnId
  225. * the id of the column to get
  226. * @return the column with the given id
  227. */
  228. public CustomColumn getColumn(String columnId) {
  229. return idToColumn.get(columnId);
  230. }
  231. @Override
  232. @SuppressWarnings("unchecked")
  233. public Grid<JsonObject> getWidget() {
  234. return (Grid<JsonObject>) super.getWidget();
  235. }
  236. /**
  237. * Method called for a row details refresh. Runs all callbacks if any
  238. * details were shown and clears the callbacks.
  239. *
  240. * @param detailsShown
  241. * True if any details were set visible
  242. */
  243. protected void detailsRefreshed(boolean detailsShown) {
  244. if (detailsShown) {
  245. refreshDetailsCallbacks.forEach(Runnable::run);
  246. }
  247. refreshDetailsCallbacks.clear();
  248. }
  249. /**
  250. * Method target for when one single details has been updated and we might
  251. * need to scroll it into view.
  252. *
  253. * @param rowIndex
  254. * index of updated row
  255. */
  256. protected void singleDetailsOpened(int rowIndex) {
  257. addDetailsRefreshCallback(() -> {
  258. if (rowHasDetails(rowIndex)) {
  259. getWidget().scrollToRow(rowIndex);
  260. }
  261. });
  262. }
  263. /**
  264. * Add a single use details runnable callback for when we get a call to
  265. * {@link #detailsRefreshed(boolean)}.
  266. *
  267. * @param refreshCallback
  268. * Details refreshed callback
  269. */
  270. private void addDetailsRefreshCallback(Runnable refreshCallback) {
  271. refreshDetailsCallbacks.add(refreshCallback);
  272. }
  273. /**
  274. * Check if we have details for given row.
  275. *
  276. * @param rowIndex
  277. * @return
  278. */
  279. private boolean rowHasDetails(int rowIndex) {
  280. JsonObject row = getWidget().getDataSource().getRow(rowIndex);
  281. return row != null && row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE)
  282. && !row.getString(GridState.JSONKEY_DETAILS_VISIBLE).isEmpty();
  283. }
  284. @Override
  285. protected void init() {
  286. super.init();
  287. updateWidgetStyleNames();
  288. Grid<JsonObject> grid = getWidget();
  289. // Trigger early redraw of both grid static sections.
  290. grid.setHeaderVisible(!grid.isHeaderVisible());
  291. grid.setFooterVisible(!grid.isFooterVisible());
  292. registerRpc(GridClientRpc.class, new GridConnectorClientRpc(grid));
  293. grid.addSortHandler(this::handleSortEvent);
  294. grid.setRowStyleGenerator(rowRef -> {
  295. JsonObject json = rowRef.getRow();
  296. return json.hasKey(GridState.JSONKEY_ROWSTYLE)
  297. ? json.getString(GridState.JSONKEY_ROWSTYLE)
  298. : null;
  299. });
  300. grid.setCellStyleGenerator(cellRef -> {
  301. JsonObject row = cellRef.getRow();
  302. if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
  303. return null;
  304. }
  305. Column<?, JsonObject> column = cellRef.getColumn();
  306. if (column instanceof CustomColumn) {
  307. String id = ((CustomColumn) column).getConnectorId();
  308. JsonObject cellStyles = row
  309. .getObject(GridState.JSONKEY_CELLSTYLES);
  310. if (cellStyles.hasKey(id)) {
  311. return cellStyles.getString(id);
  312. }
  313. }
  314. return null;
  315. });
  316. grid.addColumnVisibilityChangeHandler(event -> {
  317. if (event.isUserOriginated()) {
  318. getRpcProxy(GridServerRpc.class).columnVisibilityChanged(
  319. getColumnId(event.getColumn()), event.isHidden());
  320. }
  321. });
  322. grid.addColumnReorderHandler(event -> {
  323. if (event.isUserOriginated()) {
  324. List<String> newColumnOrder = mapColumnsToIds(
  325. event.getNewColumnOrder());
  326. List<String> oldColumnOrder = mapColumnsToIds(
  327. event.getOldColumnOrder());
  328. getRpcProxy(GridServerRpc.class)
  329. .columnsReordered(newColumnOrder, oldColumnOrder);
  330. }
  331. });
  332. grid.addColumnResizeHandler(event -> {
  333. Column<?, JsonObject> column = event.getColumn();
  334. getRpcProxy(GridServerRpc.class).columnResized(getColumnId(column),
  335. column.getWidthActual());
  336. });
  337. // Handling row height changes
  338. grid.addRowHeightChangedHandler(event -> {
  339. getLayoutManager().setNeedsMeasureRecursively(GridConnector.this);
  340. getLayoutManager().layoutNow();
  341. });
  342. /* Item click events */
  343. grid.addBodyClickHandler(itemClickHandler);
  344. grid.addBodyDoubleClickHandler(itemClickHandler);
  345. layout();
  346. }
  347. @Override
  348. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  349. super.onStateChanged(stateChangeEvent);
  350. if (!getState().columnOrder.containsAll(idToColumn.keySet())) {
  351. updateColumns();
  352. } else if (stateChangeEvent.hasPropertyChanged("columnOrder")) {
  353. updateColumnOrder();
  354. }
  355. if (stateChangeEvent.hasPropertyChanged("header")) {
  356. updateHeader();
  357. }
  358. if (stateChangeEvent.hasPropertyChanged("footer")) {
  359. updateFooter();
  360. }
  361. }
  362. void updateColumnOrder() {
  363. getWidget().setColumnOrder(getState().columnOrder.stream()
  364. .map(this::getColumn).toArray(size -> new CustomColumn[size]));
  365. }
  366. @OnStateChange("columnResizeMode")
  367. void updateColumnResizeMode() {
  368. getWidget().setColumnResizeMode(getState().columnResizeMode);
  369. }
  370. /**
  371. * Updates the grid header section on state change.
  372. */
  373. void updateHeader() {
  374. final Grid<JsonObject> grid = getWidget();
  375. final SectionState state = getState().header;
  376. while (grid.getHeaderRowCount() > 0) {
  377. grid.removeHeaderRow(0);
  378. }
  379. for (RowState rowState : state.rows) {
  380. HeaderRow row = grid.appendHeaderRow();
  381. if (rowState.defaultHeader) {
  382. grid.setDefaultHeaderRow(row);
  383. }
  384. updateStaticRow(rowState, row);
  385. }
  386. grid.setHeaderVisible(state.visible);
  387. }
  388. @OnStateChange({ "bodyRowHeight", "headerRowHeight", "footerRowHeight" })
  389. void updateRowHeight() {
  390. if (rowHeightScheduled) {
  391. return;
  392. }
  393. Scheduler.get().scheduleFinally(() -> {
  394. GridState state = getState();
  395. Grid<JsonObject> grid = getWidget();
  396. if (grid.isAttached() && rowHeightNeedsReset()) {
  397. grid.resetSizesFromDom();
  398. }
  399. updateContainerRowHeigth(grid.getEscalator().getBody(),
  400. state.bodyRowHeight);
  401. updateContainerRowHeigth(grid.getEscalator().getHeader(),
  402. state.headerRowHeight);
  403. updateContainerRowHeigth(grid.getEscalator().getFooter(),
  404. state.footerRowHeight);
  405. rowHeightScheduled = false;
  406. });
  407. rowHeightScheduled = true;
  408. }
  409. private boolean rowHeightNeedsReset() {
  410. GridState state = getState();
  411. // Body
  412. boolean bodyAutoCalc = state.bodyRowHeight < 0;
  413. // Header
  414. boolean headerAutoCalc = state.headerRowHeight < 0;
  415. boolean headerReset = headerAutoCalc && hasVisibleContent(state.header);
  416. // Footer
  417. boolean footerAutoCalc = state.footerRowHeight < 0;
  418. boolean footerReset = footerAutoCalc && hasVisibleContent(state.footer);
  419. return bodyAutoCalc || headerReset || footerReset;
  420. }
  421. private boolean hasVisibleContent(SectionState state) {
  422. return state.visible && !state.rows.isEmpty();
  423. }
  424. private void updateContainerRowHeigth(RowContainer container,
  425. double height) {
  426. if (height >= 0) {
  427. container.setDefaultRowHeight(height);
  428. }
  429. }
  430. private void updateStaticRow(RowState rowState,
  431. Grid.StaticSection.StaticRow row) {
  432. rowState.cells
  433. .forEach((columnId, cellState) -> updateStaticCellFromState(
  434. row.getCell(getColumn(columnId)), cellState));
  435. for (Map.Entry<CellState, Set<String>> cellGroupEntry : rowState.cellGroups
  436. .entrySet()) {
  437. Set<String> group = cellGroupEntry.getValue();
  438. Grid.Column<?, ?>[] columns = group.stream().map(idToColumn::get)
  439. .toArray(size -> new Grid.Column<?, ?>[size]);
  440. // Set state to be the same as first in group.
  441. updateStaticCellFromState(row.join(columns),
  442. cellGroupEntry.getKey());
  443. }
  444. row.setStyleName(rowState.styleName);
  445. }
  446. private void updateStaticCellFromState(Grid.StaticSection.StaticCell cell,
  447. CellState cellState) {
  448. switch (cellState.type) {
  449. case TEXT:
  450. cell.setText(cellState.text);
  451. break;
  452. case HTML:
  453. cell.setHtml(cellState.html);
  454. break;
  455. case WIDGET:
  456. ComponentConnector connector = (ComponentConnector) cellState.connector;
  457. if (connector != null) {
  458. cell.setWidget(connector.getWidget());
  459. } else {
  460. // This happens if you do setVisible(false) on the component on
  461. // the server side
  462. cell.setWidget(null);
  463. }
  464. break;
  465. default:
  466. throw new IllegalStateException(
  467. "unexpected cell type: " + cellState.type);
  468. }
  469. cell.setStyleName(cellState.styleName);
  470. cell.setDescription(cellState.description);
  471. cell.setDescriptionContentMode(cellState.descriptionContentMode);
  472. }
  473. /**
  474. * Updates the grid footer section on state change.
  475. */
  476. void updateFooter() {
  477. final Grid<JsonObject> grid = getWidget();
  478. final SectionState state = getState().footer;
  479. while (grid.getFooterRowCount() > 0) {
  480. grid.removeFooterRow(0);
  481. }
  482. for (RowState rowState : state.rows) {
  483. FooterRow row = grid.appendFooterRow();
  484. updateStaticRow(rowState, row);
  485. }
  486. grid.setFooterVisible(state.visible);
  487. }
  488. @OnStateChange({ "sortColumns", "sortDirs" })
  489. void updateSortOrder() {
  490. List<SortOrder> sortOrder = new ArrayList<SortOrder>();
  491. String[] sortColumns = getState().sortColumns;
  492. SortDirection[] sortDirs = getState().sortDirs;
  493. for (int i = 0; i < sortColumns.length; i++) {
  494. sortOrder
  495. .add(new SortOrder(getColumn(sortColumns[i]), sortDirs[i]));
  496. }
  497. getWidget().setSortOrder(sortOrder);
  498. }
  499. @Override
  500. public void setDataSource(DataSource<JsonObject> dataSource) {
  501. super.setDataSource(dataSource);
  502. getWidget().setDataSource(dataSource);
  503. }
  504. /**
  505. * Adds a column to the Grid widget. For each column a communication id
  506. * stored for client to server communication.
  507. *
  508. * @param column
  509. * column to add
  510. * @param id
  511. * communication id
  512. */
  513. public void addColumn(CustomColumn column, String id) {
  514. assert !columnToIdMap.containsKey(column) && !columnToIdMap
  515. .containsValue(id) : "Column with given id already exists.";
  516. columnToIdMap.put(column, id);
  517. idToColumn.put(id, column);
  518. if (idToColumn.keySet().containsAll(getState().columnOrder)) {
  519. // All columns are available.
  520. updateColumns();
  521. }
  522. }
  523. /**
  524. * Updates the widgets columns to match the map in this connector.
  525. */
  526. protected void updateColumns() {
  527. List<Column<?, JsonObject>> currentColumns = getWidget().getColumns();
  528. List<CustomColumn> columnOrder = getState().columnOrder.stream()
  529. .map(this::getColumn).collect(Collectors.toList());
  530. if (isColumnOrderCorrect(currentColumns, columnOrder)) {
  531. // All up to date
  532. return;
  533. }
  534. Grid<JsonObject> grid = getWidget();
  535. // Remove old column
  536. currentColumns.stream()
  537. .filter(col -> !(columnOrder.contains(col)
  538. || col instanceof SelectionColumn))
  539. .forEach(grid::removeColumn);
  540. // Add new columns
  541. grid.addColumns(columnOrder.stream()
  542. .filter(col -> !currentColumns.contains(col))
  543. .toArray(CustomColumn[]::new));
  544. // Make sure order is correct.
  545. grid.setColumnOrder(
  546. columnOrder.toArray(new CustomColumn[columnOrder.size()]));
  547. }
  548. private boolean isColumnOrderCorrect(List<Column<?, JsonObject>> current,
  549. List<CustomColumn> order) {
  550. List<Column<?, JsonObject>> columnsToCompare = current;
  551. if (current.size() > 0 && current.get(0) instanceof SelectionColumn) {
  552. // Remove selection column.
  553. columnsToCompare = current.subList(1, current.size());
  554. }
  555. return columnsToCompare.equals(order);
  556. }
  557. /**
  558. * Removes the given column from mappings in this Connector.
  559. *
  560. * @param column
  561. * column to remove from the mapping
  562. */
  563. public void removeColumnMapping(CustomColumn column) {
  564. assert columnToIdMap
  565. .containsKey(column) : "Given Column does not exist.";
  566. // Remove mapping. Columns are removed from Grid when state changes.
  567. String id = columnToIdMap.remove(column);
  568. idToColumn.remove(id);
  569. }
  570. /**
  571. * Method called by {@code CustomColumn} when its renderer changes. This
  572. * method is used to maintain hierarchical renderer wrap in
  573. * {@code TreeGrid}.
  574. *
  575. * @param column
  576. * the column which now has a new renderer
  577. *
  578. * @since 8.1
  579. */
  580. public void onColumnRendererChanged(CustomColumn column) {
  581. // NO-OP
  582. }
  583. @Override
  584. public void onUnregister() {
  585. super.onUnregister();
  586. }
  587. @Override
  588. public boolean isWorkPending() {
  589. return getWidget().isWorkPending();
  590. }
  591. @Override
  592. public void layout() {
  593. getWidget().onResize();
  594. }
  595. /**
  596. * Sends sort information from an event to the server-side of the Grid.
  597. *
  598. * @param event
  599. * the sort event
  600. */
  601. private void handleSortEvent(SortEvent<JsonObject> event) {
  602. List<String> columnIds = new ArrayList<>();
  603. List<SortDirection> sortDirections = new ArrayList<>();
  604. for (SortOrder so : event.getOrder()) {
  605. if (columnToIdMap.containsKey(so.getColumn())) {
  606. columnIds.add(columnToIdMap.get(so.getColumn()));
  607. sortDirections.add(so.getDirection());
  608. }
  609. }
  610. String[] colArray = columnIds.toArray(new String[0]);
  611. SortDirection[] dirArray = sortDirections.toArray(new SortDirection[0]);
  612. if (!Arrays.equals(colArray, getState().sortColumns)
  613. || !Arrays.equals(dirArray, getState().sortDirs)) {
  614. // State has actually changed, send to server
  615. getRpcProxy(GridServerRpc.class).sort(colArray, dirArray,
  616. event.isUserOriginated());
  617. }
  618. }
  619. /* HasComponentsConnector */
  620. @Override
  621. public void updateCaption(ComponentConnector connector) {
  622. // Details components don't support captions.
  623. }
  624. @Override
  625. public List<ComponentConnector> getChildComponents() {
  626. if (childComponents == null) {
  627. return Collections.emptyList();
  628. }
  629. return childComponents;
  630. }
  631. @Override
  632. public void setChildComponents(List<ComponentConnector> children) {
  633. childComponents = children;
  634. }
  635. @Override
  636. public HandlerRegistration addConnectorHierarchyChangeHandler(
  637. ConnectorHierarchyChangeHandler handler) {
  638. return ensureHandlerManager()
  639. .addHandler(ConnectorHierarchyChangeEvent.TYPE, handler);
  640. }
  641. @Override
  642. public GridState getState() {
  643. return (GridState) super.getState();
  644. }
  645. @Override
  646. public boolean hasTooltip() {
  647. // Always check for generated descriptions.
  648. return true;
  649. }
  650. @Override
  651. public TooltipInfo getTooltipInfo(Element element) {
  652. CellReference<JsonObject> cell = getWidget().getCellReference(element);
  653. if (cell != null) {
  654. JsonObject row = cell.getRow();
  655. TooltipInfo tooltip = getHeaderFooterTooltip(cell);
  656. if (tooltip != null) {
  657. return tooltip;
  658. }
  659. if (row != null && (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)
  660. || row.hasKey(GridState.JSONKEY_CELLDESCRIPTION))) {
  661. Column<?, JsonObject> column = cell.getColumn();
  662. if (column instanceof CustomColumn) {
  663. JsonObject cellDescriptions = row
  664. .getObject(GridState.JSONKEY_CELLDESCRIPTION);
  665. String id = ((CustomColumn) column).getConnectorId();
  666. if (cellDescriptions != null
  667. && cellDescriptions.hasKey(id)) {
  668. return new TooltipInfo(cellDescriptions.getString(id),
  669. ((CustomColumn) column)
  670. .getTooltipContentMode());
  671. } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
  672. return new TooltipInfo(
  673. row.getString(GridState.JSONKEY_ROWDESCRIPTION),
  674. getState().rowDescriptionContentMode);
  675. }
  676. }
  677. }
  678. }
  679. if (super.hasTooltip()) {
  680. return super.getTooltipInfo(element);
  681. }
  682. return null;
  683. }
  684. private TooltipInfo getHeaderFooterTooltip(CellReference cell) {
  685. Section section = Section.BODY;
  686. if (cell instanceof EventCellReference) {
  687. // Header or footer
  688. section = ((EventCellReference) cell).getSection();
  689. }
  690. StaticCell staticCell = null;
  691. if (section == Section.HEADER) {
  692. staticCell = getWidget().getHeaderRow(cell.getRowIndex())
  693. .getCell(cell.getColumn());
  694. } else if (section == Section.FOOTER) {
  695. staticCell = getWidget().getFooterRow(cell.getRowIndex())
  696. .getCell(cell.getColumn());
  697. }
  698. if (staticCell != null && staticCell.getDescription() != null) {
  699. return new TooltipInfo(staticCell.getDescription(),
  700. staticCell.getDescriptionContentMode());
  701. }
  702. return null;
  703. }
  704. @Override
  705. protected void sendContextClickEvent(MouseEventDetails details,
  706. EventTarget eventTarget) {
  707. // if element is the resize indicator, ignore the event
  708. if (isResizeHandle(eventTarget)) {
  709. WidgetUtil.clearTextSelection();
  710. return;
  711. }
  712. EventCellReference<JsonObject> eventCell = getWidget().getEventCell();
  713. Section section = eventCell.getSection();
  714. String rowKey = null;
  715. if (eventCell.isBody() && eventCell.getRow() != null) {
  716. rowKey = getRowKey(eventCell.getRow());
  717. }
  718. String columnId = getColumnId(eventCell.getColumn());
  719. getRpcProxy(GridServerRpc.class).contextClick(eventCell.getRowIndex(),
  720. rowKey, columnId, section, details);
  721. WidgetUtil.clearTextSelection();
  722. }
  723. private boolean isResizeHandle(EventTarget eventTarget) {
  724. if (Element.is(eventTarget)) {
  725. Element e = Element.as(eventTarget);
  726. if (e.getClassName().contains("-column-resize-handle")) {
  727. return true;
  728. }
  729. }
  730. return false;
  731. }
  732. private List<String> mapColumnsToIds(List<Column<?, JsonObject>> columns) {
  733. return columns.stream().map(this::getColumnId).filter(Objects::nonNull)
  734. .collect(Collectors.toList());
  735. }
  736. }