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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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.LinkedHashSet;
  24. import java.util.List;
  25. import java.util.Map;
  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.NativeEvent;
  31. import com.google.gwt.user.client.ui.Widget;
  32. import com.vaadin.client.ComponentConnector;
  33. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  34. import com.vaadin.client.MouseEventDetailsBuilder;
  35. import com.vaadin.client.annotations.OnStateChange;
  36. import com.vaadin.client.communication.StateChangeEvent;
  37. import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
  38. import com.vaadin.client.data.DataSource.RowHandle;
  39. import com.vaadin.client.renderers.Renderer;
  40. import com.vaadin.client.ui.AbstractFieldConnector;
  41. import com.vaadin.client.ui.AbstractHasComponentsConnector;
  42. import com.vaadin.client.ui.SimpleManagedLayout;
  43. import com.vaadin.client.widget.grid.CellReference;
  44. import com.vaadin.client.widget.grid.CellStyleGenerator;
  45. import com.vaadin.client.widget.grid.EditorHandler;
  46. import com.vaadin.client.widget.grid.RowReference;
  47. import com.vaadin.client.widget.grid.RowStyleGenerator;
  48. import com.vaadin.client.widget.grid.events.BodyClickHandler;
  49. import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
  50. import com.vaadin.client.widget.grid.events.GridClickEvent;
  51. import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
  52. import com.vaadin.client.widget.grid.events.SelectAllEvent;
  53. import com.vaadin.client.widget.grid.events.SelectAllHandler;
  54. import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel;
  55. import com.vaadin.client.widget.grid.selection.SelectionEvent;
  56. import com.vaadin.client.widget.grid.selection.SelectionHandler;
  57. import com.vaadin.client.widget.grid.selection.SelectionModel;
  58. import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
  59. import com.vaadin.client.widget.grid.selection.SelectionModelNone;
  60. import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
  61. import com.vaadin.client.widget.grid.sort.SortEvent;
  62. import com.vaadin.client.widget.grid.sort.SortHandler;
  63. import com.vaadin.client.widget.grid.sort.SortOrder;
  64. import com.vaadin.client.widgets.Grid;
  65. import com.vaadin.client.widgets.Grid.Column;
  66. import com.vaadin.client.widgets.Grid.FooterCell;
  67. import com.vaadin.client.widgets.Grid.FooterRow;
  68. import com.vaadin.client.widgets.Grid.HeaderCell;
  69. import com.vaadin.client.widgets.Grid.HeaderRow;
  70. import com.vaadin.shared.data.sort.SortDirection;
  71. import com.vaadin.shared.ui.Connect;
  72. import com.vaadin.shared.ui.grid.EditorClientRpc;
  73. import com.vaadin.shared.ui.grid.EditorServerRpc;
  74. import com.vaadin.shared.ui.grid.GridClientRpc;
  75. import com.vaadin.shared.ui.grid.GridColumnState;
  76. import com.vaadin.shared.ui.grid.GridConstants;
  77. import com.vaadin.shared.ui.grid.GridServerRpc;
  78. import com.vaadin.shared.ui.grid.GridState;
  79. import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode;
  80. import com.vaadin.shared.ui.grid.GridStaticSectionState;
  81. import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
  82. import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
  83. import com.vaadin.shared.ui.grid.ScrollDestination;
  84. import elemental.json.JsonObject;
  85. import elemental.json.JsonValue;
  86. /**
  87. * Connects the client side {@link Grid} widget with the server side
  88. * {@link com.vaadin.ui.components.grid.Grid} component.
  89. * <p>
  90. * The Grid is typed to JSONObject. The structure of the JSONObject is described
  91. * at {@link com.vaadin.shared.data.DataProviderRpc#setRowData(int, List)
  92. * DataProviderRpc.setRowData(int, List)}.
  93. *
  94. * @since 7.4
  95. * @author Vaadin Ltd
  96. */
  97. @Connect(com.vaadin.ui.Grid.class)
  98. public class GridConnector extends AbstractHasComponentsConnector implements
  99. SimpleManagedLayout {
  100. private static final class CustomCellStyleGenerator implements
  101. CellStyleGenerator<JsonObject> {
  102. @Override
  103. public String getStyle(CellReference<JsonObject> cellReference) {
  104. JsonObject row = cellReference.getRow();
  105. if (!row.hasKey(GridState.JSONKEY_CELLSTYLES)) {
  106. return null;
  107. }
  108. Column<?, JsonObject> column = cellReference.getColumn();
  109. if (!(column instanceof CustomGridColumn)) {
  110. // Selection checkbox column
  111. return null;
  112. }
  113. CustomGridColumn c = (CustomGridColumn) column;
  114. JsonObject cellStylesObject = row
  115. .getObject(GridState.JSONKEY_CELLSTYLES);
  116. assert cellStylesObject != null;
  117. if (cellStylesObject.hasKey(c.id)) {
  118. return cellStylesObject.getString(c.id);
  119. } else {
  120. return null;
  121. }
  122. }
  123. }
  124. private static final class CustomRowStyleGenerator implements
  125. RowStyleGenerator<JsonObject> {
  126. @Override
  127. public String getStyle(RowReference<JsonObject> rowReference) {
  128. JsonObject row = rowReference.getRow();
  129. if (row.hasKey(GridState.JSONKEY_ROWSTYLE)) {
  130. return row.getString(GridState.JSONKEY_ROWSTYLE);
  131. } else {
  132. return null;
  133. }
  134. }
  135. }
  136. /**
  137. * Custom implementation of the custom grid column using a JSONObject to
  138. * represent the cell value and String as a column type.
  139. */
  140. private class CustomGridColumn extends Grid.Column<Object, JsonObject> {
  141. private final String id;
  142. private AbstractRendererConnector<Object> rendererConnector;
  143. private AbstractFieldConnector editorConnector;
  144. public CustomGridColumn(String id,
  145. AbstractRendererConnector<Object> rendererConnector) {
  146. super(rendererConnector.getRenderer());
  147. this.rendererConnector = rendererConnector;
  148. this.id = id;
  149. }
  150. /**
  151. * Sets a new renderer for this column object
  152. *
  153. * @param rendererConnector
  154. * a renderer connector object
  155. */
  156. public void setRenderer(
  157. AbstractRendererConnector<Object> rendererConnector) {
  158. setRenderer(rendererConnector.getRenderer());
  159. this.rendererConnector = rendererConnector;
  160. }
  161. @Override
  162. public Object getValue(final JsonObject obj) {
  163. final JsonObject rowData = obj.getObject(GridState.JSONKEY_DATA);
  164. if (rowData.hasKey(id)) {
  165. final JsonValue columnValue = rowData.get(id);
  166. return rendererConnector.decode(columnValue);
  167. }
  168. return null;
  169. }
  170. private AbstractFieldConnector getEditorConnector() {
  171. return editorConnector;
  172. }
  173. private void setEditorConnector(AbstractFieldConnector editorConnector) {
  174. this.editorConnector = editorConnector;
  175. }
  176. }
  177. /*
  178. * An editor handler using Vaadin RPC to manage the editor state.
  179. */
  180. private class CustomEditorHandler implements EditorHandler<JsonObject> {
  181. private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
  182. private EditorRequest<JsonObject> currentRequest = null;
  183. private boolean serverInitiated = false;
  184. public CustomEditorHandler() {
  185. registerRpc(EditorClientRpc.class, new EditorClientRpc() {
  186. @Override
  187. public void bind(final int rowIndex) {
  188. // call this finally to avoid issues with editing on init
  189. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  190. @Override
  191. public void execute() {
  192. GridConnector.this.getWidget().editRow(rowIndex);
  193. }
  194. });
  195. }
  196. @Override
  197. public void cancel(int rowIndex) {
  198. serverInitiated = true;
  199. GridConnector.this.getWidget().cancelEditor();
  200. }
  201. @Override
  202. public void confirmBind(final boolean bindSucceeded) {
  203. endRequest(bindSucceeded, null, null);
  204. }
  205. @Override
  206. public void confirmSave(boolean saveSucceeded,
  207. String errorMessage, List<String> errorColumnsIds) {
  208. endRequest(saveSucceeded, errorMessage, errorColumnsIds);
  209. }
  210. });
  211. }
  212. @Override
  213. public void bind(EditorRequest<JsonObject> request) {
  214. startRequest(request);
  215. rpc.bind(request.getRowIndex());
  216. }
  217. @Override
  218. public void save(EditorRequest<JsonObject> request) {
  219. startRequest(request);
  220. rpc.save(request.getRowIndex());
  221. }
  222. @Override
  223. public void cancel(EditorRequest<JsonObject> request) {
  224. if (!handleServerInitiated(request)) {
  225. // No startRequest as we don't get (or need)
  226. // a confirmation from the server
  227. rpc.cancel(request.getRowIndex());
  228. }
  229. }
  230. @Override
  231. public Widget getWidget(Grid.Column<?, JsonObject> column) {
  232. assert column != null;
  233. if (column instanceof CustomGridColumn) {
  234. AbstractFieldConnector c = ((CustomGridColumn) column)
  235. .getEditorConnector();
  236. return c != null ? c.getWidget() : null;
  237. } else {
  238. throw new IllegalStateException("Unexpected column type: "
  239. + column.getClass().getName());
  240. }
  241. }
  242. /**
  243. * Used to handle the case where the editor calls us because it was
  244. * invoked by the server via RPC and not by the client. In that case,
  245. * the request can be simply synchronously completed.
  246. *
  247. * @param request
  248. * the request object
  249. * @return true if the request was originally triggered by the server,
  250. * false otherwise
  251. */
  252. private boolean handleServerInitiated(EditorRequest<?> request) {
  253. assert request != null : "Cannot handle null request";
  254. assert currentRequest == null : "Earlier request not yet finished";
  255. if (serverInitiated) {
  256. serverInitiated = false;
  257. request.success();
  258. return true;
  259. } else {
  260. return false;
  261. }
  262. }
  263. private void startRequest(EditorRequest<JsonObject> request) {
  264. assert currentRequest == null : "Earlier request not yet finished";
  265. currentRequest = request;
  266. }
  267. private void endRequest(boolean succeeded, String errorMessage,
  268. List<String> errorColumnsIds) {
  269. assert currentRequest != null : "Current request was null";
  270. /*
  271. * Clear current request first to ensure the state is valid if
  272. * another request is made in the callback.
  273. */
  274. EditorRequest<JsonObject> request = currentRequest;
  275. currentRequest = null;
  276. if (succeeded) {
  277. request.success();
  278. } else {
  279. Collection<Column<?, JsonObject>> errorColumns;
  280. if (errorColumnsIds != null) {
  281. errorColumns = new ArrayList<Grid.Column<?, JsonObject>>();
  282. for (String colId : errorColumnsIds) {
  283. errorColumns.add(columnIdToColumn.get(colId));
  284. }
  285. } else {
  286. errorColumns = null;
  287. }
  288. request.failure(errorMessage, errorColumns);
  289. }
  290. }
  291. }
  292. private class ItemClickHandler implements BodyClickHandler,
  293. BodyDoubleClickHandler {
  294. @Override
  295. public void onClick(GridClickEvent event) {
  296. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  297. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  298. }
  299. }
  300. @Override
  301. public void onDoubleClick(GridDoubleClickEvent event) {
  302. if (hasEventListener(GridConstants.ITEM_CLICK_EVENT_ID)) {
  303. fireItemClick(event.getTargetCell(), event.getNativeEvent());
  304. }
  305. }
  306. private void fireItemClick(CellReference<?> cell, NativeEvent mouseEvent) {
  307. String rowKey = getRowKey((JsonObject) cell.getRow());
  308. String columnId = getColumnId(cell.getColumn());
  309. getRpcProxy(GridServerRpc.class)
  310. .itemClick(
  311. rowKey,
  312. columnId,
  313. MouseEventDetailsBuilder
  314. .buildMouseEventDetails(mouseEvent));
  315. }
  316. }
  317. /**
  318. * Maps a generated column id to a grid column instance
  319. */
  320. private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
  321. private AbstractRowHandleSelectionModel<JsonObject> selectionModel;
  322. private Set<String> selectedKeys = new LinkedHashSet<String>();
  323. private List<String> columnOrder = new ArrayList<String>();
  324. /**
  325. * updateFromState is set to true when {@link #updateSelectionFromState()}
  326. * makes changes to selection. This flag tells the
  327. * {@code internalSelectionChangeHandler} to not send same data straight
  328. * back to server. Said listener sets it back to false when handling that
  329. * event.
  330. */
  331. private boolean updatedFromState = false;
  332. private RpcDataSource dataSource;
  333. private SelectionHandler<JsonObject> internalSelectionChangeHandler = new SelectionHandler<JsonObject>() {
  334. @Override
  335. public void onSelect(SelectionEvent<JsonObject> event) {
  336. if (event.isBatchedSelection()) {
  337. return;
  338. }
  339. if (!updatedFromState) {
  340. for (JsonObject row : event.getRemoved()) {
  341. selectedKeys.remove(dataSource.getRowKey(row));
  342. }
  343. for (JsonObject row : event.getAdded()) {
  344. selectedKeys.add(dataSource.getRowKey(row));
  345. }
  346. getRpcProxy(GridServerRpc.class).select(
  347. new ArrayList<String>(selectedKeys));
  348. } else {
  349. updatedFromState = false;
  350. }
  351. }
  352. };
  353. private ItemClickHandler itemClickHandler = new ItemClickHandler();
  354. private String lastKnownTheme = null;
  355. @Override
  356. @SuppressWarnings("unchecked")
  357. public Grid<JsonObject> getWidget() {
  358. return (Grid<JsonObject>) super.getWidget();
  359. }
  360. @Override
  361. public GridState getState() {
  362. return (GridState) super.getState();
  363. }
  364. @Override
  365. protected void init() {
  366. super.init();
  367. // All scroll RPC calls are executed finally to avoid issues on init
  368. registerRpc(GridClientRpc.class, new GridClientRpc() {
  369. @Override
  370. public void scrollToStart() {
  371. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  372. @Override
  373. public void execute() {
  374. getWidget().scrollToStart();
  375. }
  376. });
  377. }
  378. @Override
  379. public void scrollToEnd() {
  380. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  381. @Override
  382. public void execute() {
  383. getWidget().scrollToEnd();
  384. }
  385. });
  386. }
  387. @Override
  388. public void scrollToRow(final int row,
  389. final ScrollDestination destination) {
  390. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  391. @Override
  392. public void execute() {
  393. getWidget().scrollToRow(row, destination);
  394. }
  395. });
  396. }
  397. @Override
  398. public void recalculateColumnWidths() {
  399. getWidget().recalculateColumnWidths();
  400. }
  401. });
  402. getWidget().addSelectionHandler(internalSelectionChangeHandler);
  403. /* Item click events */
  404. getWidget().addBodyClickHandler(itemClickHandler);
  405. getWidget().addBodyDoubleClickHandler(itemClickHandler);
  406. getWidget().addSortHandler(new SortHandler<JsonObject>() {
  407. @Override
  408. public void sort(SortEvent<JsonObject> event) {
  409. List<SortOrder> order = event.getOrder();
  410. String[] columnIds = new String[order.size()];
  411. SortDirection[] directions = new SortDirection[order.size()];
  412. for (int i = 0; i < order.size(); i++) {
  413. SortOrder sortOrder = order.get(i);
  414. CustomGridColumn column = (CustomGridColumn) sortOrder
  415. .getColumn();
  416. columnIds[i] = column.id;
  417. directions[i] = sortOrder.getDirection();
  418. }
  419. if (!Arrays.equals(columnIds, getState().sortColumns)
  420. || !Arrays.equals(directions, getState().sortDirs)) {
  421. // Report back to server if changed
  422. getRpcProxy(GridServerRpc.class).sort(columnIds,
  423. directions, event.isUserOriginated());
  424. }
  425. }
  426. });
  427. getWidget().addSelectAllHandler(new SelectAllHandler<JsonObject>() {
  428. @Override
  429. public void onSelectAll(SelectAllEvent<JsonObject> event) {
  430. getRpcProxy(GridServerRpc.class).selectAll();
  431. }
  432. });
  433. getWidget().setEditorHandler(new CustomEditorHandler());
  434. getLayoutManager().registerDependency(this, getWidget().getElement());
  435. layout();
  436. }
  437. @Override
  438. public void onStateChanged(final StateChangeEvent stateChangeEvent) {
  439. super.onStateChanged(stateChangeEvent);
  440. // Column updates
  441. if (stateChangeEvent.hasPropertyChanged("columns")) {
  442. // Remove old columns
  443. purgeRemovedColumns();
  444. // Add new columns
  445. for (GridColumnState state : getState().columns) {
  446. if (!columnIdToColumn.containsKey(state.id)) {
  447. addColumnFromStateChangeEvent(state);
  448. }
  449. updateColumnFromState(columnIdToColumn.get(state.id), state);
  450. }
  451. }
  452. if (stateChangeEvent.hasPropertyChanged("columnOrder")) {
  453. if (orderNeedsUpdate(getState().columnOrder)) {
  454. updateColumnOrderFromState(getState().columnOrder);
  455. }
  456. }
  457. // Header and footer
  458. if (stateChangeEvent.hasPropertyChanged("header")) {
  459. updateHeaderFromState(getState().header);
  460. }
  461. if (stateChangeEvent.hasPropertyChanged("footer")) {
  462. updateFooterFromState(getState().footer);
  463. }
  464. // Selection
  465. if (stateChangeEvent.hasPropertyChanged("selectionMode")) {
  466. onSelectionModeChange();
  467. updateSelectDeselectAllowed();
  468. } else if (stateChangeEvent
  469. .hasPropertyChanged("singleSelectDeselectAllowed")) {
  470. updateSelectDeselectAllowed();
  471. }
  472. if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
  473. updateSelectionFromState();
  474. }
  475. // Sorting
  476. if (stateChangeEvent.hasPropertyChanged("sortColumns")
  477. || stateChangeEvent.hasPropertyChanged("sortDirs")) {
  478. onSortStateChange();
  479. }
  480. // Editor
  481. if (stateChangeEvent.hasPropertyChanged("editorEnabled")) {
  482. getWidget().setEditorEnabled(getState().editorEnabled);
  483. }
  484. // Frozen columns
  485. if (stateChangeEvent.hasPropertyChanged("frozenColumnCount")) {
  486. getWidget().setFrozenColumnCount(getState().frozenColumnCount);
  487. }
  488. // Theme features
  489. String activeTheme = getConnection().getUIConnector().getActiveTheme();
  490. if (lastKnownTheme == null) {
  491. lastKnownTheme = activeTheme;
  492. } else if (!lastKnownTheme.equals(activeTheme)) {
  493. getWidget().resetSizesFromDom();
  494. lastKnownTheme = activeTheme;
  495. }
  496. }
  497. private void updateSelectDeselectAllowed() {
  498. SelectionModel<JsonObject> model = getWidget().getSelectionModel();
  499. if (model instanceof SelectionModel.Single<?>) {
  500. ((SelectionModel.Single<?>) model)
  501. .setDeselectAllowed(getState().singleSelectDeselectAllowed);
  502. }
  503. }
  504. private void updateColumnOrderFromState(List<String> stateColumnOrder) {
  505. CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
  506. .size()];
  507. int i = 0;
  508. for (String id : stateColumnOrder) {
  509. columns[i] = columnIdToColumn.get(id);
  510. i++;
  511. }
  512. getWidget().setColumnOrder(columns);
  513. columnOrder = stateColumnOrder;
  514. }
  515. private boolean orderNeedsUpdate(List<String> stateColumnOrder) {
  516. if (stateColumnOrder.size() == columnOrder.size()) {
  517. for (int i = 0; i < columnOrder.size(); ++i) {
  518. if (!stateColumnOrder.get(i).equals(columnOrder.get(i))) {
  519. return true;
  520. }
  521. }
  522. return false;
  523. }
  524. return true;
  525. }
  526. private void updateHeaderFromState(GridStaticSectionState state) {
  527. getWidget().setHeaderVisible(state.visible);
  528. while (getWidget().getHeaderRowCount() > 0) {
  529. getWidget().removeHeaderRow(0);
  530. }
  531. for (RowState rowState : state.rows) {
  532. HeaderRow row = getWidget().appendHeaderRow();
  533. for (CellState cellState : rowState.cells) {
  534. CustomGridColumn column = columnIdToColumn
  535. .get(cellState.columnId);
  536. updateHeaderCellFromState(row.getCell(column), cellState);
  537. }
  538. for (Set<String> group : rowState.cellGroups.keySet()) {
  539. Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
  540. .size()];
  541. CellState cellState = rowState.cellGroups.get(group);
  542. int i = 0;
  543. for (String columnId : group) {
  544. columns[i] = columnIdToColumn.get(columnId);
  545. i++;
  546. }
  547. // Set state to be the same as first in group.
  548. updateHeaderCellFromState(row.join(columns), cellState);
  549. }
  550. if (rowState.defaultRow) {
  551. getWidget().setDefaultHeaderRow(row);
  552. }
  553. row.setStyleName(rowState.styleName);
  554. }
  555. }
  556. private void updateHeaderCellFromState(HeaderCell cell, CellState cellState) {
  557. switch (cellState.type) {
  558. case TEXT:
  559. cell.setText(cellState.text);
  560. break;
  561. case HTML:
  562. cell.setHtml(cellState.html);
  563. break;
  564. case WIDGET:
  565. ComponentConnector connector = (ComponentConnector) cellState.connector;
  566. cell.setWidget(connector.getWidget());
  567. break;
  568. default:
  569. throw new IllegalStateException("unexpected cell type: "
  570. + cellState.type);
  571. }
  572. cell.setStyleName(cellState.styleName);
  573. }
  574. private void updateFooterFromState(GridStaticSectionState state) {
  575. getWidget().setFooterVisible(state.visible);
  576. while (getWidget().getFooterRowCount() > 0) {
  577. getWidget().removeFooterRow(0);
  578. }
  579. for (RowState rowState : state.rows) {
  580. FooterRow row = getWidget().appendFooterRow();
  581. for (CellState cellState : rowState.cells) {
  582. CustomGridColumn column = columnIdToColumn
  583. .get(cellState.columnId);
  584. updateFooterCellFromState(row.getCell(column), cellState);
  585. }
  586. for (Set<String> group : rowState.cellGroups.keySet()) {
  587. Grid.Column<?, ?>[] columns = new Grid.Column<?, ?>[group
  588. .size()];
  589. CellState cellState = rowState.cellGroups.get(group);
  590. int i = 0;
  591. for (String columnId : group) {
  592. columns[i] = columnIdToColumn.get(columnId);
  593. i++;
  594. }
  595. // Set state to be the same as first in group.
  596. updateFooterCellFromState(row.join(columns), cellState);
  597. }
  598. row.setStyleName(rowState.styleName);
  599. }
  600. }
  601. private void updateFooterCellFromState(FooterCell cell, CellState cellState) {
  602. switch (cellState.type) {
  603. case TEXT:
  604. cell.setText(cellState.text);
  605. break;
  606. case HTML:
  607. cell.setHtml(cellState.html);
  608. break;
  609. case WIDGET:
  610. ComponentConnector connector = (ComponentConnector) cellState.connector;
  611. cell.setWidget(connector.getWidget());
  612. break;
  613. default:
  614. throw new IllegalStateException("unexpected cell type: "
  615. + cellState.type);
  616. }
  617. cell.setStyleName(cellState.styleName);
  618. }
  619. /**
  620. * Updates a column from a state change event.
  621. *
  622. * @param columnIndex
  623. * The index of the column to update
  624. */
  625. private void updateColumnFromStateChangeEvent(GridColumnState columnState) {
  626. CustomGridColumn column = columnIdToColumn.get(columnState.id);
  627. updateColumnFromState(column, columnState);
  628. }
  629. /**
  630. * Adds a new column to the grid widget from a state change event
  631. *
  632. * @param columnIndex
  633. * The index of the column, according to how it
  634. */
  635. private void addColumnFromStateChangeEvent(GridColumnState state) {
  636. @SuppressWarnings("unchecked")
  637. CustomGridColumn column = new CustomGridColumn(state.id,
  638. ((AbstractRendererConnector<Object>) state.rendererConnector));
  639. columnIdToColumn.put(state.id, column);
  640. /*
  641. * Add column to grid. Reordering is handled as a separate problem.
  642. */
  643. getWidget().addColumn(column);
  644. columnOrder.add(state.id);
  645. }
  646. /**
  647. * If we have a selection column renderer, we need to offset the index by
  648. * one when referring to the column index in the widget.
  649. */
  650. private int getWidgetColumnIndex(final int columnIndex) {
  651. Renderer<Boolean> selectionColumnRenderer = getWidget()
  652. .getSelectionModel().getSelectionColumnRenderer();
  653. int widgetColumnIndex = columnIndex;
  654. if (selectionColumnRenderer != null) {
  655. widgetColumnIndex++;
  656. }
  657. return widgetColumnIndex;
  658. }
  659. /**
  660. * Updates the column values from a state
  661. *
  662. * @param column
  663. * The column to update
  664. * @param state
  665. * The state to get the data from
  666. */
  667. @SuppressWarnings("unchecked")
  668. private static void updateColumnFromState(CustomGridColumn column,
  669. GridColumnState state) {
  670. column.setWidth(state.width);
  671. column.setMinimumWidth(state.minWidth);
  672. column.setMaximumWidth(state.maxWidth);
  673. column.setExpandRatio(state.expandRatio);
  674. assert state.rendererConnector instanceof AbstractRendererConnector : "GridColumnState.rendererConnector is invalid (not subclass of AbstractRendererConnector)";
  675. column.setRenderer((AbstractRendererConnector<Object>) state.rendererConnector);
  676. column.setSortable(state.sortable);
  677. column.setEditable(state.editable);
  678. column.setEditorConnector((AbstractFieldConnector) state.editorConnector);
  679. }
  680. /**
  681. * Removes any orphan columns that has been removed from the state from the
  682. * grid
  683. */
  684. private void purgeRemovedColumns() {
  685. // Get columns still registered in the state
  686. Set<String> columnsInState = new HashSet<String>();
  687. for (GridColumnState columnState : getState().columns) {
  688. columnsInState.add(columnState.id);
  689. }
  690. // Remove column no longer in state
  691. Iterator<String> columnIdIterator = columnIdToColumn.keySet()
  692. .iterator();
  693. while (columnIdIterator.hasNext()) {
  694. String id = columnIdIterator.next();
  695. if (!columnsInState.contains(id)) {
  696. CustomGridColumn column = columnIdToColumn.get(id);
  697. columnIdIterator.remove();
  698. getWidget().removeColumn(column);
  699. columnOrder.remove(id);
  700. }
  701. }
  702. }
  703. public void setDataSource(RpcDataSource dataSource) {
  704. this.dataSource = dataSource;
  705. getWidget().setDataSource(this.dataSource);
  706. }
  707. private void onSelectionModeChange() {
  708. SharedSelectionMode mode = getState().selectionMode;
  709. if (mode == null) {
  710. getLogger().fine("ignored mode change");
  711. return;
  712. }
  713. AbstractRowHandleSelectionModel<JsonObject> model = createSelectionModel(mode);
  714. if (selectionModel == null
  715. || !model.getClass().equals(selectionModel.getClass())) {
  716. selectionModel = model;
  717. getWidget().setSelectionModel(model);
  718. selectedKeys.clear();
  719. }
  720. }
  721. @OnStateChange("hasCellStyleGenerator")
  722. private void onCellStyleGeneratorChange() {
  723. if (getState().hasCellStyleGenerator) {
  724. getWidget().setCellStyleGenerator(new CustomCellStyleGenerator());
  725. } else {
  726. getWidget().setCellStyleGenerator(null);
  727. }
  728. }
  729. @OnStateChange("hasRowStyleGenerator")
  730. private void onRowStyleGeneratorChange() {
  731. if (getState().hasRowStyleGenerator) {
  732. getWidget().setRowStyleGenerator(new CustomRowStyleGenerator());
  733. } else {
  734. getWidget().setRowStyleGenerator(null);
  735. }
  736. }
  737. private void updateSelectionFromState() {
  738. boolean changed = false;
  739. List<String> stateKeys = getState().selectedKeys;
  740. // find new deselections
  741. for (String key : selectedKeys) {
  742. if (!stateKeys.contains(key)) {
  743. changed = true;
  744. deselectByHandle(dataSource.getHandleByKey(key));
  745. }
  746. }
  747. // find new selections
  748. for (String key : stateKeys) {
  749. if (!selectedKeys.contains(key)) {
  750. changed = true;
  751. selectByHandle(dataSource.getHandleByKey(key));
  752. }
  753. }
  754. /*
  755. * A defensive copy in case the collection in the state is mutated
  756. * instead of re-assigned.
  757. */
  758. selectedKeys = new LinkedHashSet<String>(stateKeys);
  759. /*
  760. * We need to fire this event so that Grid is able to re-render the
  761. * selection changes (if applicable).
  762. */
  763. if (changed) {
  764. // At least for now there's no way to send the selected and/or
  765. // deselected row data. Some data is only stored as keys
  766. updatedFromState = true;
  767. getWidget().fireEvent(
  768. new SelectionEvent<JsonObject>(getWidget(),
  769. (List<JsonObject>) null, null, false));
  770. }
  771. }
  772. private void onSortStateChange() {
  773. List<SortOrder> sortOrder = new ArrayList<SortOrder>();
  774. String[] sortColumns = getState().sortColumns;
  775. SortDirection[] sortDirs = getState().sortDirs;
  776. for (int i = 0; i < sortColumns.length; i++) {
  777. sortOrder.add(new SortOrder(columnIdToColumn.get(sortColumns[i]),
  778. sortDirs[i]));
  779. }
  780. getWidget().setSortOrder(sortOrder);
  781. }
  782. private Logger getLogger() {
  783. return Logger.getLogger(getClass().getName());
  784. }
  785. @SuppressWarnings("static-method")
  786. private AbstractRowHandleSelectionModel<JsonObject> createSelectionModel(
  787. SharedSelectionMode mode) {
  788. switch (mode) {
  789. case SINGLE:
  790. return new SelectionModelSingle<JsonObject>();
  791. case MULTI:
  792. return new SelectionModelMulti<JsonObject>();
  793. case NONE:
  794. return new SelectionModelNone<JsonObject>();
  795. default:
  796. throw new IllegalStateException("unexpected mode value: " + mode);
  797. }
  798. }
  799. /**
  800. * A workaround method for accessing the protected method
  801. * {@code AbstractRowHandleSelectionModel.selectByHandle}
  802. */
  803. private native void selectByHandle(RowHandle<JsonObject> handle)
  804. /*-{
  805. var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
  806. model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle);
  807. }-*/;
  808. /**
  809. * A workaround method for accessing the protected method
  810. * {@code AbstractRowHandleSelectionModel.deselectByHandle}
  811. */
  812. private native void deselectByHandle(RowHandle<JsonObject> handle)
  813. /*-{
  814. var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
  815. model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle);
  816. }-*/;
  817. /**
  818. * Gets the row key for a row object.
  819. *
  820. * @param row
  821. * the row object
  822. * @return the key for the given row
  823. */
  824. public String getRowKey(JsonObject row) {
  825. final Object key = dataSource.getRowKey(row);
  826. assert key instanceof String : "Internal key was not a String but a "
  827. + key.getClass().getSimpleName() + " (" + key + ")";
  828. return (String) key;
  829. }
  830. /*
  831. * (non-Javadoc)
  832. *
  833. * @see
  834. * com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin.client
  835. * .ComponentConnector)
  836. */
  837. @Override
  838. public void updateCaption(ComponentConnector connector) {
  839. // TODO Auto-generated method stub
  840. }
  841. @Override
  842. public void onConnectorHierarchyChange(
  843. ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
  844. }
  845. public String getColumnId(Grid.Column<?, ?> column) {
  846. if (column instanceof CustomGridColumn) {
  847. return ((CustomGridColumn) column).id;
  848. }
  849. return null;
  850. }
  851. @Override
  852. public void layout() {
  853. getWidget().onResize();
  854. }
  855. }