]> source.dussan.org Git - vaadin-framework.git/commitdiff
Adds the ability to swap between the three client side selection models (#13334)
authorHenrik Paul <henrik@vaadin.com>
Mon, 30 Jun 2014 15:18:32 +0000 (18:18 +0300)
committerLeif Åstrand <leif@vaadin.com>
Fri, 4 Jul 2014 13:45:32 +0000 (13:45 +0000)
So, this means when the selection model is modified on the server side,
the client side changes accordingly.

Change-Id: I3c7e3802cecdf9dfd64f5296c48fca5dfc58787d

client/src/com/vaadin/client/ui/grid/Grid.java
client/src/com/vaadin/client/ui/grid/GridConnector.java
client/src/com/vaadin/client/ui/grid/selection/AbstractRowHandleSelectionModel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/grid/selection/HasSelectionChangeHandlers.java
client/src/com/vaadin/client/ui/grid/selection/SelectionChangeHandler.java
client/src/com/vaadin/client/ui/grid/selection/SelectionModelMulti.java
client/src/com/vaadin/client/ui/grid/selection/SelectionModelNone.java
client/src/com/vaadin/client/ui/grid/selection/SelectionModelSingle.java

index da5adfc34a8cf4cb527be6892cd794d658ca5197..4bd07f19092d2c19ebf36973eb4617d78a8d3ca1 100644 (file)
@@ -1077,10 +1077,10 @@ public class Grid<T> extends Composite implements
 
         // Default action on SelectionChangeEvents. Refresh the body so changed
         // become visible.
-        addSelectionChangeHandler(new SelectionChangeHandler() {
+        addSelectionChangeHandler(new SelectionChangeHandler<T>() {
 
             @Override
-            public void onSelectionChange(SelectionChangeEvent<?> event) {
+            public void onSelectionChange(SelectionChangeEvent<T> event) {
                 refreshBody();
             }
         });
@@ -2370,7 +2370,7 @@ public class Grid<T> extends Composite implements
 
     @Override
     public HandlerRegistration addSelectionChangeHandler(
-            final SelectionChangeHandler handler) {
+            final SelectionChangeHandler<T> handler) {
         return addHandler(handler, SelectionChangeEvent.getType());
     }
 
index ee1cc0ee759f865ef6ff64276be0ceac431a92fc..daf938a7843535ab636a8b3838f86f4906ea7f8d 100644 (file)
@@ -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<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
@@ -103,22 +146,6 @@ public class GridConnector extends AbstractComponentConnector {
                                 (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);
-        }
     }
 
     /**
@@ -176,9 +203,24 @@ public class GridConnector extends AbstractComponentConnector {
      * 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() {
@@ -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<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);
+    }-*/;
 }
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 (file)
index 0000000..f55229d
--- /dev/null
@@ -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.
+ * <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;
+}
index 78b6f098d9e9a11e275d98574950b7b3efc3eb6b..c531265590d77fd493c6cf2859420c583f765358 100644 (file)
 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<T> {
     /**
      * Register a selection change handler.
      * <p>
-     * 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<T> handler);
 
 }
index e5d15386c0d6554791da927a744452471ccfdb83..aa61bdecdf3391b73eed78bf516b9f1f15426181 100644 (file)
@@ -19,19 +19,21 @@ import com.google.gwt.event.shared.EventHandler;
 
 /**
  * Handler for {@link SelectionChangeEvent}s.
- *
+ * 
  * @since 7.4
  * @author Vaadin Ltd
+ * @param <T>
+ *            The row data type
  */
-public interface SelectionChangeHandler extends EventHandler {
+public interface SelectionChangeHandler<T> 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<T> event);
 
 }
index de62dc9cbc9a3b80d7e16662ecb42ddbec31b3d8..6ebd7f40441cdf4df4a7f33aba6775b254b75d9b 100644 (file)
@@ -30,7 +30,8 @@ import com.vaadin.client.ui.grid.Renderer;
  * @author Vaadin Ltd
  * @since 7.4
  */
-public class SelectionModelMulti<T> implements SelectionModel.Multi<T> {
+public class SelectionModelMulti<T> extends AbstractRowHandleSelectionModel<T>
+        implements SelectionModel.Multi<T> {
 
     private final Set<RowHandle<T>> selectedRows;
     private Renderer<Boolean> renderer;
@@ -147,6 +148,7 @@ public class SelectionModelMulti<T> implements SelectionModel.Multi<T> {
         return selectedRows.contains(handle);
     }
 
+    @Override
     protected boolean selectByHandle(RowHandle<T> handle) {
         if (selectedRows.add(handle)) {
             handle.pin();
@@ -155,6 +157,7 @@ public class SelectionModelMulti<T> implements SelectionModel.Multi<T> {
         return false;
     }
 
+    @Override
     protected boolean deselectByHandle(RowHandle<T> handle) {
         if (selectedRows.remove(handle)) {
             handle.unpin();
index 93dfb49df2d3a173e4526648f6310d3c11b3bdec..59bf248032f1e10d4aa1edaf8e8811c626eef8e4 100644 (file)
@@ -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<T> implements SelectionModel.None<T> {
+public class SelectionModelNone<T> extends AbstractRowHandleSelectionModel<T>
+        implements SelectionModel.None<T> {
 
     @Override
     public boolean isSelected(T row) {
@@ -41,12 +43,12 @@ public class SelectionModelNone<T> implements SelectionModel.None<T> {
 
     @Override
     public void setGrid(Grid<T> grid) {
-
+        // noop
     }
 
     @Override
     public void reset() {
-
+        // noop
     }
 
     @Override
@@ -54,4 +56,18 @@ public class SelectionModelNone<T> implements SelectionModel.None<T> {
         return Collections.emptySet();
     }
 
+    @Override
+    protected boolean selectByHandle(RowHandle<T> handle)
+            throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("This selection model "
+                + "does not support selection");
+    }
+
+    @Override
+    protected boolean deselectByHandle(RowHandle<T> handle)
+            throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("This selection model "
+                + "does not support deselection");
+    }
+
 }
index 775e1878c5241f041796d284ded6fc5406ff12c4..4ef792f1c76235ab22733a996002ead6eda9887f 100644 (file)
@@ -28,7 +28,8 @@ import com.vaadin.client.ui.grid.Renderer;
  * @author Vaadin Ltd
  * @since 7.4
  */
-public class SelectionModelSingle<T> implements SelectionModel.Single<T> {
+public class SelectionModelSingle<T> extends AbstractRowHandleSelectionModel<T>
+        implements SelectionModel.Single<T> {
 
     private Grid<T> grid;
     private RowHandle<T> selectedRow;
@@ -123,4 +124,23 @@ public class SelectionModelSingle<T> implements SelectionModel.Single<T> {
         return Collections.emptySet();
     }
 
+    @Override
+    protected boolean selectByHandle(RowHandle<T> handle) {
+        if (!handle.equals(selectedRow)) {
+            selectedRow = handle;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    protected boolean deselectByHandle(RowHandle<T> handle) {
+        if (handle.equals(selectedRow)) {
+            selectedRow = null;
+            return true;
+        } else {
+            return false;
+        }
+    }
 }