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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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.Collections;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Objects;
  23. import java.util.Set;
  24. import java.util.stream.Collectors;
  25. import com.google.gwt.dom.client.Element;
  26. import com.google.gwt.dom.client.EventTarget;
  27. import com.google.gwt.dom.client.NativeEvent;
  28. import com.google.gwt.event.shared.HandlerRegistration;
  29. import com.vaadin.client.ComponentConnector;
  30. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  31. import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
  32. import com.vaadin.client.DeferredWorker;
  33. import com.vaadin.client.HasComponentsConnector;
  34. import com.vaadin.client.MouseEventDetailsBuilder;
  35. import com.vaadin.client.TooltipInfo;
  36. import com.vaadin.client.WidgetUtil;
  37. import com.vaadin.client.annotations.OnStateChange;
  38. import com.vaadin.client.connectors.AbstractListingConnector;
  39. import com.vaadin.client.connectors.grid.ColumnConnector.CustomColumn;
  40. import com.vaadin.client.data.DataSource;
  41. import com.vaadin.client.ui.SimpleManagedLayout;
  42. import com.vaadin.client.widget.grid.CellReference;
  43. import com.vaadin.client.widget.grid.EventCellReference;
  44. import com.vaadin.client.widget.grid.events.BodyClickHandler;
  45. import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
  46. import com.vaadin.client.widget.grid.events.GridClickEvent;
  47. import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
  48. import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
  49. import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
  50. import com.vaadin.client.widget.grid.sort.SortEvent;
  51. import com.vaadin.client.widget.grid.sort.SortOrder;
  52. import com.vaadin.client.widgets.Grid;
  53. import com.vaadin.client.widgets.Grid.Column;
  54. import com.vaadin.client.widgets.Grid.FooterRow;
  55. import com.vaadin.client.widgets.Grid.HeaderRow;
  56. import com.vaadin.shared.MouseEventDetails;
  57. import com.vaadin.shared.data.sort.SortDirection;
  58. import com.vaadin.shared.ui.Connect;
  59. import com.vaadin.shared.ui.grid.GridConstants;
  60. import com.vaadin.shared.ui.grid.GridConstants.Section;
  61. import com.vaadin.shared.ui.grid.GridServerRpc;
  62. import com.vaadin.shared.ui.grid.GridState;
  63. import com.vaadin.shared.ui.grid.SectionState;
  64. import com.vaadin.shared.ui.grid.SectionState.CellState;
  65. import com.vaadin.shared.ui.grid.SectionState.RowState;
  66. import elemental.json.JsonObject;
  67. /**
  68. * A connector class for the typed Grid component.
  69. *
  70. * @author Vaadin Ltd
  71. * @since 8.0
  72. */
  73. @Connect(com.vaadin.ui.Grid.class)
  74. public class GridConnector extends AbstractListingConnector
  75. implements HasComponentsConnector, SimpleManagedLayout, DeferredWorker {
  76. private class ItemClickHandler
  77. implements BodyClickHandler, BodyDoubleClickHandler {
  78. @Override
  79. public void onClick(GridClickEvent event) {
  80. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  81. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  82. }
  83. }
  84. @Override
  85. public void onDoubleClick(GridDoubleClickEvent event) {
  86. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  87. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  88. }
  89. }
  90. private void fireItemClick(CellReference<?> cell,
  91. NativeEvent mouseEvent) {
  92. String rowKey = getRowKey((JsonObject) cell.getRow());
  93. String columnId = columnToIdMap.get(cell.getColumn());
  94. getRpcProxy(GridServerRpc.class).itemClick(rowKey, columnId,
  95. MouseEventDetailsBuilder
  96. .buildMouseEventDetails(mouseEvent));
  97. }
  98. }
  99. /* Map to keep track of all added columns */
  100. private Map<CustomColumn, String> columnToIdMap = new HashMap<>();
  101. private Map<String, CustomColumn> idToColumn = new HashMap<>();
  102. /* Child component list for HasComponentsConnector */
  103. private List<ComponentConnector> childComponents;
  104. private SpaceSelectHandler<JsonObject> spaceSelectHandler;
  105. private ClickSelectHandler<JsonObject> clickSelectHandler;
  106. private ItemClickHandler itemClickHandler = new ItemClickHandler();
  107. /**
  108. * Gets the string identifier of the given column in this grid.
  109. *
  110. * @param column
  111. * the column whose id to get
  112. * @return the string id of the column
  113. */
  114. public String getColumnId(Column<?, ?> column) {
  115. return columnToIdMap.get(column);
  116. }
  117. /**
  118. * Gets the column corresponding to the given string identifier.
  119. *
  120. * @param columnId
  121. * the id of the column to get
  122. * @return the column with the given id
  123. */
  124. public CustomColumn getColumn(String columnId) {
  125. return idToColumn.get(columnId);
  126. }
  127. @Override
  128. @SuppressWarnings("unchecked")
  129. public Grid<JsonObject> getWidget() {
  130. return (Grid<JsonObject>) super.getWidget();
  131. }
  132. @Override
  133. protected void init() {
  134. super.init();
  135. // Default selection style is space key.
  136. spaceSelectHandler = new SpaceSelectHandler<>(getWidget());
  137. getWidget().addSortHandler(this::handleSortEvent);
  138. getWidget().setRowStyleGenerator(rowRef -> {
  139. JsonObject json = rowRef.getRow();
  140. return json.hasKey(GridState.JSONKEY_ROWSTYLE)
  141. ? json.getString(GridState.JSONKEY_ROWSTYLE) : null;
  142. });
  143. getWidget().setCellStyleGenerator(cellRef -> {
  144. JsonObject row = cellRef.getRow();
  145. if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
  146. return null;
  147. }
  148. Column<?, JsonObject> column = cellRef.getColumn();
  149. if (column instanceof CustomColumn) {
  150. String id = ((CustomColumn) column).getConnectorId();
  151. JsonObject cellStyles = row
  152. .getObject(GridState.JSONKEY_CELLSTYLES);
  153. if (cellStyles.hasKey(id)) {
  154. return cellStyles.getString(id);
  155. }
  156. }
  157. return null;
  158. });
  159. getWidget().addColumnVisibilityChangeHandler(event -> {
  160. if (event.isUserOriginated()) {
  161. getRpcProxy(GridServerRpc.class).columnVisibilityChanged(
  162. getColumnId(event.getColumn()), event.isHidden());
  163. }
  164. });
  165. getWidget().addColumnReorderHandler(event -> {
  166. if (event.isUserOriginated()) {
  167. List<String> newColumnOrder = mapColumnsToIds(
  168. event.getNewColumnOrder());
  169. List<String> oldColumnOrder = mapColumnsToIds(
  170. event.getOldColumnOrder());
  171. getRpcProxy(GridServerRpc.class)
  172. .columnsReordered(newColumnOrder, oldColumnOrder);
  173. }
  174. });
  175. getWidget().addColumnResizeHandler(event -> {
  176. Column<?, JsonObject> column = event.getColumn();
  177. getRpcProxy(GridServerRpc.class).columnResized(getColumnId(column),
  178. column.getWidthActual());
  179. });
  180. /* Item click events */
  181. getWidget().addBodyClickHandler(itemClickHandler);
  182. getWidget().addBodyDoubleClickHandler(itemClickHandler);
  183. layout();
  184. }
  185. @SuppressWarnings("unchecked")
  186. @OnStateChange("columnOrder")
  187. void updateColumnOrder() {
  188. getWidget().setColumnOrder(getState().columnOrder.stream()
  189. .map(this::getColumn).toArray(size -> new Column[size]));
  190. }
  191. @OnStateChange("columnResizeMode")
  192. void updateColumnResizeMode() {
  193. getWidget().setColumnResizeMode(getState().columnResizeMode);
  194. }
  195. /**
  196. * Updates the grid header section on state change.
  197. */
  198. @OnStateChange("header")
  199. void updateHeader() {
  200. final Grid<JsonObject> grid = getWidget();
  201. final SectionState state = getState().header;
  202. while (grid.getHeaderRowCount() > 0) {
  203. grid.removeHeaderRow(0);
  204. }
  205. for (RowState rowState : state.rows) {
  206. HeaderRow row = grid.appendHeaderRow();
  207. if (rowState.defaultHeader) {
  208. grid.setDefaultHeaderRow(row);
  209. }
  210. updateStaticRow(rowState, row);
  211. }
  212. }
  213. private void updateStaticRow(RowState rowState,
  214. Grid.StaticSection.StaticRow row) {
  215. rowState.cells.forEach((columnId, cellState) -> {
  216. updateStaticCellFromState(row.getCell(getColumn(columnId)),
  217. cellState);
  218. });
  219. for (Map.Entry<CellState, Set<String>> cellGroupEntry : rowState.cellGroups
  220. .entrySet()) {
  221. Set<String> group = cellGroupEntry.getValue();
  222. Grid.Column<?, ?>[] columns = group.stream().map(idToColumn::get)
  223. .toArray(size -> new Grid.Column<?, ?>[size]);
  224. // Set state to be the same as first in group.
  225. updateStaticCellFromState(row.join(columns),
  226. cellGroupEntry.getKey());
  227. }
  228. }
  229. private void updateStaticCellFromState(Grid.StaticSection.StaticCell cell,
  230. CellState cellState) {
  231. switch (cellState.type) {
  232. case TEXT:
  233. cell.setText(cellState.text);
  234. break;
  235. case HTML:
  236. cell.setHtml(cellState.html);
  237. break;
  238. case WIDGET:
  239. ComponentConnector connector = (ComponentConnector) cellState.connector;
  240. if (connector != null) {
  241. cell.setWidget(connector.getWidget());
  242. } else {
  243. // This happens if you do setVisible(false) on the component on
  244. // the server side
  245. cell.setWidget(null);
  246. }
  247. break;
  248. default:
  249. throw new IllegalStateException(
  250. "unexpected cell type: " + cellState.type);
  251. }
  252. cell.setStyleName(cellState.styleName);
  253. }
  254. /**
  255. * Updates the grid footer section on state change.
  256. */
  257. @OnStateChange("footer")
  258. void updateFooter() {
  259. final Grid<JsonObject> grid = getWidget();
  260. final SectionState state = getState().footer;
  261. while (grid.getFooterRowCount() > 0) {
  262. grid.removeFooterRow(0);
  263. }
  264. for (RowState rowState : state.rows) {
  265. FooterRow row = grid.appendFooterRow();
  266. updateStaticRow(rowState, row);
  267. }
  268. }
  269. @Override
  270. public void setDataSource(DataSource<JsonObject> dataSource) {
  271. super.setDataSource(dataSource);
  272. getWidget().setDataSource(dataSource);
  273. }
  274. /**
  275. * Adds a column to the Grid widget. For each column a communication id
  276. * stored for client to server communication.
  277. *
  278. * @param column
  279. * column to add
  280. * @param id
  281. * communication id
  282. */
  283. public void addColumn(CustomColumn column, String id) {
  284. assert !columnToIdMap.containsKey(column) && !columnToIdMap
  285. .containsValue(id) : "Column with given id already exists.";
  286. getWidget().addColumn(column);
  287. columnToIdMap.put(column, id);
  288. idToColumn.put(id, column);
  289. }
  290. /**
  291. * Removes a column from Grid widget. This method also removes communication
  292. * id mapping for the column.
  293. *
  294. * @param column
  295. * column to remove
  296. */
  297. public void removeColumn(CustomColumn column) {
  298. assert columnToIdMap
  299. .containsKey(column) : "Given Column does not exist.";
  300. getWidget().removeColumn(column);
  301. String id = columnToIdMap.remove(column);
  302. idToColumn.remove(id);
  303. }
  304. @Override
  305. public void onUnregister() {
  306. super.onUnregister();
  307. columnToIdMap.clear();
  308. removeClickHandler();
  309. if (spaceSelectHandler != null) {
  310. spaceSelectHandler.removeHandler();
  311. spaceSelectHandler = null;
  312. }
  313. }
  314. @Override
  315. public boolean isWorkPending() {
  316. return getWidget().isWorkPending();
  317. }
  318. @Override
  319. public void layout() {
  320. getWidget().onResize();
  321. }
  322. /**
  323. * Sends sort information from an event to the server-side of the Grid.
  324. *
  325. * @param event
  326. * the sort event
  327. */
  328. private void handleSortEvent(SortEvent<JsonObject> event) {
  329. List<String> columnIds = new ArrayList<>();
  330. List<SortDirection> sortDirections = new ArrayList<>();
  331. for (SortOrder so : event.getOrder()) {
  332. if (columnToIdMap.containsKey(so.getColumn())) {
  333. columnIds.add(columnToIdMap.get(so.getColumn()));
  334. sortDirections.add(so.getDirection());
  335. }
  336. }
  337. getRpcProxy(GridServerRpc.class).sort(columnIds.toArray(new String[0]),
  338. sortDirections.toArray(new SortDirection[0]),
  339. event.isUserOriginated());
  340. }
  341. /* HasComponentsConnector */
  342. @Override
  343. public void updateCaption(ComponentConnector connector) {
  344. // Details components don't support captions.
  345. }
  346. @Override
  347. public List<ComponentConnector> getChildComponents() {
  348. if (childComponents == null) {
  349. return Collections.emptyList();
  350. }
  351. return childComponents;
  352. }
  353. @Override
  354. public void setChildComponents(List<ComponentConnector> children) {
  355. childComponents = children;
  356. }
  357. @Override
  358. public HandlerRegistration addConnectorHierarchyChangeHandler(
  359. ConnectorHierarchyChangeHandler handler) {
  360. return ensureHandlerManager()
  361. .addHandler(ConnectorHierarchyChangeEvent.TYPE, handler);
  362. }
  363. @Override
  364. public GridState getState() {
  365. return (GridState) super.getState();
  366. }
  367. private void removeClickHandler() {
  368. if (clickSelectHandler != null) {
  369. clickSelectHandler.removeHandler();
  370. clickSelectHandler = null;
  371. }
  372. }
  373. @Override
  374. public boolean hasTooltip() {
  375. // Always check for generated descriptions.
  376. return true;
  377. }
  378. @Override
  379. public TooltipInfo getTooltipInfo(Element element) {
  380. CellReference<JsonObject> cell = getWidget().getCellReference(element);
  381. if (cell != null) {
  382. JsonObject row = cell.getRow();
  383. if (row != null && (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)
  384. || row.hasKey(GridState.JSONKEY_CELLDESCRIPTION))) {
  385. Column<?, JsonObject> column = cell.getColumn();
  386. if (column instanceof CustomColumn) {
  387. JsonObject cellDescriptions = row
  388. .getObject(GridState.JSONKEY_CELLDESCRIPTION);
  389. String id = ((CustomColumn) column).getConnectorId();
  390. if (cellDescriptions != null
  391. && cellDescriptions.hasKey(id)) {
  392. return new TooltipInfo(cellDescriptions.getString(id));
  393. } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
  394. return new TooltipInfo(row
  395. .getString(GridState.JSONKEY_ROWDESCRIPTION));
  396. }
  397. }
  398. }
  399. }
  400. if (super.hasTooltip()) {
  401. return super.getTooltipInfo(element);
  402. } else {
  403. return null;
  404. }
  405. }
  406. @Override
  407. protected void sendContextClickEvent(MouseEventDetails details,
  408. EventTarget eventTarget) {
  409. // if element is the resize indicator, ignore the event
  410. if (isResizeHandle(eventTarget)) {
  411. WidgetUtil.clearTextSelection();
  412. return;
  413. }
  414. EventCellReference<JsonObject> eventCell = getWidget().getEventCell();
  415. Section section = eventCell.getSection();
  416. String rowKey = null;
  417. if (eventCell.isBody() && eventCell.getRow() != null) {
  418. rowKey = getRowKey(eventCell.getRow());
  419. }
  420. String columnId = getColumnId(eventCell.getColumn());
  421. getRpcProxy(GridServerRpc.class).contextClick(eventCell.getRowIndex(),
  422. rowKey, columnId, section, details);
  423. WidgetUtil.clearTextSelection();
  424. }
  425. private boolean isResizeHandle(EventTarget eventTarget) {
  426. if (Element.is(eventTarget)) {
  427. Element e = Element.as(eventTarget);
  428. if (e.getClassName().contains("-column-resize-handle")) {
  429. return true;
  430. }
  431. }
  432. return false;
  433. }
  434. private List<String> mapColumnsToIds(List<Column<?, JsonObject>> columns) {
  435. return columns.stream().map(this::getColumnId).filter(Objects::nonNull)
  436. .collect(Collectors.toList());
  437. }
  438. }