Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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.haulmont.cuba.web.gui.components; import com.haulmont.bali.util.Dom4j; import com.haulmont.bali.util.Preconditions; import com.haulmont.chile.core.datatypes.Datatype; import com.haulmont.chile.core.datatypes.Datatypes; import com.haulmont.chile.core.model.Instance; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.chile.core.model.MetaProperty; import com.haulmont.chile.core.model.MetaPropertyPath; import com.haulmont.cuba.client.ClientConfig; import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesUtils; import com.haulmont.cuba.core.entity.CategoryAttribute; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.entity.LocaleHelper; import com.haulmont.cuba.core.entity.SoftDelete; import com.haulmont.cuba.core.global.*; import com.haulmont.cuba.gui.ComponentsHelper; import com.haulmont.cuba.gui.WindowManager; import com.haulmont.cuba.gui.WindowManager.OpenType; import com.haulmont.cuba.gui.components.*; import com.haulmont.cuba.gui.components.CheckBox; import com.haulmont.cuba.gui.components.Field; import com.haulmont.cuba.gui.components.Formatter; import com.haulmont.cuba.gui.components.Table; import com.haulmont.cuba.gui.components.Window; import com.haulmont.cuba.gui.components.formatters.CollectionFormatter; import com.haulmont.cuba.gui.config.WindowConfig; import com.haulmont.cuba.gui.data.*; import com.haulmont.cuba.gui.data.aggregation.Aggregation; import com.haulmont.cuba.gui.data.aggregation.Aggregations; import com.haulmont.cuba.gui.data.impl.CollectionDsListenersWrapper; import com.haulmont.cuba.gui.data.impl.DatasourceImplementation; import com.haulmont.cuba.gui.presentations.Presentations; import com.haulmont.cuba.gui.presentations.PresentationsImpl; import com.haulmont.cuba.gui.theme.ThemeConstants; import com.haulmont.cuba.security.entity.Presentation; import com.haulmont.cuba.web.App; import com.haulmont.cuba.web.AppUI; import com.haulmont.cuba.web.WebConfig; import com.haulmont.cuba.web.gui.components.presentations.TablePresentations; import com.haulmont.cuba.web.gui.data.CollectionDsWrapper; import com.haulmont.cuba.web.gui.data.ItemWrapper; import com.haulmont.cuba.web.gui.data.PropertyWrapper; import com.haulmont.cuba.web.toolkit.data.AggregationContainer; import com.haulmont.cuba.web.toolkit.ui.CubaEnhancedTable; import com.haulmont.cuba.web.toolkit.ui.CubaResizableTextAreaWrapper; import com.haulmont.cuba.web.toolkit.ui.CubaTextArea; import com.haulmont.cuba.web.toolkit.ui.client.resizabletextarea.ResizeDirection; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.event.ShortcutListener; import com.vaadin.server.Resource; import com.vaadin.ui.*; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.Label; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.reflect.MethodUtils; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.*; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; public abstract class WebAbstractTable<T extends com.vaadin.ui.Table & CubaEnhancedTable, E extends Entity> extends WebAbstractList<T, E> implements Table<E>, LookupComponent.LookupSelectionChangeNotifier { private static final String HAS_TOP_PANEL_STYLENAME = "has-top-panel"; private static final String CUSTOM_STYLE_NAME_PREFIX = "cs "; // Style names used by table itself protected List<String> internalStyles = new ArrayList<>(); protected Map<Object, Column> columns = new HashMap<>(); protected List<Table.Column> columnsOrder = new ArrayList<>(); protected boolean editable; protected Action itemClickAction; protected Action enterPressAction; protected List<Table.StyleProvider> styleProviders; // lazily initialized List protected Table.IconProvider<? super E> iconProvider; protected Map<Table.Column, String> requiredColumns; // lazily initialized Map protected Map<Table.Column, Set<Field.Validator>> validatorsMap; // lazily initialized Map protected Set<com.haulmont.cuba.gui.components.Field.Validator> tableValidators; // lazily initialized LinkedHashSet protected Map<Entity, Datasource> fieldDatasources; // lazily initialized WeakHashMap; protected TableComposition componentComposition; protected HorizontalLayout topPanel; protected ButtonsPanel buttonsPanel; protected RowsCount rowsCount; protected Map<Table.Column, String> aggregationCells = null; protected boolean usePresentations; protected Presentations presentations; protected Document defaultSettings; protected List<ColumnCollapseListener> columnCollapseListeners; // lazily initialized List // Map column id to Printable representation protected Map<String, Printable> printables; // lazily initialized Map protected static final int MAX_TEXT_LENGTH_GAP = 10; protected Security security = AppBeans.get(Security.NAME); protected MetadataTools metadataTools = AppBeans.get(MetadataTools.NAME); protected boolean settingsEnabled = true; protected CollectionDsListenersWrapper collectionDsListenersWrapper; protected CollectionDsWrapper containerDatasource; protected boolean ignoreUnfetchedAttributes = false; public WebAbstractTable() { Configuration configuration = AppBeans.get(Configuration.NAME); ClientConfig clientConfig = configuration.getConfig(ClientConfig.class); ignoreUnfetchedAttributes = clientConfig.getIgnoreUnfetchedAttributesInTable(); } @Override public java.util.List<Table.Column> getColumns() { return Collections.unmodifiableList(columnsOrder); } @Override public Table.Column getColumn(String id) { for (Table.Column column : columnsOrder) { if (column.getId().toString().equals(id)) return column; } return null; } @Override public void addColumn(Table.Column column) { checkNotNullArgument(column, "Column must be non null"); Object columnId = column.getId(); component.addContainerProperty(columnId, column.getType(), null); if (StringUtils.isNotBlank(column.getDescription())) { component.setColumnDescription(columnId, column.getDescription()); } if (StringUtils.isNotBlank(column.getValueDescription())) { component.setAggregationDescription(columnId, column.getValueDescription()); } else if (column.getAggregation() != null && column.getAggregation().getType() != AggregationInfo.Type.CUSTOM) { Messages messages = AppBeans.get(Messages.NAME); String aggregationTypeLabel; switch (column.getAggregation().getType()) { case AVG: aggregationTypeLabel = "aggreagtion.avg"; break; case COUNT: aggregationTypeLabel = "aggreagtion.count"; break; case SUM: aggregationTypeLabel = "aggreagtion.sum"; break; case MIN: aggregationTypeLabel = "aggreagtion.min"; break; case MAX: aggregationTypeLabel = "aggreagtion.max"; break; default: throw new IllegalArgumentException(String.format("AggregationType %s is not supported", column.getAggregation().getType().toString())); } component.setAggregationDescription(columnId, messages.getMainMessage(aggregationTypeLabel)); } if (!column.isSortable()) { component.setColumnSortable(columnId, column.isSortable()); } columns.put(columnId, column); columnsOrder.add(column); if (column.getWidth() != null) { component.setColumnWidth(columnId, column.getWidth()); } if (column.getAlignment() != null) { component.setColumnAlignment(columnId, WebComponentsHelper.convertColumnAlignment(column.getAlignment())); } final String caption = getColumnCaption(columnId, column); setColumnHeader(columnId, caption); column.setOwner(this); if (column.getFormatter() == null && columnId instanceof MetaPropertyPath) { MetaProperty metaProperty = ((MetaPropertyPath) columnId).getMetaProperty(); if (Collection.class.isAssignableFrom(metaProperty.getJavaType())) { final Formatter collectionFormatter = new CollectionFormatter(); column.setFormatter(collectionFormatter); } } } @Override public void removeColumn(Table.Column column) { if (column == null) { return; } component.removeContainerProperty(column.getId()); columns.remove(column.getId()); columnsOrder.remove(column); if (!(component.getContainerDataSource() instanceof com.vaadin.data.Container.ItemSetChangeNotifier)) { component.refreshRowCache(); } column.setOwner(null); } @SuppressWarnings("unchecked") @Override public Datasource getItemDatasource(Entity item) { if (fieldDatasources == null) { fieldDatasources = new WeakHashMap<>(); } Datasource fieldDatasource = fieldDatasources.get(item); if (fieldDatasource == null) { fieldDatasource = DsBuilder.create().setAllowCommit(false).setMetaClass(datasource.getMetaClass()) .setRefreshMode(CollectionDatasource.RefreshMode.NEVER).setViewName("_local").buildDatasource(); ((DatasourceImplementation) fieldDatasource).valid(); fieldDatasource.setItem(item); fieldDatasources.put(item, fieldDatasource); } return fieldDatasource; } protected void addGeneratedColumn(Object id, Object generator) { component.addGeneratedColumn(id, (com.vaadin.ui.Table.ColumnGenerator) generator); } protected void removeGeneratedColumn(Object id) { boolean wasEnabled = component.disableContentBufferRefreshing(); com.vaadin.ui.Table.ColumnGenerator columnGenerator = component.getColumnGenerator(id); if (columnGenerator instanceof CustomColumnGenerator) { CustomColumnGenerator tableGenerator = (CustomColumnGenerator) columnGenerator; if (tableGenerator.getAssociatedRuntimeColumn() != null) { removeColumn(tableGenerator.getAssociatedRuntimeColumn()); } } component.removeGeneratedColumn(id); component.enableContentBufferRefreshing(wasEnabled); } @Override public void addPrintable(String columnId, Printable<? super E, ?> printable) { if (printables == null) { printables = new HashMap<>(); } printables.put(columnId, printable); } @Override public void removePrintable(String columnId) { if (printables != null) { printables.remove(columnId); } } @Override @Nullable public Printable getPrintable(Table.Column column) { return getPrintable(String.valueOf(column.getId())); } @Nullable @Override public Printable getPrintable(String columnId) { Printable printable = printables != null ? printables.get(columnId) : null; if (printable != null) { return printable; } else { com.vaadin.ui.Table.ColumnGenerator vColumnGenerator = component .getColumnGenerator(getColumn(columnId).getId()); if (vColumnGenerator instanceof CustomColumnGenerator) { ColumnGenerator columnGenerator = ((CustomColumnGenerator) vColumnGenerator).getColumnGenerator(); if (columnGenerator instanceof Printable) return (Printable) columnGenerator; } return null; } } @Override public boolean isEditable() { return editable; } @Override public void setEditable(boolean editable) { if (this.editable != editable) { this.editable = editable; component.disableContentBufferRefreshing(); if (datasource != null) { com.vaadin.data.Container ds = component.getContainerDataSource(); @SuppressWarnings("unchecked") final Collection<MetaPropertyPath> propertyIds = (Collection<MetaPropertyPath>) ds .getContainerPropertyIds(); if (editable) { MetaClass metaClass = datasource.getMetaClass(); final List<MetaPropertyPath> editableColumns = new ArrayList<>(propertyIds.size()); for (final MetaPropertyPath propertyId : propertyIds) { if (!security.isEntityAttrUpdatePermitted(metaClass, propertyId.toString())) { continue; } final Table.Column column = getColumn(propertyId.toString()); if (BooleanUtils.isTrue(column.isEditable())) { com.vaadin.ui.Table.ColumnGenerator generator = component .getColumnGenerator(column.getId()); if (generator != null) { if (generator instanceof SystemTableColumnGenerator) { // remove default generator component.removeGeneratedColumn(propertyId); } else { // do not edit generated columns continue; } } editableColumns.add(propertyId); } } setEditableColumns(editableColumns); } else { setEditableColumns(Collections.emptyList()); Window window = ComponentsHelper.getWindowImplementation(this); boolean isLookup = window instanceof Window.Lookup; // restore generators for some type of attributes for (MetaPropertyPath propertyId : propertyIds) { final Table.Column column = columns.get(propertyId); if (column != null) { final String isLink = column.getXmlDescriptor() == null ? null : column.getXmlDescriptor().attributeValue("link"); if (component.getColumnGenerator(column.getId()) == null) { if (propertyId.getRange().isClass()) { if (!isLookup && StringUtils.isNotEmpty(isLink)) { setClickListener(propertyId.toString(), new LinkCellClickListener()); } } else if (propertyId.getRange().isDatatype()) { if (!isLookup && !StringUtils.isEmpty(isLink)) { setClickListener(propertyId.toString(), new LinkCellClickListener()); } else { if (column.getMaxTextLength() != null) { addGeneratedColumn(propertyId, new AbbreviatedColumnGenerator(column)); } } } } } } } } component.setEditable(editable); component.enableContentBufferRefreshing(true); } } protected void setEditableColumns(List<MetaPropertyPath> editableColumns) { component.setEditableColumns(editableColumns.toArray()); } @Override public boolean isSortable() { return component.isSortEnabled(); } @Override public void setSortable(boolean sortable) { component.setSortEnabled(sortable && canBeSorted(datasource)); } @Override public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { component.setColumnReorderingAllowed(columnReorderingAllowed); } @Override public boolean getColumnReorderingAllowed() { return component.isColumnReorderingAllowed(); } @Override public void setColumnControlVisible(boolean columnCollapsingAllowed) { component.setColumnCollapsingAllowed(columnCollapsingAllowed); } @Override public boolean getColumnControlVisible() { return component.isColumnCollapsingAllowed(); } @Override public void sortBy(Object propertyId, boolean ascending) { if (isSortable()) { component.setSortAscending(ascending); component.setSortContainerPropertyId(propertyId); component.sort(); } } @Override public void sort(String columnId, SortDirection direction) { Column column = getColumn(columnId); if (column == null) { throw new IllegalArgumentException("Unable to find column " + columnId); } if (isSortable()) { component.setSortAscending(direction == SortDirection.ASCENDING); component.setSortContainerPropertyId(column.getId()); component.sort(); } } @Nullable @Override public SortInfo getSortInfo() { Object sortContainerPropertyId = component.getSortContainerPropertyId(); return sortContainerPropertyId != null ? new SortInfo(sortContainerPropertyId, component.isSortAscending()) : null; } @Override public RowsCount getRowsCount() { return rowsCount; } @Override public void setRowsCount(RowsCount rowsCount) { if (this.rowsCount != null && topPanel != null) { topPanel.removeComponent(WebComponentsHelper.unwrap(this.rowsCount)); } this.rowsCount = rowsCount; if (rowsCount != null) { if (topPanel == null) { topPanel = createTopPanel(); topPanel.setWidth("100%"); componentComposition.addComponentAsFirst(topPanel); } com.vaadin.ui.Component rc = WebComponentsHelper.unwrap(rowsCount); topPanel.addComponent(rc); topPanel.setExpandRatio(rc, 1); topPanel.setComponentAlignment(rc, com.vaadin.ui.Alignment.BOTTOM_RIGHT); if (rowsCount instanceof VisibilityChangeNotifier) { ((VisibilityChangeNotifier) rowsCount) .addVisibilityChangeListener(event -> updateCompositionStylesTopPanelVisible()); } } updateCompositionStylesTopPanelVisible(); } // if buttons panel becomes hidden we need to set top panel height to 0 protected void updateCompositionStylesTopPanelVisible() { if (topPanel != null) { boolean hasChildren = topPanel.getComponentCount() > 0; boolean anyChildVisible = false; for (Component childComponent : topPanel) { if (childComponent.isVisible()) { anyChildVisible = true; break; } } boolean topPanelVisible = hasChildren && anyChildVisible; if (!topPanelVisible) { componentComposition.removeStyleName(HAS_TOP_PANEL_STYLENAME); internalStyles.remove(HAS_TOP_PANEL_STYLENAME); } else { componentComposition.addStyleName(HAS_TOP_PANEL_STYLENAME); if (!internalStyles.contains(HAS_TOP_PANEL_STYLENAME)) { internalStyles.add(HAS_TOP_PANEL_STYLENAME); } } } } @Override public void setMultiLineCells(boolean multiLineCells) { component.setMultiLineCells(multiLineCells); } @Override public boolean isMultiLineCells() { return component.isMultiLineCells(); } @Override public boolean isAggregatable() { return component.isAggregatable(); } @Override public void setAggregatable(boolean aggregatable) { component.setAggregatable(aggregatable); } @Override public Map<Object, Object> getAggregationResults() { CollectionDatasource ds = WebAbstractTable.this.getDatasource(); return component.aggregate(new AggregationContainer.Context(ds.getItemIds())); } @Override public void setAggregationStyle(AggregationStyle aggregationStyle) { component.setAggregationStyle(aggregationStyle); } @Override public AggregationStyle getAggregationStyle() { return component.getAggregationStyle(); } @Override public void setShowTotalAggregation(boolean showAggregation) { component.setShowTotalAggregation(showAggregation); } @Override public boolean isShowTotalAggregation() { return component.isShowTotalAggregation(); } @Override public com.vaadin.ui.Component getComposition() { return componentComposition; } @Override public boolean isContextMenuEnabled() { return component.isContextMenuEnabled(); } @Override public void setContextMenuEnabled(boolean contextMenuEnabled) { component.setContextMenuEnabled(contextMenuEnabled); } @Override public int getTabIndex() { return component.getTabIndex(); } @Override public void setTabIndex(int tabIndex) { component.setTabIndex(tabIndex); } protected void setTablePresentations(TablePresentations tablePresentations) { component.setPresentations(tablePresentations); } protected void initComponent(final T component) { component.setMultiSelect(false); component.setImmediate(true); component.setValidationVisible(false); component.setShowBufferedSourceException(false); component.setBeforePaintListener(() -> { com.vaadin.ui.Table.CellStyleGenerator generator = component.getCellStyleGenerator(); if (generator instanceof WebAbstractTable.StyleGeneratorAdapter) { //noinspection unchecked ((StyleGeneratorAdapter) generator).resetExceptionHandledFlag(); } }); Messages messages = AppBeans.get(Messages.NAME); component.setSortAscendingLabel(messages.getMainMessage("tableSort.ascending")); component.setSortResetLabel(messages.getMainMessage("tableSort.reset")); component.setSortDescendingLabel(messages.getMainMessage("tableSort.descending")); setClientCaching(component); int defaultRowHeaderWidth = 16; ThemeConstants theme = App.getInstance().getThemeConstants(); if (theme != null) { defaultRowHeaderWidth = theme.getInt("cuba.web.Table.defaultRowHeaderWidth"); } // CAUTION: vaadin considers null as row header property id; component.setColumnWidth(null, defaultRowHeaderWidth); // todo get width from theme contextMenuPopup.setParent(component); component.setContextMenuPopup(contextMenuPopup); shortcutsDelegate.setAllowEnterShortcut(false); component.addValueChangeListener(event -> { if (datasource == null) { return; } final Set<? extends Entity> selected = getSelected(); if (selected.isEmpty()) { Entity dsItem = datasource.getItemIfValid(); datasource.setItem(null); if (dsItem == null) { // in this case item change event will not be generated refreshActionsState(); } } else { // reset selection and select new item if (isMultiSelect()) { datasource.setItem(null); } Entity newItem = selected.iterator().next(); Entity dsItem = datasource.getItemIfValid(); datasource.setItem(newItem); if (Objects.equals(dsItem, newItem)) { // in this case item change event will not be generated refreshActionsState(); } } LookupSelectionChangeEvent selectionChangeEvent = new LookupSelectionChangeEvent(this); getEventRouter().fireEvent(LookupSelectionChangeListener.class, LookupSelectionChangeListener::lookupValueChanged, selectionChangeEvent); }); component.addShortcutListener( new ShortcutListener("tableEnter", com.vaadin.event.ShortcutAction.KeyCode.ENTER, null) { @Override public void handleAction(Object sender, Object target) { if (target == WebAbstractTable.this.component) { if (enterPressAction != null) { enterPressAction.actionPerform(WebAbstractTable.this); } else { handleClickAction(); } } } }); component.addItemClickListener(event -> { if (event.isDoubleClick() && event.getItem() != null) { handleClickAction(); } }); component.setSelectable(true); component.setTableFieldFactory(createFieldFactory()); component.setColumnCollapsingAllowed(true); component.setColumnReorderingAllowed(true); setEditable(false); componentComposition = new TableComposition(); componentComposition.setTable(component); componentComposition.setPrimaryStyleName("c-table-composition"); componentComposition.addComponent(component); component.setCellStyleGenerator(createStyleGenerator()); component.addColumnCollapseListener(this::handleColumnCollapsed); // force default sizes componentComposition.setHeightUndefined(); componentComposition.setWidthUndefined(); } protected WebTableFieldFactory createFieldFactory() { return new WebTableFieldFactory(this); } protected void setFieldFactory(WebTableFieldFactory fieldFactory) { component.setTableFieldFactory(fieldFactory); } protected void setClientCaching(T component) { Configuration configuration = AppBeans.get(Configuration.NAME); WebConfig webConfig = configuration.getConfig(WebConfig.class); double cacheRate = webConfig.getTableCacheRate(); if (cacheRate >= 0) { component.setCacheRate(cacheRate); } int pageLength = webConfig.getTablePageLength(); if (pageLength >= 0) { component.setPageLength(pageLength); } } protected void refreshActionsState() { for (Action action : getActions()) { action.refreshState(); } } protected StyleGeneratorAdapter createStyleGenerator() { return new StyleGeneratorAdapter(); } @SuppressWarnings("unchecked") protected String getGeneratedCellStyle(Object itemId, Object propertyId) { if (styleProviders == null) { return null; } Entity item = datasource.getItem(itemId); String joinedStyle = null; for (StyleProvider styleProvider : styleProviders) { String styleName = styleProvider.getStyleName(item, propertyId == null ? null : propertyId.toString()); if (styleName != null) { if (joinedStyle == null) { joinedStyle = styleName; } else { joinedStyle += " " + styleName; } } } return joinedStyle; } @Override protected ContextMenuButton createContextMenuButton() { //noinspection IncorrectCreateGuiComponent return new ContextMenuButton(showIconsForPopupMenuActions) { @Override protected void beforeActionPerformed() { WebAbstractTable.this.component.hideContextMenuPopup(); } @Override protected void performAction(Action action) { // do action for table component action.actionPerform(WebAbstractTable.this); } }; } protected void handleClickAction() { Action action = getItemClickAction(); if (action == null) { action = getEnterAction(); if (action == null) { action = getAction("edit"); if (action == null) { action = getAction("view"); } } } if (action != null && action.isEnabled()) { Window window = ComponentsHelper.getWindowImplementation(WebAbstractTable.this); if (window instanceof Window.Wrapper) { window = ((Window.Wrapper) window).getWrappedWindow(); } if (!(window instanceof Window.Lookup)) { action.actionPerform(WebAbstractTable.this); } else { Window.Lookup lookup = (Window.Lookup) window; com.haulmont.cuba.gui.components.Component lookupComponent = lookup.getLookupComponent(); if (lookupComponent != this) action.actionPerform(WebAbstractTable.this); else if (action.getId().equals(WindowDelegate.LOOKUP_ITEM_CLICK_ACTION_ID)) { action.actionPerform(WebAbstractTable.this); } } } } @Override public void setLookupSelectHandler(Runnable selectHandler) { setEnterPressAction(new AbstractAction(WindowDelegate.LOOKUP_ENTER_PRESSED_ACTION_ID) { @Override public void actionPerform(com.haulmont.cuba.gui.components.Component component) { selectHandler.run(); } }); setItemClickAction(new AbstractAction(WindowDelegate.LOOKUP_ITEM_CLICK_ACTION_ID) { @Override public void actionPerform(com.haulmont.cuba.gui.components.Component component) { selectHandler.run(); } }); } @Override public Collection getLookupSelectedItems() { return getSelected(); } protected Action getEnterAction() { for (Action action : getActions()) { KeyCombination kc = action.getShortcutCombination(); if (kc != null) { if ((kc.getModifiers() == null || kc.getModifiers().length == 0) && kc.getKey() == KeyCombination.Key.ENTER) { return action; } } } return null; } protected Collection<MetaPropertyPath> createColumns(com.vaadin.data.Container ds) { @SuppressWarnings("unchecked") final Collection<MetaPropertyPath> properties = (Collection<MetaPropertyPath>) ds.getContainerPropertyIds(); Window window = ComponentsHelper.getWindowImplementation(this); boolean isLookup = window instanceof Window.Lookup; for (MetaPropertyPath propertyPath : properties) { final Table.Column column = columns.get(propertyPath); if (column != null && !(editable && BooleanUtils.isTrue(column.isEditable()))) { final String isLink = column.getXmlDescriptor() == null ? null : column.getXmlDescriptor().attributeValue("link"); if (propertyPath.getRange().isClass()) { if (!isLookup && StringUtils.isNotEmpty(isLink)) { setClickListener(propertyPath.toString(), new LinkCellClickListener()); } } else if (propertyPath.getRange().isDatatype()) { if (!isLookup && !StringUtils.isEmpty(isLink)) { setClickListener(propertyPath.toString(), new LinkCellClickListener()); } else if (editable && BooleanUtils.isTrue(column.isCalculatable())) { addGeneratedColumn(propertyPath, new CalculatableColumnGenerator()); } else { if (column.getMaxTextLength() != null) { addGeneratedColumn(propertyPath, new AbbreviatedColumnGenerator(column)); setClickListener(propertyPath.toString(), new AbbreviatedCellClickListener()); } } } else if (!propertyPath.getRange().isEnum()) { throw new UnsupportedOperationException(); } } } return properties; } @Override public void setDatasource(final CollectionDatasource datasource) { Preconditions.checkNotNullArgument(datasource, "datasource is null"); if (this.datasource != null) { if (!this.datasource.getMetaClass().equals(datasource.getMetaClass())) { throw new IllegalArgumentException("The new datasource must correspond to the same MetaClass"); } if (fieldDatasources != null) { fieldDatasources.clear(); } if (collectionDsListenersWrapper != null) { collectionDsListenersWrapper.unbind(this.datasource); if (containerDatasource != null) { containerDatasource.unsubscribe(); containerDatasource = null; } } } MessageTools messageTools = AppBeans.get(MessageTools.NAME); final Collection<Object> columns; if (this.columns.isEmpty()) { Collection<MetaPropertyPath> paths = datasource.getView() != null ? // if a view is specified - use view properties metadataTools.getViewPropertyPaths(datasource.getView(), datasource.getMetaClass()) : // otherwise use all properties from meta-class metadataTools.getPropertyPaths(datasource.getMetaClass()); for (MetaPropertyPath metaPropertyPath : paths) { MetaProperty property = metaPropertyPath.getMetaProperty(); if (!property.getRange().getCardinality().isMany() && !metadataTools.isSystem(property)) { Table.Column column = new Table.Column(metaPropertyPath); String propertyName = property.getName(); MetaClass propertyMetaClass = metadataTools.getPropertyEnclosingMetaClass(metaPropertyPath); column.setCaption(messageTools.getPropertyCaption(propertyMetaClass, propertyName)); column.setType(metaPropertyPath.getRangeJavaClass()); Element element = DocumentHelper.createElement("column"); column.setXmlDescriptor(element); addColumn(column); } } } columns = this.columns.keySet(); this.datasource = datasource; if (collectionDsListenersWrapper == null) { collectionDsListenersWrapper = createCollectionDsListenersWrapper(); } containerDatasource = createContainerDatasource(datasource, getPropertyColumns(), collectionDsListenersWrapper); component.setContainerDataSource(containerDatasource); List<MetaPropertyPath> editableColumns = null; if (isEditable()) { editableColumns = new LinkedList<>(); } MetaClass metaClass = datasource.getMetaClass(); for (final Object columnId : columns) { final Table.Column column = this.columns.get(columnId); final String caption; if (column != null) { caption = getColumnCaption(columnId, column); } else { caption = StringUtils.capitalize(getColumnCaption(columnId)); } setColumnHeader(columnId, caption); if (column != null) { if (editableColumns != null && column.isEditable() && (columnId instanceof MetaPropertyPath)) { MetaPropertyPath propertyPath = ((MetaPropertyPath) columnId); if (security.isEntityAttrUpdatePermitted(metaClass, propertyPath.toString())) { editableColumns.add(propertyPath); } } if (column.isCollapsed() && component.isColumnCollapsingAllowed()) { if (!(columnId instanceof MetaPropertyPath) || security.isEntityAttrReadPermitted(metaClass, columnId.toString())) { component.setColumnCollapsed(column.getId(), true); } } if (column.getAggregation() != null && isAggregatable()) { checkAggregation(column.getAggregation()); component.addContainerPropertyAggregation(column.getId(), WebComponentsHelper.convertAggregationType(column.getAggregation().getType())); } } } if (editableColumns != null && !editableColumns.isEmpty()) { setEditableColumns(editableColumns); } createColumns(containerDatasource); for (Table.Column column : this.columnsOrder) { if (editable && column.getAggregation() != null && (BooleanUtils.isTrue(column.isEditable()) || BooleanUtils.isTrue(column.isCalculatable()))) { addAggregationCell(column); } } createStubsForGeneratedColumns(); setVisibleColumns(getInitialVisibleColumns()); if (security.isSpecificPermitted(ShowInfoAction.ACTION_PERMISSION)) { ShowInfoAction action = (ShowInfoAction) getAction(ShowInfoAction.ACTION_ID); if (action == null) { action = new ShowInfoAction(); addAction(action); } action.setDatasource(datasource); } if (rowsCount != null) { rowsCount.setDatasource(datasource); } collectionDsListenersWrapper.bind(datasource); for (Action action : getActions()) { action.refreshState(); } if (!canBeSorted(datasource)) setSortable(false); assignAutoDebugId(); } protected CollectionDsListenersWrapper createCollectionDsListenersWrapper() { return new TableCollectionDsListenersWrapper(); } protected boolean canBeSorted(CollectionDatasource datasource) { //noinspection SimplifiableConditionalExpression return datasource instanceof PropertyDatasource ? ((PropertyDatasource) datasource).getProperty().getRange().isOrdered() : true; } @Override public void setDebugId(String id) { super.setDebugId(id); if (id != null) { componentComposition.setId(AppUI.getCurrent().getTestIdManager().getTestId(id + "_composition")); } } @Override public void setId(String id) { super.setId(id); if (id != null && AppUI.getCurrent().isTestMode()) { componentComposition.setCubaId(id + "_composition"); } } @Override public void assignAutoDebugId() { super.assignAutoDebugId(); if (buttonsPanel != null) { for (com.haulmont.cuba.gui.components.Component subComponent : buttonsPanel.getComponents()) { if (subComponent instanceof WebAbstractComponent) { ((WebAbstractComponent) subComponent).assignAutoDebugId(); } } } } @Override protected String getAlternativeDebugId() { if (id != null) { return id; } if (datasource != null && StringUtils.isNotEmpty(datasource.getId())) { return "table_" + datasource.getId(); } return getClass().getSimpleName(); } protected String getColumnCaption(Object columnId) { if (columnId instanceof MetaPropertyPath) return ((MetaPropertyPath) columnId).getMetaProperty().getName(); else return columnId.toString(); } protected String getColumnCaption(Object columnId, Column column) { String caption = column.getCaption(); if (caption != null) { return caption; } if (!(columnId instanceof MetaPropertyPath)) { return StringUtils.capitalize(getColumnCaption(columnId)); } MetaPropertyPath mpp = (MetaPropertyPath) columnId; MetaProperty metaProperty = mpp.getMetaProperty(); if (DynamicAttributesUtils.isDynamicAttribute(metaProperty)) { CategoryAttribute categoryAttribute = DynamicAttributesUtils.getCategoryAttribute(metaProperty); if (LocaleHelper.isLocalizedValueDefined(categoryAttribute.getLocaleNames())) { return categoryAttribute.getLocaleName(); } caption = StringUtils.capitalize(categoryAttribute.getName()); } else { caption = StringUtils.capitalize(getColumnCaption(columnId)); } return caption; } protected void createStubsForGeneratedColumns() { com.vaadin.ui.Table.ColumnGenerator columnGeneratorStub = new com.vaadin.ui.Table.ColumnGenerator() { @Override public Object generateCell(com.vaadin.ui.Table source, Object itemId, Object columnId) { return null; } }; for (Column column : columnsOrder) { if (!(column.getId() instanceof MetaPropertyPath) && component.getColumnGenerator(column.getId()) == null) { component.addGeneratedColumn(column.getId(), columnGeneratorStub); } } } protected List<Object> getInitialVisibleColumns() { List<Object> result = new ArrayList<>(); MetaClass metaClass = datasource.getMetaClass(); for (Column column : columnsOrder) { if (column.getId() instanceof MetaPropertyPath) { MetaPropertyPath propertyPath = (MetaPropertyPath) column.getId(); if (security.isEntityAttrReadPermitted(metaClass, propertyPath.toString())) { result.add(column.getId()); } } else { result.add(column.getId()); } } return result; } protected List<MetaPropertyPath> getPropertyColumns() { List<MetaPropertyPath> result = new ArrayList<>(); MetaClass metaClass = datasource.getMetaClass(); for (Column column : columnsOrder) { if (column.getId() instanceof MetaPropertyPath) { MetaPropertyPath propertyPath = (MetaPropertyPath) column.getId(); if (security.isEntityAttrReadPermitted(metaClass, propertyPath.toString())) { result.add((MetaPropertyPath) column.getId()); } } } return result; } protected abstract CollectionDsWrapper createContainerDatasource(CollectionDatasource datasource, Collection<MetaPropertyPath> columns, CollectionDsListenersWrapper collectionDsListenersWrapper); protected void setVisibleColumns(List<?> columnsOrder) { component.setVisibleColumns(columnsOrder.toArray()); } protected void setColumnHeader(Object columnId, String caption) { component.setColumnHeader(columnId, caption); } @Override public void setRowHeaderMode(com.haulmont.cuba.gui.components.Table.RowHeaderMode rowHeaderMode) { switch (rowHeaderMode) { case NONE: { component.setRowHeaderMode(com.vaadin.ui.Table.RowHeaderMode.HIDDEN); break; } case ICON: { component.setRowHeaderMode(com.vaadin.ui.Table.RowHeaderMode.ICON_ONLY); break; } default: { throw new UnsupportedOperationException(); } } } @Override public void setRequired(Table.Column column, boolean required, String message) { if (required) { if (requiredColumns == null) { requiredColumns = new HashMap<>(); } requiredColumns.put(column, message); } else { if (requiredColumns != null) { requiredColumns.remove(column); } } } @Override public void addValidator(Table.Column column, final com.haulmont.cuba.gui.components.Field.Validator validator) { if (validatorsMap == null) { validatorsMap = new HashMap<>(); } Set<com.haulmont.cuba.gui.components.Field.Validator> validators = validatorsMap.get(column); if (validators == null) { validators = new HashSet<>(); validatorsMap.put(column, validators); } validators.add(validator); } @Override public void addValidator(final com.haulmont.cuba.gui.components.Field.Validator validator) { if (tableValidators == null) { tableValidators = new LinkedHashSet<>(); } tableValidators.add(validator); } @Override public void setStyleName(String name) { super.setStyleName(name); for (String internalStyle : internalStyles) { componentComposition.addStyleName(internalStyle); } } @Override public String getStyleName() { String styleName = super.getStyleName(); for (String internalStyle : internalStyles) { styleName = styleName.replace(internalStyle, ""); } return StringUtils.normalizeSpace(styleName); } public void validate() throws ValidationException { if (tableValidators != null) { for (com.haulmont.cuba.gui.components.Field.Validator tableValidator : tableValidators) { tableValidator.validate(getSelected()); } } } @Override public void setStyleProvider(@Nullable Table.StyleProvider<? super E> styleProvider) { if (styleProvider != null) { if (this.styleProviders == null) { this.styleProviders = new LinkedList<>(); } else { this.styleProviders.clear(); } this.styleProviders.add(styleProvider); } else { this.styleProviders = null; } component.refreshCellStyles(); } @Override public void addStyleProvider(StyleProvider<? super E> styleProvider) { if (this.styleProviders == null) { this.styleProviders = new LinkedList<>(); } if (!this.styleProviders.contains(styleProvider)) { this.styleProviders.add(styleProvider); component.refreshCellStyles(); } } @Override public void removeStyleProvider(StyleProvider<? super E> styleProvider) { if (this.styleProviders != null) { if (this.styleProviders.remove(styleProvider)) { component.refreshCellStyles(); } } } @Override public void setIconProvider(IconProvider<? super E> iconProvider) { this.iconProvider = iconProvider; if (iconProvider != null) { setRowHeaderMode(RowHeaderMode.ICON); } else { setRowHeaderMode(RowHeaderMode.NONE); } component.refreshRowCache(); } // For vaadin component extensions @SuppressWarnings("unchecked") protected Resource getItemIcon(Object itemId) { if (iconProvider == null) { return null; } E item = (E) datasource.getItem(itemId); if (item == null) { return null; } String resourceUrl = iconProvider.getItemIcon(item); if (StringUtils.isBlank(resourceUrl)) { return null; } // noinspection ConstantConditions if (!resourceUrl.contains(":")) { resourceUrl = "theme:" + resourceUrl; } return WebComponentsHelper.getResource(resourceUrl); } @Override public int getRowHeaderWidth() { // CAUTION: vaadin considers null as row header property id; return component.getColumnWidth(null); } @Override public void setRowHeaderWidth(int width) { // CAUTION: vaadin considers null as row header property id; component.setColumnWidth(null, width); } @Override public void applySettings(Element element) { if (!isSettingsEnabled()) { return; } if (defaultSettings == null) { // save default view before apply custom defaultSettings = DocumentHelper.createDocument(); defaultSettings.setRootElement(defaultSettings.addElement("presentation")); saveSettings(defaultSettings.getRootElement()); } String textSelection = element.attributeValue("textSelection"); if (StringUtils.isNotEmpty(textSelection)) { component.setTextSelectionEnabled(Boolean.parseBoolean(textSelection)); if (component.getPresentations() != null) { component.getPresentations().updateTextSelection(); } } final Element columnsElem = element.element("columns"); if (columnsElem != null) { boolean refreshWasEnabled = component.disableContentBufferRefreshing(); Collection<String> modelIds = new LinkedList<>(); for (Object column : component.getVisibleColumns()) { modelIds.add(String.valueOf(column)); } Collection<String> loadedIds = new LinkedList<>(); for (Element colElem : Dom4j.elements(columnsElem, "columns")) { loadedIds.add(colElem.attributeValue("id")); } Configuration configuration = AppBeans.get(Configuration.NAME); ClientConfig clientConfig = configuration.getConfig(ClientConfig.class); if (clientConfig.getLoadObsoleteSettingsForTable() || CollectionUtils.isEqualCollection(modelIds, loadedIds)) { applyColumnSettings(element); } component.enableContentBufferRefreshing(refreshWasEnabled); } } protected void applyColumnSettings(Element element) { final Element columnsElem = element.element("columns"); Object[] oldColumns = component.getVisibleColumns(); List<Object> newColumns = new ArrayList<>(); // add columns from saved settings for (Element colElem : Dom4j.elements(columnsElem, "columns")) { for (Object column : oldColumns) { if (column.toString().equals(colElem.attributeValue("id"))) { newColumns.add(column); String width = colElem.attributeValue("width"); if (width != null) { component.setColumnWidth(column, Integer.parseInt(width)); } else { component.setColumnWidth(column, -1); } String visible = colElem.attributeValue("visible"); if (visible != null) { if (component.isColumnCollapsingAllowed()) { // throws exception if not component.setColumnCollapsed(column, !Boolean.parseBoolean(visible)); } } break; } } } // add columns not saved in settings (perhaps new) for (Object column : oldColumns) { if (!newColumns.contains(column)) { newColumns.add(column); } } // if the table contains only one column, always show it if (newColumns.size() == 1) { if (component.isColumnCollapsingAllowed()) { // throws exception if not component.setColumnCollapsed(newColumns.get(0), false); } } component.setVisibleColumns(newColumns.toArray()); if (isSortable()) { //apply sorting String sortProp = columnsElem.attributeValue("sortProperty"); if (!StringUtils.isEmpty(sortProp)) { MetaPropertyPath sortProperty = datasource.getMetaClass().getPropertyPath(sortProp); if (newColumns.contains(sortProperty)) { boolean sortAscending = Boolean.parseBoolean(columnsElem.attributeValue("sortAscending")); component.setSortContainerPropertyId(null); component.setSortAscending(sortAscending); component.setSortContainerPropertyId(sortProperty); } } else { component.setSortContainerPropertyId(null); } } } @Override public boolean saveSettings(Element element) { if (!isSettingsEnabled()) { return false; } if (isUsePresentations()) { element.addAttribute("textSelection", String.valueOf(component.isTextSelectionEnabled())); } Element columnsElem = element.element("columns"); if (columnsElem != null) { element.remove(columnsElem); } columnsElem = element.addElement("columns"); Object[] visibleColumns = component.getVisibleColumns(); for (Object column : visibleColumns) { Element colElem = columnsElem.addElement("columns"); colElem.addAttribute("id", column.toString()); int width = component.getColumnWidth(column); if (width > -1) colElem.addAttribute("width", String.valueOf(width)); Boolean visible = !component.isColumnCollapsed(column); colElem.addAttribute("visible", visible.toString()); } MetaPropertyPath sortProperty = (MetaPropertyPath) component.getSortContainerPropertyId(); if (sortProperty != null) { Boolean sortAscending = component.isSortAscending(); columnsElem.addAttribute("sortProperty", sortProperty.toString()); columnsElem.addAttribute("sortAscending", sortAscending.toString()); } return true; } @Override public boolean isSettingsEnabled() { return settingsEnabled; } @Override public void setSettingsEnabled(boolean settingsEnabled) { this.settingsEnabled = settingsEnabled; } @Override public void setEnterPressAction(Action action) { enterPressAction = action; } @Override public Action getEnterPressAction() { return enterPressAction; } @Override public void setItemClickAction(Action action) { if (itemClickAction != null) { removeAction(itemClickAction); } itemClickAction = action; if (!getActions().contains(action)) { addAction(action); } } @Override public Action getItemClickAction() { return itemClickAction; } public String getCaption() { return getComposition().getCaption(); } public void setCaption(String caption) { getComposition().setCaption(caption); } @Override public void setMultiSelect(boolean multiselect) { super.setMultiSelect(multiselect); } @Override public ButtonsPanel getButtonsPanel() { return buttonsPanel; } @Override public void setButtonsPanel(ButtonsPanel panel) { if (buttonsPanel != null && topPanel != null) { topPanel.removeComponent(buttonsPanel.unwrap(Component.class)); buttonsPanel.setParent(null); } buttonsPanel = panel; if (panel != null) { if (panel.getParent() != null && panel.getParent() != this) { throw new IllegalStateException("Component already has parent"); } if (topPanel == null) { topPanel = createTopPanel(); topPanel.setWidth("100%"); componentComposition.addComponentAsFirst(topPanel); } topPanel.addComponent(panel.unwrap(Component.class)); if (panel instanceof VisibilityChangeNotifier) { ((VisibilityChangeNotifier) panel) .addVisibilityChangeListener(event -> updateCompositionStylesTopPanelVisible()); } panel.setParent(this); } updateCompositionStylesTopPanelVisible(); } protected HorizontalLayout createTopPanel() { HorizontalLayout topPanel = new HorizontalLayout(); topPanel.setStyleName("c-table-top"); return topPanel; } @Override public void addGeneratedColumn(String columnId, ColumnGenerator<? super E> generator) { checkNotNullArgument(columnId, "columnId is null"); checkNotNullArgument(generator, "generator is null for column id '%s'", columnId); MetaPropertyPath targetCol = getDatasource().getMetaClass().getPropertyPath(columnId); Object generatedColumnId = targetCol != null ? targetCol : columnId; Column column = getColumn(columnId); Column associatedRuntimeColumn = null; if (column == null) { Column newColumn = new Column(generatedColumnId); columns.put(newColumn.getId(), newColumn); columnsOrder.add(newColumn); associatedRuntimeColumn = newColumn; newColumn.setOwner(this); } // save column order Object[] visibleColumns = component.getVisibleColumns(); boolean removeOldGeneratedColumn = component.getColumnGenerator(generatedColumnId) != null; // replace generator for column if exist if (removeOldGeneratedColumn) { component.removeGeneratedColumn(generatedColumnId); } component.addGeneratedColumn(generatedColumnId, new CustomColumnGenerator(generator, associatedRuntimeColumn) { @SuppressWarnings("unchecked") @Override public Object generateCell(com.vaadin.ui.Table source, Object itemId, Object columnId) { Entity entity = getDatasource().getItem(itemId); com.haulmont.cuba.gui.components.Component component = getColumnGenerator() .generateCell(entity); if (component == null) { return null; } if (component instanceof PlainTextCell) { return ((PlainTextCell) component).getText(); } if (component instanceof BelongToFrame) { BelongToFrame belongToFrame = (BelongToFrame) component; if (belongToFrame.getFrame() == null) { belongToFrame.setFrame(getFrame()); } } component.setParent(WebAbstractTable.this); com.vaadin.ui.Component vComponent = component.unwrapComposition(Component.class); // wrap field for show required asterisk if ((vComponent instanceof com.vaadin.ui.Field) && (((com.vaadin.ui.Field) vComponent).isRequired())) { VerticalLayout layout = new VerticalLayout(); layout.addComponent(vComponent); if (vComponent.getWidth() < 0) { layout.setWidthUndefined(); } layout.addComponent(vComponent); vComponent = layout; } return vComponent; } }); if (removeOldGeneratedColumn) { // restore column order component.setVisibleColumns(visibleColumns); } } @Override public void addGeneratedColumn(String columnId, ColumnGenerator<? super E> generator, Class<? extends com.haulmont.cuba.gui.components.Component> componentClass) { // web ui doesn't make any improvements with componentClass known addGeneratedColumn(columnId, generator); } @Override public void removeGeneratedColumn(String columnId) { MetaPropertyPath targetCol = getDatasource().getMetaClass().getPropertyPath(columnId); removeGeneratedColumn(targetCol == null ? columnId : targetCol); } @Override public void addAggregationProperty(String columnId, AggregationInfo.Type type) { addAggregationProperty(getColumn(columnId), type); } @Override public void addAggregationProperty(Column column, AggregationInfo.Type type) { checkAggregation(column.getAggregation()); component.addContainerPropertyAggregation(column.getId(), WebComponentsHelper.convertAggregationType(type)); if (column.getAggregation() != null) { addAggregationCell(column); } } @Override public void removeAggregationProperty(String columnId) { component.removeContainerPropertyAggregation(getColumn(columnId).getId()); removeAggregationCell(getColumn(columnId)); } @Override public void setColumnCaption(String columnId, String caption) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnCaption(column, caption); } @Override public void setColumnCaption(Column column, String caption) { checkNotNullArgument(column, "column must be non null"); if (!Objects.equals(column.getCaption(), caption)) { column.setCaption(caption); } component.setColumnHeader(column.getId(), caption); } @Override public void setColumnDescription(String columnId, String description) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnDescription(column, description); } @Override public void setColumnDescription(Column column, String description) { checkNotNullArgument(column, "column must be non null"); if (!Objects.equals(column.getDescription(), description)) { column.setDescription(description); } component.setColumnDescription(column.getId(), description); } @Override public void setTextSelectionEnabled(boolean value) { component.setTextSelectionEnabled(value); } @Override public boolean isTextSelectionEnabled() { return component.isTextSelectionEnabled(); } @Override public void setColumnSortable(String columnId, boolean sortable) { Column column = getColumn(columnId); setColumnSortable(column, sortable); } @Override public boolean getColumnSortable(String columnId) { Column column = getColumn(columnId); return getColumnSortable(column); } @Override public void setColumnSortable(Column column, boolean sortable) { checkNotNullArgument(column, "column must be non null"); if (column.isSortable() != sortable) { column.setSortable(sortable); } component.setColumnSortable(column.getId(), sortable); } @Override public boolean getColumnSortable(Column column) { checkNotNullArgument(column, "column must be non null"); return component.getColumnSortable(column.getId()); } @Override public void setColumnCollapsed(String columnId, boolean collapsed) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnCollapsed(column, collapsed); } @Override public void setColumnCollapsed(Column column, boolean collapsed) { if (!getColumnControlVisible()) { return; } checkNotNullArgument(column, "column must be non null"); if (column.isCollapsed() != collapsed) { column.setCollapsed(collapsed); } component.setColumnCollapsed(column.getId(), collapsed); } @Override public void setColumnAlignment(Column column, ColumnAlignment alignment) { checkNotNullArgument(column, "column must be non null"); if (column.getAlignment() != alignment) { column.setAlignment(alignment); } component.setColumnAlignment(column.getId(), WebComponentsHelper.convertColumnAlignment(alignment)); } @Override public void setColumnAlignment(String columnId, ColumnAlignment alignment) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnAlignment(column, alignment); } @Override public void setColumnWidth(Column column, int width) { checkNotNullArgument(column, "column must be non null"); if (column.getWidth() == null || column.getWidth() != width) { column.setWidth(width); } component.setColumnWidth(column.getId(), width); } @Override public void setColumnWidth(String columnId, int width) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnWidth(column, width); } @Override public void refresh() { datasource.refresh(); } /** * {@inheritDoc} */ @Override public void repaint() { component.markAsDirtyRecursive(); } @Override public void selectAll() { if (isMultiSelect()) { component.setValue(component.getItemIds()); } } protected void checkAggregation(AggregationInfo aggregationInfo) { MetaPropertyPath propertyPath = aggregationInfo.getPropertyPath(); Class<?> javaType = propertyPath.getMetaProperty().getJavaType(); Aggregation<?> aggregation = Aggregations.get(javaType); AggregationInfo.Type aggregationType = aggregationInfo.getType(); if (aggregationType == AggregationInfo.Type.CUSTOM) return; if (aggregation != null && aggregation.getSupportedAggregationTypes().contains(aggregationType)) return; String msg = String.format( "Unable to aggregate column \"%s\" with data type %s with default aggregation strategy: %s", propertyPath, propertyPath.getRange(), aggregationInfo.getType()); throw new IllegalArgumentException(msg); } protected Map<Object, Object> __aggregate(AggregationContainer container, AggregationContainer.Context context) { final List<AggregationInfo> aggregationInfos = new LinkedList<>(); for (final Object propertyId : container.getAggregationPropertyIds()) { final Table.Column column = columns.get(propertyId); AggregationInfo aggregation = column.getAggregation(); if (aggregation != null) { checkAggregation(aggregation); aggregationInfos.add(aggregation); } } @SuppressWarnings("unchecked") Map<AggregationInfo, Object> results = ((CollectionDatasource.Aggregatable) datasource).aggregate( aggregationInfos.toArray(new AggregationInfo[aggregationInfos.size()]), context.getItemIds()); Map<Object, Object> resultsByColumns = new LinkedHashMap<>(); for (final Object propertyId : container.getAggregationPropertyIds()) { final Table.Column column = columns.get(propertyId); if (column.getAggregation() != null) { resultsByColumns.put(column.getId(), results.get(column.getAggregation())); } } if (aggregationCells != null) { resultsByColumns = __handleAggregationResults(context, resultsByColumns); } return resultsByColumns; } protected Map<Object, Object> __handleAggregationResults(AggregationContainer.Context context, Map<Object, Object> results) { for (final Map.Entry<Object, Object> entry : results.entrySet()) { final Table.Column column = columns.get(entry.getKey()); if (aggregationCells.get(column) != null) { Object value = entry.getValue(); String cellText = getFormattedValue(column, value); entry.setValue(cellText); } } return results; } protected String getFormattedValue(Column column, Object value) { String cellText; if (value == null) { cellText = ""; } else { if (value instanceof String) { cellText = (String) value; } else { Formatter formatter = column.getFormatter(); if (formatter != null) { //noinspection unchecked cellText = formatter.format(value); } else { Datatype datatype = Datatypes.get(value.getClass()); if (datatype != null) { UserSessionSource sessionSource = AppBeans.get(UserSessionSource.NAME); cellText = datatype.format(value, sessionSource.getLocale()); } else { cellText = value.toString(); } } } } return cellText; } protected class TablePropertyWrapper extends PropertyWrapper { protected ValueChangeListener calcListener; public TablePropertyWrapper(Object item, MetaPropertyPath propertyPath) { super(item, propertyPath); } @Override public void addListener(ValueChangeListener listener) { super.addListener(listener); //A listener of a calculatable property must be only one if (listener instanceof CalculatablePropertyValueChangeListener) { if (this.calcListener != null) { removeListener(calcListener); } calcListener = listener; } } @Override public void removeListener(ValueChangeListener listener) { super.removeListener(listener); if (calcListener == listener) { calcListener = null; } } @Override public boolean isReadOnly() { final Table.Column column = WebAbstractTable.this.columns.get(propertyPath); if (column != null) { return !editable || !(BooleanUtils.isTrue(column.isEditable()) || BooleanUtils.isTrue(column.isCalculatable())); } else { return super.isReadOnly(); } } @SuppressWarnings("unchecked") @Override public String getFormattedValue() { final Table.Column column = WebAbstractTable.this.columns.get(propertyPath); if (column != null) { if (column.getFormatter() != null) { return column.getFormatter().format(getValue()); } else if (column.getXmlDescriptor() != null) { String captionProperty = column.getXmlDescriptor().attributeValue("captionProperty"); if (StringUtils.isNotEmpty(captionProperty)) { final Instance item = getInstance(); final Object captionValue = item.getValueEx(captionProperty); return captionValue != null ? String.valueOf(captionValue) : null; } } } return super.getFormattedValue(); } @Override public Object getValue() { Instance instance = getInstance(); if (instance == null) { return null; } if (ignoreUnfetchedAttributes) { return getValueExIgnoreUnfetched(instance, propertyPath.getPath()); } return super.getValue(); } protected Object getValueExIgnoreUnfetched(Instance instance, String[] properties) { Object currentValue = null; Instance currentInstance = instance; for (String property : properties) { if (currentInstance == null) { break; } if (!PersistenceHelper.isLoaded(currentInstance, property)) { LoggerFactory.getLogger(WebAbstractTable.class).warn( "Ignored unfetched attribute {} of instance {} in Table cell", property, currentInstance); return null; } currentValue = currentInstance.getValue(property); if (currentValue == null) { break; } currentInstance = currentValue instanceof Instance ? (Instance) currentValue : null; } return currentValue; } } protected interface SystemTableColumnGenerator extends com.vaadin.ui.Table.ColumnGenerator { } protected static abstract class CustomColumnGenerator implements com.vaadin.ui.Table.ColumnGenerator { protected ColumnGenerator columnGenerator; // Used for properly removing column from table protected Column associatedRuntimeColumn; protected CustomColumnGenerator(ColumnGenerator columnGenerator, @Nullable Column associatedRuntimeColumn) { this.columnGenerator = columnGenerator; this.associatedRuntimeColumn = associatedRuntimeColumn; } public Column getAssociatedRuntimeColumn() { return associatedRuntimeColumn; } public ColumnGenerator getColumnGenerator() { return columnGenerator; } } protected class LinkCellClickListener implements CellClickListener { @Override public void onClick(final Entity rowItem, final String columnId) { Column column = getColumn(columnId); if (column.getXmlDescriptor() != null) { String invokeMethodName = column.getXmlDescriptor().attributeValue("linkInvoke"); if (StringUtils.isNotEmpty(invokeMethodName)) { callControllerInvoke(rowItem, columnId, invokeMethodName); return; } } Entity entity; Object value = rowItem.getValueEx(columnId); if (value instanceof Entity) { entity = (Entity) value; } else { entity = rowItem; } WindowManager wm; Window window = ComponentsHelper.getWindow(WebAbstractTable.this); if (window == null) { throw new IllegalStateException("Please specify Frame for Table"); } else { wm = window.getWindowManager(); } if (entity instanceof SoftDelete && ((SoftDelete) entity).isDeleted()) { Messages messages = AppBeans.get(Messages.NAME); wm.showNotification(messages.getMainMessage("OpenAction.objectIsDeleted"), Frame.NotificationType.HUMANIZED); return; } DataSupplier dataSupplier = window.getDsContext().getDataSupplier(); entity = dataSupplier.reload(entity, View.MINIMAL); WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME); String windowAlias = null; if (column.getXmlDescriptor() != null) { windowAlias = column.getXmlDescriptor().attributeValue("linkScreen"); } if (StringUtils.isEmpty(windowAlias)) { windowAlias = windowConfig.getEditorScreenId(entity.getMetaClass()); } OpenType screenOpenType = OpenType.THIS_TAB; if (column.getXmlDescriptor() != null) { String openTypeAttribute = column.getXmlDescriptor().attributeValue("linkScreenOpenType"); if (StringUtils.isNotEmpty(openTypeAttribute)) { screenOpenType = OpenType.valueOf(openTypeAttribute); } } final Window.Editor editor = wm.openEditor(windowConfig.getWindowInfo(windowAlias), entity, screenOpenType); editor.addCloseListener(actionId -> { // move focus to component requestFocus(); if (Window.COMMIT_ACTION_ID.equals(actionId)) { Entity editorItem = editor.getItem(); handleEditorCommit(editorItem, rowItem, columnId); } }); } protected void handleEditorCommit(Entity editorItem, Entity rowItem, String columnId) { MetaPropertyPath mpp = rowItem.getMetaClass().getPropertyPath(columnId); if (mpp == null) { throw new IllegalStateException(String.format("Unable to find metaproperty %s for class %s", columnId, rowItem.getMetaClass())); } if (mpp.getRange().isClass()) { DatasourceImplementation ds = ((DatasourceImplementation) getDatasource()); boolean modifiedInTable = ds.getItemsToUpdate().contains(rowItem); boolean ownerDsModified = ds.isModified(); rowItem.setValueEx(columnId, null); rowItem.setValueEx(columnId, editorItem); // restore modified for owner datasource // remove from items to update if it was not modified before setValue if (!modifiedInTable) { ds.getItemsToUpdate().remove(rowItem); } ds.setModified(ownerDsModified); } else { //noinspection unchecked getDatasource().updateItem(editorItem); } } protected void callControllerInvoke(Entity rowItem, String columnId, String invokeMethodName) { Object controller = ComponentsHelper.getFrameController(frame); Method method; method = findLinkInvokeMethod(controller.getClass(), invokeMethodName); if (method != null) { try { method.invoke(controller, rowItem, columnId); } catch (Exception e) { throw new RuntimeException("Unable to cal linkInvoke method for table column", e); } } else { try { method = controller.getClass().getMethod(invokeMethodName); try { method.invoke(controller); } catch (Exception e1) { throw new RuntimeException("Unable to call linkInvoke method for table column", e1); } } catch (NoSuchMethodException e1) { throw new IllegalStateException( "No suitable methods named " + invokeMethodName + " for invoke"); } } } protected Method findLinkInvokeMethod(Class cls, String methodName) { Method exactMethod = MethodUtils.getAccessibleMethod(cls, methodName, new Class[] { Entity.class, String.class }); if (exactMethod != null) { return exactMethod; } // search through all methods Method[] methods = cls.getMethods(); for (Method availableMethod : methods) { if (availableMethod.getName().equals(methodName)) { if (availableMethod.getParameterCount() == 2 && Void.TYPE.equals(availableMethod.getReturnType())) { if (Entity.class.isAssignableFrom(availableMethod.getParameterTypes()[0]) && String.class == availableMethod.getParameterTypes()[1]) { // get accessible version of method return MethodUtils.getAccessibleMethod(availableMethod); } } } } return null; } } protected static class AbbreviatedColumnGenerator implements SystemTableColumnGenerator, CubaEnhancedTable.PlainTextGeneratedColumn { protected Table.Column column; public AbbreviatedColumnGenerator(Table.Column column) { this.column = column; } @Override public Object generateCell(com.vaadin.ui.Table source, Object itemId, Object columnId) { final Property property = source.getItem(itemId).getItemProperty(columnId); final Object value = property.getValue(); if (value == null) { return null; } String stringValue = value.toString(); if (columnId instanceof MetaPropertyPath) { MetaProperty metaProperty = ((MetaPropertyPath) columnId).getMetaProperty(); if (DynamicAttributesUtils.isDynamicAttribute(metaProperty)) stringValue = DynamicAttributesUtils.getDynamicAttributeValueAsString(metaProperty, value); } String cellValue = stringValue; boolean isMultiLineCell = StringUtils.contains(stringValue, "\n"); if (isMultiLineCell) { cellValue = StringUtils.replace(cellValue, "\n", " "); } int maxTextLength = column.getMaxTextLength(); if (stringValue.length() > maxTextLength + MAX_TEXT_LENGTH_GAP || isMultiLineCell) { return StringUtils.abbreviate(cellValue, maxTextLength); } else { return cellValue; } } } protected class AbbreviatedCellClickListener implements CellClickListener { @Override public void onClick(Entity item, String columnId) { Column column = getColumn(columnId); MetaProperty metaProperty; String value; if (DynamicAttributesUtils.isDynamicAttribute(columnId)) { metaProperty = DynamicAttributesUtils.getMetaPropertyPath(item.getMetaClass(), columnId) .getMetaProperty(); value = DynamicAttributesUtils.getDynamicAttributeValueAsString(metaProperty, item.getValueEx(columnId)); } else { value = item.getValueEx(columnId); } if (column.getMaxTextLength() != null) { boolean isMultiLineCell = StringUtils.contains(value, "\n"); if (value == null || (value.length() <= column.getMaxTextLength() + MAX_TEXT_LENGTH_GAP && !isMultiLineCell)) { // todo artamonov if we click with CTRL and Table is multiselect then we lose previous selected items if (!getSelected().contains(item)) { setSelected((E) item); } // do not show popup view return; } } VerticalLayout layout = new VerticalLayout(); layout.setWidthUndefined(); layout.setStyleName("c-table-view-textcut"); CubaTextArea textArea = new CubaTextArea(); textArea.setValue(value); textArea.setReadOnly(true); CubaResizableTextAreaWrapper content = new CubaResizableTextAreaWrapper(textArea); content.setResizableDirection(ResizeDirection.BOTH); ThemeConstants theme = App.getInstance().getThemeConstants(); if (theme != null) { content.setWidth(theme.get("cuba.web.Table.abbreviatedPopupWidth")); content.setHeight(theme.get("cuba.web.Table.abbreviatedPopupHeight")); } else { content.setWidth("320px"); content.setHeight("200px"); } layout.addComponent(content); component.showCustomPopup(layout); component.setCustomPopupAutoClose(false); } } protected class CalculatableColumnGenerator implements SystemTableColumnGenerator { @Override public com.vaadin.ui.Component generateCell(com.vaadin.ui.Table source, Object itemId, Object columnId) { return generateCell((AbstractSelect) source, itemId, columnId); } protected com.vaadin.ui.Component generateCell(AbstractSelect source, Object itemId, Object columnId) { CollectionDatasource ds = WebAbstractTable.this.getDatasource(); MetaPropertyPath propertyPath = ds.getMetaClass().getPropertyPath(columnId.toString()); PropertyWrapper propertyWrapper = (PropertyWrapper) source.getContainerProperty(itemId, propertyPath); com.haulmont.cuba.gui.components.Formatter formatter = null; Table.Column column = WebAbstractTable.this.getColumn(columnId.toString()); if (column != null) { formatter = column.getFormatter(); } final com.vaadin.ui.Label label = new com.vaadin.ui.Label(); WebComponentsHelper.setLabelText(label, propertyWrapper.getValue(), formatter); label.setWidth("-1px"); //add property change listener that will update a label value propertyWrapper.addListener(new CalculatablePropertyValueChangeListener(label, formatter)); return label; } } protected static class CalculatablePropertyValueChangeListener implements Property.ValueChangeListener { private Label component; private com.haulmont.cuba.gui.components.Formatter formatter; private static final long serialVersionUID = 8041384664735759397L; private CalculatablePropertyValueChangeListener(Label component, com.haulmont.cuba.gui.components.Formatter formatter) { this.component = component; this.formatter = formatter; } @Override public void valueChange(Property.ValueChangeEvent event) { WebComponentsHelper.setLabelText(component, event.getProperty().getValue(), formatter); } } protected void removeAggregationCell(Table.Column column) { if (aggregationCells != null) { aggregationCells.remove(column); } } protected void addAggregationCell(Table.Column column) { if (aggregationCells == null) { aggregationCells = new HashMap<>(); } aggregationCells.put(column, ""); } protected static class WebTableFieldFactory extends AbstractFieldFactory implements TableFieldFactory { protected WebAbstractTable<?, ?> webTable; public WebTableFieldFactory(WebAbstractTable<?, ?> webTable) { this.webTable = webTable; } @Override public com.vaadin.ui.Field<?> createField(com.vaadin.data.Container container, Object itemId, Object propertyId, Component uiContext) { String fieldPropertyId = String.valueOf(propertyId); Column columnConf = webTable.columns.get(propertyId); Item item = container.getItem(itemId); Entity entity = ((ItemWrapper) item).getItem(); Datasource fieldDatasource = webTable.getItemDatasource(entity); com.haulmont.cuba.gui.components.Component columnComponent = createField(fieldDatasource, fieldPropertyId, columnConf.getXmlDescriptor()); if (columnComponent instanceof Field) { Field cubaField = (Field) columnComponent; if (webTable.requiredColumns != null && webTable.requiredColumns.containsKey(columnConf)) { cubaField.setRequired(true); cubaField.setRequiredMessage(webTable.requiredColumns.get(columnConf)); } } if (!(columnComponent instanceof CheckBox)) { columnComponent.setWidthFull(); } if (columnComponent instanceof BelongToFrame) { BelongToFrame belongToFrame = (BelongToFrame) columnComponent; if (belongToFrame.getFrame() == null) { belongToFrame.setFrame(webTable.getFrame()); } } applyPermissions(columnComponent); columnComponent.setParent(webTable); Component componentImpl = getComponentImplementation(columnComponent); if (componentImpl instanceof com.vaadin.ui.Field) { return (com.vaadin.ui.Field<?>) componentImpl; } return new EditableColumnFieldWrapper(componentImpl, columnComponent); } protected Component getComponentImplementation(com.haulmont.cuba.gui.components.Component columnComponent) { com.vaadin.ui.Component composition = WebComponentsHelper.getComposition(columnComponent); Component componentImpl = composition; if (composition instanceof com.vaadin.ui.Field && ((com.vaadin.ui.Field) composition).isRequired()) { VerticalLayout layout = new VerticalLayout(); layout.addComponent(composition); if (composition.getWidth() < 0) { layout.setWidthUndefined(); } componentImpl = layout; } return componentImpl; } protected void applyPermissions(com.haulmont.cuba.gui.components.Component columnComponent) { if (columnComponent instanceof DatasourceComponent) { DatasourceComponent dsComponent = (DatasourceComponent) columnComponent; MetaPropertyPath propertyPath = dsComponent.getMetaPropertyPath(); if (propertyPath != null) { MetaClass metaClass = dsComponent.getDatasource().getMetaClass(); dsComponent.setEditable(dsComponent.isEditable() && webTable.security.isEntityAttrUpdatePermitted(metaClass, propertyPath.toString())); } } } @Override @Nullable protected CollectionDatasource getOptionsDatasource(Datasource fieldDatasource, String propertyId) { if (webTable.datasource == null) { throw new IllegalStateException("Table datasource is null"); } MetaClass metaClass = webTable.datasource.getMetaClass(); MetaPropertyPath metaPropertyPath = webTable.metadataTools.resolveMetaPropertyPath(metaClass, propertyId); Column columnConf = webTable.columns.get(metaPropertyPath); DsContext dsContext = webTable.datasource.getDsContext(); String optDsName = columnConf.getXmlDescriptor() != null ? columnConf.getXmlDescriptor().attributeValue("optionsDatasource") : ""; if (StringUtils.isBlank(optDsName)) { return null; } else { CollectionDatasource ds = (CollectionDatasource) dsContext.get(optDsName); if (ds == null) { throw new IllegalStateException(String.format( "Options datasource for table column '%s' not found: %s", propertyId, optDsName)); } return ds; } } } protected boolean handleSpecificVariables(Map<String, Object> variables) { boolean needReload = false; if (isUsePresentations() && presentations != null) { final Presentations p = getPresentations(); if (p.getCurrent() != null && p.isAutoSave(p.getCurrent()) && needUpdatePresentation(variables)) { Element e = p.getSettings(p.getCurrent()); saveSettings(e); p.setSettings(p.getCurrent(), e); } } //noinspection ConstantConditions return needReload; } protected boolean needUpdatePresentation(Map<String, Object> variables) { return variables.containsKey("colwidth") || variables.containsKey("sortcolumn") || variables.containsKey("sortascending") || variables.containsKey("columnorder") || variables.containsKey("collapsedcolumns") || variables.containsKey("groupedcolumns"); } @Override public List<Table.Column> getNotCollapsedColumns() { if (component.getVisibleColumns() == null) return Collections.emptyList(); final List<Table.Column> visibleColumns = new ArrayList<>(component.getVisibleColumns().length); Object[] keys = component.getVisibleColumns(); for (final Object key : keys) { if (!component.isColumnCollapsed(key)) { Column column = columns.get(key); if (column != null) visibleColumns.add(column); } } return visibleColumns; } @Override public void usePresentations(boolean use) { usePresentations = use; } @Override public boolean isUsePresentations() { return usePresentations; } @Override public void resetPresentation() { if (defaultSettings != null) { applySettings(defaultSettings.getRootElement()); if (presentations != null) { presentations.setCurrent(null); } } } @Override public void loadPresentations() { if (isUsePresentations()) { presentations = new PresentationsImpl(this); setTablePresentations(new TablePresentations(this)); } else { throw new UnsupportedOperationException("Component doesn't use presentations"); } } @Override public Presentations getPresentations() { if (isUsePresentations()) { return presentations; } else { throw new UnsupportedOperationException("Component doesn't use presentations"); } } @Override public void applyPresentation(Object id) { if (isUsePresentations() && presentations != null) { Presentation p = presentations.getPresentation(id); applyPresentation(p); } else { throw new UnsupportedOperationException("Component doesn't use presentations"); } } @Override public void applyPresentationAsDefault(Object id) { if (isUsePresentations() && presentations != null) { Presentation p = presentations.getPresentation(id); if (p != null) { presentations.setDefault(p); applyPresentation(p); } } else { throw new UnsupportedOperationException("Component doesn't use presentations"); } } protected void applyPresentation(Presentation p) { if (presentations != null) { Element settingsElement = presentations.getSettings(p); applySettings(settingsElement); presentations.setCurrent(p); component.markAsDirty(); } } @Override public Object getDefaultPresentationId() { if (presentations == null) { return null; } Presentation def = presentations.getDefault(); return def == null ? null : def.getId(); } @Override public void addColumnCollapsedListener(ColumnCollapseListener columnCollapsedListener) { if (columnCollapseListeners == null) { columnCollapseListeners = new LinkedList<>(); component.addColumnCollapseListener((com.vaadin.ui.Table.ColumnCollapseListener) event -> { Column collapsedColumn = getColumn(event.getPropertyId().toString()); boolean collapsed = component.isColumnCollapsed(event.getPropertyId()); for (ColumnCollapseListener listener : columnCollapseListeners) { listener.columnCollapsed(collapsedColumn, collapsed); } }); } columnCollapseListeners.add(columnCollapsedListener); } @Override public void removeColumnCollapseListener(ColumnCollapseListener columnCollapseListener) { if (columnCollapseListeners != null) { columnCollapseListeners.remove(columnCollapseListener); } } @Override public void setClickListener(String columnId, final CellClickListener clickListener) { component.setClickListener(getColumn(columnId).getId(), (itemId, columnId1) -> { ItemWrapper wrapper = (ItemWrapper) component.getItem(itemId); Entity entity = wrapper.getItem(); clickListener.onClick(entity, columnId1.toString()); }); } @Override public void removeClickListener(String columnId) { component.removeClickListener(getColumn(columnId).getId()); } @Override public void showCustomPopup(com.haulmont.cuba.gui.components.Component popupComponent) { Component vComponent = WebComponentsHelper.unwrap(popupComponent); component.showCustomPopup(vComponent); component.setCustomPopupAutoClose(false); } @Override public void showCustomPopupActions(List<Action> actions) { VerticalLayout customContextMenu = new VerticalLayout(); customContextMenu.setWidthUndefined(); customContextMenu.setStyleName("c-cm-container"); for (Action action : actions) { ContextMenuButton contextMenuButton = createContextMenuButton(); contextMenuButton.setStyleName("c-cm-button"); contextMenuButton.setAction(action); Component vButton = WebComponentsHelper.unwrap(contextMenuButton); customContextMenu.addComponent(vButton); } if (customContextMenu.getComponentCount() > 0) { component.showCustomPopup(customContextMenu); component.setCustomPopupAutoClose(true); } } @Override public void setColumnHeaderVisible(boolean visible) { component.setColumnHeaderMode(visible ? com.vaadin.ui.Table.ColumnHeaderMode.EXPLICIT_DEFAULTS_ID : com.vaadin.ui.Table.ColumnHeaderMode.HIDDEN); } @Override public boolean isColumnHeaderVisible() { if (component.getColumnHeaderMode() == com.vaadin.ui.Table.ColumnHeaderMode.HIDDEN) { return false; } else { return true; } } @Override public void setShowSelection(boolean showSelection) { component.setSelectable(showSelection); } @Override public boolean isShowSelection() { return component.isSelectable(); } protected String generateCellStyle(Object itemId, Object propertyId) { String style = null; if (propertyId != null && itemId != null && !component.isColumnEditable(propertyId) && (component.getColumnGenerator(propertyId) == null || component .getColumnGenerator(propertyId) instanceof WebAbstractTable.AbbreviatedColumnGenerator)) { MetaPropertyPath propertyPath; if (propertyId instanceof MetaPropertyPath) { propertyPath = (MetaPropertyPath) propertyId; } else { propertyPath = datasource.getMetaClass().getPropertyPath(propertyId.toString()); } if (propertyPath != null) { style = generateDefaultCellStyle(itemId, propertyId, propertyPath); } } if (styleProviders != null) { String generatedStyle = getGeneratedCellStyle(itemId, propertyId); // we use style names without v-table-cell-content prefix, so we add cs prefix // all cells with custom styles will have v-table-cell-content-cs style name in class if (style != null) { if (generatedStyle != null) { style = CUSTOM_STYLE_NAME_PREFIX + generatedStyle + " " + style; } } else if (generatedStyle != null) { style = CUSTOM_STYLE_NAME_PREFIX + generatedStyle; } } return style == null ? null : (CUSTOM_STYLE_NAME_PREFIX + style); } protected String generateDefaultCellStyle(Object itemId, Object propertyId, MetaPropertyPath propertyPath) { String style = null; Column column = getColumn(propertyId.toString()); if (column != null) { final String isLink = column.getXmlDescriptor() == null ? null : column.getXmlDescriptor().attributeValue("link"); if (propertyPath.getRange().isClass()) { if (StringUtils.isNotEmpty(isLink) && Boolean.valueOf(isLink)) { style = "c-table-cell-link"; } } else if (propertyPath.getRange().isDatatype()) { if (StringUtils.isNotEmpty(isLink) && Boolean.valueOf(isLink)) { style = "c-table-cell-link"; } else if (column.getMaxTextLength() != null) { Entity item = getDatasource().getItemNN(itemId); Object value = item.getValueEx(propertyId.toString()); String stringValue; if (value instanceof String) { stringValue = item.getValueEx(propertyId.toString()); } else { if (DynamicAttributesUtils.isDynamicAttribute(propertyPath.getMetaProperty())) { stringValue = DynamicAttributesUtils .getDynamicAttributeValueAsString(propertyPath.getMetaProperty(), value); } else { stringValue = value == null ? null : value.toString(); } } if (column.getMaxTextLength() != null) { boolean isMultiLineCell = StringUtils.contains(stringValue, "\n"); if ((stringValue != null && stringValue.length() > column.getMaxTextLength() + MAX_TEXT_LENGTH_GAP) || isMultiLineCell) { style = "c-table-cell-textcut"; } else { // use special marker stylename style = "c-table-clickable-text"; } } } } } if (propertyPath.getRangeJavaClass() == Boolean.class) { Entity item = datasource.getItem(itemId); if (item != null) { Boolean value = item.getValueEx(propertyId.toString()); if (BooleanUtils.isTrue(value)) { style = "boolean-cell boolean-cell-true"; } else { style = "boolean-cell boolean-cell-false"; } } } return style; } protected String getStringPropertyValue(Entity entity, MetaProperty metaProperty) { Object value = entity.getValueEx(metaProperty.getName()); if (value instanceof String) { return (String) value; } else { if (DynamicAttributesUtils.isDynamicAttribute(metaProperty)) { return DynamicAttributesUtils.getDynamicAttributeValueAsString(metaProperty, value); } else { return value.toString(); } } } protected class StyleGeneratorAdapter implements com.vaadin.ui.Table.CellStyleGenerator { protected boolean exceptionHandled = false; @SuppressWarnings({ "unchecked" }) @Override public String getStyle(com.vaadin.ui.Table source, Object itemId, Object propertyId) { if (exceptionHandled) { return null; } try { return generateCellStyle(itemId, propertyId); } catch (Exception e) { LoggerFactory.getLogger(WebAbstractTable.class).error("Uncautch exception in Table StyleProvider", e); this.exceptionHandled = true; return null; } } public void resetExceptionHandledFlag() { this.exceptionHandled = false; } } protected static class EditableColumnFieldWrapper extends CustomField { protected Component component; public EditableColumnFieldWrapper(Component component, com.haulmont.cuba.gui.components.Component columnComponent) { this.component = component; if (component.getWidth() < 0) { setWidthUndefined(); } if (columnComponent instanceof Field) { AbstractComponent vComponent = columnComponent.unwrap(AbstractComponent.class); if (vComponent instanceof Focusable) { setFocusDelegate((Focusable) vComponent); } } } @Override public Class getType() { return Object.class; } @Override protected Component initContent() { return component; } @Override public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (component != null) { if (width < 0) { component.setWidth(com.haulmont.cuba.gui.components.Component.AUTO_SIZE); } else { component.setWidth("100%"); } } } @Override public void setHeight(float height, Unit unit) { super.setHeight(height, unit); if (component != null) { if (height < 0) { component.setHeight(com.haulmont.cuba.gui.components.Component.AUTO_SIZE); } else { component.setHeight("100%"); } } } } @Override public void requestFocus(E item, String columnId) { Preconditions.checkNotNullArgument(item); Preconditions.checkNotNullArgument(columnId); component.requestFocus(item.getId(), getColumn(columnId).getId()); } @Override public void scrollTo(E item) { Preconditions.checkNotNullArgument(item); if (!component.getItemIds().contains(item.getId())) { throw new IllegalArgumentException("Unable to find item in Table"); } component.setCurrentPageFirstItemId(item.getId()); } @Override public String getDescription() { return component.getDescription(); } @Override public void setDescription(String description) { component.setDescription(description); } @Override public void addLookupValueChangeListener(LookupSelectionChangeListener listener) { getEventRouter().addListener(LookupSelectionChangeListener.class, listener); } @Override public void removeLookupValueChangeListener(LookupSelectionChangeListener listener) { getEventRouter().removeListener(LookupSelectionChangeListener.class, listener); } protected void handleColumnCollapsed(com.vaadin.ui.Table.ColumnCollapseEvent event) { Object propertyId = event.getPropertyId(); boolean columnCollapsed = component.isColumnCollapsed(propertyId); columns.get(propertyId).setCollapsed(columnCollapsed); } protected static class TableComposition extends CssLayout { protected com.vaadin.ui.Table table; public com.vaadin.ui.Table getTable() { return table; } public void setTable(com.vaadin.ui.Table table) { this.table = table; } @Override public void setHeight(float height, Unit unit) { super.setHeight(height, unit); if (getHeight() < 0) { table.setHeightUndefined(); } else { table.setHeight(100, Unit.PERCENTAGE); } } @Override public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (getWidth() < 0) { table.setWidthUndefined(); } else { table.setWidth(100, Unit.PERCENTAGE); } } } public class TableCollectionDsListenersWrapper extends CollectionDsListenersWrapper { @SuppressWarnings("unchecked") @Override public void collectionChanged(CollectionDatasource.CollectionChangeEvent e) { // replacement for collectionChangeListener if (fieldDatasources != null) { switch (e.getOperation()) { case CLEAR: case REFRESH: fieldDatasources.clear(); break; case UPDATE: case REMOVE: for (Object entity : e.getItems()) { fieldDatasources.remove(entity); } break; } } // replacement for collectionChangeSelectionListener // #PL-2035, reload selection from ds Set<Object> selectedItemIds = getSelectedItemIds(); if (selectedItemIds == null) { selectedItemIds = Collections.emptySet(); } Set<Object> newSelection = new HashSet<>(); for (Object entityId : selectedItemIds) { //noinspection unchecked if (e.getDs().containsItem(entityId)) { newSelection.add(entityId); } } if (e.getDs().getState() == Datasource.State.VALID && e.getDs().getItem() != null) { newSelection.add(e.getDs().getItem().getId()); } if (newSelection.isEmpty()) { setSelected((E) null); } else { setSelectedIds(newSelection); } super.collectionChanged(e); } @SuppressWarnings("unchecked") @Override public void itemChanged(Datasource.ItemChangeEvent e) { for (Action action : getActions()) { action.refreshState(); } super.itemChanged(e); } @SuppressWarnings("unchecked") @Override public void itemPropertyChanged(Datasource.ItemPropertyChangeEvent e) { // replacement for aggregationDatasourceListener handleAggregation(); for (Action action : getActions()) { action.refreshState(); } super.itemPropertyChanged(e); } protected void handleAggregation() { if (isAggregatable() && aggregationCells != null) { final CollectionDatasource ds = WebAbstractTable.this.getDatasource(); component.aggregate(new AggregationContainer.Context(ds.getItemIds())); // trigger aggregation repaint component.markAsDirty(); } } @SuppressWarnings("unchecked") @Override public void stateChanged(Datasource.StateChangeEvent e) { for (Action action : getActions()) { action.refreshState(); } super.stateChanged(e); } } }