/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 de.unioninvestment.eai.portal.portlet.crud.domain.model; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.ui.Table.ColumnGenerator; import de.unioninvestment.eai.portal.portlet.crud.config.TableConfig; import de.unioninvestment.eai.portal.portlet.crud.domain.container.GeneratedColumnsDataStreamWrapper; import de.unioninvestment.eai.portal.portlet.crud.domain.events.*; import de.unioninvestment.eai.portal.portlet.crud.domain.model.DataContainer.ExportWithExportSettings; import de.unioninvestment.eai.portal.portlet.crud.domain.model.container.DataStream; import de.unioninvestment.eai.portal.portlet.crud.domain.support.EmptyColumnGenerator; import de.unioninvestment.eai.portal.support.vaadin.mvp.EventRouter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.*; import static java.util.Collections.singleton; import static java.util.Collections.unmodifiableSet; /** * Modell-Klasse für Tabellen. * * @author max.hartmann * @author Jan Malcomess (codecentric AG) * */ public class Table extends Component implements Component.ExpandableComponent, Serializable { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(Table.class); /** * Permission Actions. */ public enum Permission { BUILD, EDIT } /** * Renderer-Klasse für Zeilen-Styles. */ public static interface RowStyleRenderer { /** * @param rowId * ZeilenId * @return den Style der Zeile. */ public String getStyle(ContainerRowId rowId); } /** * Renderer-Klasse für Spalten-Styles. */ public static interface ColumnStyleRenderer { /** * @param rowId * ZeilenId * @return den Style der Spalte. */ public String getStyle(ContainerRowId rowId); } /** * Definiert die Erwartungen des Models an den Presenter. * * @author eugen.melnichuk */ public interface Presenter { /** * Fügt der Tabelle dynamisch eine neue Spalte hinzu. * * @param columnName * die ID der Spalte * @param columnTitle * die Überschrift der Spalte * @param columnGenerator * der Generator, der den Inhalt der einzelnen Zellen * generiert */ void addGeneratedColumn(String columnName, String columnTitle, ColumnGenerator columnGenerator); /** * Entfernt eine zuvor per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} hinzugefügte * Spalte aus der Tabelle. * * @param columnName * der Name der Spalte, die bei * {@link #addGeneratedColumn(String, String, ColumnGenerator)} * verwendet wurde */ void removeGeneratedColumn(String columnName); /** * Führt Änderungen an der Tabelle zu wie beispielsweise dynamisches * Hinzufügen und Entfernen von Spalten. Das Content-Refreshing * (Rerendering) wird vor dem Ausführen von {@code change} ausgeschaltet * und (falls es vorher eingeschaltet war) danach wieder eingeschaltet. * * @param changes * die durchzuführenden Änderungen */ public void renderOnce(DynamicColumnChanges changes); /** * @param columnName * der Spaltenname * @return {@code true} wenn die Tabelle eine per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} * hinzugefügte Spalte hat */ public boolean hasGeneratedColumn(String columnName); /** * Entfernt alle per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} hinzugefügten * Spalten. */ public void clearAllGeneratedColumns(); /** * @return die Namen der sichtbaren Spalten in der Reihenfolge, in der * sie angezeigt werden. * @see #setVisibleColumns(List) */ public List<String> getVisibleColumns(); /** * Setzt gleichzeitig die Reihenfolge und die Sichtbarkeit der Spalten. * Alle Spalten, deren ID in der übergebenen Liste * {@code visibleColumns} enthalten sind, werden angezeigt, alle anderen * nicht. Das bezieht sich nicht nur auf per {@code addGeneratedColumn} * hinzugefügten Spalten sondern auf alle Spalten inkl. der fest in der * Konfiguration deklarierten. Die Spalten werden in der Reihenfolge * angeordnet, wie sie in {@code visibleColumns} stehen. * * @param visibleColumns * die Liste der ColumnNames. Für eine per * {@link #addGeneratedColumn(String, String, com.vaadin.ui.Table.ColumnGenerator)} * hinzugefügte Spalten ist das der String, der dabei als * Argument {@code columnName} verwendet wurde, für eine * deklarierte Spalte das Attribut {@code name} */ public void setVisibleColumns(List<String> visibleColumns); /** * Setzt die Sichtbarkeit der TableAction (Button) mit der ID {@code id} * . * * @param id * die ID der TableAction, so wie sie im Attribut <tt>id</tt> * des Tags <tt>action</tt> definiert ist. * * @param visible * {@code true}: sichtbar, {@code false}: unsichtbar, */ public void setTableActionVisibility(String id, boolean visible); /** * Fügt der Tabelle eine neue Zeile hinzu. Werte der Zeile können über * {@code values} vorbelegt werden. * * @param values * die Werte, die in der neuen Zeile bereits gesetzt sein * sollen. Die Keys der Map entsprechen den Column Names, die * dazugehörigen Values sind die Werte in der Zeile. */ public ContainerRow createNewRow(Map<String, Object> values); void download(Download download); /** * Verwirft die Änderungen auf der Tabelle. */ public void revertChanges(); } /** * Ein Wrapper für eine Reihe von * {@link Table.Presenter#addGeneratedColumn(String, String, com.vaadin.ui.Table.ColumnGenerator)} * and {@link Table.Presenter#removeGeneratedColumn(String)} Anweisungen. * * @author Bastian Krol */ public interface DynamicColumnChanges { /** * Wendet die Änderungen an. {@code addGeneratedColum} und * {@code removeGeneratedColumn} Anweisungen sollten in dieser Methode * ausgeführt werden. */ public void apply(); } /** * Ansichtsmodus der Tabellen-Komponente. */ public enum Mode { VIEW, EDIT }; public enum DisplayMode { TABLE, FORM } public enum SelectionMode { DISABLED, SINGLE, MULTIPLE } private final TableConfig config; private final TableColumns columns; private DataContainer container; private List<TableAction> actions; private RowStyleRenderer rowStyleRenderer; private EventRouter<SelectionEventHandler, SelectionEvent> selectionEventRouter = new EventRouter<SelectionEventHandler, SelectionEvent>(); private EventRouter<TableDoubleClickEventHandler, TableDoubleClickEvent> doubleClickEventRouter = new EventRouter<TableDoubleClickEventHandler, TableDoubleClickEvent>(); private EventRouter<ModeChangeEventHandler<Table, Mode>, ModeChangeEvent<Table, Mode>> editModeChangeEventRouter = new EventRouter<ModeChangeEventHandler<Table, Mode>, ModeChangeEvent<Table, Mode>>(); private EventRouter<ModeChangeEventHandler<Table, DisplayMode>, ModeChangeEvent<Table, DisplayMode>> displayModeChangeEventRouter = new EventRouter<ModeChangeEventHandler<Table, DisplayMode>, ModeChangeEvent<Table, DisplayMode>>(); private EventRouter<RowChangeEventHandler, RowChangeEvent> rowChangeEventRouter = new EventRouter<RowChangeEventHandler, RowChangeEvent>(); private EventRouter<InitializeEventHandler<Table>, InitializeEvent<Table>> initializeEventRouter = new EventRouter<InitializeEventHandler<Table>, InitializeEvent<Table>>(); Mode mode = null; DisplayMode displayMode = DisplayMode.TABLE; private Set<ContainerRowId> selection = new LinkedHashSet<ContainerRowId>(); private final boolean editable; private final boolean directEdit; private RowEditableChecker editableChecker; private RowDeletableChecker deletableChecker; private Table.Presenter presenter; private Validator rowValidator = null; private SelectionMode selectionMode; /** * Konstruktor mit Parametern. * @param config * Konfiguration der Tabelle * @param tableColumns * Tabellen-Spalten * @param container * the underlying data container * @param editable * the editable status of the table itself (not dependent on container) */ public Table(TableConfig config, TableColumns tableColumns, DataContainer container, boolean editable, boolean directEdit) { this.config = config; this.editable = editable; this.directEdit = directEdit; this.selectionMode = SelectionMode.valueOf(config.getSelectionMode().name()); this.columns = tableColumns; if (columns != null) { columns.setTable(this); } this.container = container; if (selectionMode == SelectionMode.DISABLED && isEditable()) { LOGGER.warn("Table is editable but selection mode is 'disabled'"); } } /** * Setzt den Presenter im Modell für den Fall, dass Ereignisse im Modell * Auswirkungen auf die Präsentation der Tabelle haben. Der Presenter darf * in der Table nur über das Interface {@link Table.Presenter} angesprochen * werden. * * @param presenter * der Presenter */ public void setPresenter(Table.Presenter presenter) { this.presenter = presenter; } public String getId() { return config.getId(); } /** * @since 1.45 */ public String getWidth() { return this.config.getWidth(); } /** * @since 1.45 */ public String getHeight() { return this.config.getHeight(); } public RowStyleRenderer getRowStyleRenderer() { return rowStyleRenderer; } public void setRowStyleRenderer(RowStyleRenderer rowStyleRenderer) { this.rowStyleRenderer = rowStyleRenderer; } public void setRowEditableChecker(RowEditableChecker checker) { this.editableChecker = checker; } public void setRowDeletableChecker(RowDeletableChecker checker) { this.deletableChecker = checker; } /** * Holt für jedes Column den jeweiligen ColumnStyleRenderer. * * @param columnName * Tabellenspaltenname * @return ColumnStyleRenderer */ public ColumnStyleRenderer getColumnStyleRenderer(String columnName) { if (columns != null) { if (columns.contains(columnName)) { TableColumn tableColumn = columns.get(columnName); if (tableColumn != null) { return tableColumn.getColumnStyleRenderer(); } } } return null; } /** * Liefert die Zeilenhöhe zurück. * * @return Zeilenhöhe */ public Integer getRowHeight() { if (config.getColumns() == null) { // no further configuration of columns (multiline), so always use // default height return null; } else { return config.getRowHeight(); } } public boolean isSortingEnabled() { return config.isSortable(); } public DataContainer getContainer() { return container; } public TableColumns getColumns() { return columns; } public List<TableAction> getActions() { return Collections.unmodifiableList(actions); } void setActions(List<TableAction> buildTableActions) { this.actions = buildTableActions; } /** * @param selection * die im View geänderte Selektion. */ public void changeSelection(Set<ContainerRowId> selection) { LinkedHashSet<ContainerRowId> newSelection = new LinkedHashSet<ContainerRowId>( selection); if (!newSelection.equals(this.selection)) { this.selection = newSelection; selectionEventRouter.fireEvent(new SelectionEvent(this, unmodifiableSet(this.selection))); } } /** * Verarbeitet den Doppelklick auf eine Zeile. * * @param row * Zeile */ public void doubleClick(ContainerRow row) { if (config.isEditForm() && (doubleClickEventRouter.getRegisteredHandlerSize() == 0 || getMode() == Mode.EDIT)) { changeDisplayMode(DisplayMode.FORM); changeSelection(singleton(row.getId())); } else { doubleClickEventRouter.fireEvent(new TableDoubleClickEvent(this, row)); } } public boolean isDirectEdit() { return directEdit; } public void changeMode() { Mode newMode = getMode() == Mode.VIEW ? Mode.EDIT : Mode.VIEW; changeMode(newMode); } /** * @param mode * der im View gesetzte Modus */ public void changeMode(Mode mode) { if (directEdit) { throw new IllegalStateException( "Cannot change mode if direct editing is enabled"); } else if (getMode() != mode) { this.mode = mode; editModeChangeEventRouter .fireEvent(new ModeChangeEvent<Table, Mode>(this, mode)); } } public void changeDisplayMode() { DisplayMode newDisplayMode = displayMode == DisplayMode.TABLE ? DisplayMode.FORM : DisplayMode.TABLE; changeDisplayMode(newDisplayMode); } /** * @param newDisplayMode * der im View gesetzte Modus */ public void changeDisplayMode(DisplayMode newDisplayMode) { if (newDisplayMode == DisplayMode.FORM && !isFormEditEnabled()) { throw new IllegalStateException( "Cannot change displayMode if form editing is disabled"); } else if (this.displayMode != newDisplayMode) { this.displayMode = newDisplayMode; displayModeChangeEventRouter .fireEvent(new ModeChangeEvent<Table, DisplayMode>(this, newDisplayMode)); } } /** * Fügt einen Event Handler für die Änderung des Anzeigemodus hinzu. * * @param handler * Event Handler */ public void addDisplayModeChangeEventHandler( ModeChangeEventHandler<Table, DisplayMode> handler) { displayModeChangeEventRouter.addHandler(handler); } /** * Fügt ein SelectionEventHandler hinzu. * * @param selectionEventHandler * EventHandler */ public void addSelectionEventHandler( SelectionEventHandler selectionEventHandler) { selectionEventRouter.addHandler(selectionEventHandler); } /** * Fügt einen TableDoubleClickEventHandler hinzu. * * @param handler * Eventhandler */ public void addDoubleClickEventHandler(TableDoubleClickEventHandler handler) { doubleClickEventRouter.addHandler(handler); } public void removeDoubleClickEventHandler( TableDoubleClickEventHandler doubleClickEventHandler) { doubleClickEventRouter.removeHandler(doubleClickEventHandler); } /** * Fügt ein ModeChangeEventHandler hinzu. * * @param onEditEventHandler * EventHandler */ public void addModeChangeEventHandler( ModeChangeEventHandler<Table, Mode> onEditEventHandler) { editModeChangeEventRouter.addHandler(onEditEventHandler); } /** * Wird durch die Table-UI-Komponente nach Änderung einer Zeile im Container * aufgerufen. * * @param uncommittedRowId * die gerade im UI geänderte Zeile * @param changedValues * Geänderten Werte als Map */ public void rowChange(ContainerRowId uncommittedRowId, Map<String, Object> changedValues) { ContainerRow containerRow = container.getRow(uncommittedRowId, false, false); rowChangeEventRouter.fireEvent(new RowChangeEvent(containerRow, changedValues)); } public void validateIfChanged(ContainerRowId uncommittedRowId) throws InvalidValueException { if (rowValidator != null) { ContainerRow containerRow = container.getRow(uncommittedRowId, false, true); if (containerRow.isModified() || containerRow.isNewItem()) { rowValidator.validate(containerRow); } } } public void doInitialize() { initializeEventRouter.fireEvent(new InitializeEvent<Table>(this)); } public Set<ContainerRowId> getSelection() { return unmodifiableSet(selection); } /** * Prüft, ob die Tabelle generell editierbar oder schreibgeschützt ist. Dies * sagt allerdings noch nichts darüber aus, ob eine bestimmte Zeile * editierbar ist, dazu muss {@link #isRowEditable(ContainerRow)} aufgerufen * werden. * * @return ob die Tabelle generell editiert werden kann */ public boolean isEditable() { return editable && (container.isInsertable() || container.isUpdateable() || container .isDeleteable()); } public boolean isModeChangeable() { return isEditable() && !isDirectEdit(); } /** * Prüft, ob die aktuelle Zeile editiert werden darf. * * @param row * die Zeile * @return ob die aktuelle Zeile editiert werden darf */ public boolean isRowEditable(ContainerRow row) { if (!isEditable()) { return false; } if (editableChecker != null && !editableChecker.isEditable(row)) { return false; } return true; } /** * Prüft, ob die aktuelle Zeile gelöscht werden darf. * * @param rowId * die ID der Zeile * @return ob die aktuelle Zeile gelöscht werden darf */ public boolean isRowDeletable(ContainerRowId rowId) { boolean rowIsDeletable = true; if (deletableChecker != null) { ContainerRow row = container.getRow(rowId, false, true); rowIsDeletable = deletableChecker.isDeletable(row); } return rowIsDeletable; } /** * Aktualisiert die Daten im Container aus der DB. */ public void refresh() { container.refresh(); } /** * Fügt einen rowChange-Eventhandler hinzu. * * @param rowChangeEventHandler * EventHandler */ public void addRowChangeEventHandler( RowChangeEventHandler rowChangeEventHandler) { rowChangeEventRouter.addHandler(rowChangeEventHandler); } public void addInitializeEventHandler( InitializeEventHandler<Table> eventHandler) { initializeEventRouter.addHandler(eventHandler); } public boolean isFormEditEnabled() { return config.isEditForm(); } /** * @return if the table is exportable * @deprecated Export should no longer be configured globally, but instead * use special export-actions. * @see TableAction#isExportAction() */ public boolean isExport() { return config.getExport() != null; } /** * @return the export format * @deprecated Export should no longer be configured globally, but instead * use special export-actions. * @see TableAction#getExportType() */ public String getExportType() { return config.getExport().value(); } /** * @return Anzahl angezeigter Zeilen */ public int getPageLength() { return config.getPageLength(); } /** * @return Fakrot gerenderter Zeilen im Verhältnis zur page-length */ public double getCacheRate() { return config.getCacheRate(); } /** * Fügt der Tabelle dynamisch eine neue Spalte hinzu. Wenn die notwendigen * Berechtigungen fehlen, werden leere Spalten generiert. * * @param columnName * der Name der Spalte * @param columnTitle * die Überschrift der Spalte * @param columnGenerator * der Generator, der den Inhalt der einzelnen Zellen generiert */ public void addGeneratedColumn(String columnName, String columnTitle, ColumnGenerator columnGenerator) { if (getPortlet().allowsDisplayGeneratedContent()) { presenter.addGeneratedColumn(columnName, columnTitle, columnGenerator); } else { presenter.addGeneratedColumn(columnName, columnTitle, new EmptyColumnGenerator()); } } private Portlet getPortlet() { return getPanel().getPortlet(); } /** * Entfernt eine zuvor per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} hinzugefügte Spalte * aus der Tabelle. * * @param columnId * die ID der Spalte, die bei * {@link #addGeneratedColumn(String, String, ColumnGenerator)} verwendet * wurde */ public void removeGeneratedColumn(String columnId) { presenter.removeGeneratedColumn(columnId); } /** * Gibt die ContainerRow zur übergebenen {@code itemId} aus der * Vaadin-Table/dem Vaadin-Container zurück. * * @param itemId * die Vaadin-ID der Zeile * @return die Modell-Zeile */ public ContainerRow getRowByItemId(Object itemId) { ContainerRowId rowId = container.convertInternalRowId(itemId); return getRow(rowId); } /** * Gibt die ContainerRow zur übergebenen {@code rowId} zurück. * * @param rowId * die ID der Zeile * @return die Zeile */ public ContainerRow getRow(ContainerRowId rowId) { return container.getRow(rowId, false, true); } /** * Führt Änderungen an der Tabelle zu wie beispielsweise dynamisches * Hinzufügen und Entfernen von Spalten. Das Content-Refreshing * (Rerendering) wird vor dem Ausführen von {@code change} ausgeschaltet und * (falls es vorher eingeschaltet war) danach wieder eingeschaltet. * * @param changes * die durchzuführenden Änderungen */ public void renderOnce(DynamicColumnChanges changes) { presenter.renderOnce(changes); } /** * @param columnId * die Spalten-ID/Spalten-Überschrift. * @return {@code true} wenn die Tabelle eine per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} hinzugefügte * Spalte hat. */ public boolean hasGeneratedColumn(String columnId) { return presenter.hasGeneratedColumn(columnId); } /** * Entfernt alle per {@link #addGeneratedColumn(String, String, ColumnGenerator)} * hinzugefügten Spalten. */ public void clearAllGeneratedColumns() { presenter.clearAllGeneratedColumns(); } /** * @return die IDs der sichtbaren Spalten in der Reihenfolge, in der sie * angezeigt werden. * @see #setVisibleColumns(List) */ public List<String> getVisibleColumns() { return presenter.getVisibleColumns(); } /** * Setzt gleichzeitig die Reihenfolge und die Sichtbarkeit der Spalten. Alle * Spalten, deren ID in der übergebenen Liste {@code visibleColumns} * enthalten sind, werden angezeigt, alle anderen nicht. Das bezieht sich * nicht nur auf per {@code addGeneratedColumn} hinzugefügten Spalten * sondern auf alle Spalten inkl. der fest in der Konfiguration * deklarierten. Die Spalten werden in der Reihenfolge angeordnet, wie sie * in {@code visibleColumns} stehen. * * @param visibleColumns * die Liste der ColumnNames. Für eine per * {@link #addGeneratedColumn(String, String, ColumnGenerator)} * hinzugefügte Spalten ist das der String, der dabei als * Argument {@code columnName} verwendet wurde, für eine * deklarierte Spalte das Attribut {@code name} */ public void setVisibleColumns(List<String> visibleColumns) { presenter.setVisibleColumns(visibleColumns); } /** * Fügt der Tabelle eine neue Zeile hinzu. Werte der Zeile können über * {@code values} vorbelegt werden. * * @param values * die Werte, die in der neuen Zeile bereits gesetzt sein sollen. * Die Keys der Map entsprechen den Column Names, die * dazugehörigen Values sind die Werte in der Zeile. * @return die neu angelegte Zeile */ public ContainerRow createNewRow(Map<String, Object> values) { // Ggf. vorhandene Änderungen in der alten Zeile vorher committen container.commit(); return presenter.createNewRow(values); } /** * Setzt die Sichtbarkeit der TableAction (Button) mit der ID {@code id}. * * @param id * die ID der TableAction, so wie sie im Attribut <tt>id</tt> des * Tags <tt>action</tt> definiert ist. * * @param visible * {@code true}: sichtbar, {@code false}: unsichtbar, */ public void setTableActionVisibility(String id, boolean visible) { presenter.setTableActionVisibility(id, visible); } public void download(Download download) { presenter.download(download); } /** * @param exportCallback * Callback im Kontext bestimmter Container-Anpassungen. */ public void withExportSettings(ExportWithExportSettings exportCallback) { container.withExportSettings(exportCallback); } /** * {@inheritDoc} * * @since 1.45 */ @Override public int getExpandRatio() { return config.getExpandRatio() == null ? 1 : config.getExpandRatio(); } /** * @return the current edit mode */ public Mode getMode() { if (mode == null) { // lazy initialize mode for easier testing mode = directEdit && isEditable() ? Mode.EDIT : Mode.VIEW; } return mode; } /** * @return the current display mode */ public DisplayMode getDisplayMode() { return displayMode; } public DataStream getStream() { DataStream stream = container.getStream(); if (columns != null) { stream = new GeneratedColumnsDataStreamWrapper(stream, container.getColumns(), columns); } return stream; } /** * Revert changes made in editing mode */ public void revertChanges() { presenter.revertChanges(); } public void setRowValidator(Validator rowValidator) { this.rowValidator = rowValidator; } public boolean hasRowValidator() { return rowValidator != null; } public SelectionMode getSelectionMode() { return selectionMode; } }