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

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