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.
/*
* 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;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.UUID;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.vaadin.data.BeanPropertySet;
import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.HasDataProvider;
import com.vaadin.data.HasValue;
import com.vaadin.data.PropertyDefinition;
import com.vaadin.data.PropertySet;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.CallbackDataProvider;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataGenerator;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.data.provider.GridSortOrder;
import com.vaadin.data.provider.GridSortOrderBuilder;
import com.vaadin.data.provider.InMemoryDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.data.provider.QuerySortOrder;
import com.vaadin.event.ConnectorEvent;
import com.vaadin.event.ContextClickEvent;
import com.vaadin.event.HasUserOriginated;
import com.vaadin.event.SortEvent;
import com.vaadin.event.SortEvent.SortListener;
import com.vaadin.event.SortEvent.SortNotifier;
import com.vaadin.event.selection.MultiSelectionListener;
import com.vaadin.event.selection.SelectionListener;
import com.vaadin.event.selection.SingleSelectionListener;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.EncodeResult;
import com.vaadin.server.Extension;
import com.vaadin.server.JsonCodec;
import com.vaadin.server.SerializableComparator;
import com.vaadin.server.SerializableSupplier;
import com.vaadin.server.Setter;
import com.vaadin.server.VaadinServiceClassLoaderUtil;
import com.vaadin.shared.Connector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.grid.AbstractGridExtensionState;
import com.vaadin.shared.ui.grid.ColumnResizeMode;
import com.vaadin.shared.ui.grid.ColumnState;
import com.vaadin.shared.ui.grid.DetailsManagerState;
import com.vaadin.shared.ui.grid.GridClientRpc;
import com.vaadin.shared.ui.grid.GridConstants;
import com.vaadin.shared.ui.grid.GridConstants.Section;
import com.vaadin.shared.ui.grid.GridConstants.SidebarColumnOrder;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.grid.SectionState;
import com.vaadin.ui.components.grid.ColumnReorderListener;
import com.vaadin.ui.components.grid.ColumnResizeListener;
import com.vaadin.ui.components.grid.ColumnVisibilityChangeListener;
import com.vaadin.ui.components.grid.DetailsGenerator;
import com.vaadin.ui.components.grid.Editor;
import com.vaadin.ui.components.grid.EditorImpl;
import com.vaadin.ui.components.grid.Footer;
import com.vaadin.ui.components.grid.FooterRow;
import com.vaadin.ui.components.grid.GridMultiSelect;
import com.vaadin.ui.components.grid.GridSelectionModel;
import com.vaadin.ui.components.grid.GridSingleSelect;
import com.vaadin.ui.components.grid.Header;
import com.vaadin.ui.components.grid.Header.Row;
import com.vaadin.ui.components.grid.HeaderCell;
import com.vaadin.ui.components.grid.HeaderRow;
import com.vaadin.ui.components.grid.ItemClickListener;
import com.vaadin.ui.components.grid.MultiSelectionModel;
import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
import com.vaadin.ui.components.grid.NoSelectionModel;
import com.vaadin.ui.components.grid.SingleSelectionModel;
import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
import com.vaadin.ui.components.grid.SortOrderProvider;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.ComponentRenderer;
import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.Renderer;
import com.vaadin.ui.renderers.TextRenderer;
import com.vaadin.util.ReflectTools;
import elemental.json.Json;
import elemental.json.JsonObject;
import elemental.json.JsonValue;
/**
* A grid component for displaying tabular data.
*
* @author Vaadin Ltd
* @since 8.0
*
* @param
* the grid bean type
*/
public class Grid extends AbstractListing implements HasComponents,
HasDataProvider, SortNotifier> {
private static final String DECLARATIVE_DATA_ITEM_TYPE = "data-item-type";
/**
* A callback method for fetching items. The callback is provided with a
* list of sort orders, offset index and limit.
*
* @param
* the grid bean type
*/
@FunctionalInterface
public interface FetchItemsCallback extends Serializable {
/**
* Returns a stream of items ordered by given sort orders, limiting the
* results with given offset and limit.
*
* This method is called after the size of the data set is asked from a
* related size callback. The offset and limit are promised to be within
* the size of the data set.
*
* @param sortOrder
* a list of sort orders
* @param offset
* the first index to fetch
* @param limit
* the fetched item count
* @return stream of items
*/
public Stream fetchItems(List sortOrder, int offset,
int limit);
}
@Deprecated
private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod(
ColumnReorderListener.class, "columnReorder",
ColumnReorderEvent.class);
private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools
.findMethod(SortListener.class, "sort", SortEvent.class);
@Deprecated
private static final Method COLUMN_RESIZE_METHOD = ReflectTools.findMethod(
ColumnResizeListener.class, "columnResize",
ColumnResizeEvent.class);
@Deprecated
private static final Method ITEM_CLICK_METHOD = ReflectTools
.findMethod(ItemClickListener.class, "itemClick", ItemClick.class);
@Deprecated
private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
.findMethod(ColumnVisibilityChangeListener.class,
"columnVisibilityChanged",
ColumnVisibilityChangeEvent.class);
/**
* Selection mode representing the built-in selection models in grid.
*
* These enums can be used in {@link Grid#setSelectionMode(SelectionMode)}
* to easily switch between the build-in selection models.
*
* @see Grid#setSelectionMode(SelectionMode)
* @see Grid#setSelectionModel(GridSelectionModel)
*/
public enum SelectionMode {
/**
* Single selection mode that maps to build-in
* {@link SingleSelectionModel}.
*
* @see SingleSelectionModelImpl
*/
SINGLE {
@Override
protected GridSelectionModel createModel() {
return new SingleSelectionModelImpl<>();
}
},
/**
* Multiselection mode that maps to build-in {@link MultiSelectionModel}
* .
*
* @see MultiSelectionModelImpl
*/
MULTI {
@Override
protected GridSelectionModel createModel() {
return new MultiSelectionModelImpl<>();
}
},
/**
* Selection model that doesn't allow selection.
*
* @see NoSelectionModel
*/
NONE {
@Override
protected GridSelectionModel createModel() {
return new NoSelectionModel<>();
}
};
/**
* Creates the selection model to use with this enum.
*
* @param
* the type of items in the grid
* @return the selection model
*/
protected abstract GridSelectionModel createModel();
}
/**
* An event that is fired when the columns are reordered.
*/
public static class ColumnReorderEvent extends Component.Event
implements HasUserOriginated {
private final boolean userOriginated;
/**
*
* @param source
* the grid where the event originated from
* @param userOriginated
* true if event is a result of user
* interaction, false if from API call
*/
public ColumnReorderEvent(Grid> source, boolean userOriginated) {
super(source);
this.userOriginated = userOriginated;
}
/**
* Returns true if the column reorder was done by the user,
* false if not and it was triggered by server side code.
*
* @return true if event is a result of user interaction
*/
@Override
public boolean isUserOriginated() {
return userOriginated;
}
}
/**
* An event that is fired when a column is resized, either programmatically
* or by the user.
*/
public static class ColumnResizeEvent extends Component.Event
implements HasUserOriginated {
private final Column, ?> column;
private final boolean userOriginated;
/**
*
* @param source
* the grid where the event originated from
* @param column
* the column that was resized
* @param userOriginated
* true if event is a result of user
* interaction, false if from API call
*/
public ColumnResizeEvent(Grid> source, Column, ?> column,
boolean userOriginated) {
super(source);
this.column = column;
this.userOriginated = userOriginated;
}
/**
* Returns the column that was resized.
*
* @return the resized column.
*/
public Column, ?> getColumn() {
return column;
}
/**
* Returns true if the column resize was done by the user,
* false if not and it was triggered by server side code.
*
* @return true if event is a result of user interaction
*/
@Override
public boolean isUserOriginated() {
return userOriginated;
}
}
/**
* An event fired when an item in the Grid has been clicked.
*
* @param
* the grid bean type
*/
public static class ItemClick extends ConnectorEvent {
private final T item;
private final Column column;
private final MouseEventDetails mouseEventDetails;
private final int rowIndex;
/**
* Creates a new {@code ItemClick} event containing the given item and
* Column originating from the given Grid.
*
* @param source
* the grid where the event originated from
* @param column
* the column that contains the clicked cell
* @param item
* the item that was clicked
* @param mouseEventDetails
* mouse event details about the click
* @param rowIndex
* the index of the row that contains the clicked cell
*/
public ItemClick(Grid source, Column column, T item,
MouseEventDetails mouseEventDetails, int rowIndex) {
super(source);
this.column = column;
this.item = item;
this.mouseEventDetails = mouseEventDetails;
this.rowIndex = rowIndex;
}
/**
* Returns the clicked item.
*
* @return the clicked item
*/
public T getItem() {
return item;
}
/**
* Returns the clicked column.
*
* @return the clicked column
*/
public Column getColumn() {
return column;
}
/**
* Returns the source Grid.
*
* @return the grid
*/
@Override
@SuppressWarnings("unchecked")
public Grid getSource() {
return (Grid) super.getSource();
}
/**
* Returns the mouse event details.
*
* @return the mouse event details
*/
public MouseEventDetails getMouseEventDetails() {
return mouseEventDetails;
}
/**
* Returns the clicked rowIndex.
*
* @return the clicked rowIndex
* @since 8.4
*/
public int getRowIndex() {
return rowIndex;
}
}
/**
* ContextClickEvent for the Grid Component.
*
*
*
* @param
* the grid bean type
*/
public static class GridContextClickEvent extends ContextClickEvent {
private final T item;
private final int rowIndex;
private final Column column;
private final Section section;
/**
* Creates a new context click event.
*
* @param source
* the grid where the context click occurred
* @param mouseEventDetails
* details about mouse position
* @param section
* the section of the grid which was clicked
* @param rowIndex
* the index of the row which was clicked
* @param item
* the item which was clicked
* @param column
* the column which was clicked
*/
public GridContextClickEvent(Grid source,
MouseEventDetails mouseEventDetails, Section section,
int rowIndex, T item, Column column) {
super(source, mouseEventDetails);
this.item = item;
this.section = section;
this.column = column;
this.rowIndex = rowIndex;
}
/**
* Returns the item of context clicked row.
*
* @return item of clicked row; null if header or footer
*/
public T getItem() {
return item;
}
/**
* Returns the clicked column.
*
* @return the clicked column
*/
public Column getColumn() {
return column;
}
/**
* Return the clicked section of Grid.
*
* @return section of grid
*/
public Section getSection() {
return section;
}
/**
* Returns the clicked row index.
*
* Header and Footer rows for index can be fetched with
* {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
*
* @return row index in section
*/
public int getRowIndex() {
return rowIndex;
}
@Override
@SuppressWarnings("unchecked")
public Grid getComponent() {
return (Grid) super.getComponent();
}
}
/**
* An event that is fired when a column's visibility changes.
*
* @since 7.5.0
*/
public static class ColumnVisibilityChangeEvent extends Component.Event
implements HasUserOriginated {
private final Column, ?> column;
private final boolean userOriginated;
private final boolean hidden;
/**
* Constructor for a column visibility change event.
*
* @param source
* the grid from which this event originates
* @param column
* the column that changed its visibility
* @param hidden
* true if the column was hidden,
* false if it became visible
* @param isUserOriginated
* true if the event was triggered by an UI
* interaction
*/
public ColumnVisibilityChangeEvent(Grid> source, Column, ?> column,
boolean hidden, boolean isUserOriginated) {
super(source);
this.column = column;
this.hidden = hidden;
userOriginated = isUserOriginated;
}
/**
* Gets the column that became hidden or visible.
*
* @return the column that became hidden or visible.
* @see Column#isHidden()
*/
public Column, ?> getColumn() {
return column;
}
/**
* Was the column set hidden or visible.
*
* @return true if the column was hidden false
* if it was set visible
*/
public boolean isHidden() {
return hidden;
}
@Override
public boolean isUserOriginated() {
return userOriginated;
}
}
/**
* A helper base class for creating extensions for the Grid component.
*
* @param
*/
public abstract static class AbstractGridExtension
extends AbstractListingExtension {
@Override
public void extend(AbstractListing grid) {
if (!(grid instanceof Grid)) {
throw new IllegalArgumentException(
getClass().getSimpleName() + " can only extend Grid");
}
super.extend(grid);
}
/**
* Adds given component to the connector hierarchy of Grid.
*
* @param c
* the component to add
*/
protected void addComponentToGrid(Component c) {
getParent().addExtensionComponent(c);
}
/**
* Removes given component from the connector hierarchy of Grid.
*
* @param c
* the component to remove
*/
protected void removeComponentFromGrid(Component c) {
getParent().removeExtensionComponent(c);
}
@Override
public Grid getParent() {
return (Grid) super.getParent();
}
@Override
protected AbstractGridExtensionState getState() {
return (AbstractGridExtensionState) super.getState();
}
@Override
protected AbstractGridExtensionState getState(boolean markAsDirty) {
return (AbstractGridExtensionState) super.getState(markAsDirty);
}
/**
* Returns the internal id for given column. This id should not be
* confused with the user-defined identifier.
*
* @param column
* the column
* @return internal id of given column
*/
protected String getInternalIdForColumn(Column column) {
return getParent().getInternalIdForColumn(column);
}
}
private final class GridServerRpcImpl implements GridServerRpc {
@Override
public void sort(String[] columnInternalIds, SortDirection[] directions,
boolean isUserOriginated) {
assert columnInternalIds.length == directions.length : "Column and sort direction counts don't match.";
List> list = new ArrayList<>(directions.length);
for (int i = 0; i < columnInternalIds.length; ++i) {
Column column = columnKeys.get(columnInternalIds[i]);
list.add(new GridSortOrder<>(column, directions[i]));
}
setSortOrder(list, isUserOriginated);
}
@Override
public void itemClick(String rowKey, String columnInternalId,
MouseEventDetails details, int rowIndex) {
Column column = getColumnByInternalId(columnInternalId);
T item = getDataCommunicator().getKeyMapper().get(rowKey);
fireEvent(new ItemClick<>(Grid.this, column, item, details,
rowIndex));
}
@Override
public void contextClick(int rowIndex, String rowKey,
String columnInternalId, Section section,
MouseEventDetails details) {
T item = null;
if (rowKey != null) {
item = getDataCommunicator().getKeyMapper().get(rowKey);
}
fireEvent(new GridContextClickEvent<>(Grid.this, details, section,
rowIndex, item, getColumnByInternalId(columnInternalId)));
}
@Override
public void columnsReordered(List newColumnOrder,
List oldColumnOrder) {
final String diffStateKey = "columnOrder";
ConnectorTracker connectorTracker = getUI().getConnectorTracker();
JsonObject diffState = connectorTracker.getDiffState(Grid.this);
// discard the change if the columns have been reordered from
// the server side, as the server side is always right
if (getState(false).columnOrder.equals(oldColumnOrder)) {
// Don't mark as dirty since client has the state already
getState(false).columnOrder = newColumnOrder;
// write changes to diffState so that possible reverting the
// column order is sent to client
assert diffState
.hasKey(diffStateKey) : "Field name has changed";
Type type = null;
try {
type = getState(false).getClass().getField(diffStateKey)
.getGenericType();
} catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
}
EncodeResult encodeResult = JsonCodec.encode(
getState(false).columnOrder, diffState, type,
connectorTracker);
diffState.put(diffStateKey, encodeResult.getEncodedValue());
fireColumnReorderEvent(true);
} else {
// make sure the client is reverted to the order that the
// server thinks it is
diffState.remove(diffStateKey);
markAsDirty();
}
}
@Override
public void columnVisibilityChanged(String internalId, boolean hidden) {
Column column = getColumnByInternalId(internalId);
column.checkColumnIsAttached();
if (column.isHidden() != hidden) {
column.getState().hidden = hidden;
fireColumnVisibilityChangeEvent(column, hidden, true);
}
}
@Override
public void columnResized(String internalId, double pixels) {
final Column column = getColumnByInternalId(internalId);
if (column != null && column.isResizable()) {
column.getState().width = pixels;
fireColumnResizeEvent(column, true);
}
}
}
/**
* Class for managing visible details rows.
*
* @param
* the grid bean type
*/
public static class DetailsManager extends AbstractGridExtension {
private final Set visibleDetails = new HashSet<>();
private final Map components = new HashMap<>();
private DetailsGenerator generator;
/**
* Sets the details component generator.
*
* @param generator
* the generator for details components
*/
public void setDetailsGenerator(DetailsGenerator generator) {
if (this.generator != generator) {
removeAllComponents();
}
getState().hasDetailsGenerator = generator != null;
this.generator = generator;
visibleDetails.forEach(this::refresh);
}
@Override
public void remove() {
removeAllComponents();
super.remove();
}
private void removeAllComponents() {
// Clean up old components
components.values().forEach(this::removeComponentFromGrid);
components.clear();
}
@Override
public void generateData(T item, JsonObject jsonObject) {
if (generator == null || !visibleDetails.contains(item)) {
return;
}
if (!components.containsKey(item)) {
Component detailsComponent = generator.apply(item);
Objects.requireNonNull(detailsComponent,
"Details generator can't create null components");
if (detailsComponent.getParent() != null) {
throw new IllegalStateException(
"Details component was already attached");
}
addComponentToGrid(detailsComponent);
components.put(item, detailsComponent);
}
jsonObject.put(GridState.JSONKEY_DETAILS_VISIBLE,
components.get(item).getConnectorId());
}
@Override
public void destroyData(T item) {
// No clean up needed. Components are removed when hiding details
// and/or changing details generator
}
/**
* Sets the visibility of details component for given item.
*
* @param item
* the item to show details for
* @param visible
* {@code true} if details component should be visible;
* {@code false} if it should be hidden
*/
public void setDetailsVisible(T item, boolean visible) {
boolean refresh = false;
if (!visible) {
refresh = visibleDetails.remove(item);
if (components.containsKey(item)) {
removeComponentFromGrid(components.remove(item));
}
} else {
refresh = visibleDetails.add(item);
}
if (refresh) {
refresh(item);
}
}
/**
* Returns the visibility of details component for given item.
*
* @param item
* the item to show details for
*
* @return {@code true} if details component should be visible;
* {@code false} if it should be hidden
*/
public boolean isDetailsVisible(T item) {
return visibleDetails.contains(item);
}
@Override
public Grid getParent() {
return super.getParent();
}
@Override
protected DetailsManagerState getState() {
return (DetailsManagerState) super.getState();
}
@Override
protected DetailsManagerState getState(boolean markAsDirty) {
return (DetailsManagerState) super.getState(markAsDirty);
}
}
/**
* This extension manages the configuration and data communication for a
* Column inside of a Grid component.
*
* @param
* the grid bean type
* @param
* the column value type
*/
public static class Column extends AbstractExtension {
/**
* Behavior when parsing nested properties which may contain
* null values in the property chain.
*/
public enum NestedNullBehavior {
/**
* Throw a NullPointerException if there is a nested
* null value.
*/
THROW,
/**
* Silently ignore any exceptions caused by nested null
* values.
*/
ALLOW_NULLS
}
private final ValueProvider valueProvider;
private ValueProvider presentationProvider;
private SortOrderProvider sortOrderProvider = direction -> {
String id = getId();
if (id == null) {
return Stream.empty();
}
return Stream.of(new QuerySortOrder(id, direction));
};
private NestedNullBehavior nestedNullBehavior = NestedNullBehavior.THROW;
private boolean sortable = true;
private SerializableComparator comparator;
private StyleGenerator styleGenerator = item -> null;
private DescriptionGenerator descriptionGenerator;
private DataGenerator dataGenerator = new DataGenerator() {
@Override
public void generateData(T item, JsonObject jsonObject) {
ColumnState state = getState(false);
String communicationId = getConnectorId();
assert communicationId != null : "No communication ID set for column "
+ state.caption;
JsonObject obj = getDataObject(jsonObject,
DataCommunicatorConstants.DATA);
obj.put(communicationId, generateRendererValue(item,
presentationProvider, state.renderer));
String style = styleGenerator.apply(item);
if (style != null && !style.isEmpty()) {
JsonObject styleObj = getDataObject(jsonObject,
GridState.JSONKEY_CELLSTYLES);
styleObj.put(communicationId, style);
}
if (descriptionGenerator != null) {
String description = descriptionGenerator.apply(item);
if (description != null && !description.isEmpty()) {
JsonObject descriptionObj = getDataObject(jsonObject,
GridState.JSONKEY_CELLDESCRIPTION);
descriptionObj.put(communicationId, description);
}
}
}
@Override
public void destroyData(T item) {
removeComponent(getGrid().getDataProvider().getId(item));
}
@Override
public void destroyAllData() {
// Make a defensive copy of keys, as the map gets cleared when
// removing components.
new HashSet<>(activeComponents.keySet())
.forEach(component -> removeComponent(component));
}
};
private Binding editorBinding;
private Map