diff options
Diffstat (limited to 'client')
5 files changed, 361 insertions, 77 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java index bcae476cf3..7d906fc4af 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java @@ -151,7 +151,6 @@ public class GridConnector extends AbstractListingConnector // Default selection style is space key. spaceSelectHandler = new SpaceSelectHandler<>(getWidget()); - clickSelectHandler = new ClickSelectHandler<>(getWidget()); getWidget().addSortHandler(this::handleSortEvent); getWidget().setRowStyleGenerator(rowRef -> { JsonObject json = rowRef.getRow(); diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java index 7b421be15b..b24cb54e63 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/MultiSelectionModelConnector.java @@ -15,21 +15,37 @@ */ package com.vaadin.client.connectors.grid; +import java.util.Optional; + +import com.google.gwt.event.shared.HandlerRegistration; 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.extensions.AbstractExtensionConnector; import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.events.SelectAllEvent; import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; import com.vaadin.client.widget.grid.selection.SelectionModel; import com.vaadin.client.widget.grid.selection.SelectionModelWithSelectionColumn; import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.SelectionColumn; +import com.vaadin.shared.Range; import com.vaadin.shared.data.DataCommunicatorConstants; import com.vaadin.shared.data.selection.GridMultiSelectServerRpc; import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.MultiSelectionModelState; import elemental.json.JsonObject; /** * Connector for server side multiselection model implementation. + * <p> + * This selection model displays a selection column {@link SelectionColumn} as + * the first column of the grid. + * <p> + * Implementation detail: The Grid selection is updated immediately on client + * side, without waiting for the server response. * * @author Vaadin Ltd * @@ -39,6 +55,10 @@ import elemental.json.JsonObject; @Connect(com.vaadin.ui.components.grid.MultiSelectionModelImpl.class) public class MultiSelectionModelConnector extends AbstractExtensionConnector { + private HandlerRegistration selectAllHandler; + private HandlerRegistration dataAvailable; + private Range availableRows; + /** * Client side multiselection model implementation. */ @@ -54,27 +74,37 @@ public class MultiSelectionModelConnector extends AbstractExtensionConnector { @Override public void select(JsonObject item) { + updateRowSelected(getGrid().getDataSource().getHandle(item), true); + getRpcProxy(GridMultiSelectServerRpc.class) .select(item.getString(DataCommunicatorConstants.KEY)); } @Override public void deselect(JsonObject item) { - // handled by diffstate + if (isAllSelected()) { + // handled by diffstate + getState().allSelected = false; + getGrid().getSelectionColumn().get().getSelectAllCheckBox() + .ifPresent(cb -> cb.setValue(false, false)); + } + updateRowSelected(getGrid().getDataSource().getHandle(item), false); + getRpcProxy(GridMultiSelectServerRpc.class) .deselect(item.getString(DataCommunicatorConstants.KEY)); } @Override public void deselectAll() { - // TODO Will be added in a separate patch - throw new UnsupportedOperationException( - "Deselect all not supported."); + // handled by diffstate + updateAllRowsSelected(false); + getState().allSelected = false; + getRpcProxy(GridMultiSelectServerRpc.class).deselectAll(); } @Override public boolean isSelected(JsonObject item) { - return SelectionModel.isItemSelected(item); + return MultiSelectionModelConnector.this.isSelected(item); } } @@ -82,6 +112,19 @@ public class MultiSelectionModelConnector extends AbstractExtensionConnector { @Override protected void extend(ServerConnector target) { getGrid().setSelectionModel(new MultiSelectionModel()); + // capture current rows so that can show selection update immediately + dataAvailable = getGrid().addDataAvailableHandler( + event -> availableRows = event.getAvailableRows()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + + dataAvailable.removeHandler(); + if (selectAllHandler != null) { + selectAllHandler.removeHandler(); + } } @Override @@ -89,6 +132,11 @@ public class MultiSelectionModelConnector extends AbstractExtensionConnector { return (GridConnector) super.getParent(); } + @Override + public MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } + /** * Shorthand for fetching the grid this selection model is bound to. * @@ -98,4 +146,126 @@ public class MultiSelectionModelConnector extends AbstractExtensionConnector { return getParent().getWidget(); } + @OnStateChange({ "selectAllCheckBoxVisible", "allSelected" }) + void onSelectAllCheckboxStateUpdates() { + // in case someone wants to override this, moved the actual updating to + // a protected method + updateSelectAllCheckBox(); + } + + /** + * Called whenever there has been a state update for select all checkbox + * visibility or all have been selected or deselected. + */ + protected void updateSelectAllCheckBox() { + final boolean selectAllCheckBoxVisible = getState().selectAllCheckBoxVisible; + if (selectAllCheckBoxVisible && selectAllHandler == null) { + selectAllHandler = getGrid() + .addSelectAllHandler(this::onSelectAllEvent); + } else if (!selectAllCheckBoxVisible && selectAllHandler != null) { + selectAllHandler.removeHandler(); + selectAllHandler = null; + } + Optional<Grid<JsonObject>.SelectionColumn> selectionColumn = getGrid() + .getSelectionColumn(); + + // if someone extends this selection model and doesn't use the selection + // column, the select all checkbox is not there either + if (selectionColumn.isPresent()) { + selectionColumn.get() + .setSelectAllCheckBoxVisible(selectAllCheckBoxVisible); + selectionColumn.get().getSelectAllCheckBox() + .ifPresent(checkbox -> checkbox + .setValue(getState().allSelected, false)); + } + } + + /** + * Returns whether all items are selected or not. + * + * @return {@code true} if all items are selected, {@code false} if not + */ + protected boolean isAllSelected() { + return getState().selectAllCheckBoxVisible && getState().allSelected; + } + + /** + * Handler for selecting / deselecting all grid rows. + * + * @param event + * the select all event from grid + */ + protected void onSelectAllEvent(SelectAllEvent<JsonObject> event) { + final boolean allSelected = event.isAllSelected(); + final boolean wasAllSelected = isAllSelected(); + assert allSelected != wasAllSelected : "Grid Select All CheckBox had invalid state"; + + if (allSelected && !wasAllSelected) { + getState().allSelected = true; + updateAllRowsSelected(true); + getRpcProxy(GridMultiSelectServerRpc.class).selectAll(); + } else if (!allSelected && wasAllSelected) { + getState().allSelected = false; + updateAllRowsSelected(false); + getRpcProxy(GridMultiSelectServerRpc.class).deselectAll(); + } + } + + /** + * Update selection for all grid rows. + * + * @param selected + * {@code true} for marking all rows selected, {@code false} for + * not selected + */ + protected void updateAllRowsSelected(boolean selected) { + if (availableRows != null) { + 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); + updateRowSelected(handle, selected); + } + } + } + } + + /** + * Returns whether the given item selected in grid or not. + * + * @param item + * the item to check + * @return {@code true} if selected {@code false} if not + */ + protected boolean isSelected(JsonObject item) { + return getState().allSelected || SelectionModel.isItemSelected(item); + } + + /** + * Marks the given row to be selected or deselected. Returns true if the + * value actually changed. + * <p> + * Note: If selection model is in batch select state, the row will be pinned + * on select. + * + * @param row + * row handle + * @param selected + * {@code true} if row should be selected; {@code false} if not + */ + protected void updateRowSelected(RowHandle<JsonObject> row, + boolean selected) { + boolean itemWasMarkedSelected = SelectionModel + .isItemSelected(row.getRow()); + if (selected && !itemWasMarkedSelected) { + row.getRow().put(DataCommunicatorConstants.SELECTED, true); + } else if (!selected && itemWasMarkedSelected) { + row.getRow().remove(DataCommunicatorConstants.SELECTED); + } + + row.updateRow(); + } + } diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java index 33a6d9d9b6..aba6d906b3 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java @@ -17,6 +17,7 @@ package com.vaadin.client.connectors.grid; import com.vaadin.client.ServerConnector; import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; import com.vaadin.client.widget.grid.selection.SelectionModel; import com.vaadin.shared.data.DataCommunicatorConstants; import com.vaadin.shared.data.selection.SelectionServerRpc; @@ -34,6 +35,8 @@ import elemental.json.JsonObject; @Connect(com.vaadin.ui.components.grid.SingleSelectionModelImpl.class) public class SingleSelectionModelConnector extends AbstractExtensionConnector { + private ClickSelectHandler clickSelectHandler; + @Override protected void extend(ServerConnector target) { getParent().getWidget() @@ -62,6 +65,15 @@ public class SingleSelectionModelConnector extends AbstractExtensionConnector { } }); + clickSelectHandler = new ClickSelectHandler<>(getParent().getWidget()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + if (clickSelectHandler != null) { + clickSelectHandler.removeHandler(); + } } @Override diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java index 25ce090d6e..f0c2a379dc 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java @@ -20,24 +20,53 @@ import com.vaadin.client.widget.grid.selection.SelectionModel; /** * A select all event, fired by the Grid when it needs all rows in data source - * to be selected. + * to be selected, OR when all rows have been selected and are now deselected. * * @since 7.4 * @author Vaadin Ltd + * @param <T> + * the type of the rows in grid */ public class SelectAllEvent<T> extends GwtEvent<SelectAllHandler<T>> { /** * Handler type. */ - private final static Type<SelectAllHandler<?>> TYPE = new Type<>();; + private static final Type<SelectAllHandler<?>> TYPE = new Type<>();; - private SelectionModel selectionModel; + private final SelectionModel<T> selectionModel; + private final boolean allSelected; - public SelectAllEvent(SelectionModel selectionModel) { + /** + * Constructs a new select all event when all rows in grid are selected. + * + * @param selectionModel + * the selection model in use + */ + public SelectAllEvent(SelectionModel<T> selectionModel) { + this(selectionModel, true); + } + + /** + * + * + * @param selectionModel + * the selection model in use + * @param allSelected + * {@code true} for all selected, {@code false} for all + * deselected + */ + public SelectAllEvent(SelectionModel<T> selectionModel, + boolean allSelected) { this.selectionModel = selectionModel; + this.allSelected = allSelected; } + /** + * Gets the type of the handlers for this event. + * + * @return the handler type + */ public static final Type<SelectAllHandler<?>> getType() { return TYPE; } @@ -53,7 +82,23 @@ public class SelectAllEvent<T> extends GwtEvent<SelectAllHandler<T>> { handler.onSelectAll(this); } - public SelectionModel getSelectionModel() { + /** + * The selection model in use. + * + * @return the selection model + */ + public SelectionModel<T> getSelectionModel() { return selectionModel; } + + /** + * Returns whether all the rows were selected, or deselected. Deselection + * can only happen if all rows were previously selected. + * + * @return {@code true} for selecting all rows, or {@code false} for + * deselecting all rows + */ + public boolean isAllSelected() { + return allSelected; + } } diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java index 97775aa482..afed21b045 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Grid.java +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -26,6 +26,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; @@ -56,8 +57,6 @@ import com.google.gwt.event.dom.client.KeyEvent; import com.google.gwt.event.dom.client.MouseEvent; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.touch.client.Point; import com.google.gwt.user.client.DOM; @@ -549,10 +548,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, return join(columns); } - private CELLTYPE getMergedCellForColumn( - Column<?, ?> column) { - for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) { - if (entry.getValue().contains(column)) return entry.getKey(); + private CELLTYPE getMergedCellForColumn(Column<?, ?> column) { + for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups + .entrySet()) { + if (entry.getValue().contains(column)) { + return entry.getKey(); + } } return null; } @@ -563,7 +564,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, cell.setColspan(1); } // Set colspan for grouped cells - for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups.entrySet()) { + for (Entry<CELLTYPE, Set<Column<?, ?>>> entry : cellGroups + .entrySet()) { CELLTYPE mergedCell = entry.getKey(); if (!checkMergedCellIsContinuous(entry.getValue())) { // on error simply break the merged cell @@ -2835,6 +2837,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, private boolean initDone = false; private boolean selected = false; private CheckBox selectAllCheckBox; + private boolean selectAllCheckBoxVisible; + private HeaderCell selectionCell; SelectionColumn(final Renderer<Boolean> selectColumnRenderer) { super(selectColumnRenderer); @@ -2853,68 +2857,27 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, @Override protected void setDefaultHeaderContent(HeaderCell selectionCell) { - final SelectionModel<T> model = getSelectionModel(); - final boolean shouldSelectAllCheckBoxBeShown = getHandlerCount( - SelectAllEvent.getType()) > 0; + this.selectionCell = selectionCell; - if (selectAllCheckBox == null && shouldSelectAllCheckBoxBeShown) { + // there is no checkbox yet -> create it + if (selectAllCheckBox == null) { selectAllCheckBox = GWT.create(CheckBox.class); selectAllCheckBox.setStylePrimaryName( getStylePrimaryName() + SELECT_ALL_CHECKBOX_CLASSNAME); - selectAllCheckBox.addValueChangeHandler( - new ValueChangeHandler<Boolean>() { - - @Override - public void onValueChange( - ValueChangeEvent<Boolean> event) { - if (event.getValue()) { - fireEvent(new SelectAllEvent<>(model)); - selected = true; - } else { - model.deselectAll(); - selected = false; - } - } - }); + selectAllCheckBox.addValueChangeHandler(event -> { + selected = event.getValue(); + fireEvent(new SelectAllEvent<>(getSelectionModel(), + selected)); + }); selectAllCheckBox.setValue(selected); - addHeaderClickHandler(new HeaderClickHandler() { - @Override - public void onClick(GridClickEvent event) { - CellReference<?> targetCell = event.getTargetCell(); - int defaultRowIndex = getHeader().getRows() - .indexOf(getDefaultHeaderRow()); - - if (targetCell.getColumnIndex() == 0 && targetCell - .getRowIndex() == defaultRowIndex) { - selectAllCheckBox.setValue( - !selectAllCheckBox.getValue(), true); - } - } - }); + addHeaderClickHandler(this::onHeaderClickEvent); // Select all with space when "select all" cell is active - addHeaderKeyUpHandler(new HeaderKeyUpHandler() { - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { - return; - } - HeaderRow targetHeaderRow = getHeader() - .getRow(event.getFocusedCell().getRowIndex()); - if (!targetHeaderRow.isDefault()) { - return; - } - if (event.getFocusedCell() - .getColumn() == SelectionColumn.this) { - // Send events to ensure state is updated - selectAllCheckBox.setValue( - !selectAllCheckBox.getValue(), true); - } - } - }); - } else if (selectAllCheckBox != null - && !shouldSelectAllCheckBoxBeShown) { + addHeaderKeyUpHandler(this::onHeaderKeyUpEvent); + + } else { // checkbox exists, but default header row has changed -> + // clear rows for (HeaderRow row : header.getRows()) { if (row.getCell(this) .getType() == GridStaticCellType.WIDGET) { @@ -2924,7 +2887,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, } } - selectionCell.setWidget(selectAllCheckBox); + // attach the checkbox to default row depending on visibility + doSetSelectAllCheckBoxVisible(); } @Override @@ -3005,6 +2969,87 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, public void onEnabled(boolean enabled) { setEnabled(enabled); } + + /** + * Sets the select all checkbox visible in the default header row for + * selection column. + * + * @param selectAllCheckBoxVisible + * {@code true} for visible, {@code false} for not + */ + public void setSelectAllCheckBoxVisible( + boolean selectAllCheckBoxVisible) { + if (this.selectAllCheckBoxVisible != selectAllCheckBoxVisible) { + this.selectAllCheckBoxVisible = selectAllCheckBoxVisible; + doSetSelectAllCheckBoxVisible(); + } + } + + /** + * Returns whether the select all checkbox is visible or not. + * + * @return {@code true} for visible, {@code false} for not + */ + public boolean isSelectAllCheckBoxVisible() { + return selectAllCheckBoxVisible; + } + + /** + * Returns the select all checkbox, which is present in the default + * header if the used selection model is of type + * {@link SelectionModelWithSelectionColumn}. + * + * To handle select all, add {@link SelectAllHandler} the grid with + * {@link #addSelectAllHandler(SelectAllHandler)}. + * + * @return the select all checkbox, or an empty optional if not in use + */ + public Optional<CheckBox> getSelectAllCheckBox() { + return Optional.ofNullable(selectionColumn == null ? null + : selectionColumn.selectAllCheckBox); + } + + /** + * Sets the select all checkbox visible or hidden. + */ + protected void doSetSelectAllCheckBoxVisible() { + assert selectAllCheckBox != null : "Select All Checkbox has not been created for selection column."; + assert selectionCell != null : "Default header cell for selection column not been set."; + + if (selectAllCheckBoxVisible) { + selectionCell.setWidget(selectAllCheckBox); + } else { + selectAllCheckBox.removeFromParent(); + selectionCell.setText(""); + } + } + + private void onHeaderClickEvent(GridClickEvent event) { + CellReference<?> targetCell = event.getTargetCell(); + int defaultRowIndex = getHeader().getRows() + .indexOf(getDefaultHeaderRow()); + + if (targetCell.getColumnIndex() == 0 + && targetCell.getRowIndex() == defaultRowIndex) { + selectAllCheckBox.setValue(!selectAllCheckBox.getValue(), true); + } + } + + private void onHeaderKeyUpEvent(GridKeyUpEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { + return; + } + HeaderRow targetHeaderRow = getHeader() + .getRow(event.getFocusedCell().getRowIndex()); + if (!targetHeaderRow.isDefault()) { + return; + } + if (event.getFocusedCell().getColumn() == SelectionColumn.this) { + // Send events to ensure state is updated + selectAllCheckBox.setValue(!selectAllCheckBox.getValue(), true); + } + } + } /** @@ -7557,8 +7602,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, /** * Sets the current selection model. - * <p> - * This function will call {@link SelectionModel#setGrid(Grid)}. * * @param selectionModel * a selection model implementation. @@ -7755,6 +7798,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, /** * Register a GWT event handler for a select all event. This handler gets * called whenever Grid needs all rows selected. + * <p> + * In case the select all checkbox is not visible in the + * {@link SelectionColumn}, it will be come visible after adding the + * handler. * * @param handler * a select all event handler @@ -7762,7 +7809,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, */ public HandlerRegistration addSelectAllHandler( SelectAllHandler<T> handler) { - return addHandler(handler, SelectAllEvent.getType()); + HandlerRegistration registration = addHandler(handler, + SelectAllEvent.getType()); + return registration; } /** @@ -7775,7 +7824,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, * * @param handler * a data available event handler - * @return the registartion for the event + * @return the registration for the event */ public HandlerRegistration addDataAvailableHandler( final DataAvailableHandler handler) { @@ -8845,4 +8894,13 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, return null; } + /** + * Returns the selection column for the grid if the selection model is of + * type {@link SelectionModelWithSelectionColumn}. + * + * @return the select all checkbox, or an empty optional if not in use + */ + public Optional<SelectionColumn> getSelectionColumn() { + return Optional.ofNullable(selectionColumn); + } } |