All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.holonplatform.vaadin7.internal.components.DefaultItemListing Maven / Gradle / Ivy

/*
 * Copyright 2016-2017 Axioma srl.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.holonplatform.vaadin7.internal.components;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.holonplatform.core.i18n.LocalizationContext;
import com.holonplatform.core.internal.Logger;
import com.holonplatform.core.internal.utils.ConversionUtils;
import com.holonplatform.core.internal.utils.ObjectUtils;
import com.holonplatform.core.property.Property;
import com.holonplatform.core.property.PropertyBox;
import com.holonplatform.core.property.PropertyRenderer;
import com.holonplatform.core.property.PropertyValueProvider;
import com.holonplatform.core.property.VirtualProperty;
import com.holonplatform.vaadin7.Registration;
import com.holonplatform.vaadin7.components.ItemListing;
import com.holonplatform.vaadin7.components.Selectable;
import com.holonplatform.vaadin7.data.ItemDataSource;
import com.holonplatform.vaadin7.data.ItemDataSource.ItemSort;
import com.holonplatform.vaadin7.internal.VaadinLogger;
import com.holonplatform.vaadin7.internal.converters.FontIconPresentationConverter;
import com.holonplatform.vaadin7.internal.converters.PropertyPresentationConverter;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.ItemSetChangeListener;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Item;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.fieldgroup.FieldGroup;
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
import com.vaadin.data.sort.Sort;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.FontIcon;
import com.vaadin.server.ThemeResource;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.DefaultFieldFactory;
import com.vaadin.ui.Field;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.CellReference;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.EditorFieldFactory;
import com.vaadin.ui.Grid.RowReference;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.Align;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.Table.ColumnHeaderMode;
import com.vaadin.ui.TableFieldFactory;
import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.ImageRenderer;
import com.vaadin.ui.renderers.Renderer;

/**
 * Abstract {@link ItemListing} component implementation.
 *
 * @param  Item type
 * @param 

Item property type * * @since 5.0.0 */ public class DefaultItemListing extends CustomComponent implements ItemListing, com.vaadin.ui.Grid.RowStyleGenerator, com.vaadin.ui.Grid.CellStyleGenerator, TableFieldFactory, com.vaadin.ui.Table.CellStyleGenerator, com.vaadin.event.SelectionEvent.SelectionListener { private static final long serialVersionUID = 9034583855570611499L; /** * Logger */ private static final Logger LOGGER = VaadinLogger.create(); /** * Listing rendering mode */ public enum RenderingMode { /** * Render using a {@link Grid} */ GRID, /** * Render using a {@link Table} */ TABLE; } /** * Rendering mode */ private final RenderingMode renderingMode; /** * Concrete content */ private final Component content; /** * Data source */ private ItemDataSource dataSource; /** * Selection listeners */ private final List> selectionListeners = new LinkedList<>(); /** * Property column definitions */ private final Map> propertyColumnDefinitions = new HashMap<>(); /** * Row style generators */ private final List> rowStyleGenerators = new LinkedList<>(); /** * Auto commit on row save */ private boolean commitOnSave = false; /** * Auto commit on row remove */ private boolean commitOnRemove = false; /** * Selection mode */ private SelectionMode selectionMode = SelectionMode.NONE; /** * Column hiding allowed */ private boolean columnHidingAllowed = true; /** * Initialization state */ private boolean duringSetup = false; @SuppressWarnings("serial") public DefaultItemListing(RenderingMode renderingMode) { super(); ObjectUtils.argumentNotNull(renderingMode, "RenderingMode must be not null"); this.renderingMode = renderingMode; // init content try { duringSetup = true; if (renderingMode == RenderingMode.TABLE) { content = new Table() { @Override public void refreshRowCache() { if (duringSetup) { return; } super.refreshRowCache(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected String formatPropertyValue(Object rowId, Object colId, com.vaadin.data.Property itemProperty) { if (colId instanceof Property) { return ((Property) colId).present(itemProperty.getValue()); } return super.formatPropertyValue(rowId, colId, itemProperty); } }; initTable((Table) content); } else { content = new Grid() { @Override public void saveEditor() throws CommitException { super.saveEditor(); // check auto commit on save if (isCommitOnSave()) { requireDataSource().commit(); } } }; initGrid((Grid) content); } } finally { duringSetup = false; } super.setWidth(100, Unit.PERCENTAGE); addStyleName("h-itemlisting", false); setCompositionRoot(content); } /* * (non-Javadoc) * @see com.vaadin.ui.AbstractComponent#setHeight(float, com.vaadin.server.Sizeable.Unit) */ @Override public void setHeight(float height, Unit unit) { super.setHeight(height, unit); if (height > -1 && getCompositionRoot() != null) { getCompositionRoot().setHeight(100, Unit.PERCENTAGE); } } /* * (non-Javadoc) * @see com.vaadin.ui.AbstractComponent#setWidth(float, com.vaadin.server.Sizeable.Unit) */ @Override public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (width > -1 && getCompositionRoot() != null) { getCompositionRoot().setWidth(100, Unit.PERCENTAGE); } } /* * (non-Javadoc) * @see com.vaadin.ui.AbstractComponent#addStyleName(java.lang.String) */ @Override public void addStyleName(String style) { addStyleName(style, true); } /** * Adds one or more style names to this component. * @param styleName Style name to add * @param reflectToContent true to add given styleName to content component too */ protected void addStyleName(String styleName, boolean reflectToContent) { super.addStyleName(styleName); if (reflectToContent) { getContent().addStyleName(styleName); } } /* * (non-Javadoc) * @see com.vaadin.ui.AbstractComponent#removeStyleName(java.lang.String) */ @Override public void removeStyleName(String style) { super.removeStyleName(style); getContent().removeStyleName(style); } /* * (non-Javadoc) * @see com.vaadin.ui.AbstractField#setReadOnly(boolean) */ @Override public void setReadOnly(boolean readOnly) { super.setReadOnly(readOnly); // reflect to content getContent().setReadOnly(readOnly); } /** * Get the rendering mode * @return the rendering mode */ protected RenderingMode getRenderingMode() { return renderingMode; } /** * Grid initialization * @param grid Grid to initialize */ protected void initGrid(Grid grid) { // reset selection model grid.setSelectionMode(com.vaadin.ui.Grid.SelectionMode.NONE); grid.setRowStyleGenerator(this); grid.setCellStyleGenerator(this); // editor FieldGroup grid.setEditorFieldGroup(new PropertyGridFieldGroup()); } /** * Table initialization * @param table Table to initialize */ protected void initTable(Table table) { table.setTableFieldFactory(this); table.setCellStyleGenerator(this); } /** * Get the content * @return the content */ protected Component getContent() { return content; } /** * Get the content {@link Grid}. * @return Content grid */ public Grid getGrid() { return (Grid) getContent(); } /** * Get the content {@link Table}. * @return Content table */ public Table getTable() { return (Table) getContent(); } /** * Get the datasource. * @return the datasource */ protected Optional> getDataSource() { return Optional.ofNullable(dataSource); } /** * Get the datasource. * @return the datasource * @throws IllegalStateException If data source is not configured */ protected ItemDataSource requireDataSource() { return getDataSource().orElseThrow(() -> new IllegalStateException("Missing ItemDataSource")); } /** * Set the listing data source. * @param Data source type * @param container The data source to set (not null) */ public & Indexed> void setDataSource(D container) { ObjectUtils.argumentNotNull(container, "Container datasource must be not null"); this.dataSource = container; switch (getRenderingMode()) { case GRID: getGrid().setContainerDataSource(container); break; case TABLE: getTable().setContainerDataSource(container); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemSetComponent#refresh() */ @Override public void refresh() { requireDataSource().refresh(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#clear() */ @Override public void clear() { if (getSelectionMode() != Selectable.SelectionMode.NONE) { deselectAll(); } requireDataSource().clear(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#getSelectionMode() */ @Override public SelectionMode getSelectionMode() { return selectionMode; } /* * (non-Javadoc) * @see * com.holonplatform.vaadin.components.ItemListing#setSelectionMode(com.holonplatform.vaadin.components.Selectable * .SelectionMode) */ @Override public void setSelectionMode(SelectionMode selectionMode) { ObjectUtils.argumentNotNull(selectionMode, "SelectionMode must be not null"); if (this.selectionMode != selectionMode) { this.selectionMode = selectionMode; switch (getRenderingMode()) { case GRID: { final Grid grid = getGrid(); grid.removeSelectionListener(this); switch (selectionMode) { case MULTI: grid.setSelectionMode(Grid.SelectionMode.MULTI); grid.addSelectionListener(this); break; case SINGLE: grid.setSelectionMode(Grid.SelectionMode.SINGLE); grid.addSelectionListener(this); break; case NONE: default: grid.setSelectionMode(Grid.SelectionMode.NONE); break; } } break; case TABLE: { final Table table = getTable(); table.removeValueChangeListener(tableSelectionChangeListener); table.setValue(null); if (selectionMode != null && selectionMode != SelectionMode.NONE) { table.setSelectable(true); table.setMultiSelect(selectionMode == SelectionMode.MULTI); table.addValueChangeListener(tableSelectionChangeListener); } else { table.setSelectable(false); } } break; default: break; } } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#selectAllItems() */ @Override public void selectAll() { switch (getRenderingMode()) { case GRID: requireDataSource().getItemIds().forEach(i -> getGrid().select(i)); break; case TABLE: getTable().setValue(new HashSet<>(requireDataSource().getItemIds())); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#getSelectedItems() */ @SuppressWarnings("unchecked") @Override public Set getSelectedItems() { Collection selectedIds = null; switch (getRenderingMode()) { case GRID: selectedIds = getGrid().getSelectionModel().getSelectedRows(); break; case TABLE: { Object value = getTable().getValue(); if (value != null) { if (getTable().isMultiSelect()) { selectedIds = (Collection) value; } else { selectedIds = Collections.singleton(value); } } } break; default: break; } return convertSelectionItems(selectedIds); } protected Set convertSelectionItems(Collection selectedIds) { if (selectedIds != null) { Set selected = new HashSet<>(selectedIds.size()); selectedIds.forEach(s -> { requireDataSource().get(s).ifPresent(i -> selected.add(i)); }); return selected; } return Collections.emptySet(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#getFirstSelectedItem() */ @Override public Optional getFirstSelectedItem() { final Set selected = getSelectedItems(); return selected.isEmpty() ? Optional.empty() : Optional.of(selected.iterator().next()); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#selectItem(java.lang.Object) */ @Override public void select(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); switch (getRenderingMode()) { case GRID: getGrid().select(requireDataSource().getId(item)); break; case TABLE: getTable().select(requireDataSource().getId(item)); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#deselectItem(java.lang.Object) */ @Override public void deselect(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); switch (getRenderingMode()) { case GRID: getGrid().deselect(requireDataSource().getId(item)); break; case TABLE: getTable().unselect(requireDataSource().getId(item)); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#deselectAllItems() */ @Override public void deselectAll() { switch (getRenderingMode()) { case GRID: getGrid().deselectAll(); break; case TABLE: getTable().setValue(null); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#addSelectionListener(com.holonplatform.vaadin.components. * Selectable.SelectionListener) */ @Override public Registration addSelectionListener(SelectionListener selectionListener) { ObjectUtils.argumentNotNull(selectionListener, "SelectionListener must be not null"); selectionListeners.add(selectionListener); return () -> selectionListeners.remove(selectionListener); } /** * Fire registered {@link SelectionListener}s. * @param event Selection event (not null) */ protected void fireSelectionListeners(SelectionEvent event) { for (SelectionListener selectionListener : selectionListeners) { selectionListener.onSelectionChange(event); } } /* * (non-Javadoc) * @see com.vaadin.event.SelectionEvent.SelectionListener#select(com.vaadin.event.SelectionEvent) */ @Override public void select(com.vaadin.event.SelectionEvent event) { fireSelectionListeners(new DefaultSelectionEvent<>(convertSelectionItems(event.getSelected()))); } public void setPropertyColumns(Iterable

columns) { ObjectUtils.argumentNotNull(columns, "Property columns must be not null"); final Object[] columnsArray = ConversionUtils.iterableAsList(columns).toArray(); switch (getRenderingMode()) { case GRID: { final Grid grid = getGrid(); // set columns grid.setColumns(columnsArray); // setup columns for (P column : columns) { Column c = grid.getColumn(column); // setup column setupGridPropertyColumn(column, c); // setup renderer and converter setupRendererAndConverter(column, c); } } break; case TABLE: { final Table table = getTable(); try { duringSetup = true; // Setup columns and check Component generated columns for (P property : columns) { // column settings setupTablePropertyColumn(property, table); // generated columns Class type = getPropertyColumnType(property); if (type != null && Component.class.isAssignableFrom(type)) { if (table.getColumnGenerator(property) == null) { table.addGeneratedColumn(property, new VirtualPropertyGenerator()); } } } } finally { duringSetup = false; } // set columns table.setVisibleColumns(columnsArray); } break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#getPropertyColumns() */ @SuppressWarnings("unchecked") @Override public List

getPropertyColumns() { switch (getRenderingMode()) { case GRID: { List columns = getGrid().getColumns(); if (columns != null && !columns.isEmpty()) { List

properties = new ArrayList<>(columns.size()); for (Column column : columns) { properties.add((P) column.getPropertyId()); } return properties; } } break; case TABLE: { Object[] visibleColumns = getTable().getVisibleColumns(); if (visibleColumns != null && visibleColumns.length > 0) { List

properties = new ArrayList<>(visibleColumns.length); for (Object column : visibleColumns) { properties.add((P) column); } return properties; } } break; default: break; } return Collections.emptyList(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#setPropertyColumnVisible(java.lang.Object, boolean) */ @Override public void setPropertyColumnVisible(P property, boolean visible) { ObjectUtils.argumentNotNull(property, "Property must be not null"); if (!hasPropertyColumn(property)) { throw new IllegalArgumentException("Property " + property + " is not a column of the listing"); } switch (getRenderingMode()) { case GRID: getGrid().getColumn(property).setHidden(!visible); break; case TABLE: getTable().setColumnCollapsed(property, !visible); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#isFooterVisible() */ @Override public boolean isFooterVisible() { switch (getRenderingMode()) { case GRID: return getGrid().isFooterVisible(); case TABLE: return getTable().isFooterVisible(); default: break; } return false; } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#setFooterVisible(boolean) */ @Override public void setFooterVisible(boolean visible) { switch (getRenderingMode()) { case GRID: getGrid().setFooterVisible(visible); break; case TABLE: getTable().setFooterVisible(visible); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#isItemDetailsVisible(java.lang.Object) */ @Override public boolean isItemDetailsVisible(T item) { return false; } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#setItemDetailsVisible(java.lang.Object, boolean) */ @Override public void setItemDetailsVisible(T item, boolean visible) throws UnsupportedOperationException { ObjectUtils.argumentNotNull(item, "Item must be not null"); switch (getRenderingMode()) { case GRID: getGrid().setDetailsVisible(requireDataSource().getId(item), visible); break; case TABLE: throw new UnsupportedOperationException(); default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#scrollToTop() */ @Override public void scrollToTop() { switch (getRenderingMode()) { case GRID: getGrid().scrollToStart(); break; case TABLE: getTable().setCurrentPageFirstItemIndex(0); break; default: break; } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#scrollToItem(java.lang.Object) */ @Override public void scrollToItem(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); switch (getRenderingMode()) { case GRID: { Object id = requireDataSource().getId(item); if (id != null) { getGrid().scrollTo(id); } } break; case TABLE: { Object id = requireDataSource().getId(item); if (id != null) { getTable().setCurrentPageFirstItemId(id); } } break; default: break; } } /* * (non-Javadoc) * @see * com.holonplatform.vaadin.components.ItemListing#sort(com.holonplatform.vaadin.data.ItemDataSource.ItemSort[]) */ @SuppressWarnings("unchecked") @Override public void sort(ItemSort

... sorts) { if (sorts != null && sorts.length > 0) { switch (getRenderingMode()) { case GRID: { Sort gridSort = null; for (ItemSort

sort : sorts) { if (gridSort == null) { gridSort = Sort.by(sort.getProperty(), sort.isAscending() ? SortDirection.ASCENDING : SortDirection.DESCENDING); } else { gridSort.then(sort.getProperty(), sort.isAscending() ? SortDirection.ASCENDING : SortDirection.DESCENDING); } } getGrid().sort(gridSort); } break; case TABLE: { Object[] pids = new Object[sorts.length]; boolean[] states = new boolean[sorts.length]; for (int i = 0; i < sorts.length; i++) { pids[i] = sorts[i].getProperty(); states[i] = sorts[i].isAscending(); } getTable().sort(pids, states); } break; default: break; } } } @SuppressWarnings("unchecked") protected void repaintRows(T... items) { if (items != null) { switch (getRenderingMode()) { case GRID: { List ids = new LinkedList<>(); for (T item : items) { Object id = requireDataSource().getId(item); if (id != null) { ids.add(id); } } if (!ids.isEmpty()) { getGrid().refreshRows(ids.toArray(new Object[ids.size()])); } } break; case TABLE: { getTable().refreshRowCache(); } break; default: break; } } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#getItemById(java.lang.Object) */ @Override public Optional getItem(Object itemId) { return requireDataSource().get(itemId); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#addItem(java.lang.Object) */ @Override public Object addItem(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); return requireDataSource().add(item); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#removeItem(java.lang.Object) */ @Override public boolean removeItem(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); final Object id = requireDataSource().getId(item); boolean removed = requireDataSource().remove(item); // check commit on remove if (isCommitOnRemove()) { commit(); } if (removed && id != null && getSelectionMode() != Selectable.SelectionMode.NONE) { switch (getRenderingMode()) { case GRID: getGrid().deselect(id); break; case TABLE: getTable().unselect(id); break; default: break; } } return removed; } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#refreshItem(java.lang.Object) */ @SuppressWarnings("unchecked") @Override public void refreshItem(T item) { ObjectUtils.argumentNotNull(item, "Item must be not null"); requireDataSource().refresh(item); repaintRows(item); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#commit() */ @Override public void commit() { requireDataSource().commit(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#discard() */ @Override public void discard() { requireDataSource().discard(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemListing#isBuffered() */ @Override public boolean isBuffered() { return requireDataSource().isBuffered(); } /** * Add a {@link RowStyleGenerator} * @param rowStyleGenerator Generator to add (not null) */ public void addRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { ObjectUtils.argumentNotNull(rowStyleGenerator, "RowStyleGenerator must be not null"); rowStyleGenerators.add(rowStyleGenerator); } /** * Remove a {@link RowStyleGenerator}. * @param rowStyleGenerator Generator to remove (not null) */ public void removeRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { ObjectUtils.argumentNotNull(rowStyleGenerator, "RowStyleGenerator must be not null"); rowStyleGenerators.remove(rowStyleGenerator); } /** * Set the buffered mode * @param buffered Buffered mode */ public void setBuffered(boolean buffered) { switch (getRenderingMode()) { case GRID: getGrid().setEditorBuffered(buffered); break; case TABLE: getTable().setBuffered(buffered); break; default: break; } } /** * Set whether the listing is editable. * @param editable whether the listing is editable */ public void setEditable(boolean editable) { switch (getRenderingMode()) { case GRID: getGrid().setEditorEnabled(editable); break; case TABLE: getTable().setEditable(true); break; default: break; } } /** * Set whether the listing column headers are visible. * @param headersVisible whether the listing column headers are visible */ public void setHeadersVisible(boolean headersVisible) { switch (getRenderingMode()) { case GRID: getGrid().setHeaderVisible(headersVisible); break; case TABLE: getTable().setColumnHeaderMode( headersVisible ? ColumnHeaderMode.EXPLICIT_DEFAULTS_ID : ColumnHeaderMode.HIDDEN); break; default: break; } } /** * Add an {@link ItemClickListener} to be notified when user clicks on an item row. * @param listener Listener to add (not null) */ @SuppressWarnings("unchecked") public void addItemClickListener(final ItemClickListener listener) { ObjectUtils.argumentNotNull(listener, "Listener must be not null"); switch (getRenderingMode()) { case GRID: getGrid().addItemClickListener(e -> getItem(e.getItemId()).ifPresent(i -> { listener.onItemClick(i, (P) e.getPropertyId(), fromClickEvent(e)); })); break; case TABLE: getTable().addItemClickListener(e -> getItem(e.getItemId()).ifPresent(i -> { listener.onItemClick(i, (P) e.getPropertyId(), fromClickEvent(e)); })); break; default: break; } } private static MouseEventDetails fromClickEvent(ItemClickEvent event) { MouseEventDetails d = new MouseEventDetails(); d.setButton(event.getButton()); d.setAltKey(event.isAltKey()); d.setCtrlKey(event.isCtrlKey()); d.setShiftKey(event.isShiftKey()); d.setType(event.isDoubleClick() ? 0x00002 : 0); d.setClientX(event.getClientX()); d.setClientY(event.getClientY()); d.setRelativeX(event.getRelativeX()); d.setRelativeY(event.getRelativeY()); return d; } /** * Adds a {@link PropertyReorderListener} that gets notified when property columns order changes. * @param listener Listener to add (not null) */ public void addPropertyReorderListener(final PropertyReorderListener

listener) { ObjectUtils.argumentNotNull(listener, "Listener must be not null"); switch (getRenderingMode()) { case GRID: getGrid().addColumnReorderListener( e -> listener.onPropertyReordered(getPropertyColumns(), e.isUserOriginated())); break; case TABLE: getTable().addColumnReorderListener(e -> listener.onPropertyReordered(getPropertyColumns(), true)); break; default: break; } } /** * Adds a {@link PropertyResizeListener} that gets notified when a property column is resized. * @param listener Listener to add (not null) */ @SuppressWarnings("unchecked") public void addPropertyResizeListener(final PropertyResizeListener

listener) { ObjectUtils.argumentNotNull(listener, "Listener must be not null"); switch (getRenderingMode()) { case GRID: getGrid().addColumnResizeListener(e -> listener.onPropertyResized((P) e.getColumn().getPropertyId(), (int) e.getColumn().getWidth(), e.isUserOriginated())); break; case TABLE: getTable().addColumnResizeListener( e -> listener.onPropertyResized((P) e.getPropertyId(), e.getCurrentWidth(), true)); break; default: break; } } /** * Adds a {@link PropertyVisibilityListener} that gets notified when a property column is hidden or shown. * @param listener Listener to add (not null) */ @SuppressWarnings("unchecked") public void addPropertyVisibilityListener(final PropertyVisibilityListener

listener) { ObjectUtils.argumentNotNull(listener, "Listener must be not null"); switch (getRenderingMode()) { case GRID: getGrid().addColumnVisibilityChangeListener(e -> listener.onPropertyVisibilityChanged( (P) e.getColumn().getPropertyId(), e.isHidden(), e.isUserOriginated())); break; case TABLE: getTable().addColumnCollapseListener(e -> listener.onPropertyVisibilityChanged((P) e.getPropertyId(), getTable().isColumnCollapsed(e.getPropertyId()), true)); break; default: break; } } /** * Set the {@link ItemDescriptionGenerator} to use to generate item descriptions (tooltips). * @param rowDescriptionGenerator Generator to set (not null) */ public void setDescriptionGenerator(final ItemDescriptionGenerator rowDescriptionGenerator) { ObjectUtils.argumentNotNull(rowDescriptionGenerator, "Generator must be not null"); switch (getRenderingMode()) { case GRID: getGrid().setRowDescriptionGenerator(row -> getItem(row.getItemId()) .map(i -> rowDescriptionGenerator.getItemDescription(i)).orElse(null)); break; case TABLE: getTable().setItemDescriptionGenerator( (c, id, p) -> getItem(id).map(i -> rowDescriptionGenerator.getItemDescription(i)).orElse(null)); break; default: break; } } /** * Sets whether column hiding by user is allowed or not. * @param columnHidingAllowed true if column hiding is allowed */ public void setColumnHidingAllowed(boolean columnHidingAllowed) { switch (getRenderingMode()) { case GRID: propertyColumnDefinitions.values().forEach(c -> c.setHidable(false)); break; case TABLE: getTable().setColumnCollapsingAllowed(columnHidingAllowed); break; default: break; } } /** * Sets whether column reordering is allowed or not. * @param columnReorderingAllowed true if column reordering is allowed */ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { switch (getRenderingMode()) { case GRID: getGrid().setColumnReorderingAllowed(columnReorderingAllowed); break; case TABLE: getTable().setColumnReorderingAllowed(columnReorderingAllowed); break; default: break; } } /** * Check whether to call commit() on data source container when Grid editor save action is triggered * @return true if should call commit() on data source container when Grid editor save action is * triggered */ public boolean isCommitOnSave() { return commitOnSave; } /** * Sets whether to call commit() on data source container when Grid editor save action is triggered * @param commitOnSave true to call commit() on data source container when Grid editor save action is * triggered */ public void setCommitOnSave(boolean commitOnSave) { this.commitOnSave = commitOnSave; } /** * Check whether to call commit() on data source container when a row is removed using * {@link ItemDataSource#remove(Object)}. * @return true if should call commit() on data source container when a row is removed using * {@link ItemDataSource#remove(Object)} */ public boolean isCommitOnRemove() { return commitOnRemove; } /** * Sets to whether call commit() on data source container when a row is removed using * {@link ItemDataSource#remove(Object)}. * @param commitOnRemove true to call commit() on data source container when a row is removed using * {@link ItemDataSource#remove(Object)} */ public void setCommitOnRemove(boolean commitOnRemove) { this.commitOnRemove = commitOnRemove; } // ------- Exposed for builders /** * Get or create the {@link PropertyColumn} definition bound to given property. * @param property Property to get the definition for (not null) * @return Property column definition */ public PropertyColumn getPropertyColumn(P property) { ObjectUtils.argumentNotNull(property, "Property must be not null"); PropertyColumn propertyColumn = propertyColumnDefinitions.get(property); if (propertyColumn == null) { propertyColumn = buildPropertyColumn(property); propertyColumnDefinitions.put(property, propertyColumn); } return propertyColumn; } /** * Build a {@link PropertyColumn} definition. * @param property Property id * @return a new {@link PropertyColumn} definition */ protected PropertyColumn buildPropertyColumn(P property) { return new DefaultPropertyColumn<>(property); } /** * Adds an {@link ItemSetChangeListener} to internal component container. * @param listener Listener to add */ public void addItemSetChangeListener(Container.ItemSetChangeListener listener) { Container container = null; switch (getRenderingMode()) { case GRID: container = getGrid().getContainerDataSource(); break; case TABLE: container = getTable().getContainerDataSource(); break; default: break; } if (container != null && container instanceof ItemSetChangeNotifier) { ((ItemSetChangeNotifier) container).addItemSetChangeListener(listener); } } // ------- Internal /** * Check whether given property is present as a colummn * @param property Property to check * @return true if present */ protected boolean hasPropertyColumn(P property) { return property != null && getPropertyColumns().contains(property); } /** * Get the property column type * @param property Property * @return Property type */ protected Class getPropertyColumnType(P property) { if (property != null) { return requireDataSource().getConfiguration().getPropertyType(property); } return null; } /** * Generate the row style names for given item using registered row style generators. * @param item Item * @return Row styles */ protected String generateRowStyle(T item) { StringBuilder sb = new StringBuilder(); if (item != null && !rowStyleGenerators.isEmpty()) { for (RowStyleGenerator rowStyleGenerator : rowStyleGenerators) { String style = rowStyleGenerator.getRowStyle(item); if (style != null && !style.trim().equals("")) { if (sb.length() > 0) { sb.append(" "); } sb.append(style); } } } return (sb.length() > 0) ? sb.toString() : null; } /** * Generate cell style names for given property and item using column definition. * @param property Column property * @param item Item * @return Cell style names */ protected String generatePropertyStyle(P property, T item) { PropertyColumn column = getPropertyColumn(property); if (column != null && (column.getStyle() != null || column.getAlignment() != null)) { final StringBuilder sb = new StringBuilder(); if (column.getAlignment() != null) { if (ColumnAlignment.CENTER.equals(column.getAlignment())) { sb.append("v-align-center"); } else if (ColumnAlignment.RIGHT.equals(column.getAlignment())) { sb.append("v-align-right"); } } if (column.getStyle() != null) { if (sb.length() > 0) { sb.append(" "); } String cellStyle = column.getStyle().getCellStyle(property, item); if (cellStyle != null) { sb.append(cellStyle); } } return (sb.length() > 0) ? sb.toString() : null; } return null; } /** * Try to render given property as a {@link Field} using a suitable {@link PropertyRenderer}, if available * @param property Property to render as Field * @return Optional Field */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected Optional renderField(P property) { if (Property.class.isAssignableFrom(property.getClass())) { return ((Property) property).renderIfAvailable(Field.class); } return Optional.empty(); } /** * Setup a renderer field, setting up validators if available. * @param property Property * @param field Field */ protected void setupField(P property, Field field) { PropertyColumn column = getPropertyColumn(property); if (column != null) { if (column.isRequired()) { field.setRequired(true); if (column.getRequiredMessage() != null) { field.setRequiredError(LocalizationContext.translate(column.getRequiredMessage(), true)); } } column.getValidators().forEach(v -> { field.addValidator(v); }); } } // ------- Grid /* * (non-Javadoc) * @see com.vaadin.ui.Grid.RowStyleGenerator#getStyle(com.vaadin.ui.Grid.RowReference) */ @Override public String getStyle(RowReference row) { if (!rowStyleGenerators.isEmpty()) { return generateRowStyle(requireDataSource().get(row.getItemId()).orElse(null)); } return null; } /* * (non-Javadoc) * @see com.vaadin.ui.Grid.CellStyleGenerator#getStyle(com.vaadin.ui.Grid.CellReference) */ @SuppressWarnings("unchecked") @Override public String getStyle(CellReference cell) { return generatePropertyStyle((P) cell.getPropertyId(), requireDataSource().get(cell.getItemId()).orElse(null)); } /** * Set the grid row details generator * @param detailsGenerator Generator to set (not null) */ public void setDetailsGenerator(final ItemDetailsGenerator detailsGenerator) { ObjectUtils.argumentNotNull(detailsGenerator, "Generator must be not null"); getGrid().setDetailsGenerator( row -> getItem(row.getItemId()).map(i -> detailsGenerator.getItemDetails(i)).orElse(null)); } /** * Setup column configuration for given property using its {@link PropertyColumn} definition. * @param property Property to which the column is bound * @param column Column to setup */ protected void setupGridPropertyColumn(P property, Column column) { PropertyColumn propertyColumn = getPropertyColumn(property); if (propertyColumn != null) { // header if (propertyColumn.getCaption() != null) { String header = LocalizationContext.translate(propertyColumn.getCaption(), true); if (header != null) { column.setHeaderCaption(header); } } // sortable column.setSortable(requireDataSource().getConfiguration().isPropertySortable(property)); // width if (propertyColumn.getWidth() > -1) { column.setWidth(propertyColumn.getWidth()); } if (propertyColumn.getMinWidth() > -1) { column.setMinimumWidth(propertyColumn.getMinWidth()); } if (propertyColumn.getMaxWidth() > -1) { column.setMaximumWidth(propertyColumn.getMinWidth()); } // expand if (propertyColumn.getGridExpandRatio() > -1) { column.setExpandRatio(propertyColumn.getGridExpandRatio()); } // editing if (requireDataSource().getConfiguration().isPropertyReadOnly(property)) { column.setEditable(false); } else { column.setEditable(propertyColumn.isEditable()); if (propertyColumn.isEditable()) { propertyColumn.getEditor().ifPresent(e -> column.setEditorField(e)); } } // hiding if (columnHidingAllowed && propertyColumn.isHidable()) { column.setHidable(true); if (propertyColumn.isHidden()) { column.setHidden(true); } if (propertyColumn.getHidingToggleCaption() != null) { column.setHidingToggleCaption( LocalizationContext.translate(propertyColumn.getHidingToggleCaption())); } } else { column.setHidable(false); } } } /** * Setup {@link Renderer}s and {@link Converter}s for given property column, using * {@link PropertyColumn} definition and falling back to defaults. * @param property Property to which the column is bound * @param column Column to setup */ protected void setupRendererAndConverter(P property, Column column) { PropertyColumn propertyColumn = getPropertyColumn(property); if (propertyColumn != null) { // Converter if (propertyColumn.getConverter() != null) { column.setConverter(propertyColumn.getConverter()); } else { // ue default, if available getDefaultPropertyConverter(property).ifPresent((c) -> column.setConverter(c)); } // Renderer if (propertyColumn.getRenderer() != null) { column.setRenderer(propertyColumn.getRenderer()); } else { // use default getDefaultPropertyRenderer(property).ifPresent(r -> column.setRenderer(r)); } } } /** * Get the default {@link Converter} for given property. * @param property Property * @return The default {@link Converter}, if available */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected Optional> getDefaultPropertyConverter(final P property) { Converter converter = null; Class type = getPropertyColumnType(property); // FontIcons if (type != null && FontIcon.class.isAssignableFrom(type)) { converter = new FontIconPresentationConverter(); } else { // Use default property presentation converter if (Property.class.isAssignableFrom(property.getClass())) { converter = new PropertyPresentationConverter<>((Property) property); } } return Optional.ofNullable(converter); } /** * Get the default {@link Renderer} for given property. * @param property Property * @return The default {@link Renderer}, if available */ protected Optional> getDefaultPropertyRenderer(P property) { Class type = getPropertyColumnType(property); // Images if (type != null && (ExternalResource.class.isAssignableFrom(type) || ThemeResource.class.isAssignableFrom(type))) { return Optional.of(new ImageRenderer()); } if (type != null && FontIcon.class.isAssignableFrom(type)) { return Optional.of(new HtmlRenderer("")); } return Optional.empty(); } /** * Grid {@link FieldGroup} extension for {@link Property} support in row editing. */ @SuppressWarnings("serial") private final class PropertyGridFieldGroup extends FieldGroup { public PropertyGridFieldGroup() { super(); setFieldFactory(EditorFieldFactory.get()); } @SuppressWarnings("unchecked") @Override protected Class getPropertyType(Object propertyId) throws BindException { // If property is a Property, return its type if (propertyId != null && Property.class.isAssignableFrom(propertyId.getClass())) { return ((Property) propertyId).getType(); } if (getItemDataSource() == null) { return requireDataSource().getConfiguration().getPropertyType((P) propertyId); } else { return super.getPropertyType(propertyId); } } /* * (non-Javadoc) * @see com.vaadin.data.fieldgroup.FieldGroup#buildAndBind(java.lang.Object) */ @SuppressWarnings("unchecked") @Override public Field buildAndBind(Object propertyId) throws BindException { // If property is a Property, try to render Field using UIContext if (propertyId != null && Property.class.isAssignableFrom(propertyId.getClass())) { Field field = renderField((P) propertyId).map((f) -> { setupField((P) propertyId, f); if (f instanceof CheckBox) f.setCaption(null); bind(f, propertyId); return f; }).orElse(null); if (field != null) { return field; } } return super.buildAndBind(propertyId); } @SuppressWarnings("rawtypes") @Override protected F build(String caption, Class dataType, Class fieldType) throws BindException { F field = super.build(caption, dataType, fieldType); if (field instanceof CheckBox) { field.setCaption(null); } return field; } @Override protected void bindFields() { List> fields = new ArrayList<>(getFields()); Item itemDataSource = getItemDataSource(); if (itemDataSource == null) { unbindFields(fields); } else { bindFields(fields, itemDataSource); } } private void unbindFields(List> fields) { for (Field field : fields) { clearField(field); unbind(field); field.setParent(null); } } private void bindFields(List> fields, Item itemDataSource) { for (Field field : fields) { if (itemDataSource.getItemProperty(getPropertyId(field)) != null) { bind(field, getPropertyId(field)); } } } } // ------- Table /* * (non-Javadoc) * @see com.vaadin.ui.Table.CellStyleGenerator#getStyle(com.vaadin.ui.Table, java.lang.Object, java.lang.Object) */ @SuppressWarnings("unchecked") @Override public String getStyle(Table source, final Object itemId, final Object propertyId) { return requireDataSource().get(itemId).map(i -> { if (propertyId == null) { // row style if (!rowStyleGenerators.isEmpty()) { return generateRowStyle(i); } } else { // property (cell) style return generatePropertyStyle((P) propertyId, i); } return null; }).orElse(null); } /** * Setup column configuration for given property using its {@link PropertyColumn} definition. * @param property Property to which the column is bound * @param table Table to setup */ protected void setupTablePropertyColumn(P property, Table table) { PropertyColumn propertyColumn = getPropertyColumn(property); if (propertyColumn != null) { // header if (propertyColumn.getCaption() != null) { String header = LocalizationContext.translate(propertyColumn.getCaption(), true); if (header != null) { table.setColumnHeader(property, header); } } // alignment if (propertyColumn.getAlignment() != null) { switch (propertyColumn.getAlignment()) { case CENTER: table.setColumnAlignment(property, Align.CENTER); break; case LEFT: table.setColumnAlignment(property, Align.LEFT); break; case RIGHT: table.setColumnAlignment(property, Align.RIGHT); break; default: break; } } // width if (propertyColumn.getWidth() > -1) { table.setColumnWidth(property, propertyColumn.getWidth()); } // expand if (propertyColumn.getTableExpandRatio() > -1) { table.setColumnExpandRatio(property, propertyColumn.getTableExpandRatio()); } // hiding if (propertyColumn.isHidable()) { table.setColumnCollapsible(property, true); if (propertyColumn.isHidden()) { table.setColumnCollapsed(property, true); } } else { table.setColumnCollapsible(property, false); } // icon if (propertyColumn.getIcon() != null) { table.setColumnIcon(property, propertyColumn.getIcon()); } } } /* * (non-Javadoc) * @see com.vaadin.ui.TableFieldFactory#createField(com.vaadin.data.Container, java.lang.Object, java.lang.Object, * com.vaadin.ui.Component) */ @SuppressWarnings("unchecked") @Override public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) { P property = (P) propertyId; // check read-only property if (requireDataSource().getConfiguration().isPropertyReadOnly(property)) { return null; } // check editable and custom editor PropertyColumn propertyColumn = getPropertyColumn(property); if (propertyColumn != null) { if (!propertyColumn.isEditable()) { return null; } if (propertyColumn.getEditor().isPresent()) { return propertyColumn.getEditor().get(); } } // check PropertyRenderer or use DefaultFieldFactory return renderField(property) .orElse(DefaultFieldFactory.get().createField(container, itemId, propertyId, uiContext)); } /** * A {@link ColumnGenerator} using a {@link VirtualProperty} as content source. */ @SuppressWarnings("serial") private class VirtualPropertyGenerator implements ColumnGenerator { /* * (non-Javadoc) * @see com.vaadin.ui.Table.ColumnGenerator#generateCell(com.vaadin.ui.Table, java.lang.Object, * java.lang.Object) */ @SuppressWarnings("rawtypes") @Override public Object generateCell(Table source, Object itemId, Object columnId) { if (!VirtualProperty.class.isAssignableFrom(columnId.getClass())) { throw new IllegalArgumentException("VirtualPropertyGenerator has to be bound to " + "a VirtualProperty column id, got " + columnId.getClass().getName()); } // get virtual property value provider final VirtualProperty property = (VirtualProperty) columnId; final PropertyValueProvider valueProvider = property.getValueProvider(); if (valueProvider == null) { throw new IllegalArgumentException("Missing PropertyValueProvider for property " + property); } // get property box final PropertyBox propertyBox = (PropertyBox) requireDataSource().get(itemId).orElse(null); try { return valueProvider.getPropertyValue(propertyBox); } catch (Exception e) { LOGGER.error("Failed to generate column for property " + property, e); throw new RuntimeException(e); } } } /** * Default selection listener to convert value change events into {@link SelectionListener}s calls. */ @SuppressWarnings("serial") private final ValueChangeListener tableSelectionChangeListener = new ValueChangeListener() { @SuppressWarnings("unchecked") @Override public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) { fireSelectionListeners(new DefaultSelectionEvent<>( convertSelectionItems((Collection) event.getProperty().getValue()))); } }; }