From: Henrik Paul Date: Mon, 30 Jun 2014 15:18:32 +0000 (+0300) Subject: Adds the ability to swap between the three client side selection models (#13334) X-Git-Tag: 7.4.0.beta1~9^2~189^2~54^2~87 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6653224d8defe79ecb8749eab96adccd626c76bf;p=vaadin-framework.git Adds the ability to swap between the three client side selection models (#13334) So, this means when the selection model is modified on the server side, the client side changes accordingly. Change-Id: I3c7e3802cecdf9dfd64f5296c48fca5dfc58787d --- diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index da5adfc34a..4bd07f1909 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -1077,10 +1077,10 @@ public class Grid extends Composite implements // Default action on SelectionChangeEvents. Refresh the body so changed // become visible. - addSelectionChangeHandler(new SelectionChangeHandler() { + addSelectionChangeHandler(new SelectionChangeHandler() { @Override - public void onSelectionChange(SelectionChangeEvent event) { + public void onSelectionChange(SelectionChangeEvent event) { refreshBody(); } }); @@ -2370,7 +2370,7 @@ public class Grid extends Composite implements @Override public HandlerRegistration addSelectionChangeHandler( - final SelectionChangeHandler handler) { + final SelectionChangeHandler handler) { return addHandler(handler, SelectionChangeEvent.getType()); } diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java index ee1cc0ee75..daf938a784 100644 --- a/client/src/com/vaadin/client/ui/grid/GridConnector.java +++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java @@ -32,12 +32,16 @@ import com.google.gwt.json.client.JSONObject; 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; @@ -62,39 +66,78 @@ import com.vaadin.shared.ui.grid.ScrollDestination; @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 { + private class RowKeyHelper { + private LinkedHashSet selectedKeys = new LinkedHashSet(); + + public LinkedHashSet getSelectedKeys() { + return selectedKeys; + } + + public void add(Collection rows) { + for (JSONObject row : rows) { + add(row); + } + } - private final LinkedHashSet selectedKeys = new LinkedHashSet(); + private void add(JSONObject row) { + selectedKeys.add((String) dataSource.getRowKey(row)); + } - public List getSelectedKeys() { - List keys = new ArrayList(); - keys.addAll(selectedKeys); - return keys; + public void remove(Collection rows) { + for (JSONObject row : rows) { + remove(row); + } + } + + private void remove(JSONObject row) { + selectedKeys.remove(dataSource.getRowKey(row)); } public void updateFromState() { boolean changed = false; - Set stateKeys = new LinkedHashSet(); - stateKeys.addAll(getState().selectedKeys); + + List 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(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 @@ -103,22 +146,6 @@ public class GridConnector extends AbstractComponentConnector { (List) null, null)); } } - - @Override - public boolean select(Collection rows) { - for (JSONObject row : rows) { - selectedKeys.add((String) dataSource.getRowKey(row)); - } - return super.select(rows); - } - - @Override - public boolean deselect(Collection rows) { - for (JSONObject row : rows) { - selectedKeys.remove(dataSource.getRowKey(row)); - } - return super.deselect(rows); - } } /** @@ -176,9 +203,24 @@ public class GridConnector extends AbstractComponentConnector { * Maps a generated column id to a grid column instance */ private Map columnIdToColumn = new HashMap(); - private final RowKeyBasedMultiSelection selectionModel = new RowKeyBasedMultiSelection(); + private AbstractRowHandleSelectionModel selectionModel = new SelectionModelMulti(); private RpcDataSource dataSource; + private final RowKeyHelper rowKeyHelper = new RowKeyHelper(); + + private SelectionChangeHandler internalSelectionChangeHandler = new SelectionChangeHandler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + rowKeyHelper.remove(event.getRemoved()); + rowKeyHelper.add(event.getAdded()); + + // TODO change this to diff based. (henrik paul 24.6.2014) + List selectedKeys = new ArrayList( + rowKeyHelper.getSelectedKeys()); + getRpcProxy(GridServerRpc.class).selectionChange(selectedKeys); + } + }; + @Override @SuppressWarnings("unchecked") public Grid getWidget() { @@ -213,14 +255,7 @@ public class GridConnector extends AbstractComponentConnector { 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); } @@ -285,7 +320,7 @@ public class GridConnector extends AbstractComponentConnector { } if (stateChangeEvent.hasPropertyChanged("selectedKeys")) { - selectionModel.updateFromState(); + rowKeyHelper.updateFromState(); } } @@ -452,13 +487,53 @@ public class GridConnector extends AbstractComponentConnector { 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 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 createSelectionModel( + SharedSelectionMode mode) { + switch (mode) { + case SINGLE: + return new SelectionModelSingle(); + case MULTI: + return new SelectionModelMulti(); + case NONE: + return new SelectionModelNone(); + default: + throw new IllegalStateException("unexpected mode value: " + mode); + } + } + + /** + * A workaround method for accessing the protected method + * {@code AbstractRowHandleSelectionModel.selectByHandle} + */ + private native void selectByHandle(RowHandle 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 handle) + /*-{ + var model = this.@com.vaadin.client.ui.grid.GridConnector::selectionModel; + model.@com.vaadin.client.ui.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle); + }-*/; } diff --git a/client/src/com/vaadin/client/ui/grid/selection/AbstractRowHandleSelectionModel.java b/client/src/com/vaadin/client/ui/grid/selection/AbstractRowHandleSelectionModel.java new file mode 100644 index 0000000000..f55229d86c --- /dev/null +++ b/client/src/com/vaadin/client/ui/grid/selection/AbstractRowHandleSelectionModel.java @@ -0,0 +1,65 @@ +/* + * 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. + *

+ * Note: This should be an interface instead of an abstract class, if + * only we could define protected methods in an interface. + * + * @author Vaadin Ltd + * @param + * The grid's row type + */ +public abstract class AbstractRowHandleSelectionModel implements + SelectionModel { + /** + * Select a row, based on its + * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. + *

+ * Note: this method may not fire selection change events. + * + * @param handle + * the handle to select by + * @return true 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 handle); + + /** + * Deselect a row, based on its + * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. + *

+ * Note: this method may not fire selection change events. + * + * @param handle + * the handle to deselect by + * @return true 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 handle) + throws UnsupportedOperationException; +} diff --git a/client/src/com/vaadin/client/ui/grid/selection/HasSelectionChangeHandlers.java b/client/src/com/vaadin/client/ui/grid/selection/HasSelectionChangeHandlers.java index 78b6f098d9..c531265590 100644 --- a/client/src/com/vaadin/client/ui/grid/selection/HasSelectionChangeHandlers.java +++ b/client/src/com/vaadin/client/ui/grid/selection/HasSelectionChangeHandlers.java @@ -16,11 +16,10 @@ package com.vaadin.client.ui.grid.selection; import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.ui.grid.Grid.SelectionMode; /** * Marker interface for widgets that fires selection change events. - * + * * @author Vaadin Ltd * @since 7.4 */ @@ -29,15 +28,16 @@ public interface HasSelectionChangeHandlers { /** * Register a selection change handler. *

- * This handler is called whenever a {@link SelectionMode} detects a change - * in selection state. - * + * This handler is called whenever a + * {@link com.vaadin.ui.components.grid.selection.SelectionModel + * SelectionModel} detects a change in selection state. + * * @param handler * a {@link SelectionChangeHandler} * @return a handler registration object, which can be used to remove the * handler. */ public HandlerRegistration addSelectionChangeHandler( - SelectionChangeHandler handler); + SelectionChangeHandler handler); } diff --git a/client/src/com/vaadin/client/ui/grid/selection/SelectionChangeHandler.java b/client/src/com/vaadin/client/ui/grid/selection/SelectionChangeHandler.java index e5d15386c0..aa61bdecdf 100644 --- a/client/src/com/vaadin/client/ui/grid/selection/SelectionChangeHandler.java +++ b/client/src/com/vaadin/client/ui/grid/selection/SelectionChangeHandler.java @@ -19,19 +19,21 @@ import com.google.gwt.event.shared.EventHandler; /** * Handler for {@link SelectionChangeEvent}s. - * + * * @since 7.4 * @author Vaadin Ltd + * @param + * The row data type */ -public interface SelectionChangeHandler extends EventHandler { +public interface SelectionChangeHandler extends EventHandler { /** * Called when a selection model's selection state is changed. - * + * * @param event * a selection change event, containing info about rows that have * been added to or removed from the selection. */ - public void onSelectionChange(SelectionChangeEvent event); + public void onSelectionChange(SelectionChangeEvent event); } diff --git a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelMulti.java b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelMulti.java index de62dc9cbc..6ebd7f4044 100644 --- a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelMulti.java +++ b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelMulti.java @@ -30,7 +30,8 @@ import com.vaadin.client.ui.grid.Renderer; * @author Vaadin Ltd * @since 7.4 */ -public class SelectionModelMulti implements SelectionModel.Multi { +public class SelectionModelMulti extends AbstractRowHandleSelectionModel + implements SelectionModel.Multi { private final Set> selectedRows; private Renderer renderer; @@ -147,6 +148,7 @@ public class SelectionModelMulti implements SelectionModel.Multi { return selectedRows.contains(handle); } + @Override protected boolean selectByHandle(RowHandle handle) { if (selectedRows.add(handle)) { handle.pin(); @@ -155,6 +157,7 @@ public class SelectionModelMulti implements SelectionModel.Multi { return false; } + @Override protected boolean deselectByHandle(RowHandle handle) { if (selectedRows.remove(handle)) { handle.unpin(); diff --git a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelNone.java b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelNone.java index 93dfb49df2..59bf248032 100644 --- a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelNone.java +++ b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelNone.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui.grid.selection; import java.util.Collection; import java.util.Collections; +import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.ui.grid.Grid; import com.vaadin.client.ui.grid.Renderer; @@ -27,7 +28,8 @@ import com.vaadin.client.ui.grid.Renderer; * @author Vaadin Ltd * @since 7.4 */ -public class SelectionModelNone implements SelectionModel.None { +public class SelectionModelNone extends AbstractRowHandleSelectionModel + implements SelectionModel.None { @Override public boolean isSelected(T row) { @@ -41,12 +43,12 @@ public class SelectionModelNone implements SelectionModel.None { @Override public void setGrid(Grid grid) { - + // noop } @Override public void reset() { - + // noop } @Override @@ -54,4 +56,18 @@ public class SelectionModelNone implements SelectionModel.None { return Collections.emptySet(); } + @Override + protected boolean selectByHandle(RowHandle handle) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("This selection model " + + "does not support selection"); + } + + @Override + protected boolean deselectByHandle(RowHandle handle) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("This selection model " + + "does not support deselection"); + } + } diff --git a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelSingle.java b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelSingle.java index 775e1878c5..4ef792f1c7 100644 --- a/client/src/com/vaadin/client/ui/grid/selection/SelectionModelSingle.java +++ b/client/src/com/vaadin/client/ui/grid/selection/SelectionModelSingle.java @@ -28,7 +28,8 @@ import com.vaadin.client.ui.grid.Renderer; * @author Vaadin Ltd * @since 7.4 */ -public class SelectionModelSingle implements SelectionModel.Single { +public class SelectionModelSingle extends AbstractRowHandleSelectionModel + implements SelectionModel.Single { private Grid grid; private RowHandle selectedRow; @@ -123,4 +124,23 @@ public class SelectionModelSingle implements SelectionModel.Single { return Collections.emptySet(); } + @Override + protected boolean selectByHandle(RowHandle handle) { + if (!handle.equals(selectedRow)) { + selectedRow = handle; + return true; + } else { + return false; + } + } + + @Override + protected boolean deselectByHandle(RowHandle handle) { + if (handle.equals(selectedRow)) { + selectedRow = null; + return true; + } else { + return false; + } + } }