All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.vaadin.ui.components.grid.MultiSelectionModelImpl Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.ui.components.grid;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.HierarchicalDataProvider;
import com.vaadin.data.provider.HierarchicalQuery;
import com.vaadin.data.provider.Query;
import com.vaadin.event.selection.MultiSelectionEvent;
import com.vaadin.event.selection.MultiSelectionListener;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.selection.GridMultiSelectServerRpc;
import com.vaadin.shared.ui.grid.MultiSelectionModelState;
import com.vaadin.ui.MultiSelect;
/**
* Multiselection model for grid.
*
* Shows a column of checkboxes as the first column of grid. Each checkbox
* triggers the selection for that row.
*
* Implementation detail: The Grid selection is updated immediately after user
* selection on client side, without waiting for the server response.
*
* @author Vaadin Ltd.
* @since 8.0
*
* @param
* the type of the selected item in grid.
*/
public class MultiSelectionModelImpl extends AbstractSelectionModel
implements MultiSelectionModel {
private class GridMultiSelectServerRpcImpl
implements GridMultiSelectServerRpc {
@Override
public void select(String key) {
MultiSelectionModelImpl.this.updateSelection(
new LinkedHashSet<>(Arrays.asList(getData(key))),
Collections.emptySet(), true);
}
@Override
public void deselect(String key) {
if (getState(false).allSelected) {
// updated right away on client side
getState(false).allSelected = false;
getUI().getConnectorTracker()
.getDiffState(MultiSelectionModelImpl.this)
.put("allSelected", false);
}
MultiSelectionModelImpl.this.updateSelection(Collections.emptySet(),
new LinkedHashSet<>(Arrays.asList(getData(key))), true);
}
@Override
public void selectAll() {
onSelectAll(true);
}
@Override
public void deselectAll() {
onDeselectAll(true);
}
}
private Map selection = new LinkedHashMap<>();
private SelectAllCheckBoxVisibility selectAllCheckBoxVisibility = SelectAllCheckBoxVisibility.DEFAULT;
@Override
protected void init() {
registerRpc(new GridMultiSelectServerRpcImpl());
}
@Override
protected MultiSelectionModelState getState() {
return (MultiSelectionModelState) super.getState();
}
@Override
protected MultiSelectionModelState getState(boolean markAsDirty) {
return (MultiSelectionModelState) super.getState(markAsDirty);
}
@Override
public void setSelectAllCheckBoxVisibility(
SelectAllCheckBoxVisibility selectAllCheckBoxVisibility) {
if (this.selectAllCheckBoxVisibility != selectAllCheckBoxVisibility) {
this.selectAllCheckBoxVisibility = selectAllCheckBoxVisibility;
markAsDirty();
}
}
@Override
public SelectAllCheckBoxVisibility getSelectAllCheckBoxVisibility() {
return selectAllCheckBoxVisibility;
}
@Override
public boolean isSelectAllCheckBoxVisible() {
updateCanSelectAll();
return getState(false).selectAllCheckBoxVisible;
}
@Override
public boolean isAllSelected() {
return getState(false).allSelected;
}
@Override
public boolean isSelected(T item) {
DataProvider dataProvider = getGrid().getDataProvider();
return selectionContainsId(dataProvider.getId(item));
}
/**
* Returns if the given id belongs to one of the selected items.
*
* @param id
* the id to check for
* @return {@code true} if id is selected, {@code false} if not
*/
protected boolean selectionContainsId(Object id) {
return selection.containsKey(id);
}
@Override
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);
updateCanSelectAll();
}
/**
* Controls whether the select all checkbox is visible in the grid default
* header, or not.
*
* This is updated as a part of {@link #beforeClientResponse(boolean)},
* since the data provider for grid can be changed on the fly.
*
* @see SelectAllCheckBoxVisibility
*/
protected void updateCanSelectAll() {
switch (selectAllCheckBoxVisibility) {
case VISIBLE:
getState(false).selectAllCheckBoxVisible = true;
break;
case HIDDEN:
getState(false).selectAllCheckBoxVisible = false;
break;
case DEFAULT:
getState(false).selectAllCheckBoxVisible = getGrid()
.getDataProvider().isInMemory();
break;
default:
break;
}
}
@Override
public Registration addMultiSelectionListener(
MultiSelectionListener listener) {
return addListener(MultiSelectionEvent.class, listener,
MultiSelectionListener.SELECTION_CHANGE_METHOD);
}
@Override
public Set getSelectedItems() {
return Collections
.unmodifiableSet(new LinkedHashSet<>(selection.values()));
}
@Override
public void updateSelection(Set addedItems, Set removedItems) {
updateSelection(addedItems, removedItems, false);
}
@Override
public void selectAll() {
onSelectAll(false);
}
@Override
public void deselectAll() {
onDeselectAll(false);
}
/**
* Gets a wrapper for using this grid as a multiselect in a binder.
*
* @return a multiselect wrapper for grid
*/
@Override
public MultiSelect asMultiSelect() {
return new MultiSelect() {
@Override
public void setValue(Set value) {
Objects.requireNonNull(value);
Set copy = value.stream().map(Objects::requireNonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
updateSelection(copy, new LinkedHashSet<>(getSelectedItems()));
}
@Override
public Set getValue() {
return getSelectedItems();
}
@Override
public Registration addValueChangeListener(
com.vaadin.data.HasValue.ValueChangeListener> listener) {
return addSelectionListener(
event -> listener.valueChange(event));
}
@Override
public void setRequiredIndicatorVisible(
boolean requiredIndicatorVisible) {
// TODO support required indicator for grid ?
throw new UnsupportedOperationException(
"Required indicator is not supported in grid.");
}
@Override
public boolean isRequiredIndicatorVisible() {
// TODO support required indicator for grid ?
throw new UnsupportedOperationException(
"Required indicator is not supported in grid.");
}
@Override
public void setReadOnly(boolean readOnly) {
setUserSelectionAllowed(!readOnly);
}
@Override
public boolean isReadOnly() {
return !isUserSelectionAllowed();
}
@Override
public void updateSelection(Set addedItems,
Set removedItems) {
MultiSelectionModelImpl.this.updateSelection(addedItems,
removedItems);
}
@Override
public Set getSelectedItems() {
return MultiSelectionModelImpl.this.getSelectedItems();
}
@Override
public Registration addSelectionListener(
MultiSelectionListener listener) {
return MultiSelectionModelImpl.this
.addMultiSelectionListener(listener);
}
};
}
/**
* Triggered when the user checks the select all checkbox.
*
* @param userOriginated
* {@code true} if originated from client side by user
*/
protected void onSelectAll(boolean userOriginated) {
if (userOriginated) {
verifyUserCanSelectAll();
// all selected state has been updated in client side already
getState(false).allSelected = true;
getUI().getConnectorTracker().getDiffState(this).put("allSelected",
true);
} else {
getState().allSelected = true;
}
Stream allItemsStream;
DataProvider dataProvider = getGrid().getDataProvider();
// this will fetch everything from backend
if (dataProvider instanceof HierarchicalDataProvider) {
allItemsStream = fetchAllHierarchical(
(HierarchicalDataProvider) dataProvider);
} else {
allItemsStream = fetchAll(dataProvider);
}
LinkedHashSet allItems = new LinkedHashSet<>();
// ensure speedy closing in case the stream is connected to IO channels
try (Stream stream = allItemsStream) {
stream.forEach(allItems::add);
}
updateSelection(allItems, Collections.emptySet(), userOriginated);
}
/**
* Fetch all items from the given hierarchical data provider.
*
* @since 8.1
* @param dataProvider
* the data provider to fetch from
* @return all items in the data provider
*/
private Stream fetchAllHierarchical(
HierarchicalDataProvider dataProvider) {
return fetchAllDescendants(null, dataProvider);
}
/**
* Fetch all the descendants of the given parent item from the given data
* provider.
*
* @since 8.1
* @param parent
* the parent item to fetch descendants for
* @param dataProvider
* the data provider to fetch from
* @return the stream of all descendant items
*/
private Stream fetchAllDescendants(T parent,
HierarchicalDataProvider dataProvider) {
List children;
try (Stream stream = dataProvider
.fetchChildren(new HierarchicalQuery<>(null, parent))) {
children = stream.collect(Collectors.toList());
}
if (children.isEmpty()) {
return Stream.empty();
}
return children.stream()
.flatMap(child -> Stream.concat(Stream.of(child),
fetchAllDescendants(child, dataProvider)));
}
/**
* Fetch all items from the given data provider.
*
* @since 8.1
* @param dataProvider
* the data provider to fetch from
* @return all items in this data provider
*/
private Stream fetchAll(DataProvider dataProvider) {
return dataProvider.fetch(new Query<>());
}
/**
* Triggered when the user unchecks the select all checkbox.
*
* @param userOriginated
* {@code true} if originated from client side by user
*/
protected void onDeselectAll(boolean userOriginated) {
if (userOriginated) {
verifyUserCanSelectAll();
// all selected state has been update in client side already
getState(false).allSelected = false;
getUI().getConnectorTracker().getDiffState(this).put("allSelected",
false);
} else {
getState().allSelected = false;
}
updateSelection(Collections.emptySet(),
new LinkedHashSet<>(selection.values()), userOriginated);
}
private void verifyUserCanSelectAll() {
if (!getState(false).selectAllCheckBoxVisible) {
throw new IllegalStateException(
"Cannot select all from client since select all checkbox should not be visible");
}
}
/**
* Updates the selection by adding and removing the given items.
*
* All selection updates should go through this method, since it handles
* incorrect parameters, removing duplicates, notifying data communicator
* and and firing events.
*
* @param addedItems
* the items added to selection, not {@code} null
* @param removedItems
* the items removed from selection, not {@code} null
* @param userOriginated
* {@code true} if this was used originated, {@code false} if not
*/
protected void updateSelection(Set addedItems, Set removedItems,
boolean userOriginated) {
Objects.requireNonNull(addedItems);
Objects.requireNonNull(removedItems);
if (userOriginated && !isUserSelectionAllowed()) {
throw new IllegalStateException("Client tried to update selection"
+ " although user selection is disallowed");
}
DataProvider dataProvider = getGrid().getDataProvider();
addedItems.removeIf(item -> {
Object id = dataProvider.getId(item);
Optional toRemove = removedItems.stream()
.filter(i -> dataProvider.getId(i).equals(id)).findFirst();
toRemove.ifPresent(i -> removedItems.remove(i));
return toRemove.isPresent();
});
if (addedItems.stream().map(dataProvider::getId)
.allMatch(this::selectionContainsId)
&& removedItems.stream().map(dataProvider::getId)
.noneMatch(this::selectionContainsId)) {
return;
}
// update allSelected for server side selection updates
if (getState(false).allSelected && !removedItems.isEmpty()
&& !userOriginated) {
getState().allSelected = false;
}
doUpdateSelection(map -> {
// order of add / remove does not matter since no duplicates
Set removedItemIds = removedItems.stream()
.map(dataProvider::getId).collect(Collectors.toSet());
removedItemIds.forEach(itemId -> map.remove(itemId));
addedItems.forEach(item -> map.put(dataProvider.getId(item), item));
// refresh method is NOOP for items that are not present client side
DataCommunicator dataCommunicator = getGrid()
.getDataCommunicator();
removedItems.forEach(dataCommunicator::refresh);
addedItems.forEach(dataCommunicator::refresh);
}, userOriginated);
}
private void doUpdateSelection(Consumer> handler,
boolean userOriginated) {
if (getParent() == null) {
throw new IllegalStateException(
"Trying to update selection for grid selection model that has been detached from the grid.");
}
LinkedHashSet oldSelection = new LinkedHashSet<>(selection.values());
handler.accept(selection);
fireEvent(new MultiSelectionEvent<>(getGrid(), asMultiSelect(),
oldSelection, userOriginated));
}
@Override
public void refreshData(T item) {
DataProvider dataProvider = getGrid().getDataProvider();
Object refreshId = dataProvider.getId(item);
if (selection.containsKey(refreshId)) {
selection.put(refreshId, item);
}
}
}