Переглянути джерело

Refactor Grid SelectionModels as extensions (#18624)

This patch removes all selection related variables and API from several
core parts of Grid.

Change-Id: Idb7aa48fda69ded1ef58a69c1f7dbc78b7f52a54
tags/7.6.0.alpha5
Teemu Suo-Anttila 9 роки тому
джерело
коміт
53a4b2c77a
21 змінених файлів з 1135 додано та 415 видалено
  1. 2
    0
      WebContent/release-notes.html
  2. 82
    0
      client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java
  3. 0
    207
      client/src/com/vaadin/client/connectors/GridConnector.java
  4. 360
    0
      client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java
  5. 42
    0
      client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java
  6. 4
    2
      client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java
  7. 148
    0
      client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java
  8. 6
    6
      client/src/com/vaadin/client/widgets/Grid.java
  9. 15
    10
      server/src/com/vaadin/data/RpcDataProviderExtension.java
  10. 156
    161
      server/src/com/vaadin/ui/Grid.java
  11. 6
    6
      shared/src/com/vaadin/shared/data/DataProviderRpc.java
  12. 0
    11
      shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java
  13. 0
    4
      shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java
  14. 7
    8
      shared/src/com/vaadin/shared/ui/grid/GridState.java
  15. 55
    0
      shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java
  16. 31
    0
      shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java
  17. 35
    0
      shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java
  18. 30
    0
      shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java
  19. 37
    0
      uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java
  20. 58
    0
      uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java
  21. 61
    0
      uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java

+ 2
- 0
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>

+ 82
- 0
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.");
}
}
}

+ 0
- 207
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,19 +570,8 @@ 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
@@ -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()];
@@ -1102,20 +1002,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements
columnOrder.add(state.id);
}

/**
* 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
*
@@ -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

+ 360
- 0
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));
}
}
}

+ 42
- 0
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>();
}
}

+ 4
- 2
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));
}
}
});
}

+ 148
- 0
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;
}
}
}

+ 6
- 6
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");
}

+ 15
- 10
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);
}

/**

+ 156
- 161
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,116 +4105,9 @@ 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) {
@@ -4120,13 +4136,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) {
@@ -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);
}
}


+ 6
- 6
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);
}

+ 0
- 11
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);
}

+ 0
- 4
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);


+ 7
- 8
shared/src/com/vaadin/shared/ui/grid/GridState.java Переглянути файл

@@ -128,6 +128,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.
*/
@@ -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];


+ 55
- 0
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();
}

+ 31
- 0
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;

}

+ 35
- 0
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);
}

+ 30
- 0
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;
}

+ 37
- 0
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);
}

}

+ 58
- 0
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();
}
}

+ 61
- 0
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;
}
}
}

Завантаження…
Відмінити
Зберегти