dataProvider) {
this();
setDataProvider(dataProvider);
}
/**
* Creates a {@code TreeGrid} using a custom {@link PropertySet}
* implementation and custom data communicator.
*
* Property set is used for configuring the initial columns and resolving
* property names for {@link #addColumn(String)} and
* {@link Column#setEditorComponent(HasValue)}.
*
* @param propertySet
* the property set implementation to use, not {@code null}
* @param dataCommunicator
* the data communicator to use, not {@code null}
*/
protected TreeGrid(PropertySet propertySet,
HierarchicalDataCommunicator dataCommunicator) {
super(propertySet, dataCommunicator);
registerTreeGridRpc();
}
/**
* Creates a new TreeGrid with the given data communicator and without
* support for creating columns based on property names.
*
* @param dataCommunicator
* the custom data communicator to set
*/
protected TreeGrid(HierarchicalDataCommunicator dataCommunicator) {
this(new PropertySet() {
@Override
public Stream> getProperties() {
// No columns configured by default
return Stream.empty();
}
@Override
public Optional> getProperty(String name) {
throw new IllegalStateException(
"A TreeGrid created without a bean type class literal or a custom property set"
+ " doesn't support finding properties by name.");
}
}, dataCommunicator);
}
/**
* Creates a {@code TreeGrid} using a custom {@link PropertySet}
* implementation for creating a default set of columns and for resolving
* property names with {@link #addColumn(String)} and
* {@link Column#setEditorComponent(HasValue)}.
*
* This functionality is provided as static method instead of as a public
* constructor in order to make it possible to use a custom property set
* without creating a subclass while still leaving the public constructors
* focused on the common use cases.
*
* @see TreeGrid#TreeGrid()
* @see TreeGrid#TreeGrid(Class)
*
* @param
* the tree grid bean type
* @param propertySet
* the property set implementation to use, not {@code null}
* @return a new tree grid using the provided property set, not {@code null}
*/
public static TreeGrid withPropertySet(
PropertySet propertySet) {
return new TreeGrid(propertySet,
new HierarchicalDataCommunicator<>());
}
private void registerTreeGridRpc() {
registerRpc((NodeCollapseRpc) (rowKey, rowIndex, collapse,
userOriginated) -> {
T item = getDataCommunicator().getKeyMapper().get(rowKey);
if (collapse && getDataCommunicator().isExpanded(item)) {
getDataCommunicator().collapse(item, rowIndex);
fireCollapseEvent(
getDataCommunicator().getKeyMapper().get(rowKey),
userOriginated);
} else if (!collapse && !getDataCommunicator().isExpanded(item)) {
getDataCommunicator().expand(item, rowIndex);
fireExpandEvent(
getDataCommunicator().getKeyMapper().get(rowKey),
userOriginated);
}
});
registerRpc((FocusParentRpc) (rowKey, cellIndex) -> {
Integer parentIndex = getDataCommunicator().getParentIndex(
getDataCommunicator().getKeyMapper().get(rowKey));
if (parentIndex != null) {
getRpcProxy(FocusRpc.class).focusCell(parentIndex, cellIndex);
}
});
}
/**
* This method is inherited from Grid but should never be called directly
* with a TreeGrid.
*/
@Override
@Deprecated
public void scrollTo(int row) throws IllegalArgumentException {
super.scrollTo(row);
}
/**
* This method is inherited from Grid but should never be called directly
* with a TreeGrid.
*/
@Deprecated
@Override
public void scrollTo(int row, ScrollDestination destination) {
super.scrollTo(row, destination);
}
/**
* Adds an ExpandListener to this TreeGrid.
*
* @see ExpandEvent
*
* @param listener
* the listener to add
* @return a registration for the listener
*/
public Registration addExpandListener(ExpandListener listener) {
return addListener(ExpandEvent.class, listener,
ExpandListener.EXPAND_METHOD);
}
/**
* Adds a CollapseListener to this TreeGrid.
*
* @see CollapseEvent
*
* @param listener
* the listener to add
* @return a registration for the listener
*/
public Registration addCollapseListener(CollapseListener listener) {
return addListener(CollapseEvent.class, listener,
CollapseListener.COLLAPSE_METHOD);
}
@Override
public void setDataProvider(DataProvider dataProvider) {
if (!(dataProvider instanceof HierarchicalDataProvider)) {
throw new IllegalArgumentException(
"TreeGrid only accepts hierarchical data providers");
}
getRpcProxy(TreeGridClientRpc.class).clearPendingExpands();
super.setDataProvider(dataProvider);
}
/**
* Get the currently set hierarchy column.
*
* @return the currently set hierarchy column, or {@code null} if no column
* has been explicitly set
*/
public Column getHierarchyColumn() {
return getColumnByInternalId(getState(false).hierarchyColumnId);
}
/**
* Set the column that displays the hierarchy of this grid's data. By
* default the hierarchy will be displayed in the first column.
*
* Setting a hierarchy column by calling this method also sets the column to
* be visible and not hidable.
*
* Note: Changing the Renderer of the hierarchy column is
* not supported.
*
* @param column
* the column to use for displaying hierarchy
*/
public void setHierarchyColumn(Column column) {
Objects.requireNonNull(column, "column may not be null");
if (!getColumns().contains(column)) {
throw new IllegalArgumentException(
"Given column is not a column of this TreeGrid");
}
column.setHidden(false);
column.setHidable(false);
getState().hierarchyColumnId = getInternalIdForColumn(column);
}
/**
* Set the column that displays the hierarchy of this grid's data. By
* default the hierarchy will be displayed in the first column.
*
* Setting a hierarchy column by calling this method also sets the column to
* be visible and not hidable.
*
* Note: Changing the Renderer of the hierarchy column is
* not supported.
*
* @see Column#setId(String)
*
* @param id
* id of the column to use for displaying hierarchy
*/
public void setHierarchyColumn(String id) {
Objects.requireNonNull(id, "id may not be null");
if (getColumn(id) == null) {
throw new IllegalArgumentException("No column found for given id");
}
setHierarchyColumn(getColumn(id));
}
/**
* Sets the item collapse allowed provider for this TreeGrid. The provider
* should return {@code true} for any item that the user can collapse.
*
* Note: This callback will be accessed often when sending
* data to the client. The callback should not do any costly operations.
*
* This method is a shortcut to method with the same name in
* {@link HierarchicalDataCommunicator}.
*
* @param provider
* the item collapse allowed provider, not {@code null}
*
* @see HierarchicalDataCommunicator#setItemCollapseAllowedProvider(ItemCollapseAllowedProvider)
*/
public void setItemCollapseAllowedProvider(
ItemCollapseAllowedProvider provider) {
getDataCommunicator().setItemCollapseAllowedProvider(provider);
}
/**
* Expands the given items.
*
* If an item is currently expanded, does nothing. If an item does not have
* any children, does nothing.
*
* @param items
* the items to expand
*/
@SuppressWarnings("unchecked")
public void expand(T... items) {
expand(Arrays.asList(items));
}
/**
* Expands the given items.
*
* If an item is currently expanded, does nothing. If an item does not have
* any children, does nothing.
*
* @param items
* the items to expand
*/
public void expand(Collection items) {
HierarchicalDataCommunicator communicator = getDataCommunicator();
items.forEach(item -> {
if (!communicator.isExpanded(item)
&& communicator.hasChildren(item)) {
communicator.expand(item);
fireExpandEvent(item, false);
}
});
}
/**
* Expands the given items and their children recursively until the given
* depth.
*
* {@code depth} describes the maximum distance between a given item and its
* descendant, meaning that {@code expandRecursively(items, 0)} expands only
* the given items while {@code expandRecursively(items, 2)} expands the
* given items as well as their children and grandchildren.
*
* This method will not fire events for expanded nodes.
*
* @param items
* the items to expand recursively
* @param depth
* the maximum depth of recursion
* @since 8.4
*/
public void expandRecursively(Collection items, int depth) {
expandRecursively(items.stream(), depth);
}
/**
* Expands the given items and their children recursively until the given
* depth.
*
* {@code depth} describes the maximum distance between a given item and its
* descendant, meaning that {@code expandRecursively(items, 0)} expands only
* the given items while {@code expandRecursively(items, 2)} expands the
* given items as well as their children and grandchildren.
*
* This method will not fire events for expanded nodes.
*
* @param items
* the items to expand recursively
* @param depth
* the maximum depth of recursion
* @since 8.4
*/
public void expandRecursively(Stream items, int depth) {
if (depth < 0) {
return;
}
HierarchicalDataCommunicator communicator = getDataCommunicator();
items.forEach(item -> {
if (communicator.hasChildren(item)) {
communicator.expand(item, false);
expandRecursively(
getDataProvider().fetchChildren(
new HierarchicalQuery<>(null, item)),
depth - 1);
}
});
getDataProvider().refreshAll();
}
/**
* Collapse the given items.
*
* For items that are already collapsed, does nothing.
*
* @param items
* the collection of items to collapse
*/
@SuppressWarnings("unchecked")
public void collapse(T... items) {
collapse(Arrays.asList(items));
}
/**
* Collapse the given items.
*
* For items that are already collapsed, does nothing.
*
* @param items
* the collection of items to collapse
*/
public void collapse(Collection items) {
HierarchicalDataCommunicator communicator = getDataCommunicator();
items.forEach(item -> {
if (communicator.isExpanded(item)) {
communicator.collapse(item);
fireCollapseEvent(item, false);
}
});
}
/**
* Collapse the given items and their children recursively until the given
* depth.
*
* {@code depth} describes the maximum distance between a given item and its
* descendant, meaning that {@code collapseRecursively(items, 0)} collapses
* only the given items while {@code collapseRecursively(items, 2)}
* collapses the given items as well as their children and grandchildren.
*
* This method will not fire events for collapsed nodes.
*
* @param items
* the items to collapse recursively
* @param depth
* the maximum depth of recursion
* @since 8.4
*/
public void collapseRecursively(Collection items, int depth) {
collapseRecursively(items.stream(), depth);
}
/**
* Collapse the given items and their children recursively until the given
* depth.
*
* {@code depth} describes the maximum distance between a given item and its
* descendant, meaning that {@code collapseRecursively(items, 0)} collapses
* only the given items while {@code collapseRecursively(items, 2)}
* collapses the given items as well as their children and grandchildren.
*
* This method will not fire events for collapsed nodes.
*
* @param items
* the items to collapse recursively
* @param depth
* the maximum depth of recursion
* @since 8.4
*/
public void collapseRecursively(Stream items, int depth) {
if (depth < 0) {
return;
}
HierarchicalDataCommunicator communicator = getDataCommunicator();
items.forEach(item -> {
if (communicator.hasChildren(item)) {
collapseRecursively(
getDataProvider().fetchChildren(
new HierarchicalQuery<>(null, item)),
depth - 1);
communicator.collapse(item, false);
}
});
getDataProvider().refreshAll();
}
/**
* Returns whether a given item is expanded or collapsed.
*
* @param item
* the item to check
* @return true if the item is expanded, false if collapsed
*/
public boolean isExpanded(T item) {
return getDataCommunicator().isExpanded(item);
}
@Override
protected TreeGridState getState() {
return (TreeGridState) super.getState();
}
@Override
protected TreeGridState getState(boolean markAsDirty) {
return (TreeGridState) super.getState(markAsDirty);
}
@Override
public HierarchicalDataCommunicator getDataCommunicator() {
return (HierarchicalDataCommunicator) super.getDataCommunicator();
}
@Override
public HierarchicalDataProvider getDataProvider() {
if (!(super.getDataProvider() instanceof HierarchicalDataProvider)) {
return null;
}
return (HierarchicalDataProvider) super.getDataProvider();
}
@Override
protected void doReadDesign(Element design, DesignContext context) {
super.doReadDesign(design, context);
Attributes attrs = design.attributes();
if (attrs.hasKey("hierarchy-column")) {
setHierarchyColumn(DesignAttributeHandler
.readAttribute("hierarchy-column", attrs, String.class));
}
}
@Override
protected void readData(Element body,
List> providers) {
getSelectionModel().deselectAll();
List selectedItems = new ArrayList<>();
TreeData data = new TreeData();
for (Element row : body.children()) {
T item = deserializeDeclarativeRepresentation(row.attr("item"));
T parent = null;
if (row.hasAttr("parent")) {
parent = deserializeDeclarativeRepresentation(
row.attr("parent"));
}
data.addItem(parent, item);
if (row.hasAttr("selected")) {
selectedItems.add(item);
}
Elements cells = row.children();
int i = 0;
for (Element cell : cells) {
providers.get(i).addValue(item, cell.html());
i++;
}
}
setDataProvider(new TreeDataProvider<>(data));
selectedItems.forEach(getSelectionModel()::select);
}
@Override
protected void doWriteDesign(Element design, DesignContext designContext) {
super.doWriteDesign(design, designContext);
if (getColumnByInternalId(getState(false).hierarchyColumnId) != null) {
String hierarchyColumn = getColumnByInternalId(
getState(false).hierarchyColumnId).getId();
DesignAttributeHandler.writeAttribute("hierarchy-column",
design.attributes(), hierarchyColumn, null, String.class,
designContext);
}
}
@Override
protected void writeData(Element body, DesignContext designContext) {
getDataProvider().fetch(new HierarchicalQuery<>(null, null))
.forEach(item -> writeRow(body, item, null, designContext));
}
private void writeRow(Element container, T item, T parent,
DesignContext context) {
Element tableRow = container.appendElement("tr");
tableRow.attr("item", serializeDeclarativeRepresentation(item));
if (parent != null) {
tableRow.attr("parent", serializeDeclarativeRepresentation(parent));
}
if (getSelectionModel().isSelected(item)) {
tableRow.attr("selected", true);
}
for (Column column : getColumns()) {
Object value = column.getValueProvider().apply(item);
tableRow.appendElement("td")
.append(Optional.ofNullable(value).map(Object::toString)
.map(DesignFormatter::encodeForTextNode)
.orElse(""));
}
getDataProvider().fetch(new HierarchicalQuery<>(null, item)).forEach(
childItem -> writeRow(container, childItem, item, context));
}
/**
* Emit an expand event.
*
* @param item
* the item that was expanded
* @param userOriginated
* whether the expand was triggered by a user interaction or the
* server
*/
private void fireExpandEvent(T item, boolean userOriginated) {
fireEvent(new ExpandEvent<>(this, item, userOriginated));
}
/**
* Emit a collapse event.
*
* @param item
* the item that was collapsed
* @param userOriginated
* whether the collapse was triggered by a user interaction or
* the server
*/
private void fireCollapseEvent(T item, boolean userOriginated) {
fireEvent(new CollapseEvent<>(this, item, userOriginated));
}
/**
* Gets the item collapse allowed provider.
*
* @return the item collapse allowed provider
*/
public ItemCollapseAllowedProvider getItemCollapseAllowedProvider() {
return getDataCommunicator().getItemCollapseAllowedProvider();
}
}