]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add API for controlling deselection for single select model (#16567)
authorLeif Åstrand <leif@vaadin.com>
Thu, 5 Feb 2015 19:04:36 +0000 (21:04 +0200)
committerHenrik Paul <henrik@vaadin.com>
Fri, 6 Feb 2015 10:33:39 +0000 (10:33 +0000)
Change-Id: Ieb245205b3a311a4563f39bc48baadc44e218b61

client/src/com/vaadin/client/connectors/GridConnector.java
client/src/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java
client/src/com/vaadin/client/widget/grid/selection/SelectionModel.java
client/src/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java
client/src/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java
server/src/com/vaadin/ui/Grid.java
shared/src/com/vaadin/shared/ui/grid/GridState.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridClientSelectionTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java
uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java

index f8aa044a8d1c809eab15265e59e672782c3517c3..f263b476427f72c7b21f0b5587e618b4524e53dd 100644 (file)
@@ -57,6 +57,7 @@ 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;
@@ -536,7 +537,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements
         // Selection
         if (stateChangeEvent.hasPropertyChanged("selectionMode")) {
             onSelectionModeChange();
+            updateSelectDeselectAllowed();
+        } else if (stateChangeEvent
+                .hasPropertyChanged("singleSelectDeselectAllowed")) {
+            updateSelectDeselectAllowed();
         }
+
         if (stateChangeEvent.hasPropertyChanged("selectedKeys")) {
             updateSelectionFromState();
         }
@@ -567,6 +573,14 @@ 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()];
index 0a1154e7878979d546f4eb65b07270d8772b73d6..c6bc52dd1c7432f94044b8cd961fa636bcd16b31 100644 (file)
@@ -30,6 +30,7 @@ public class ClickSelectHandler<T> {
 
     private Grid<T> grid;
     private HandlerRegistration clickHandler;
+    private boolean deselectAllowed = true;
 
     private class RowClickHandler implements BodyClickHandler {
 
@@ -38,6 +39,8 @@ public class ClickSelectHandler<T> {
             T row = (T) event.getTargetCell().getRow();
             if (!grid.isSelected(row)) {
                 grid.select(row);
+            } else if (deselectAllowed) {
+                grid.deselect(row);
             }
         }
     }
@@ -60,4 +63,15 @@ public class ClickSelectHandler<T> {
     public void removeHandler() {
         clickHandler.removeHandler();
     }
+
+    /**
+     * Sets whether clicking the currently selected row should deselect the row.
+     * 
+     * @param deselectAllowed
+     *            <code>true</code> to allow deselecting the selected row;
+     *            otherwise <code>false</code>
+     */
+    public void setDeselectAllowed(boolean deselectAllowed) {
+        this.deselectAllowed = deselectAllowed;
+    }
 }
index 37f6fb48c3fc1790932101c5cc361f99ea4f0e18..ec36ab52e82f2911f4523fe179d0f7cfeb9b0d65 100644 (file)
@@ -116,6 +116,26 @@ public interface SelectionModel<T> {
          */
         public T getSelectedRow();
 
+        /**
+         * Sets whether it's allowed to deselect the selected row through the
+         * UI. Deselection is allowed by default.
+         * 
+         * @param deselectAllowed
+         *            <code>true</code> if the selected row can be deselected
+         *            without selecting another row instead; otherwise
+         *            <code>false</code>.
+         */
+        public void setDeselectAllowed(boolean deselectAllowed);
+
+        /**
+         * Sets whether it's allowed to deselect the selected row through the
+         * UI.
+         * 
+         * @return <code>true</code> if deselection is allowed; otherwise
+         *         <code>false</code>
+         */
+        public boolean isDeselectAllowed();
+
     }
 
     /**
index 20eb3c1e63e2a663e95063353841d73b36ab222c..38605db12cb9c75aa2e2a60a05caf2b2cf71d056 100644 (file)
@@ -40,6 +40,8 @@ public class SelectionModelSingle<T> extends AbstractRowHandleSelectionModel<T>
     /** Event handling for selection by clicking cells */
     private ClickSelectHandler<T> clickSelectHandler;
 
+    private boolean deselectAllowed = true;
+
     @Override
     public boolean isSelected(T row) {
         return selectedRow != null
@@ -66,6 +68,7 @@ public class SelectionModelSingle<T> extends AbstractRowHandleSelectionModel<T>
         if (this.grid != null) {
             spaceSelectHandler = new SpaceSelectHandler<T>(grid);
             clickSelectHandler = new ClickSelectHandler<T>(grid);
+            updateHandlerDeselectAllowed();
         } else {
             spaceSelectHandler.removeHandler();
             clickSelectHandler.removeHandler();
@@ -148,4 +151,25 @@ public class SelectionModelSingle<T> extends AbstractRowHandleSelectionModel<T>
             return false;
         }
     }
+
+    @Override
+    public void setDeselectAllowed(boolean deselectAllowed) {
+        this.deselectAllowed = deselectAllowed;
+        updateHandlerDeselectAllowed();
+    }
+
+    @Override
+    public boolean isDeselectAllowed() {
+        return deselectAllowed;
+    }
+
+    private void updateHandlerDeselectAllowed() {
+        if (spaceSelectHandler != null) {
+            spaceSelectHandler.setDeselectAllowed(deselectAllowed);
+        }
+        if (clickSelectHandler != null) {
+            clickSelectHandler.setDeselectAllowed(deselectAllowed);
+        }
+    }
+
 }
index 7a1bf2dc067d16b9dc3bbaa9e1469ee1aa58e38f..3e04a6dfac0f019e662ab688a6c906144a4089a3 100644 (file)
@@ -79,10 +79,10 @@ public class SpaceSelectHandler<T> {
         protected void setSelected(Grid<T> grid, int rowIndex) {
             T row = grid.getDataSource().getRow(rowIndex);
 
-            if (grid.isSelected(row)) {
-                grid.deselect(row);
-            } else {
+            if (!grid.isSelected(row)) {
                 grid.select(row);
+            } else if (deselectAllowed) {
+                grid.deselect(row);
             }
         }
     }
@@ -91,6 +91,7 @@ public class SpaceSelectHandler<T> {
     private Grid<T> grid;
     private HandlerRegistration spaceUpHandler;
     private HandlerRegistration spaceDownHandler;
+    private boolean deselectAllowed = true;
 
     /**
      * Constructor for SpaceSelectHandler. This constructor will add all
@@ -121,4 +122,16 @@ public class SpaceSelectHandler<T> {
         spaceDownHandler.removeHandler();
         spaceUpHandler.removeHandler();
     }
+
+    /**
+     * Sets whether pressing space for the currently selected row should
+     * deselect the row.
+     * 
+     * @param deselectAllowed
+     *            <code>true</code> to allow deselecting the selected row;
+     *            otherwise <code>false</code>
+     */
+    public void setDeselectAllowed(boolean deselectAllowed) {
+        this.deselectAllowed = deselectAllowed;
+    }
 }
\ No newline at end of file
index 150f6728356f2a95656b35c216ab60b07f3b13db..bbae501782c65391d4302489f470d4132adbb371 100644 (file)
@@ -641,6 +641,26 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
              *         <code>null</code> if nothing is selected
              */
             Object getSelectedRow();
+
+            /**
+             * Sets whether it's allowed to deselect the selected row through
+             * the UI. Deselection is allowed by default.
+             * 
+             * @param deselectAllowed
+             *            <code>true</code> if the selected row can be
+             *            deselected without selecting another row instead;
+             *            otherwise <code>false</code>.
+             */
+            public void setDeselectAllowed(boolean deselectAllowed);
+
+            /**
+             * Sets whether it's allowed to deselect the selected row through
+             * the UI.
+             * 
+             * @return <code>true</code> if deselection is allowed; otherwise
+             *         <code>false</code>
+             */
+            public boolean isDeselectAllowed();
         }
 
         /**
@@ -814,6 +834,16 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
         public void reset() {
             deselect(getSelectedRow());
         }
+
+        @Override
+        public void setDeselectAllowed(boolean deselectAllowed) {
+            grid.getState().singleSelectDeselectAllowed = deselectAllowed;
+        }
+
+        @Override
+        public boolean isDeselectAllowed() {
+            return grid.getState(false).singleSelectDeselectAllowed;
+        }
     }
 
     /**
index 9d94a2cb8e9e8ead137f0db6f6ac0281c2f21837..7018df1413adae97589aa37e27413285bf47ca6c 100644 (file)
@@ -132,6 +132,9 @@ public class GridState extends AbstractComponentState {
 
     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];
 
index fe4a31d9e781800b377307334b246ae1fb035f2a..0c335f58b28ba17c604a0bf6a389ed8b070bb892 100644 (file)
@@ -57,6 +57,7 @@ import com.vaadin.ui.Grid.MultiSelectionModel;
 import com.vaadin.ui.Grid.RowReference;
 import com.vaadin.ui.Grid.RowStyleGenerator;
 import com.vaadin.ui.Grid.SelectionMode;
+import com.vaadin.ui.Grid.SelectionModel;
 import com.vaadin.ui.renderer.DateRenderer;
 import com.vaadin.ui.renderer.HtmlRenderer;
 import com.vaadin.ui.renderer.NumberRenderer;
@@ -82,6 +83,8 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
 
     private int containerDelay = 0;
 
+    private boolean singleSelectAllowDeselect = true;
+
     private IndexedContainer ds;
     private Grid grid;
     private SelectionListener selectionListener = new SelectionListener() {
@@ -320,6 +323,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                         grid.setSelectionMode(selectionMode);
                         if (selectionMode == SelectionMode.SINGLE) {
                             grid.addSelectionListener(selectionListener);
+
+                            ((SelectionModel.Single) grid.getSelectionModel())
+                                    .setDeselectAllowed(singleSelectAllowDeselect);
                         } else {
                             grid.removeSelectionListener(selectionListener);
                         }
@@ -488,6 +494,20 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
                     }
 
                 });
+
+        createBooleanAction("Single select allow deselect", "State",
+                singleSelectAllowDeselect, new Command<Grid, Boolean>() {
+                    @Override
+                    public void execute(Grid c, Boolean value, Object data) {
+                        singleSelectAllowDeselect = value.booleanValue();
+
+                        SelectionModel model = c.getSelectionModel();
+                        if (model instanceof SelectionModel.Single) {
+                            ((SelectionModel.Single) model)
+                                    .setDeselectAllowed(singleSelectAllowDeselect);
+                        }
+                    }
+                });
     }
 
     protected void createHeaderActions() {
index d4c10da626170ce2f3133f4db42e211793e3c16b..dccf2c0974bf8a37f7def7b4ce72da3221ad40f1 100644 (file)
@@ -19,6 +19,8 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
 
 import com.vaadin.testbench.By;
 import com.vaadin.testbench.elements.GridElement.GridCellElement;
@@ -74,7 +76,7 @@ public class GridClientSelectionTest extends GridBasicClientFeaturesTest {
         assertTrue("Multi Selection Model should have select all checkbox",
                 header.isElementPresent(By.tagName("input")));
 
-        setSelectionModelSingle();
+        setSelectionModelSingle(true);
         header = getGridElement().getHeaderCell(0, 0);
         assertFalse(
                 "Check box shouldn't have been in header for Single Selection Model",
@@ -88,15 +90,133 @@ public class GridClientSelectionTest extends GridBasicClientFeaturesTest {
 
     }
 
+    @Test
+    public void testDeselectAllowedMouseInput() {
+        openTestURL();
+
+        setSelectionModelSingle(true);
+
+        getGridElement().getCell(5, 1).click();
+
+        assertTrue("Row 5 should be selected after clicking", isRowSelected(5));
+
+        getGridElement().getCell(7, 1).click();
+
+        assertFalse("Row 5 should be deselected after clicking another row",
+                isRowSelected(5));
+        assertTrue("Row 7 should be selected after clicking", isRowSelected(7));
+
+        getGridElement().getCell(7, 1).click();
+
+        assertFalse("Row should be deselected after clicking again",
+                isRowSelected(7));
+    }
+
+    @Test
+    public void testDeselectAllowedKeyboardInput() {
+        openTestURL();
+
+        setSelectionModelSingle(true);
+
+        getGridElement().getHeaderCell(0, 1).click();
+
+        new Actions(getDriver()).sendKeys(Keys.ARROW_DOWN).perform();
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Row 0 should be selected after pressing space",
+                isRowSelected(0));
+
+        new Actions(getDriver()).sendKeys(Keys.ARROW_DOWN).perform();
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertFalse(
+                "Row 0 should be deselected after pressing space another row",
+                isRowSelected(0));
+        assertTrue("Row 1 should be selected after pressing space",
+                isRowSelected(1));
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertFalse("Row should be deselected after pressing space again",
+                isRowSelected(1));
+    }
+
+    @Test
+    public void testDeselectNotAllowedMouseInput() {
+        openTestURL();
+
+        setSelectionModelSingle(false);
+
+        getGridElement().getCell(5, 1).click();
+
+        assertTrue("Row 5 should be selected after clicking", isRowSelected(5));
+
+        getGridElement().getCell(7, 1).click();
+
+        assertFalse("Row 5 should be deselected after clicking another row",
+                isRowSelected(5));
+        assertTrue("Row 7 should be selected after clicking", isRowSelected(7));
+
+        getGridElement().getCell(7, 1).click();
+
+        assertTrue("Row should remain selected after clicking again",
+                isRowSelected(7));
+    }
+
+    @Test
+    public void testDeselectNotAllowedKeyboardInput() {
+        openTestURL();
+
+        setSelectionModelSingle(false);
+
+        getGridElement().getHeaderCell(0, 1).click();
+        new Actions(getDriver()).sendKeys(Keys.ARROW_DOWN).perform();
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Row 0 should be selected after pressing space",
+                isRowSelected(0));
+
+        new Actions(getDriver()).sendKeys(Keys.ARROW_DOWN).perform();
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertFalse(
+                "Row 0 should be deselected after pressing space another row",
+                isRowSelected(0));
+        assertTrue("Row 1 should be selected after pressing space",
+                isRowSelected(1));
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Row should remain selected after pressing space again",
+                isRowSelected(1));
+    }
+
+    private boolean isRowSelected(int index) {
+        boolean selected = getGridElement().getRow(index).isSelected();
+        return selected;
+    }
+
     private void setSelectionModelMulti() {
-        selectMenuPath("Component", "State", "Selection mode", "multi");
+        setSelectionModel("multi");
     }
 
-    private void setSelectionModelSingle() {
-        selectMenuPath("Component", "State", "Selection mode", "single");
+    private void setSelectionModelSingle(boolean deselectAllowed) {
+        String mode = "single";
+        if (!deselectAllowed) {
+            mode += " (no deselect)";
+        }
+        setSelectionModel(mode);
     }
 
     private void setSelectionModelNone() {
-        selectMenuPath("Component", "State", "Selection mode", "none");
+        setSelectionModel("none");
+    }
+
+    private void setSelectionModel(String model) {
+        selectMenuPath("Component", "State", "Selection mode", model);
     }
 }
index 3dbf613ba0b643deea65a5aaee78f3df0bb52520..b4eb473d4bdc479ded6c0cbbfb2f4b6c303b9b9c 100644 (file)
@@ -270,6 +270,41 @@ public class GridSelectionTest extends GridBasicFeaturesTest {
 
     }
 
+    @Test
+    public void testToggleDeselectAllowed() {
+        openTestURL();
+
+        setSelectionModelSingle();
+        // Deselect allowed already enabled
+
+        getGridElement().getCell(5, 1).click();
+        getGridElement().getCell(5, 1).click();
+        assertFalse("Row should be not selected after two clicks", getRow(5)
+                .isSelected());
+
+        selectMenuPath("Component", "State", "Single select allow deselect");
+        getGridElement().getCell(5, 1).click();
+        getGridElement().getCell(5, 1).click();
+        assertTrue("Row should be selected after two clicks", getRow(5)
+                .isSelected());
+
+        selectMenuPath("Component", "State", "Single select allow deselect");
+        getGridElement().getCell(5, 1).click();
+        assertFalse("Row should be not selected after another click", getRow(5)
+                .isSelected());
+
+        // Also verify that state is updated together with the model
+        setSelectionModelNone();
+        selectMenuPath("Component", "State", "Single select allow deselect");
+        setSelectionModelSingle();
+
+        getGridElement().getCell(5, 1).click();
+        getGridElement().getCell(5, 1).click();
+
+        assertTrue("Row should stay selected after two clicks", getRow(5)
+                .isSelected());
+    }
+
     private void setSelectionModelMulti() {
         selectMenuPath("Component", "State", "Selection mode", "multi");
     }
index 232a3a780ea6f9207e56745d40b9c4928100fa6d..0452aa65d1c6883997b9803bf8092f51a8e52a14 100644 (file)
@@ -66,6 +66,7 @@ import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler;
 import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler;
 import com.vaadin.client.widget.grid.events.ScrollEvent;
 import com.vaadin.client.widget.grid.events.ScrollHandler;
+import com.vaadin.client.widget.grid.selection.SelectionModel;
 import com.vaadin.client.widget.grid.selection.SelectionModel.None;
 import com.vaadin.client.widgets.Grid;
 import com.vaadin.client.widgets.Grid.Column;
@@ -468,6 +469,15 @@ public class GridBasicClientFeaturesWidget extends
             }
         }, selectionModePath);
 
+        addMenuCommand("single (no deselect)", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                grid.setSelectionMode(SelectionMode.SINGLE);
+                ((SelectionModel.Single<?>) grid.getSelectionModel())
+                        .setDeselectAllowed(false);
+            }
+        }, selectionModePath);
+
         addMenuCommand("none", new ScheduledCommand() {
             @Override
             public void execute() {