summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <teemusa@vaadin.com>2015-07-06 10:01:17 +0300
committerTeemu Suo-Anttila <teemusa@vaadin.com>2015-09-01 11:58:57 +0000
commit53a4b2c77a6af86c157884c62e6193911242a7f9 (patch)
tree033b8b64744abb3a8b63df52a748261b656247cd
parent9899aa2009d32e375ccd21e2edf50cb6d2d557bb (diff)
downloadvaadin-framework-53a4b2c77a6af86c157884c62e6193911242a7f9.tar.gz
vaadin-framework-53a4b2c77a6af86c157884c62e6193911242a7f9.zip
Refactor Grid SelectionModels as extensions (#18624)
This patch removes all selection related variables and API from several core parts of Grid. Change-Id: Idb7aa48fda69ded1ef58a69c1f7dbc78b7f52a54
-rw-r--r--WebContent/release-notes.html2
-rw-r--r--client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java82
-rw-r--r--client/src/com/vaadin/client/connectors/GridConnector.java207
-rw-r--r--client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java360
-rw-r--r--client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java42
-rw-r--r--client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java6
-rw-r--r--client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java148
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java12
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java25
-rw-r--r--server/src/com/vaadin/ui/Grid.java317
-rw-r--r--shared/src/com/vaadin/shared/data/DataProviderRpc.java12
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java11
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java4
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridState.java15
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java55
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java31
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java35
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java30
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java37
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java58
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java61
21 files changed, 1135 insertions, 415 deletions
diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html
index 61511b3002..e9b4a24ce1 100644
--- a/WebContent/release-notes.html
+++ b/WebContent/release-notes.html
@@ -119,6 +119,8 @@
This may interfere with custom response compression solutions that do not respect the Content-Encoding response header.</li>
<li>Unused methods related to the "out of sync" message have been removed from SystemMessages class.</li>
<li>All notifications use the WAI-ARIA alert role to be compatible with Jaws</li>
+ <li>Grid SelectionModels are now Extensions. This update removes all selection related variables and API from
+ GridConnector, GridState, GridServerRpc and GridClientRpc</li>
</ul>
<h3 id="knownissues">Known Issues and Limitations</h3>
<ul>
diff --git a/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
new file mode 100644
index 0000000000..8ca2292bc5
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import java.util.Collection;
+
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.shared.ui.grid.GridState;
+
+import elemental.json.JsonObject;
+
+/**
+ * Base class for all selection model connectors.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractSelectionModelConnector<T extends SelectionModel<JsonObject>>
+ extends AbstractExtensionConnector {
+
+ @Override
+ public GridConnector getParent() {
+ return (GridConnector) super.getParent();
+ }
+
+ protected Grid<JsonObject> getGrid() {
+ return getParent().getWidget();
+ }
+
+ protected RowHandle<JsonObject> getRowHandle(JsonObject row) {
+ return getGrid().getDataSource().getHandle(row);
+ }
+
+ protected String getRowKey(JsonObject row) {
+ return row != null ? getParent().getRowKey(row) : null;
+ }
+
+ protected abstract T createSelectionModel();
+
+ public abstract static class AbstractSelectionModel implements
+ SelectionModel<JsonObject> {
+
+ @Override
+ public boolean isSelected(JsonObject row) {
+ return row.hasKey(GridState.JSONKEY_SELECTED);
+ }
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ // NO-OP
+ }
+
+ @Override
+ public void reset() {
+ // Should not need any actions.
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRows() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected rows.");
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java
index 5f9341c068..1070a46287 100644
--- a/client/src/com/vaadin/client/connectors/GridConnector.java
+++ b/client/src/com/vaadin/client/connectors/GridConnector.java
@@ -22,7 +22,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -36,7 +35,6 @@ import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
@@ -48,8 +46,6 @@ import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener;
import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource;
-import com.vaadin.client.data.DataSource.RowHandle;
-import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.AbstractHasComponentsConnector;
import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
@@ -72,15 +68,6 @@ import com.vaadin.client.widget.grid.events.EditorMoveEvent;
import com.vaadin.client.widget.grid.events.EditorOpenEvent;
import com.vaadin.client.widget.grid.events.GridClickEvent;
import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
-import com.vaadin.client.widget.grid.events.SelectAllEvent;
-import com.vaadin.client.widget.grid.events.SelectAllHandler;
-import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel;
-import com.vaadin.client.widget.grid.selection.SelectionEvent;
-import com.vaadin.client.widget.grid.selection.SelectionHandler;
-import com.vaadin.client.widget.grid.selection.SelectionModel;
-import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
-import com.vaadin.client.widget.grid.selection.SelectionModelNone;
-import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
import com.vaadin.client.widget.grid.sort.SortEvent;
import com.vaadin.client.widget.grid.sort.SortHandler;
import com.vaadin.client.widget.grid.sort.SortOrder;
@@ -99,7 +86,6 @@ import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
-import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode;
import com.vaadin.shared.ui.grid.GridStaticSectionState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
@@ -584,20 +570,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements
*/
private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
- private AbstractRowHandleSelectionModel<JsonObject> selectionModel;
- private Set<String> selectedKeys = new LinkedHashSet<String>();
private List<String> columnOrder = new ArrayList<String>();
/**
- * {@link #selectionUpdatedFromState} is set to true when
- * {@link #updateSelectionFromState()} makes changes to selection. This flag
- * tells the {@code internalSelectionChangeHandler} to not send same data
- * straight back to server. Said listener sets it back to false when
- * handling that event.
- */
- private boolean selectionUpdatedFromState;
-
- /**
* {@link #columnsUpdatedFromState} is set to true when
* {@link #updateColumnOrderFromState(List)} is updating the column order
* for the widget. This flag tells the {@link #columnReorderHandler} to not
@@ -608,29 +583,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
private RpcDataSource dataSource;
- private SelectionHandler<JsonObject> internalSelectionChangeHandler = new SelectionHandler<JsonObject>() {
- @Override
- public void onSelect(SelectionEvent<JsonObject> event) {
- if (event.isBatchedSelection()) {
- return;
- }
- if (!selectionUpdatedFromState) {
- for (JsonObject row : event.getRemoved()) {
- selectedKeys.remove(dataSource.getRowKey(row));
- }
-
- for (JsonObject row : event.getAdded()) {
- selectedKeys.add(dataSource.getRowKey(row));
- }
-
- getRpcProxy(GridServerRpc.class).select(
- new ArrayList<String>(selectedKeys));
- } else {
- selectionUpdatedFromState = false;
- }
- }
- };
-
/* Used to track Grid editor columns with validation errors */
private final Map<Column<?, JsonObject>, String> columnToErrorMessage = new HashMap<Column<?, JsonObject>, String>();
@@ -744,30 +696,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements
public void recalculateColumnWidths() {
getWidget().recalculateColumnWidths();
}
-
- @Override
- public void setSelectAll(boolean allSelected) {
- if (selectionModel instanceof SelectionModel.Multi
- && selectionModel.getSelectionColumnRenderer() != null) {
- final Widget widget;
- try {
- HeaderRow defaultHeaderRow = getWidget()
- .getDefaultHeaderRow();
- if (defaultHeaderRow != null) {
- widget = defaultHeaderRow.getCell(
- getWidget().getColumn(0)).getWidget();
- ((CheckBox) widget).setValue(allSelected, false);
- }
- } catch (Exception e) {
- getLogger().warning(
- "Problems finding select all checkbox.");
- }
- }
- }
});
- getWidget().addSelectionHandler(internalSelectionChangeHandler);
-
/* Item click events */
getWidget().addBodyClickHandler(itemClickHandler);
getWidget().addBodyDoubleClickHandler(itemClickHandler);
@@ -800,15 +730,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
});
- getWidget().addSelectAllHandler(new SelectAllHandler<JsonObject>() {
-
- @Override
- public void onSelectAll(SelectAllEvent<JsonObject> event) {
- getRpcProxy(GridServerRpc.class).selectAll();
- }
-
- });
-
getWidget().setEditorHandler(editorHandler);
getWidget().addColumnReorderHandler(columnReorderHandler);
getWidget().addColumnVisibilityChangeHandler(
@@ -884,19 +805,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
updateFooterFromState(getState().footer);
}
- // Selection
- if (stateChangeEvent.hasPropertyChanged("selectionMode")) {
- onSelectionModeChange();
- updateSelectDeselectAllowed();
- } else if (stateChangeEvent
- .hasPropertyChanged("singleSelectDeselectAllowed")) {
- updateSelectDeselectAllowed();
- }
-
- if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
- updateSelectionFromState();
- }
-
// Sorting
if (stateChangeEvent.hasPropertyChanged("sortColumns")
|| stateChangeEvent.hasPropertyChanged("sortDirs")) {
@@ -923,14 +831,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
}
- private void updateSelectDeselectAllowed() {
- SelectionModel<JsonObject> model = getWidget().getSelectionModel();
- if (model instanceof SelectionModel.Single<?>) {
- ((SelectionModel.Single<?>) model)
- .setDeselectAllowed(getState().singleSelectDeselectAllowed);
- }
- }
-
private void updateColumnOrderFromState(List<String> stateColumnOrder) {
CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder
.size()];
@@ -1103,20 +1003,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
}
/**
- * If we have a selection column renderer, we need to offset the index by
- * one when referring to the column index in the widget.
- */
- private int getWidgetColumnIndex(final int columnIndex) {
- Renderer<Boolean> selectionColumnRenderer = getWidget()
- .getSelectionModel().getSelectionColumnRenderer();
- int widgetColumnIndex = columnIndex;
- if (selectionColumnRenderer != null) {
- widgetColumnIndex++;
- }
- return widgetColumnIndex;
- }
-
- /**
* Updates the column values from a state
*
* @param column
@@ -1178,63 +1064,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
getWidget().setDataSource(this.dataSource);
}
- private void onSelectionModeChange() {
- SharedSelectionMode mode = getState().selectionMode;
- if (mode == null) {
- getLogger().fine("ignored mode change");
- return;
- }
-
- AbstractRowHandleSelectionModel<JsonObject> model = createSelectionModel(mode);
- if (selectionModel == null
- || !model.getClass().equals(selectionModel.getClass())) {
- selectionModel = model;
- getWidget().setSelectionModel(model);
- selectedKeys.clear();
- }
- }
-
- private void updateSelectionFromState() {
- boolean changed = false;
-
- List<String> stateKeys = getState().selectedKeys;
-
- // find new deselections
- for (String key : selectedKeys) {
- if (!stateKeys.contains(key)) {
- changed = true;
- deselectByHandle(dataSource.getHandleByKey(key));
- }
- }
-
- // find new selections
- for (String key : stateKeys) {
- if (!selectedKeys.contains(key)) {
- changed = true;
- selectByHandle(dataSource.getHandleByKey(key));
- }
- }
-
- /*
- * A defensive copy in case the collection in the state is mutated
- * instead of re-assigned.
- */
- selectedKeys = new LinkedHashSet<String>(stateKeys);
-
- /*
- * We need to fire this event so that Grid is able to re-render the
- * selection changes (if applicable).
- */
- if (changed) {
- // At least for now there's no way to send the selected and/or
- // deselected row data. Some data is only stored as keys
- selectionUpdatedFromState = true;
- getWidget().fireEvent(
- new SelectionEvent<JsonObject>(getWidget(),
- (List<JsonObject>) null, null, false));
- }
- }
-
private void onSortStateChange() {
List<SortOrder> sortOrder = new ArrayList<SortOrder>();
@@ -1253,41 +1082,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
return Logger.getLogger(getClass().getName());
}
- @SuppressWarnings("static-method")
- private AbstractRowHandleSelectionModel<JsonObject> createSelectionModel(
- SharedSelectionMode mode) {
- switch (mode) {
- case SINGLE:
- return new SelectionModelSingle<JsonObject>();
- case MULTI:
- return new SelectionModelMulti<JsonObject>();
- case NONE:
- return new SelectionModelNone<JsonObject>();
- default:
- throw new IllegalStateException("unexpected mode value: " + mode);
- }
- }
-
- /**
- * A workaround method for accessing the protected method
- * {@code AbstractRowHandleSelectionModel.selectByHandle}
- */
- private native void selectByHandle(RowHandle<JsonObject> handle)
- /*-{
- var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
- model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle);
- }-*/;
-
- /**
- * A workaround method for accessing the protected method
- * {@code AbstractRowHandleSelectionModel.deselectByHandle}
- */
- private native void deselectByHandle(RowHandle<JsonObject> handle)
- /*-{
- var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel;
- model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle);
- }-*/;
-
/**
* Gets the row key for a row object.
*
@@ -1312,7 +1106,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
@Override
public void updateCaption(ComponentConnector connector) {
// TODO Auto-generated method stub
-
}
@Override
diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java
new file mode 100644
index 0000000000..c1f5d87d68
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.ComplexRenderer;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.DataAvailableEvent;
+import com.vaadin.client.widget.grid.DataAvailableHandler;
+import com.vaadin.client.widget.grid.events.SelectAllEvent;
+import com.vaadin.client.widget.grid.events.SelectAllHandler;
+import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.HeaderCell;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.Range;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
+import com.vaadin.ui.Grid.MultiSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link MultiSelectionModel}.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@Connect(MultiSelectionModel.class)
+public class MultiSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Multi<JsonObject>> {
+
+ private Multi<JsonObject> selectionModel = createSelectionModel();
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ }
+
+ @Override
+ protected Multi<JsonObject> createSelectionModel() {
+ return new MultiSelectionModel();
+ }
+
+ @Override
+ public MultiSelectionModelState getState() {
+ return (MultiSelectionModelState) super.getState();
+ }
+
+ @OnStateChange("allSelected")
+ void updateSelectAllCheckbox() {
+ if (selectionModel.getSelectionColumnRenderer() != null) {
+ HeaderCell cell = getGrid().getDefaultHeaderRow().getCell(
+ getGrid().getColumn(0));
+ CheckBox widget = (CheckBox) cell.getWidget();
+ widget.setValue(getState().allSelected, false);
+ }
+ }
+
+ protected class MultiSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.Multi.Batched<JsonObject> {
+
+ private ComplexRenderer<Boolean> renderer = null;
+ private Set<RowHandle<JsonObject>> selected = new HashSet<RowHandle<JsonObject>>();
+ private Set<RowHandle<JsonObject>> deselected = new HashSet<RowHandle<JsonObject>>();
+ private HandlerRegistration selectAll;
+ private HandlerRegistration dataAvailable;
+ private Range availableRows;
+ private boolean batchSelect = false;
+
+ @Override
+ public void setGrid(Grid<JsonObject> grid) {
+ super.setGrid(grid);
+ if (grid != null) {
+ renderer = createSelectionColumnRenderer(grid);
+ selectAll = getGrid().addSelectAllHandler(
+ new SelectAllHandler<JsonObject>() {
+
+ @Override
+ public void onSelectAll(
+ SelectAllEvent<JsonObject> event) {
+ selectAll();
+ }
+ });
+ dataAvailable = getGrid().addDataAvailableHandler(
+ new DataAvailableHandler() {
+
+ @Override
+ public void onDataAvailable(DataAvailableEvent event) {
+ availableRows = event.getAvailableRows();
+ }
+ });
+ } else if (renderer != null) {
+ renderer.destroy();
+ selectAll.removeHandler();
+ dataAvailable.removeHandler();
+ renderer = null;
+ }
+ }
+
+ /**
+ * Creates a selection column renderer. This method can be overridden to
+ * use a custom renderer or use {@code null} to disable the selection
+ * column.
+ *
+ * @param grid
+ * the grid for this selection model
+ * @return selection column renderer or {@code null} if not needed
+ */
+ protected ComplexRenderer<Boolean> createSelectionColumnRenderer(
+ Grid<JsonObject> grid) {
+ return new MultiSelectionRenderer<JsonObject>(grid);
+ }
+
+ /**
+ * Selects all available rows, sends request to server to select
+ * everything.
+ */
+ public void selectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ Set<RowHandle<JsonObject>> rows = new HashSet<DataSource.RowHandle<JsonObject>>();
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, true);
+ rows.add(handle);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).selectAll();
+ cleanRowCache(rows);
+ }
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return renderer;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(JsonObject... rows) {
+ return select(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(JsonObject... rows) {
+ return deselect(Arrays.asList(rows));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always {@code true}
+ */
+ @Override
+ public boolean deselectAll() {
+ assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection.";
+
+ Set<RowHandle<JsonObject>> rows = new HashSet<DataSource.RowHandle<JsonObject>>();
+ DataSource<JsonObject> dataSource = getGrid().getDataSource();
+ for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) {
+ final JsonObject row = dataSource.getRow(i);
+ if (row != null) {
+ RowHandle<JsonObject> handle = dataSource.getHandle(row);
+ markAsSelected(handle, false);
+ rows.add(handle);
+ }
+ }
+
+ getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll();
+ cleanRowCache(rows);
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean select(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ markAsSelected(rowHandle, true);
+ selected.add(rowHandle);
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendSelected();
+ }
+ return true;
+ }
+
+ /**
+ * Marks the JsonObject pointed by RowHandle to have selected
+ * information equal to given boolean
+ *
+ * @param row
+ * row handle
+ * @param selected
+ * should row be selected
+ */
+ protected void markAsSelected(RowHandle<JsonObject> row,
+ boolean selected) {
+ row.pin();
+ if (selected) {
+ row.getRow().put(GridState.JSONKEY_SELECTED, true);
+ } else {
+ row.getRow().remove(GridState.JSONKEY_SELECTED);
+ }
+ row.updateRow();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@code false} if rows is empty, else {@code true}
+ */
+ @Override
+ public boolean deselect(Collection<JsonObject> rows) {
+ if (rows.isEmpty()) {
+ return false;
+ }
+
+ for (JsonObject row : rows) {
+ RowHandle<JsonObject> rowHandle = getRowHandle(row);
+ markAsSelected(rowHandle, false);
+ deselected.add(rowHandle);
+ }
+
+ if (!isBeingBatchSelected()) {
+ sendDeselected();
+ }
+ return true;
+ }
+
+ private void sendDeselected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class).deselect(
+ getRowKeys(deselected));
+ cleanRowCache(deselected);
+ }
+
+ private void sendSelected() {
+ getRpcProxy(MultiSelectionModelServerRpc.class).select(
+ getRowKeys(selected));
+ cleanRowCache(selected);
+ }
+
+ private List<String> getRowKeys(Set<RowHandle<JsonObject>> handles) {
+ List<String> keys = new ArrayList<String>();
+ for (RowHandle<JsonObject> handle : handles) {
+ keys.add(getRowKey(handle.getRow()));
+ }
+ return keys;
+ }
+
+ private Set<JsonObject> getRows(Set<RowHandle<JsonObject>> handles) {
+ Set<JsonObject> rows = new HashSet<JsonObject>();
+ for (RowHandle<JsonObject> handle : handles) {
+ rows.add(handle.getRow());
+ }
+ return rows;
+ }
+
+ private void cleanRowCache(Set<RowHandle<JsonObject>> handles) {
+ for (RowHandle<JsonObject> handle : handles) {
+ handle.unpin();
+ }
+ handles.clear();
+ }
+
+ @Override
+ public void startBatchSelect() {
+ assert selected.isEmpty() && deselected.isEmpty() : "Row caches were not clear.";
+ batchSelect = true;
+ }
+
+ @Override
+ public void commitBatchSelect() {
+ assert batchSelect : "Not batch selecting.";
+ if (!selected.isEmpty()) {
+ sendSelected();
+ }
+
+ if (!deselected.isEmpty()) {
+ sendDeselected();
+ }
+ batchSelect = false;
+ }
+
+ @Override
+ public boolean isBeingBatchSelected() {
+ return batchSelect;
+ }
+
+ @Override
+ public Collection<JsonObject> getSelectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(selected));
+ }
+
+ @Override
+ public Collection<JsonObject> getDeselectedRowsBatch() {
+ return Collections.unmodifiableSet(getRows(deselected));
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java
new file mode 100644
index 0000000000..b540eed6d5
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModelNone;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.ui.Grid.NoSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link NoSelectionModel}.
+ */
+@Connect(NoSelectionModel.class)
+public class NoSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel<JsonObject>> {
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(createSelectionModel());
+ }
+
+ @Override
+ protected SelectionModel<JsonObject> createSelectionModel() {
+ return new SelectionModelNone<JsonObject>();
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
index 78aaebaca1..bcca8816b1 100644
--- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
+++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
@@ -99,8 +99,10 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector {
}
@Override
- public void updateRowData(JsonObject row) {
- RpcDataSource.this.updateRowData(row);
+ public void updateRowData(JsonArray rowArray) {
+ for (int i = 0; i < rowArray.length(); ++i) {
+ RpcDataSource.this.updateRowData(rowArray.getObject(i));
+ }
}
});
}
diff --git a/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java
new file mode 100644
index 0000000000..7c66903c2c
--- /dev/null
+++ b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.data.DataSource.RowHandle;
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Single;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
+import com.vaadin.ui.Grid.SingleSelectionModel;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for server-side {@link SingleSelectionModel}.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@Connect(SingleSelectionModel.class)
+public class SingleSelectionModelConnector extends
+ AbstractSelectionModelConnector<SelectionModel.Single<JsonObject>> {
+
+ private SpaceSelectHandler<JsonObject> spaceHandler;
+ private ClickSelectHandler<JsonObject> clickHandler;
+ private Single<JsonObject> selectionModel = createSelectionModel();
+
+ @Override
+ protected void extend(ServerConnector target) {
+ getGrid().setSelectionModel(selectionModel);
+ spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid());
+ clickHandler = new ClickSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public SingleSelectionModelState getState() {
+ return (SingleSelectionModelState) super.getState();
+ }
+
+ @Override
+ public void onUnregister() {
+ spaceHandler.removeHandler();
+ clickHandler.removeHandler();
+
+ super.onUnregister();
+ }
+
+ @Override
+ protected Single<JsonObject> createSelectionModel() {
+ return new SingleSelectionModel();
+ }
+
+ @OnStateChange("deselectAllowed")
+ void updateDeselectAllowed() {
+ selectionModel.setDeselectAllowed(getState().deselectAllowed);
+ }
+
+ /**
+ * SingleSelectionModel without a selection column renderer.
+ */
+ public class SingleSelectionModel extends AbstractSelectionModel implements
+ SelectionModel.Single<JsonObject> {
+
+ private RowHandle<JsonObject> selectedRow;
+ private boolean deselectAllowed;
+
+ @Override
+ public Renderer<Boolean> getSelectionColumnRenderer() {
+ return null;
+ }
+
+ @Override
+ public boolean select(JsonObject row) {
+ boolean changed = false;
+ if ((row == null && isDeselectAllowed())
+ || (row != null && !getRowHandle(row).equals(selectedRow))) {
+ if (selectedRow != null) {
+ selectedRow.getRow().remove(GridState.JSONKEY_SELECTED);
+ selectedRow.updateRow();
+ selectedRow.unpin();
+ selectedRow = null;
+ changed = true;
+ }
+
+ if (row != null) {
+ selectedRow = getRowHandle(row);
+ selectedRow.pin();
+ selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true);
+ selectedRow.updateRow();
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ getRpcProxy(SingleSelectionModelServerRpc.class).select(
+ getRowKey(row));
+ }
+
+ return changed;
+ }
+
+ @Override
+ public boolean deselect(JsonObject row) {
+ if (getRowHandle(row).equals(selectedRow)) {
+ select(null);
+ }
+ return false;
+ }
+
+ @Override
+ public JsonObject getSelectedRow() {
+ throw new UnsupportedOperationException(
+ "This client-side selection model "
+ + getClass().getSimpleName()
+ + " does not know selected row.");
+ }
+
+ @Override
+ public void setDeselectAllowed(boolean deselectAllowed) {
+ this.deselectAllowed = deselectAllowed;
+ }
+
+ @Override
+ public boolean isDeselectAllowed() {
+ return deselectAllowed;
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index a2eedf4203..91fc87d2c7 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -5287,7 +5287,7 @@ public class Grid<T> extends ResizeComposite implements
@Override
public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
for (FlyweightCell cell : cellsToDetach) {
- Renderer renderer = findRenderer(cell);
+ Renderer<?> renderer = findRenderer(cell);
if (renderer instanceof WidgetRenderer) {
try {
Widget w = WidgetUtil.findWidget(cell.getElement()
@@ -5317,7 +5317,7 @@ public class Grid<T> extends ResizeComposite implements
// any more
rowReference.set(rowIndex, null, row.getElement());
for (FlyweightCell cell : detachedCells) {
- Renderer renderer = findRenderer(cell);
+ Renderer<?> renderer = findRenderer(cell);
if (renderer instanceof ComplexRenderer) {
try {
Column<?, T> column = getVisibleColumn(cell.getColumn());
@@ -7233,12 +7233,12 @@ public class Grid<T> extends ResizeComposite implements
* if the current selection model is not an instance of
* {@link SelectionModel.Single} or {@link SelectionModel.Multi}
*/
- @SuppressWarnings("unchecked")
public boolean select(T row) {
if (selectionModel instanceof SelectionModel.Single<?>) {
return ((SelectionModel.Single<T>) selectionModel).select(row);
} else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).select(row);
+ return ((SelectionModel.Multi<T>) selectionModel)
+ .select(Collections.singleton(row));
} else {
throw new IllegalStateException("Unsupported selection model");
}
@@ -7258,12 +7258,12 @@ public class Grid<T> extends ResizeComposite implements
* if the current selection model is not an instance of
* {@link SelectionModel.Single} or {@link SelectionModel.Multi}
*/
- @SuppressWarnings("unchecked")
public boolean deselect(T row) {
if (selectionModel instanceof SelectionModel.Single<?>) {
return ((SelectionModel.Single<T>) selectionModel).deselect(row);
} else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).deselect(row);
+ return ((SelectionModel.Multi<T>) selectionModel)
+ .deselect(Collections.singleton(row));
} else {
throw new IllegalStateException("Unsupported selection model");
}
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index f5d712f6b2..c8f3604fd9 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -744,15 +744,11 @@ public class RpcDataProviderExtension extends AbstractExtension {
// Send current rows again if needed.
if (refreshCache) {
- for (Object itemId : activeItemHandler.getActiveItemIds()) {
- internalUpdateRowData(itemId);
- }
+ updatedItemIds.addAll(activeItemHandler.getActiveItemIds());
}
}
- for (Object itemId : updatedItemIds) {
- internalUpdateRowData(itemId);
- }
+ internalUpdateRows(updatedItemIds);
// Clear all changes.
rowChanges.clear();
@@ -913,11 +909,20 @@ public class RpcDataProviderExtension extends AbstractExtension {
updatedItemIds.add(itemId);
}
- private void internalUpdateRowData(Object itemId) {
- if (activeItemHandler.getActiveItemIds().contains(itemId)) {
- JsonObject row = getRowData(getGrid().getColumns(), itemId);
- rpc.updateRowData(row);
+ private void internalUpdateRows(Set<Object> itemIds) {
+ if (itemIds.isEmpty()) {
+ return;
+ }
+
+ JsonArray rowData = Json.createArray();
+ int i = 0;
+ for (Object itemId : itemIds) {
+ if (activeItemHandler.getActiveItemIds().contains(itemId)) {
+ JsonObject row = getRowData(getGrid().getColumns(), itemId);
+ rowData.set(i++, row);
+ }
}
+ rpc.updateRowData(rowData);
}
/**
diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java
index d9c011677b..f58280c6fe 100644
--- a/server/src/com/vaadin/ui/Grid.java
+++ b/server/src/com/vaadin/ui/Grid.java
@@ -82,6 +82,7 @@ import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.EncodeResult;
import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.Extension;
import com.vaadin.server.JsonCodec;
import com.vaadin.server.KeyMapper;
import com.vaadin.server.VaadinSession;
@@ -94,13 +95,16 @@ import com.vaadin.shared.ui.grid.GridColumnState;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
-import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.GridStaticSectionState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.ScrollDestination;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
+import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
@@ -111,7 +115,6 @@ import com.vaadin.ui.renderers.TextRenderer;
import com.vaadin.util.ReflectTools;
import elemental.json.Json;
-import elemental.json.JsonArray;
import elemental.json.JsonObject;
import elemental.json.JsonValue;
@@ -710,8 +713,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
/**
* The server-side interface that controls Grid's selection state.
+ * SelectionModel should extend {@link AbstractGridExtension}.
*/
- public interface SelectionModel extends Serializable {
+ public interface SelectionModel extends Serializable, Extension {
/**
* Checks whether an item is selected or not.
*
@@ -730,6 +734,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
/**
* Injects the current {@link Grid} instance into the SelectionModel.
+ * This method should usually call the extend method of
+ * {@link AbstractExtension}.
* <p>
* <em>Note:</em> This method should not be called manually.
*
@@ -971,10 +977,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* A base class for SelectionModels that contains some of the logic that is
* reusable.
*/
- public static abstract class AbstractSelectionModel implements
- SelectionModel {
+ public static abstract class AbstractSelectionModel extends
+ AbstractGridExtension implements SelectionModel, DataGenerator {
protected final LinkedHashSet<Object> selection = new LinkedHashSet<Object>();
- protected Grid grid = null;
@Override
public boolean isSelected(final Object itemId) {
@@ -988,7 +993,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
@Override
public void setGrid(final Grid grid) {
- this.grid = grid;
+ if (grid != null) {
+ extend(grid);
+ }
}
/**
@@ -1002,7 +1009,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
*/
protected void checkItemIdExists(Object itemId)
throws IllegalArgumentException {
- if (!grid.getContainerDataSource().containsId(itemId)) {
+ if (!getParentGrid().getContainerDataSource().containsId(itemId)) {
throw new IllegalArgumentException("Given item id (" + itemId
+ ") does not exist in the container");
}
@@ -1044,7 +1051,19 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
protected void fireSelectionEvent(
final Collection<Object> oldSelection,
final Collection<Object> newSelection) {
- grid.fireSelectionEvent(oldSelection, newSelection);
+ getParentGrid().fireSelectionEvent(oldSelection, newSelection);
+ }
+
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ if (isSelected(itemId)) {
+ rowData.put(GridState.JSONKEY_SELECTED, true);
+ }
+ }
+
+ @Override
+ protected Object getItemId(String rowKey) {
+ return rowKey != null ? super.getItemId(rowKey) : null;
}
}
@@ -1053,8 +1072,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
*/
public static class SingleSelectionModel extends AbstractSelectionModel
implements SelectionModel.Single {
+
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+ registerRpc(new SingleSelectionModelServerRpc() {
+
+ @Override
+ public void select(String rowKey) {
+ SingleSelectionModel.this.select(getItemId(rowKey), false);
+ }
+ });
+ }
+
@Override
public boolean select(final Object itemId) {
+ return select(itemId, true);
+ }
+
+ protected boolean select(final Object itemId, boolean refresh) {
if (itemId == null) {
return deselect(getSelectedRow());
}
@@ -1066,7 +1102,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
if (modified) {
final Collection<Object> deselected;
if (selectedRow != null) {
- deselectInternal(selectedRow, false);
+ deselectInternal(selectedRow, false, true);
deselected = Collections.singleton(selectedRow);
} else {
deselected = Collections.emptySet();
@@ -1075,19 +1111,28 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
fireSelectionEvent(deselected, selection);
}
+ if (refresh) {
+ refreshRow(itemId);
+ }
+
return modified;
}
private boolean deselect(final Object itemId) {
- return deselectInternal(itemId, true);
+ return deselectInternal(itemId, true, true);
}
private boolean deselectInternal(final Object itemId,
- boolean fireEventIfNeeded) {
+ boolean fireEventIfNeeded, boolean refresh) {
final boolean modified = selection.remove(itemId);
- if (fireEventIfNeeded && modified) {
- fireSelectionEvent(Collections.singleton(itemId),
- Collections.emptySet());
+ if (modified) {
+ if (refresh) {
+ refreshRow(itemId);
+ }
+ if (fireEventIfNeeded) {
+ fireSelectionEvent(Collections.singleton(itemId),
+ Collections.emptySet());
+ }
}
return modified;
}
@@ -1113,23 +1158,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
@Override
public void setDeselectAllowed(boolean deselectAllowed) {
- grid.getState().singleSelectDeselectAllowed = deselectAllowed;
+ getState().deselectAllowed = deselectAllowed;
}
@Override
public boolean isDeselectAllowed() {
- return grid.getState(false).singleSelectDeselectAllowed;
+ return getState().deselectAllowed;
+ }
+
+ @Override
+ protected SingleSelectionModelState getState() {
+ return (SingleSelectionModelState) super.getState();
}
}
/**
* A default implementation for a {@link SelectionModel.None}
*/
- public static class NoSelectionModel implements SelectionModel.None {
- @Override
- public void setGrid(final Grid grid) {
- // NOOP, not needed for anything
- }
+ public static class NoSelectionModel extends AbstractSelectionModel
+ implements SelectionModel.None {
@Override
public boolean isSelected(final Object itemId) {
@@ -1167,7 +1214,40 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
private int selectionLimit = DEFAULT_MAX_SELECTIONS;
- private boolean allSelected;
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+ registerRpc(new MultiSelectionModelServerRpc() {
+
+ @Override
+ public void select(List<String> rowKeys) {
+ List<Object> items = new ArrayList<Object>();
+ for (String rowKey : rowKeys) {
+ items.add(getItemId(rowKey));
+ }
+ MultiSelectionModel.this.select(items, false);
+ }
+
+ @Override
+ public void deselect(List<String> rowKeys) {
+ List<Object> items = new ArrayList<Object>();
+ for (String rowKey : rowKeys) {
+ items.add(getItemId(rowKey));
+ }
+ MultiSelectionModel.this.deselect(items, false);
+ }
+
+ @Override
+ public void selectAll() {
+ MultiSelectionModel.this.selectAll(false);
+ }
+
+ @Override
+ public void deselectAll() {
+ MultiSelectionModel.this.deselectAll(false);
+ }
+ });
+ }
@Override
public boolean select(final Object... itemIds)
@@ -1190,6 +1270,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
@Override
public boolean select(final Collection<?> itemIds)
throws IllegalArgumentException {
+ return select(itemIds, true);
+ }
+
+ protected boolean select(final Collection<?> itemIds, boolean refresh) {
if (itemIds == null) {
throw new IllegalArgumentException("itemIds may not be null");
}
@@ -1217,6 +1301,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
updateAllSelectedState();
+ if (refresh) {
+ for (Object itemId : itemIds) {
+ refreshRow(itemId);
+ }
+ }
+
return selectionWillChange;
}
@@ -1270,6 +1360,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
@Override
public boolean deselect(final Collection<?> itemIds)
throws IllegalArgumentException {
+ return deselect(itemIds, true);
+ }
+
+ protected boolean deselect(final Collection<?> itemIds, boolean refresh) {
if (itemIds == null) {
throw new IllegalArgumentException("itemIds may not be null");
}
@@ -1285,15 +1379,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
updateAllSelectedState();
+ if (refresh) {
+ for (Object itemId : itemIds) {
+ refreshRow(itemId);
+ }
+ }
+
return hasCommonElements;
}
@Override
public boolean selectAll() {
+ return selectAll(true);
+ }
+
+ protected boolean selectAll(boolean refresh) {
// select will fire the event
- final Indexed container = grid.getContainerDataSource();
+ final Indexed container = getParentGrid().getContainerDataSource();
if (container != null) {
- return select(container.getItemIds());
+ return select(container.getItemIds(), refresh);
} else if (selection.isEmpty()) {
return false;
} else {
@@ -1302,14 +1406,18 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* but I guess the only theoretically correct course of
* action...
*/
- return deselectAll();
+ return deselectAll(false);
}
}
@Override
public boolean deselectAll() {
+ return deselectAll(true);
+ }
+
+ protected boolean deselectAll(boolean refresh) {
// deselect will fire the event
- return deselect(getSelectedRows());
+ return deselect(getSelectedRows(), refresh);
}
/**
@@ -1382,11 +1490,15 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
}
private void updateAllSelectedState() {
- if (allSelected != selection.size() >= selectionLimit) {
- allSelected = selection.size() >= selectionLimit;
- grid.getRpcProxy(GridClientRpc.class).setSelectAll(allSelected);
+ if (getState().allSelected != selection.size() >= selectionLimit) {
+ getState().allSelected = selection.size() >= selectionLimit;
}
}
+
+ @Override
+ protected MultiSelectionModelState getState() {
+ return (MultiSelectionModelState) super.getState();
+ }
}
/**
@@ -1571,7 +1683,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* Grid rows. If a description is generated for a row, it is used for all
* the cells in the row for which a {@link CellDescriptionGenerator cell
* description} is not generated.
- *
+ *
* @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator)
*
* @since 7.6
@@ -3783,6 +3895,17 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
+ " instead");
}
}
+
+ /**
+ * Resends the row data for given item id to the client.
+ *
+ * @since
+ * @param itemId
+ * row to refresh
+ */
+ protected void refreshRow(Object itemId) {
+ getParentGrid().datasourceExtension.updateRowData(itemId);
+ }
}
/**
@@ -3982,117 +4105,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
*/
private void initGrid() {
setSelectionMode(getDefaultSelectionMode());
- addSelectionListener(new SelectionListener() {
- @Override
- public void select(SelectionEvent event) {
- if (applyingSelectionFromClient) {
- /*
- * Avoid sending changes back to the client if they
- * originated from the client. Instead, the RPC handler is
- * responsible for keeping track of the resulting selection
- * state and notifying the client if it doens't match the
- * expectation.
- */
- return;
- }
-
- /*
- * The rows are pinned here to ensure that the client gets the
- * correct key from server when the selected row is first
- * loaded.
- *
- * Once the client has gotten info that it is supposed to select
- * a row, it will pin the data from the client side as well and
- * it will be unpinned once it gets deselected. Nothing on the
- * server side should ever unpin anything from KeyMapper.
- * Pinning is mostly a client feature and is only used when
- * selecting something from the server side.
- */
- for (Object addedItemId : event.getAdded()) {
- if (!getKeyMapper().isPinned(addedItemId)) {
- getKeyMapper().pin(addedItemId);
- }
- }
-
- getState().selectedKeys = getKeyMapper().getKeys(
- getSelectedRows());
- }
- });
registerRpc(new GridServerRpc() {
@Override
- public void select(List<String> selection) {
- Collection<Object> receivedSelection = getKeyMapper()
- .getItemIds(selection);
-
- applyingSelectionFromClient = true;
- try {
- SelectionModel selectionModel = getSelectionModel();
- if (selectionModel instanceof SelectionModel.Single
- && selection.size() <= 1) {
- Object select = null;
- if (selection.size() == 1) {
- select = getKeyMapper().getItemId(selection.get(0));
- }
- ((SelectionModel.Single) selectionModel).select(select);
- } else if (selectionModel instanceof SelectionModel.Multi) {
- ((SelectionModel.Multi) selectionModel)
- .setSelected(receivedSelection);
- } else {
- throw new IllegalStateException("SelectionModel "
- + selectionModel.getClass().getSimpleName()
- + " does not support selecting the given "
- + selection.size() + " items.");
- }
- } finally {
- applyingSelectionFromClient = false;
- }
-
- Collection<Object> actualSelection = getSelectedRows();
-
- // Make sure all selected rows are pinned
- for (Object itemId : actualSelection) {
- if (!getKeyMapper().isPinned(itemId)) {
- getKeyMapper().pin(itemId);
- }
- }
-
- // Don't mark as dirty since this might be the expected state
- getState(false).selectedKeys = getKeyMapper().getKeys(
- actualSelection);
-
- JsonObject diffState = getUI().getConnectorTracker()
- .getDiffState(Grid.this);
-
- final String diffstateKey = "selectedKeys";
-
- assert diffState.hasKey(diffstateKey) : "Field name has changed";
-
- if (receivedSelection.equals(actualSelection)) {
- /*
- * We ended up with the same selection state that the client
- * sent us. There's nothing to send back to the client, just
- * update the diffstate so subsequent changes will be
- * detected.
- */
- JsonArray diffSelected = Json.createArray();
- for (String rowKey : getState(false).selectedKeys) {
- diffSelected.set(diffSelected.length(), rowKey);
- }
- diffState.put(diffstateKey, diffSelected);
- } else {
- /*
- * Actual selection is not what the client expects. Make
- * sure the client gets a state change event by clearing the
- * diffstate and marking as dirty
- */
- diffState.remove(diffstateKey);
- markAsDirty();
- }
- }
-
- @Override
public void sort(String[] columnIds, SortDirection[] directions,
boolean userOriginated) {
assert columnIds.length == directions.length;
@@ -4121,13 +4137,6 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
}
@Override
- public void selectAll() {
- assert getSelectionModel() instanceof SelectionModel.Multi : "Not a multi selection model!";
-
- ((SelectionModel.Multi) getSelectionModel()).selectAll();
- }
-
- @Override
public void itemClick(String rowKey, String columnId,
MouseEventDetails details) {
Object itemId = getKeyMapper().getItemId(rowKey);
@@ -5019,25 +5028,11 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
if (this.selectionModel != selectionModel) {
// this.selectionModel is null on init
if (this.selectionModel != null) {
- this.selectionModel.reset();
- this.selectionModel.setGrid(null);
+ this.selectionModel.remove();
}
this.selectionModel = selectionModel;
- this.selectionModel.setGrid(this);
- this.selectionModel.reset();
-
- if (selectionModel.getClass().equals(SingleSelectionModel.class)) {
- getState().selectionMode = SharedSelectionMode.SINGLE;
- } else if (selectionModel.getClass().equals(
- MultiSelectionModel.class)) {
- getState().selectionMode = SharedSelectionMode.MULTI;
- } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
- getState().selectionMode = SharedSelectionMode.NONE;
- } else {
- throw new UnsupportedOperationException("Grid currently "
- + "supports only its own bundled selection models");
- }
+ selectionModel.setGrid(this);
}
}
diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
index 28e50d9747..05965ea56c 100644
--- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java
+++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
@@ -20,7 +20,6 @@ import com.vaadin.shared.annotations.NoLayout;
import com.vaadin.shared.communication.ClientRpc;
import elemental.json.JsonArray;
-import elemental.json.JsonObject;
/**
* RPC interface used for pushing container data to the client.
@@ -94,13 +93,14 @@ public interface DataProviderRpc extends ClientRpc {
public void resetDataAndSize(int size);
/**
- * Informs the client that a row has updated. The client-side DataSource
- * will map the given data to correct index if it should be in the cache.
+ * Informs the client that rows have been updated. The client-side
+ * DataSource will map the given data to correct index if it should be in
+ * the cache.
*
* @since 7.6
- * @param row
- * the updated row data
+ * @param rowArray
+ * array of updated rows
*/
@NoLayout
- public void updateRowData(JsonObject row);
+ public void updateRowData(JsonArray rowArray);
}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java
index 8711a757a1..ac1b1d5a78 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java
@@ -55,15 +55,4 @@ public interface GridClientRpc extends ClientRpc {
* Command client Grid to recalculate column widths.
*/
public void recalculateColumnWidths();
-
- /**
- * Informs the Grid that all items have been selected or not selected on the
- * server side.
- *
- * @since 7.5.3
- * @param allSelected
- * <code>true</code> to check the select all checkbox,
- * <code>false</code> to uncheck it.
- */
- public void setSelectAll(boolean allSelected);
}
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
index a178ff63d0..8d64794b41 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
@@ -29,10 +29,6 @@ import com.vaadin.shared.data.sort.SortDirection;
*/
public interface GridServerRpc extends ServerRpc {
- void select(List<String> newSelection);
-
- void selectAll();
-
void sort(String[] columnIds, SortDirection[] directions,
boolean userOriginated);
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java
index 0d0a5d3e9f..e51b1a78a7 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridState.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java
@@ -129,6 +129,13 @@ public class GridState extends TabIndexState {
public static final String JSONKEY_DETAILS_VISIBLE = "dv";
/**
+ * The key that tells whether row is selected.
+ *
+ * @since
+ */
+ public static final String JSONKEY_SELECTED = "s";
+
+ /**
* Columns in grid.
*/
public List<GridColumnState> columns = new ArrayList<GridColumnState>();
@@ -153,14 +160,6 @@ public class GridState extends TabIndexState {
@DelegateToWidget
public HeightMode heightMode = HeightMode.CSS;
- // instantiated just to avoid NPEs
- public List<String> selectedKeys = new ArrayList<String>();
-
- public SharedSelectionMode selectionMode;
-
- /** Whether single select mode can be cleared through the UI */
- public boolean singleSelectDeselectAllowed = true;
-
/** Keys of the currently sorted columns */
public String[] sortColumns = new String[0];
diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java
new file mode 100644
index 0000000000..e7324552f4
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.selection;
+
+import java.util.List;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * ServerRpc for MultiSelectionModel.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface MultiSelectionModelServerRpc extends ServerRpc {
+
+ /**
+ * Select a list of rows based on their row keys on the server-side.
+ *
+ * @param rowKeys
+ * list of row keys to select
+ */
+ public void select(List<String> rowKeys);
+
+ /**
+ * Deselect a list of rows based on their row keys on the server-side.
+ *
+ * @param rowKeys
+ * list of row keys to deselect
+ */
+ public void deselect(List<String> rowKeys);
+
+ /**
+ * Selects all rows on the server-side.
+ */
+ public void selectAll();
+
+ /**
+ * Deselects all rows on the server-side.
+ */
+ public void deselectAll();
+}
diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java
new file mode 100644
index 0000000000..3ffd0d0f93
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.selection;
+
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * SharedState object for MultiSelectionModel.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class MultiSelectionModelState extends SharedState {
+
+ /* Select All -checkbox status */
+ public boolean allSelected;
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java
new file mode 100644
index 0000000000..3e2a8ec847
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.selection;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * ServerRpc for SingleSelectionModel.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface SingleSelectionModelServerRpc extends ServerRpc {
+
+ /**
+ * Selects a row on server-side.
+ *
+ * @param rowKey
+ * row key of selected row; {@code null} if deselect
+ */
+ public void select(String rowKey);
+}
diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java
new file mode 100644
index 0000000000..719b60b2ba
--- /dev/null
+++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.selection;
+
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * SharedState object for SingleSelectionModel.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class SingleSelectionModelState extends SharedState {
+
+ /* Allow deselecting rows */
+ public boolean deselectAllowed;
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java
new file mode 100644
index 0000000000..008c24cdd3
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.tests.widgetset.TestingWidgetSet;
+import com.vaadin.ui.Grid.MultiSelectionModel;
+
+@Widgetset(TestingWidgetSet.NAME)
+public class GridCustomSelectionModel extends AbstractTestUI {
+
+ public static class MySelectionModel extends MultiSelectionModel {
+ }
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ PersonTestGrid grid = new PersonTestGrid(500);
+ grid.setSelectionModel(new MySelectionModel());
+ addComponent(grid);
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java
new file mode 100644
index 0000000000..976e1e78fe
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.parallel.TestCategory;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+@TestCategory("grid")
+public class GridCustomSelectionModelTest extends MultiBrowserTest {
+
+ @Test
+ public void testCustomSelectionModel() {
+ setDebug(true);
+ openTestURL();
+
+ GridElement grid = $(GridElement.class).first();
+ GridCellElement cell = grid.getCell(0, 0);
+ assertTrue("First column of Grid should not have an input element",
+ cell.findElements(By.className("input")).isEmpty());
+
+ assertFalse("Row should not be selected initially", grid.getRow(0)
+ .isSelected());
+
+ cell.click(5, 5);
+ assertTrue("Click should select row", grid.getRow(0).isSelected());
+ cell.click(5, 5);
+ assertFalse("Click should deselect row", grid.getRow(0).isSelected());
+
+ grid.sendKeys(Keys.SPACE);
+ assertTrue("Space should select row", grid.getRow(0).isSelected());
+ grid.sendKeys(Keys.SPACE);
+ assertFalse("Space should deselect row", grid.getRow(0).isSelected());
+
+ assertNoErrorNotifications();
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java
new file mode 100644
index 0000000000..81a9ab5bf1
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.widgetset.client.grid;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.connectors.MultiSelectionModelConnector;
+import com.vaadin.client.renderers.ComplexRenderer;
+import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.tests.components.grid.GridCustomSelectionModel.MySelectionModel;
+
+import elemental.json.JsonObject;
+
+@Connect(MySelectionModel.class)
+public class MySelectionModelConnector extends MultiSelectionModelConnector {
+
+ private ClickSelectHandler<JsonObject> handler;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ super.extend(target);
+ handler = new ClickSelectHandler<JsonObject>(getGrid());
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ handler.removeHandler();
+ handler = null;
+ }
+
+ @Override
+ protected Multi<JsonObject> createSelectionModel() {
+ return new MySelectionModel();
+ }
+
+ public class MySelectionModel extends MultiSelectionModel {
+
+ @Override
+ protected ComplexRenderer<Boolean> createSelectionColumnRenderer(
+ Grid<JsonObject> grid) {
+ // No Selection Column.
+ return null;
+ }
+ }
+}