import com.google.gwt.json.client.JSONValue;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.data.DataSource.RowHandle;
import com.vaadin.client.data.RpcDataSourceConnector.RpcDataSource;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.grid.renderers.AbstractRendererConnector;
+import com.vaadin.client.ui.grid.selection.AbstractRowHandleSelectionModel;
import com.vaadin.client.ui.grid.selection.SelectionChangeEvent;
import com.vaadin.client.ui.grid.selection.SelectionChangeHandler;
import com.vaadin.client.ui.grid.selection.SelectionModelMulti;
+import com.vaadin.client.ui.grid.selection.SelectionModelNone;
+import com.vaadin.client.ui.grid.selection.SelectionModelSingle;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.grid.ColumnGroupRowState;
import com.vaadin.shared.ui.grid.ColumnGroupState;
@Connect(com.vaadin.ui.components.grid.Grid.class)
public class GridConnector extends AbstractComponentConnector {
- /**
- * Hacked SelectionModelMulti to make selection communication work for now.
+ /*
+ * TODO: henrik paul (4.7.2014)
+ *
+ * This class should optimally not be needed. We should be able to use the
+ * keys in the state as the primary source of selection, and "simply" diff
+ * things once the state changes (we can't rebuild the selection pins from
+ * scratch, since we might lose some data that's currently out of view).
+ *
+ * I was unable to remove this class with little effort, so it may remain as
+ * a todo for now.
*/
- private class RowKeyBasedMultiSelection extends
- SelectionModelMulti<JSONObject> {
+ private class RowKeyHelper {
+ private LinkedHashSet<String> selectedKeys = new LinkedHashSet<String>();
+
+ public LinkedHashSet<String> getSelectedKeys() {
+ return selectedKeys;
+ }
+
+ public void add(Collection<JSONObject> rows) {
+ for (JSONObject row : rows) {
+ add(row);
+ }
+ }
- private final LinkedHashSet<String> selectedKeys = new LinkedHashSet<String>();
+ private void add(JSONObject row) {
+ selectedKeys.add((String) dataSource.getRowKey(row));
+ }
- public List<String> getSelectedKeys() {
- List<String> keys = new ArrayList<String>();
- keys.addAll(selectedKeys);
- return keys;
+ public void remove(Collection<JSONObject> rows) {
+ for (JSONObject row : rows) {
+ remove(row);
+ }
+ }
+
+ private void remove(JSONObject row) {
+ selectedKeys.remove(dataSource.getRowKey(row));
}
public void updateFromState() {
boolean changed = false;
- Set<String> stateKeys = new LinkedHashSet<String>();
- stateKeys.addAll(getState().selectedKeys);
+
+ List<String> stateKeys = getState().selectedKeys;
+
+ // find new selections
for (String key : stateKeys) {
if (!selectedKeys.contains(key)) {
changed = true;
selectByHandle(dataSource.getHandleByKey(key));
}
}
+
+ // find new deselections
for (String key : selectedKeys) {
changed = true;
if (!stateKeys.contains(key)) {
deselectByHandle(dataSource.getHandleByKey(key));
}
}
- selectedKeys.clear();
- selectedKeys.addAll(stateKeys);
+ /*
+ * 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).
+ *
+ * add/remove methods will be called from the
+ * internalSelectionChangeHandler, so they shouldn't be called here.
+ */
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
(List<JSONObject>) null, null));
}
}
-
- @Override
- public boolean select(Collection<JSONObject> rows) {
- for (JSONObject row : rows) {
- selectedKeys.add((String) dataSource.getRowKey(row));
- }
- return super.select(rows);
- }
-
- @Override
- public boolean deselect(Collection<JSONObject> rows) {
- for (JSONObject row : rows) {
- selectedKeys.remove(dataSource.getRowKey(row));
- }
- return super.deselect(rows);
- }
}
/**
* Maps a generated column id to a grid column instance
*/
private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>();
- private final RowKeyBasedMultiSelection selectionModel = new RowKeyBasedMultiSelection();
+ private AbstractRowHandleSelectionModel<JSONObject> selectionModel = new SelectionModelMulti<JSONObject>();
private RpcDataSource dataSource;
+ private final RowKeyHelper rowKeyHelper = new RowKeyHelper();
+
+ private SelectionChangeHandler<JSONObject> internalSelectionChangeHandler = new SelectionChangeHandler<JSONObject>() {
+ @Override
+ public void onSelectionChange(SelectionChangeEvent<JSONObject> event) {
+ rowKeyHelper.remove(event.getRemoved());
+ rowKeyHelper.add(event.getAdded());
+
+ // TODO change this to diff based. (henrik paul 24.6.2014)
+ List<String> selectedKeys = new ArrayList<String>(
+ rowKeyHelper.getSelectedKeys());
+ getRpcProxy(GridServerRpc.class).selectionChange(selectedKeys);
+ }
+ };
+
@Override
@SuppressWarnings("unchecked")
public Grid<JSONObject> getWidget() {
getWidget().setSelectionModel(selectionModel);
- getWidget().addSelectionChangeHandler(new SelectionChangeHandler() {
- @Override
- public void onSelectionChange(SelectionChangeEvent<?> event) {
- // TODO change this to diff based. (henrik paul 24.6.2014)
- getRpcProxy(GridServerRpc.class).selectionChange(
- selectionModel.getSelectedKeys());
- }
- });
+ getWidget().addSelectionChangeHandler(internalSelectionChangeHandler);
}
}
if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
- selectionModel.updateFromState();
+ rowKeyHelper.updateFromState();
}
}
private void onSelectionModeChange() {
SharedSelectionMode mode = getState().selectionMode;
if (mode == null) {
- getLogger().warning("ignored mode change");
+ getLogger().fine("ignored mode change");
return;
}
- getLogger().warning(mode.toString());
+
+ AbstractRowHandleSelectionModel<JSONObject> model = createSelectionModel(mode);
+ if (!model.getClass().equals(selectionModel.getClass())) {
+ selectionModel = model;
+ getWidget().setSelectionModel(model);
+ }
}
private Logger getLogger() {
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.ui.grid.GridConnector::selectionModel;
+ model.@com.vaadin.client.ui.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.ui.grid.GridConnector::selectionModel;
+ model.@com.vaadin.client.ui.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle);
+ }-*/;
}
--- /dev/null
+/*
+ * 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.ui.grid.selection;
+
+import com.vaadin.client.data.DataSource.RowHandle;
+
+/**
+ * An abstract class that adds a consistent API for common methods that's needed
+ * by Vaadin's server-based selection models to work.
+ * <p>
+ * <em>Note:</em> This should be an interface instead of an abstract class, if
+ * only we could define protected methods in an interface.
+ *
+ * @author Vaadin Ltd
+ * @param <T>
+ * The grid's row type
+ */
+public abstract class AbstractRowHandleSelectionModel<T> implements
+ SelectionModel<T> {
+ /**
+ * Select a row, based on its
+ * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}.
+ * <p>
+ * <em>Note:</em> this method may not fire selection change events.
+ *
+ * @param handle
+ * the handle to select by
+ * @return <code>true</code> iff the selection state was changed by this
+ * call
+ * @throws UnsupportedOperationException
+ * if the selection model does not support either handles or
+ * selection
+ */
+ protected abstract boolean selectByHandle(RowHandle<T> handle);
+
+ /**
+ * Deselect a row, based on its
+ * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}.
+ * <p>
+ * <em>Note:</em> this method may not fire selection change events.
+ *
+ * @param handle
+ * the handle to deselect by
+ * @return <code>true</code> iff the selection state was changed by this
+ * call
+ * @throws UnsupportedOperationException
+ * if the selection model does not support either handles or
+ * deselection
+ */
+ protected abstract boolean deselectByHandle(RowHandle<T> handle)
+ throws UnsupportedOperationException;
+}