From 0fee10e494bc4f7842bede393c5b242090c89b37 Mon Sep 17 00:00:00 2001 From: felmer <felmer> Date: Mon, 12 Dec 2011 08:51:11 +0000 Subject: [PATCH] Refactoring: AbstractBrowserGrid absorbed into TypedTableGrid SVN: 23968 --- .../{grid => }/ColumnSettingsConfigurer.java | 24 +- .../client/application/ui/TypedTableGrid.java | 2449 ++++++++++++++++- .../ui/grid/AbstractBrowserGrid.java | 2283 --------------- .../ui/grid/BrowserGridPagingToolBar.java | 4 +- .../grid/CustomColumnsMetadataProvider.java | 2 +- .../application/ui/grid/ExtendedGridView.java | 2 +- .../ui/grid/PendingFetchManager.java | 2 +- .../AbstractGridDataRefreshCallback.java | 17 +- .../AuthorizationManagementConsolTest.java | 4 +- .../client/application/SampleBrowserTest.java | 2 +- .../application/ui/util/GridTestUtils.java | 4 +- 11 files changed, 2382 insertions(+), 2411 deletions(-) rename openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/{grid => }/ColumnSettingsConfigurer.java (90%) delete mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ColumnSettingsConfigurer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/ColumnSettingsConfigurer.java similarity index 90% rename from openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ColumnSettingsConfigurer.java rename to openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/ColumnSettingsConfigurer.java index 148b87f1687..d754eccd8bf 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ColumnSettingsConfigurer.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/ColumnSettingsConfigurer.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid; +package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui; +import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -28,11 +29,15 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAs import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext; import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager; import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDisplaySettingsGetter; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.BaseEntityModel; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.AbstractColumnSettingsDataModelProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnDataModel; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnSettingsDialog; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.CustomColumnsMetadataProvider; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.expressions.filter.FilterToolbar; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridCustomColumnInfo; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConfig; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject; /** * A class that manages the configuring of column settings in the AbstractBrowserGrid. @@ -42,15 +47,15 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo; * * @author Chandrasekhar Ramakrishnan */ -public class ColumnSettingsConfigurer<T, M extends BaseEntityModel<T>> +public class ColumnSettingsConfigurer<T extends Serializable> { public static final int DEFAULT_COLUMN_WIDTH = 150; - private final AbstractBrowserGrid<T, M> browserGrid; + private final TypedTableGrid<T> browserGrid; private final IViewContext<ICommonClientServiceAsync> viewContext; - private final FilterToolbar<T> filterToolbar; + private final FilterToolbar<TableModelRowWithObject<T>> filterToolbar; private final CustomColumnsMetadataProvider customColumnsMetadataProvider; @@ -61,8 +66,9 @@ public class ColumnSettingsConfigurer<T, M extends BaseEntityModel<T>> // finished. private final ResultSetFetchConfig<String> pendingFetchConfigOrNull; - public ColumnSettingsConfigurer(AbstractBrowserGrid<T, M> browserGrid, - IViewContext<ICommonClientServiceAsync> viewContext, FilterToolbar<T> filterToolbar, + public ColumnSettingsConfigurer(TypedTableGrid<T> browserGrid, + IViewContext<ICommonClientServiceAsync> viewContext, + FilterToolbar<TableModelRowWithObject<T>> filterToolbar, CustomColumnsMetadataProvider customColumnsMetadataProvider, String resultSetKeyOrNull, ResultSetFetchConfig<String> pendingFetchConfigOrNull) { @@ -77,7 +83,7 @@ public class ColumnSettingsConfigurer<T, M extends BaseEntityModel<T>> public void showDialog() { List<ColumnDataModel> settingsModel = - AbstractBrowserGrid.createColumnsSettingsModel(browserGrid.getFullColumnModel(), + TypedTableGrid.createColumnsSettingsModel(browserGrid.getFullColumnModel(), filterToolbar.extractFilteredColumnIds()); AbstractColumnSettingsDataModelProvider provider = new AbstractColumnSettingsDataModelProvider(settingsModel) @@ -167,7 +173,7 @@ public class ColumnSettingsConfigurer<T, M extends BaseEntityModel<T>> /** * Creates a new column model based on the specified column data models and the old full column - * model as provided by {@link AbstractBrowserGrid#getFullColumnModel()}. + * model as provided by {@link TypedTableGrid#getFullColumnModel()}. */ private ColumnModel createNewColumnModel(List<ColumnDataModel> newColumnDataModels) { diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java index 7edec7e8acc..05af29bbac3 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java @@ -20,186 +20,2422 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import com.extjs.gxt.ui.client.GXT; +import com.extjs.gxt.ui.client.Style.Orientation; +import com.extjs.gxt.ui.client.Style.SelectionMode; +import com.extjs.gxt.ui.client.data.BasePagingLoadConfig; +import com.extjs.gxt.ui.client.data.BasePagingLoadResult; +import com.extjs.gxt.ui.client.data.BasePagingLoader; +import com.extjs.gxt.ui.client.data.Loader; +import com.extjs.gxt.ui.client.data.ModelData; +import com.extjs.gxt.ui.client.data.PagingLoadConfig; +import com.extjs.gxt.ui.client.data.PagingLoadResult; +import com.extjs.gxt.ui.client.data.PagingLoader; +import com.extjs.gxt.ui.client.data.RpcProxy; +import com.extjs.gxt.ui.client.event.BaseEvent; +import com.extjs.gxt.ui.client.event.ButtonEvent; +import com.extjs.gxt.ui.client.event.Events; +import com.extjs.gxt.ui.client.event.GridEvent; +import com.extjs.gxt.ui.client.event.Listener; +import com.extjs.gxt.ui.client.event.MessageBoxEvent; +import com.extjs.gxt.ui.client.event.SelectionChangedEvent; +import com.extjs.gxt.ui.client.event.SelectionListener; +import com.extjs.gxt.ui.client.store.ListStore; +import com.extjs.gxt.ui.client.widget.Component; +import com.extjs.gxt.ui.client.widget.Container; +import com.extjs.gxt.ui.client.widget.ContentPanel; +import com.extjs.gxt.ui.client.widget.Dialog; +import com.extjs.gxt.ui.client.widget.Info; +import com.extjs.gxt.ui.client.widget.InfoConfig; +import com.extjs.gxt.ui.client.widget.Label; +import com.extjs.gxt.ui.client.widget.LayoutContainer; +import com.extjs.gxt.ui.client.widget.MessageBox; +import com.extjs.gxt.ui.client.widget.button.Button; +import com.extjs.gxt.ui.client.widget.grid.ColumnConfig; +import com.extjs.gxt.ui.client.widget.grid.ColumnModel; +import com.extjs.gxt.ui.client.widget.grid.EditorGrid; +import com.extjs.gxt.ui.client.widget.grid.EditorGrid.ClicksToEdit; +import com.extjs.gxt.ui.client.widget.grid.Grid; import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer; +import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel; +import com.extjs.gxt.ui.client.widget.layout.FitLayout; +import com.extjs.gxt.ui.client.widget.layout.RowData; +import com.extjs.gxt.ui.client.widget.layout.RowLayout; +import com.extjs.gxt.ui.client.widget.menu.Menu; +import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar; +import com.extjs.gxt.ui.client.widget.toolbar.SeparatorToolItem; +import com.extjs.gxt.ui.client.widget.toolbar.ToolBar; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AbstractImagePrototype; +import ch.systemsx.cisd.common.shared.basic.utils.StringUtils; import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAsync; import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.IGenericImageBundle; import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ShowRelatedDatasetsDialog; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.VoidAsyncCallback; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.AbstractTabItemFactory; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DispatcherHelper; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager.GridDisplaySettings; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDatabaseModificationObserver; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDisplaySettingsGetter; import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDisplayTypeIDGenerator; import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.BaseEntityModel; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IClientPlugin; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IClientPluginFactory; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.MaterialRenderer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.MultilineStringCellRenderer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.RealNumberRenderer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.TimestampStringCellRenderer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.VocabularyTermStringCellRenderer; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.customcolumn.core.CustomColumnStringRenderer; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.ComponentEventLogger.EventPair; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.IColumnDefinitionUI; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.LinkExtractor; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.AbstractBrowserGrid; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.specific.GridCustomColumnDefinition; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.BrowserGridPagingToolBar; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.BrowserGridPagingToolBar.PagingToolBarButtonKind; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnDataModel; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnDefsAndConfigs; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnListener; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.CustomColumnsMetadataProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.DisposableEntityChooser; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ExtendedGridView; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IBrowserGridActionInvoker; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ICellListener; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ICellListenerAndLinkGenerator; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IColumnDefinitionProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisplayTypeIDProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisposableComponent; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.PendingFetchManager; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.expressions.filter.FilterToolbar; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityDetailsTabHelper; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityEditorTabClickListener; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.IDataRefreshCallback; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils.DisplayInfoTime; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.WindowUtils; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.lang.StringEscapeUtils; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.Constants; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DefaultResultSetConfig; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridCustomColumnInfo; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridFilters; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridRowModels; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.RelatedDataSetCriteria; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet; +import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConfig; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConfig.ResultSetFetchMode; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TableExportCriteria; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TypedTableResultSet; +import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter; import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel; import ch.systemsx.cisd.openbis.generic.shared.basic.IColumnDefinition; import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder; +import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId; +import ch.systemsx.cisd.openbis.generic.shared.basic.URLMethodWithParameters; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BasicEntityType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ColumnSetting; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityTableCell; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ISerializableComparable; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo.SortDir; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TypedTableModel; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.VocabularyTerm; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebClientConfiguration; /** * Abstract superclass of all grids based on {@link TypedTableModel}. * * @author Franz-Josef Elmer */ -public abstract class TypedTableGrid<T extends Serializable> - extends - AbstractBrowserGrid<TableModelRowWithObject<T>, BaseEntityModel<TableModelRowWithObject<T>>> +public abstract class TypedTableGrid<T extends Serializable> extends LayoutContainer implements + IDatabaseModificationObserver, IDisplayTypeIDProvider, + IColumnDefinitionProvider<TableModelRowWithObject<T>> { + private static final IGenericImageBundle IMAGE_BUNDLE = GWT + .<IGenericImageBundle> create(IGenericImageBundle.class); + public static final String GRID_POSTFIX = "-grid"; /** - * Do not display more than this amount of columns in the report, web browsers have problem with - * it + * Do not display more than this amount of columns in the report, web browsers have problem with + * it + */ + private static final int MAX_SHOWN_COLUMNS = 200; + + /** + * Called when user wants to export the data. It can happen only after a previous refresh of the + * data has taken place. The export criteria has only the cache key + */ + abstract protected void prepareExportEntities( + TableExportCriteria<TableModelRowWithObject<T>> exportCriteria, + AbstractAsyncCallback<String> callback); + + // -------- + + /** + * If user selected some entities in given browser first a dialog is shown where he can select + * between showing data sets related to selected/displayed entities. Then a tab is displayed + * where these related data sets are listed.<br> + * <br> + * If no entities were selected in given browser the tab is displayed where data sets related to + * all entities displayed in the grid are listed. + */ + protected static final <E extends IEntityInformationHolder> void showRelatedDataSets( + final IViewContext<ICommonClientServiceAsync> viewContext, + final TypedTableGrid<E> browser) + { + final List<TableModelRowWithObject<E>> selectedEntities = browser.getSelectedBaseObjects(); + final TableExportCriteria<TableModelRowWithObject<E>> displayedEntities = + browser.createTableExportCriteria(); + if (selectedEntities.isEmpty()) + { + // no entity selected - show datasets related to all displayed + RelatedDataSetCriteria<E> relatedCriteria = + RelatedDataSetCriteria.<E> createDisplayedEntities(displayedEntities); + ShowRelatedDatasetsDialog.showRelatedDatasetsTab(viewContext, relatedCriteria); + } else + { + // > 0 entity selected - show dialog with all/selected radio + new ShowRelatedDatasetsDialog<E>(viewContext, selectedEntities, displayedEntities, + browser.getTotalCount()).show(); + } + } + + private final class CellListenerAndLinkGenerator implements ICellListenerAndLinkGenerator<T> + { + private final EntityKind entityKind; + + private final TableModelColumnHeader header; + + private CellListenerAndLinkGenerator(EntityKind entityKind, TableModelColumnHeader header) + { + this.entityKind = entityKind; + this.header = header; + } + + public String tryGetLink(T entity, final ISerializableComparable value) + { + if (value == null || value.toString().length() == 0) + { + return null; + } + if (value instanceof EntityTableCell) + { + EntityTableCell entityTableCell = (EntityTableCell) value; + if (entityTableCell.isMissing() || entityTableCell.isFake()) + { + return null; + } + String permId = entityTableCell.getPermId(); + if (entityTableCell.getEntityKind() == EntityKind.MATERIAL) + { + return LinkExtractor.tryExtract(MaterialIdentifier.tryParseIdentifier(permId)); + } else + { + return LinkExtractor.createPermlink(entityTableCell.getEntityKind(), permId); + } + + } + return LinkExtractor.createPermlink(entityKind, value.toString()); + } + + public void handle(TableModelRowWithObject<T> rowItem, boolean specialKeyPressed) + { + ISerializableComparable cellValue = rowItem.getValues().get(header.getIndex()); + if (cellValue instanceof EntityTableCell) + { + EntityTableCell entityTableCell = (EntityTableCell) cellValue; + String permId = entityTableCell.getPermId(); + if (entityTableCell.getEntityKind() == EntityKind.MATERIAL) + { + MaterialIdentifier materialIdentifier = + MaterialIdentifier.tryParseIdentifier(permId); + OpenEntityDetailsTabHelper.open(viewContext, materialIdentifier, + specialKeyPressed); + } else + { + OpenEntityDetailsTabHelper.open(viewContext, entityTableCell.getEntityKind(), + permId, specialKeyPressed); + } + } else + { + OpenEntityDetailsTabHelper.open(viewContext, entityKind, cellValue.toString(), + specialKeyPressed); + } + } + } + + /** Manager of table modifications */ + public interface ITableModificationsManager<M extends ModelData> + { + /** @return <code>true</code> iff there are any uncommitted modifications */ + boolean isTableDirty(); + + /** save all modifications made in the table to the DB */ + void saveModifications(); + + /** save all modifications made in the table to the DB and call the after save action */ + void saveModifications(IDelegatedAction afterSaveAction); + + /** cancel all modifications made in the table */ + void cancelModifications(); + + /** handle cell editing event */ + void handleEditingEvent(M model, String columnID, String stringOrNull); + + /** @return callback for given modifications made to specified model. */ + AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback(final M model, + final List<IModification> modifications); + } + + protected interface IModification + { + String getColumnID(); + + String tryGetNewValue(); + } + + private static class Modification implements IModification + { + private final String columnID; + + private final String newValueOrNull; + + public Modification(String columnID, String newValueOrNull) + { + super(); + this.columnID = columnID; + this.newValueOrNull = + newValueOrNull == null ? null : StringEscapeUtils.unescapeHtml(newValueOrNull); + } + + public String getColumnID() + { + return columnID; + } + + public String tryGetNewValue() + { + return newValueOrNull; + } + } + + protected final IViewContext<ICommonClientServiceAsync> viewContext; + + protected final ICellListener<TableModelRowWithObject<T>> showEntityViewerLinkClickListener; + + protected final ITableModificationsManager<BaseEntityModel<TableModelRowWithObject<T>>> tableModificationsManager; + + // ------ private section. NOTE: it should remain unaccessible to subclasses! --------------- + + private static final int PAGE_SIZE = Constants.GRID_PAGE_SIZE; + + // set to true to see some useful debugging messages + private static final boolean DEBUG = false; + + private final PagingLoader<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> pagingLoader; + + private final ContentPanel contentPanel; + + private final Grid<BaseEntityModel<TableModelRowWithObject<T>>> grid; + + private final ColumnListener<TableModelRowWithObject<T>, BaseEntityModel<TableModelRowWithObject<T>>> columnListener; + + private final boolean refreshAutomatically; + + // the toolbar has the refresh and export buttons besides the paging controls + private final BrowserGridPagingToolBar pagingToolbar; + + // used to change displayed filter widgets + private final FilterToolbar<TableModelRowWithObject<T>> filterToolbar; + + private final ToolBar modificationsToolbar; + + private final IDisplayTypeIDGenerator displayTypeIDGenerator; + + // --------- private non-final fields + + // available columns definitions + private Set<IColumnDefinition<TableModelRowWithObject<T>>> columnDefinitions; + + private final CustomColumnsMetadataProvider customColumnsMetadataProvider; + + // result set key of the last refreshed data + private String resultSetKeyOrNull; + + // Keeps track of the pending fetch (only tracks 1) + private final PendingFetchManager pendingFetchManager; + + private IDataRefreshCallback refreshCallback; + + private LayoutContainer bottomToolbars; + + private ColumnModel fullColumnModel; + + private final Map<String, ICellListenerAndLinkGenerator<T>> listenerLinkGenerators = + new HashMap<String, ICellListenerAndLinkGenerator<T>>(); + + private List<TableModelColumnHeader> headers; + + private List<IColumnDefinitionUI<TableModelRowWithObject<T>>> columnUIDefinitions; + + private String downloadURL; + + private Map<String, IColumnDefinition<TableModelRowWithObject<T>>> columnDefinitionsMap; + + private String currentGridDisplayTypeID; + + private List<IColumnDefinitionUI<TableModelRowWithObject<T>>> visibleColDefinitions; + + protected TypedTableGrid(final IViewContext<ICommonClientServiceAsync> viewContext, + String browserId, IDisplayTypeIDGenerator displayTypeIDGenerator) + { + this(viewContext, browserId, false, displayTypeIDGenerator); + } + + /** + * @param refreshAutomatically should the data be automatically loaded when the grid is rendered + * for the first time? + * @param browserId unique id of the browser grid + */ + protected TypedTableGrid(IViewContext<ICommonClientServiceAsync> viewContext, String browserId, + boolean refreshAutomatically, IDisplayTypeIDGenerator displayTypeIDGenerator) + { + String gridId = browserId + GRID_POSTFIX; + pendingFetchManager = new PendingFetchManager(); + this.displayTypeIDGenerator = displayTypeIDGenerator; + this.viewContext = viewContext; + int logID = log("create browser grid " + gridId); + this.refreshAutomatically = refreshAutomatically; + this.pagingLoader = createPagingLoader(); + this.customColumnsMetadataProvider = new CustomColumnsMetadataProvider(); + this.grid = createGrid(pagingLoader, gridId); + // WORKAROUND + // Lazy loading of rows causes tests using experiment browser fail (selection of + // project in project tree grid doesn't work). + // Turning it off for all grids is the safest solution for our system tests framework + // and should improve GUI speed in development mode a bit. + grid.setLazyRowRender(0); + this.pagingToolbar = + new BrowserGridPagingToolBar(asActionInvoker(), viewContext, PAGE_SIZE, gridId); + pagingToolbar.bind(pagingLoader); + this.filterToolbar = + new FilterToolbar<TableModelRowWithObject<T>>(viewContext, gridId, this, + createApplyFiltersDelagator()); + this.tableModificationsManager = new TableModificationsManager(); + this.modificationsToolbar = + new TableModificationsToolbar(viewContext, tableModificationsManager); + + this.contentPanel = createEmptyContentPanel(); + bottomToolbars = createBottomToolbars(contentPanel, pagingToolbar); + configureBottomToolbarSyncSize(); + contentPanel.add(grid); + contentPanel.setBottomComponent(bottomToolbars); + contentPanel.setHeaderVisible(false); + columnListener = + new ColumnListener<TableModelRowWithObject<T>, BaseEntityModel<TableModelRowWithObject<T>>>( + grid); + showEntityViewerLinkClickListener = createShowEntityViewerLinkClickListener(); + registerLinkClickListenerFor(Dict.CODE, showEntityViewerLinkClickListener); + setLayout(new FitLayout()); + add(contentPanel); + + configureLoggingBetweenEvents(logID); + + grid.addListener(Events.HeaderContextMenu, new Listener<GridEvent<ModelData>>() + { + public void handleEvent(final GridEvent<ModelData> ge) + { + Menu menu = ge.getMenu(); + int itemCount = menu.getItemCount(); + for (int i = 2; i < itemCount; i++) + { + menu.remove(menu.getItem(2)); + } + } + }); + if (viewContext.getModel().isEmbeddedMode()) + { + removeButtons(PagingToolBarButtonKind.CONFIG, PagingToolBarButtonKind.REFRESH); + } + setId(browserId); + } + + public void removeButtons(PagingToolBarButtonKind... buttonKinds) + { + pagingToolbar.removeButtons(buttonKinds); + } + + private ICellListener<TableModelRowWithObject<T>> createShowEntityViewerLinkClickListener() + { + return new ICellListener<TableModelRowWithObject<T>>() + { + public void handle(TableModelRowWithObject<T> rowItem, boolean keyPressed) + { + showEntityViewer(rowItem, false, keyPressed); + } + }; + } + + private void configureBottomToolbarSyncSize() + { + // fixes problems with: + // - no 'overflow' button when some buttons don't fit into pagingToolbar + pagingLoader.addListener(Loader.Load, new Listener<BaseEvent>() + { + public void handleEvent(BaseEvent be) + { + pagingToolbar.syncSize(); + } + }); + // - hidden paging toolbar + pagingToolbar.addListener(Events.AfterLayout, new Listener<BaseEvent>() + { + public void handleEvent(BaseEvent be) + { + contentPanel.syncSize(); + } + }); + // - bottom toolbar is not resized when new filter row appears + filterToolbar.addListener(Events.AfterLayout, new Listener<BaseEvent>() + { + public void handleEvent(BaseEvent be) + { + contentPanel.syncSize(); + } + }); + } + + private void configureLoggingBetweenEvents(int logID) + { + if (viewContext.isLoggingEnabled()) + { + ComponentEventLogger logger = new ComponentEventLogger(viewContext, getId()); + logger.prepareLoggingBetweenEvents(contentPanel, EventPair.RENDER); + logger.prepareLoggingBetweenEvents(this, EventPair.LAYOUT); + logger.prepareLoggingBetweenEvents(grid, EventPair.LAYOUT); + logger.prepareLoggingBetweenEvents(contentPanel, EventPair.LAYOUT); + logger.prepareLoggingBetweenEvents(bottomToolbars, EventPair.LAYOUT); + logger.prepareLoggingBetweenEvents(filterToolbar, EventPair.LAYOUT); + logger.prepareLoggingBetweenEvents(pagingToolbar, EventPair.LAYOUT); + viewContext.logStop(logID); + } + } + + protected int log(String message) + { + return viewContext.log(message + " [" + getId() + "]"); + } + + protected void showEntityInformationHolderViewer(IEntityInformationHolderWithPermId entity, + boolean editMode, boolean inBackground) + { + if (editMode + && OpenEntityEditorTabClickListener.forbidDeletedEntityModification(viewContext, + entity)) + { + return; + } + final EntityKind entityKind = entity.getEntityKind(); + final AbstractTabItemFactory tabView; + BasicEntityType entityType = entity.getEntityType(); + final IClientPluginFactory clientPluginFactory = + viewContext.getClientPluginFactoryProvider().getClientPluginFactory(entityKind, + entityType); + final IClientPlugin<BasicEntityType, IEntityInformationHolderWithPermId> createClientPlugin = + clientPluginFactory.createClientPlugin(entityKind); + if (editMode) + { + tabView = createClientPlugin.createEntityEditor(entity); + } else + { + tabView = createClientPlugin.createEntityViewer(entity); + } + tabView.setInBackground(inBackground); + DispatcherHelper.dispatchNaviEvent(tabView); + } + + /** Refreshes the grid without showing the loading progress bar */ + protected final void refreshGridSilently() + { + grid.setLoadMask(false); + int id = log("refresh silently"); + refresh(); + grid.setLoadMask(true); + viewContext.logStop(id); + } + + /** + * Shows/hides the load mask immediately. + * + * @param loadMask Load mask is shown if <code>true</code> otherwise it is hidden. + */ + public void setLoadMaskImmediately(boolean loadMask) + { + if (grid.isRendered()) + { + if (loadMask) + { + grid.el().mask(GXT.MESSAGES.loadMask_msg()); + } else + { + grid.el().unmask(); + } + } + + } + + /** + * Registers the specified listener for clicks on links in the specified column. + * + * @param columnID Column ID. Not case sensitive. + * @param listener Listener handle single clicks. + */ + protected final void registerLinkClickListenerFor(final String columnID, + final ICellListener<TableModelRowWithObject<T>> listener) + { + if (viewContext.isSimpleOrEmbeddedMode() == false) + { + columnListener.registerLinkClickListener(columnID, listener); + } + } + + /** + * Allows multiple selection instead of single selection. + */ + protected final void allowMultipleSelection() + { + grid.getSelectionModel().setSelectionMode(SelectionMode.MULTI); + } + + protected List<TableModelRowWithObject<T>> getGridElements() + { + List<BaseEntityModel<TableModelRowWithObject<T>>> models = grid.getStore().getModels(); + List<TableModelRowWithObject<T>> elements = new ArrayList<TableModelRowWithObject<T>>(); + for (BaseEntityModel<TableModelRowWithObject<T>> model : models) + { + elements.add(model.getBaseObject()); + } + return elements; + } + + private IDelegatedAction createApplyFiltersDelagator() + { + return new IDelegatedAction() + { + public void execute() + { + if (resultSetKeyOrNull != null && pendingFetchManager.hasNoPendingFetch()) + { + ResultSetFetchConfig<String> fetchConfig = + ResultSetFetchConfig.createFetchFromCache(resultSetKeyOrNull); + reloadData(fetchConfig); + } + } + }; + } + + /** @return this grid as a disposable component with a specified toolbar at the top. */ + protected DisposableEntityChooser<TableModelRowWithObject<T>> asDisposableWithToolbar( + final IDisposableComponent toolbar) + { + final LayoutContainer container = new LayoutContainer(); + container.setLayout(new RowLayout()); + container.add(toolbar.getComponent()); + container.add(this, new RowData(1, 1)); + + return asDisposableEntityChooser(container, toolbar); + } + + /** @return this grid as a disposable component */ + protected final DisposableEntityChooser<TableModelRowWithObject<T>> asDisposableWithoutToolbar() + { + return asDisposableEntityChooser(this); + } + + /** + * @return this grid as a disposable component with a specified toolbar at the top and a tree on + * the left. + */ + protected final DisposableEntityChooser<TableModelRowWithObject<T>> asDisposableWithToolbarAndTree( + final IDisposableComponent toolbar, final Component tree, String headerOrNull) + { + // WORKAROUND: BorderLayout causes problems when rendered in a tab + // We use RowLayout here but we loose the split this way. + final LayoutContainer container = new LayoutContainer(); + container.setLayout(new RowLayout(Orientation.VERTICAL)); + container.add(toolbar.getComponent(), new RowData(1, -1)); + + final LayoutContainer subContainer = new LayoutContainer(); + subContainer.setLayout(new RowLayout(Orientation.HORIZONTAL)); + subContainer.add(tree, new RowData(300, 1)); + setHeader(headerOrNull); + subContainer.add(this, new RowData(1, 1)); + container.add(subContainer, new RowData(1, 1)); + + return asDisposableEntityChooser(container, toolbar); + } + + protected final void setHeader(String headerOrNull) + { + if (headerOrNull != null) + { + this.contentPanel.setHeaderVisible(true); + this.contentPanel.setHeading(headerOrNull); + } else + { + this.contentPanel.setHeaderVisible(false); + } + } + + protected final DisposableEntityChooser<TableModelRowWithObject<T>> asDisposableEntityChooser( + final Component mainComponent, final IDisposableComponent... disposableComponents) + { + final TypedTableGrid<T> self = this; + return new DisposableEntityChooser<TableModelRowWithObject<T>>() + { + public TableModelRowWithObject<T> tryGetSingleSelected() + { + List<BaseEntityModel<TableModelRowWithObject<T>>> items = getSelectedItems(); + if (items.isEmpty()) + { + return null; + } else + { + return items.get(0).getBaseObject(); + } + } + + public void dispose() + { + debug("dispose a browser"); + self.disposeCache(); + for (IDisposableComponent disposableComponent : disposableComponents) + { + disposableComponent.dispose(); + } + } + + public Component getComponent() + { + return mainComponent; + } + + public DatabaseModificationKind[] getRelevantModifications() + { + return self.getRelevantModifications(); + } + + public void update(Set<DatabaseModificationKind> observedModifications) + { + self.update(observedModifications); + } + + }; + } + + @Override + protected void onRender(final Element parent, final int pos) + { + super.onRender(parent, pos); + if (refreshAutomatically) + { + int id = log("layout automatically"); + layout(); + viewContext.logStop(id); + id = log("refresh automatically"); + refresh(); + viewContext.logStop(id); + } + } + + private PagingLoader<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> createPagingLoader() + { + final RpcProxy<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> proxy = + new RpcProxy<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>>() + { + @Override + protected void load( + Object loadConfig, + AsyncCallback<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> callback) + { + loadData((PagingLoadConfig) loadConfig, callback); + } + }; + final BasePagingLoader<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> newPagingLoader = + new BasePagingLoader<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>>( + proxy); + newPagingLoader.setRemoteSort(true); + + return newPagingLoader; + } + + private void loadData( + final PagingLoadConfig loadConfig, + final AsyncCallback<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> callback) + { + if (pendingFetchManager.hasNoPendingFetch()) + { + // this can happen when user wants to sort data - the refresh method is not called + if (resultSetKeyOrNull == null) + { + // data are not yet cached, so we ignore this call - should not really happen + return; + } + pendingFetchManager.pushPendingFetchConfig(ResultSetFetchConfig + .createFetchFromCache(resultSetKeyOrNull)); + } + GridFilters<TableModelRowWithObject<T>> filters = filterToolbar.getFilters(); + final DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig = + createPagingConfig(loadConfig, filters, getGridDisplayTypeID()); + debug("create a refresh callback " + pendingFetchManager.tryTopPendingFetchConfig()); + final ListEntitiesCallback listCallback = + new ListEntitiesCallback(viewContext, callback, resultSetConfig); + + listEntities(resultSetConfig, listCallback); + } + + // Default visibility so that friend classes can use -- should otherwise be considered private + void debug(String msg) + { + if (DEBUG) + { + String text = + "[grid: " + getGridDisplayTypeID() + ", cache: " + resultSetKeyOrNull + "] " + + msg; + System.out.println(text); + } + } + + private DefaultResultSetConfig<String, TableModelRowWithObject<T>> createPagingConfig( + PagingLoadConfig loadConfig, GridFilters<TableModelRowWithObject<T>> filters, + String gridDisplayId) + { + int limit = loadConfig.getLimit(); + int offset = loadConfig.getOffset(); + com.extjs.gxt.ui.client.data.SortInfo sortInfo = loadConfig.getSortInfo(); + + DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig = + new DefaultResultSetConfig<String, TableModelRowWithObject<T>>(); + resultSetConfig.setLimit(limit); + resultSetConfig.setOffset(offset); + resultSetConfig.setAvailableColumns(columnDefinitions); + SortInfo translatedSortInfo = translateSortInfo(sortInfo); + Set<String> columnIDs = getIDsOfColumnsToBeShown(); + resultSetConfig.setIDsOfPresentedColumns(columnIDs); + resultSetConfig.setSortInfo(translatedSortInfo); + resultSetConfig.setFilters(filters); + resultSetConfig.setCacheConfig(pendingFetchManager.tryTopPendingFetchConfig()); + resultSetConfig.setGridDisplayId(gridDisplayId); + resultSetConfig.setCustomColumnErrorMessageLong(viewContext.getDisplaySettingsManager() + .isDebuggingModeEnabled()); + return resultSetConfig; + } + + private Set<String> getIDsOfColumnsToBeShown() + { + Set<String> columnIDs = new HashSet<String>(); + DisplaySettingsManager manager = viewContext.getDisplaySettingsManager(); + List<ColumnSetting> columnSettings = manager.getColumnSettings(getGridDisplayTypeID()); + if (columnSettings != null) + { + for (ColumnSetting columnSetting : columnSettings) + { + if (columnSetting.isHidden() == false) + { + columnIDs.add(columnSetting.getColumnID()); + } + } + } + List<IColumnDefinition<TableModelRowWithObject<T>>> visibleColumns = + getVisibleColumns(columnDefinitions); + for (IColumnDefinition<TableModelRowWithObject<T>> definition : visibleColumns) + { + columnIDs.add(definition.getIdentifier()); + } + return columnIDs; + } + + private static <T> SortInfo translateSortInfo(com.extjs.gxt.ui.client.data.SortInfo sortInfo) + { + return translateSortInfo(sortInfo.getSortField(), sortInfo.getSortDir()); + } + + private static <T> SortInfo translateSortInfo(String sortFieldId, + com.extjs.gxt.ui.client.Style.SortDir sortDir) + { + SortInfo sortInfo = new SortInfo(); + sortInfo.setSortField(sortFieldId); + sortInfo.setSortDir(translate(sortDir)); + + return sortInfo; + } + + private static SortDir translate(com.extjs.gxt.ui.client.Style.SortDir sortDir) + { + if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.ASC)) + { + return SortDir.ASC; + } else if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.DESC)) + { + return SortDir.DESC; + } else if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.NONE)) + { + return SortDir.NONE; + } else + { + throw new IllegalStateException("unknown sort dir: " + sortDir); + } + } + + private static com.extjs.gxt.ui.client.Style.SortDir translate(SortDir sortDir) + { + if (sortDir.equals(SortDir.ASC)) + { + return com.extjs.gxt.ui.client.Style.SortDir.ASC; + } else if (sortDir.equals(SortDir.DESC)) + { + return com.extjs.gxt.ui.client.Style.SortDir.DESC; + } else if (sortDir.equals(SortDir.NONE)) + { + return com.extjs.gxt.ui.client.Style.SortDir.NONE; + } else + { + throw new IllegalStateException("unknown sort dir: " + sortDir); + } + } + + /** @return number of rows in the grid */ + public final int getRowNumber() + { + return grid.getStore().getCount(); + } + + public final class ListEntitiesCallback extends + AbstractAsyncCallback<ResultSet<TableModelRowWithObject<T>>> + { + private final AsyncCallback<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> delegate; + + // configuration with which the listing was called + private DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig; + + private int logID; + + private boolean reloadingPhase; + + public ListEntitiesCallback( + final IViewContext<?> viewContext, + final AsyncCallback<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> delegate, + final DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig) + { + super(viewContext); + this.delegate = delegate; + this.resultSetConfig = resultSetConfig; + logID = log("load data"); + } + + // + // AbstractAsyncCallback + // + + @Override + public final void finishOnFailure(final Throwable caught) + { + reenableAfterFailure(); + // no need to show error message - it should be shown by DEFAULT_CALLBACK_LISTENER + caught.printStackTrace(); + delegate.onFailure(caught); + } + + public final void reenableAfterFailure() + { + grid.el().unmask(); + onComplete(false); + pagingToolbar.enable(); // somehow enabling toolbar is lost in its handleEvent() method + } + + @Override + protected void performSuccessActionOrIgnore(final IDelegatedAction successAction) + { + if (tableModificationsManager.isTableDirty()) + { + final Listener<MessageBoxEvent> listener = new Listener<MessageBoxEvent>() + { + public void handleEvent(MessageBoxEvent me) + { + if (me.getButtonClicked().getItemId().equals(Dialog.YES)) + { + tableModificationsManager.saveModifications(new IDelegatedAction() + { + public void execute() + { + // ignore this callback and refresh the table + ignore(); + reenableAfterFailure(); + refresh(); + } + }); + } else + { + tableModificationsManager.cancelModifications(); + successAction.execute(); + } + } + }; + final String title = + viewContext.getMessage(Dict.CONFIRM_SAVE_TABLE_MODIFICATIONS_DIALOG_TITLE); + final String msg = + viewContext + .getMessage(Dict.CONFIRM_SAVE_TABLE_MODIFICATIONS_DIALOG_MESSAGE); + MessageBox.confirm(title, msg, listener); + } else + { + successAction.execute(); + } + } + + @Override + protected final void process(final ResultSet<TableModelRowWithObject<T>> result) + { + viewContext.logStop(logID); + logID = log("process loaded data"); + // save the key of the result, later we can refer to the result in the cache using this + // key + String key = result.getResultSetKey(); + saveCacheKey(key); + GridRowModels<TableModelRowWithObject<T>> rowModels = result.getList(); + boolean partial = result.isPartial(); + List<GridCustomColumnInfo> customColumnMetadata = rowModels.getCustomColumnsMetadata(); + customColumnsMetadataProvider.setCustomColumnsMetadata(customColumnMetadata); + + if (reloadingPhase) + { + reloadingPhase = false; + } else if (partial) + { + reloadingPhase = true; + BasePagingLoadConfig loadConfig = new BasePagingLoadConfig(); + loadConfig.setLimit(resultSetConfig.getLimit()); + loadConfig.setOffset(resultSetConfig.getOffset()); + SortInfo sortInfo = resultSetConfig.getSortInfo(); + if (sortInfo != null) + { + String sortField = sortInfo.getSortField(); + if (sortField != null) + { + loadConfig.setSortField(sortField); + loadConfig.setSortDir(translate(sortInfo.getSortDir())); + } + } + resultSetConfig = + createPagingConfig(loadConfig, filterToolbar.getFilters(), + resultSetConfig.tryGetGridDisplayId()); + resultSetConfig.setCacheConfig(ResultSetFetchConfig + .createFetchFromCacheAndRecompute(key)); + this.reuse(); + listEntities(resultSetConfig, this); + } + // convert the result to the model data for the grid control + final List<BaseEntityModel<TableModelRowWithObject<T>>> models = + createModels(rowModels); + final PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>> loadResult = + new BasePagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>(models, + resultSetConfig.getOffset(), result.getTotalLength()); + + delegate.onSuccess(loadResult); + pagingToolbar.enableExportButton(); + pagingToolbar.updateDefaultConfigButton(true); + + if (reloadingPhase == false) + { + pagingToolbar.enable(); + filterToolbar.refreshColumnFiltersDistinctValues(rowModels + .getColumnDistinctValues()); + } else + { + pagingToolbar.disableForLoadingRest(); + } + onComplete(true); + + viewContext.logStop(logID); + } + + // notify that the refresh is done + private void onComplete(boolean wasSuccessful) + { + pendingFetchManager.popPendingFetch(); + refreshCallback.postRefresh(wasSuccessful); + } + + private List<BaseEntityModel<TableModelRowWithObject<T>>> createModels( + final GridRowModels<TableModelRowWithObject<T>> gridRowModels) + { + final List<BaseEntityModel<TableModelRowWithObject<T>>> result = + new ArrayList<BaseEntityModel<TableModelRowWithObject<T>>>(); + initializeModelCreation(); + for (final GridRowModel<TableModelRowWithObject<T>> entity : gridRowModels) + { + BaseEntityModel<TableModelRowWithObject<T>> model = createModel(entity); + result.add(model); + } + return result; + } + + @Override + /* Note: we want to differentiate between callbacks in different subclasses of this grid. */ + public String getCallbackId() + { + return grid.getId(); + } + } + + protected Set<String> getIDsOfVisibleColumns() + { + Set<String> visibleColumnIds = new HashSet<String>(); + for (int i = 0, n = fullColumnModel.getColumnCount(); i < n; i++) + { + ColumnConfig column = fullColumnModel.getColumn(i); + if (column.isHidden() == false) + { + visibleColumnIds.add(column.getId()); + } + } + return visibleColumnIds; + } + + // wraps this browser into the interface appropriate for the toolbar. If this class would just + // implement the interface it could be very confusing for the code reader. + protected IBrowserGridActionInvoker asActionInvoker() + { + final TypedTableGrid<T> delegate = this; + return new IBrowserGridActionInvoker() + { + public void export(boolean allColumns) + { + delegate.export(allColumns); + } + + public void refresh() + { + int id = log("refresh in action invoker"); + delegate.refresh(); + viewContext.logStop(id); + } + + public void configure() + { + delegate.configureColumnSettings(); + } + + public void toggleFilters(boolean show) + { + if (show) + { + int logId = log("adding filters"); + delegate.showFiltersBar(); + viewContext.logStop(logId); + } else + { + int logId = log("removing filters"); + bottomToolbars.remove(filterToolbar); + bottomToolbars.layout(); + viewContext.logStop(logId); + } + } + + }; + } + + protected void showFiltersBar() + { + // always show filters under modifications + int position = bottomToolbars.getItemCount() > 1 ? 1 : 0; + bottomToolbars.insert(filterToolbar, position); + bottomToolbars.layout(); + } + + protected void showModificationsBar() + { + if (bottomToolbars.getItems().contains(modificationsToolbar) == false) + { + GWTUtils.displayInfo(viewContext.getMessage(Dict.TABLE_MODIFICATIONS_INFO_TITLE), + viewContext.getMessage(Dict.TABLE_MODIFICATIONS_INFO_TEXT), + DisplayInfoTime.LONG); + bottomToolbars.insert(modificationsToolbar, 0); + bottomToolbars.layout(); + } + } + + protected void hideModificationsBar() + { + bottomToolbars.remove(modificationsToolbar); + bottomToolbars.layout(); + } + + protected static interface ISelectedEntityInvoker<M> + { + void invoke(M selectedItem, boolean keyPressed); + } + + protected final ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>> asShowEntityInvoker( + final boolean editMode) + { + return new ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>>() + { + public void invoke(BaseEntityModel<TableModelRowWithObject<T>> selectedItem, + boolean keyPressed) + { + if (selectedItem != null) + { + showEntityViewer(selectedItem.getBaseObject(), editMode, keyPressed); + } + } + }; + } + + private ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>> createNotImplementedInvoker() + { + return new ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>>() + { + public void invoke(BaseEntityModel<TableModelRowWithObject<T>> selectedItem, + boolean keyPressed) + { + MessageBox.alert(viewContext.getMessage(Dict.MESSAGEBOX_WARNING), + viewContext.getMessage(Dict.NOT_IMPLEMENTED), null); + } + }; + } + + /** + * @return a button which has no action but is enabled only when one entity in the grid is + * selected. Useful only for writing prototypes. + */ + protected final Button createSelectedItemDummyButton(final String title) + { + return createSelectedItemButton(title, createNotImplementedInvoker()); + } + + /** + * @return like {@link #createSelectedItemButton(String, ISelectedEntityInvoker)} with button id + * set + */ + protected final Button createSelectedItemButton(final String title, final String id, + final ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>> invoker) + { + final Button button = createSelectedItemButton(title, invoker); + button.setId(id); + return button; + } + + /** + * @return a button which is enabled only when one entity in the grid is selected. When button + * is pressed, the specified invoker action is performed. + */ + protected final Button createSelectedItemButton(final String title, + final ISelectedEntityInvoker<BaseEntityModel<TableModelRowWithObject<T>>> invoker) + { + final Button button = new Button(title, new SelectionListener<ButtonEvent>() + { + @Override + public void componentSelected(ButtonEvent ce) + { + List<BaseEntityModel<TableModelRowWithObject<T>>> selectedItems = + getSelectedItems(); + if (selectedItems.isEmpty() == false) + { + invoker.invoke(selectedItems.get(0), false); + } + } + }); + enableButtonOnSelectedItem(button); + return button; + } + + /** + * @return a button which is enabled only when at least one entity in the grid is selected, and + * with specified selection listener set. + */ + protected final Button createSelectedItemsButton(final String title, + SelectionListener<ButtonEvent> listener) + { + Button button = new Button(title); + button.addSelectionListener(listener); + enableButtonOnSelectedItems(button); + return button; + } + + /** adds given <var>button</var> to grid {@link PagingToolBar} */ + protected final void addButton(Button button) + { + pagingToolbar.add(button); + } + + /** + * Given <var>button</var> will be enabled only if at least one item is selected in the grid. + */ + protected final void enableButtonOnSelectedItems(final Button button) + { + button.setEnabled(false); + addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() + { + public void handleEvent(SelectionChangedEvent<ModelData> se) + { + boolean enabled = se.getSelection().size() > 0; + button.setEnabled(enabled); + } + + }); + } + + /** + * Given <var>button</var> will be enabled only if exactly one item is selected in the grid. + */ + protected final void enableButtonOnSelectedItem(final Button button) + { + button.setEnabled(false); + addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() + { + public void handleEvent(SelectionChangedEvent<ModelData> se) + { + boolean enabled = se.getSelection().size() == 1; + button.setEnabled(enabled); + } + + }); + } + + /** + * Given <var>button</var> will have title changed depending on number of items selected in the + * grid. + */ + protected final void changeButtonTitleOnSelectedItems(final Button button, + final String noSelectedItemsTitle, final String selectedItemsTitle) + { + addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() + { + public void handleEvent(SelectionChangedEvent<ModelData> se) + { + boolean noSelected = se.getSelection().size() == 0; + button.setText(noSelected ? noSelectedItemsTitle : selectedItemsTitle); + } + + }); + } + + public void addGridSelectionChangeListener(Listener<SelectionChangedEvent<ModelData>> listener) + { + grid.getSelectionModel().addListener(Events.SelectionChange, listener); + } + + /** + * Returns all models of selected items or an empty list if nothing selected. + */ + public final List<BaseEntityModel<TableModelRowWithObject<T>>> getSelectedItems() + { + return grid.getSelectionModel().getSelectedItems(); + } + + /** + * Returns all base objects of selected items or an empty list if nothing selected. + */ + protected final List<TableModelRowWithObject<T>> getSelectedBaseObjects() + { + List<BaseEntityModel<TableModelRowWithObject<T>>> items = getSelectedItems(); + List<TableModelRowWithObject<T>> data = new ArrayList<TableModelRowWithObject<T>>(); + for (BaseEntityModel<TableModelRowWithObject<T>> item : items) + { + data.add(item.getBaseObject()); + } + return data; + } + + protected final IDelegatedAction createRefreshGridAction() + { + return new IDelegatedAction() + { + public void execute() + { + int id = log("execute refrish grid action"); + refresh(); + viewContext.logStop(id); + } + }; + } + + /** Refreshes grid and filters (resets filter selection) */ + protected final void refreshGridWithFilters() + { + // N.B. -- The order in which things are refreshed and configured is significant + + // export and config buttons are enabled when ListEntitiesCallback is complete + pagingToolbar.disableExportButton(); + pagingToolbar.updateDefaultConfigButton(false); + + // Need to reset filter fields *before* refreshing the grid so the list can + // be correctly retrieved + filterToolbar.resetFilterFields(); + filterToolbar.resetFilterSelectionWithoutApply(); + + int id = log("refresh grid with filters"); + refresh(); + viewContext.logStop(id); + + // Need to refresh the filter toolbar *after* refreshing the grid, because it + // has a dependency on information from the grid that gets updated with the refresh + filterToolbar.refresh(); + } + + protected final void updateDefaultRefreshButton() + { + boolean isEnabled = isRefreshEnabled(); + pagingToolbar.updateDefaultRefreshButton(isEnabled); + } + + /** + * Refreshes the grid. + * <p> + * Note that, doing so, the result set associated on the server side will be removed. + * </p> + */ + protected final void refresh(boolean refreshColumnsDefinition) + { + refresh(null, refreshColumnsDefinition); + } + + /** + * @param externalRefreshCallbackOrNull external class can define it's own refresh callback + * method. It will be merged with the internal one. + */ + protected final void refresh(final IDataRefreshCallback externalRefreshCallbackOrNull, + boolean refreshColumnsDefinition) + { + int id = log("refresh (refreshColumnsDefinition=" + refreshColumnsDefinition + ")"); + pagingToolbar.updateDefaultRefreshButton(false); + debug("clean cache for refresh"); + this.refreshCallback = createRefreshCallback(externalRefreshCallbackOrNull); + if (columnDefinitions == null || refreshColumnsDefinition) + { + recreateColumnModelAndRefreshColumnsWithFilters(); + } + reloadData(createDisposeAndRefreshFetchMode()); + viewContext.logStop(id); + } + + private ResultSetFetchConfig<String> createDisposeAndRefreshFetchMode() + { + if (resultSetKeyOrNull != null) + { + return ResultSetFetchConfig.createClearComputeAndCache(resultSetKeyOrNull); + } else + { + return ResultSetFetchConfig.createComputeAndCache(); + } + } + + private ColumnDefsAndConfigs<TableModelRowWithObject<T>> createColumnDefsAndConfigs() + { + ColumnDefsAndConfigs<TableModelRowWithObject<T>> defsAndConfigs = createColumnsDefinition(); + // add custom columns + List<GridCustomColumnInfo> customColumnsMetadata = + customColumnsMetadataProvider.getCustomColumnsMetadata(); + if (customColumnsMetadata.size() > 0) + { + List<IColumnDefinitionUI<TableModelRowWithObject<T>>> customColumnsDefs = + createCustomColumnDefinitions(customColumnsMetadata); + defsAndConfigs.addColumns(customColumnsDefs, viewContext); + + for (GridCustomColumnInfo gridCustomColumnInfo : customColumnsMetadata) + { + + DataTypeCode columnType = gridCustomColumnInfo.getDataType(); + GridCellRenderer<BaseEntityModel<?>> columnRenderer = null; + + if (DataTypeCode.REAL.equals(columnType)) + { + columnRenderer = + new RealNumberRenderer(viewContext.getDisplaySettingsManager() + .getRealNumberFormatingParameters()); + + } else if (DataTypeCode.VARCHAR.equals(columnType)) + { + columnRenderer = new CustomColumnStringRenderer(); + } + + if (columnRenderer != null) + { + defsAndConfigs.setGridCellRendererFor(gridCustomColumnInfo.getCode(), + columnRenderer); + } + } + } + + return defsAndConfigs; + } + + protected final void recreateColumnModelAndRefreshColumnsWithFilters() + { + int logId = log("recreateColumnModelAndRefreshColumnsWithFilters"); + + ColumnDefsAndConfigs<TableModelRowWithObject<T>> defsAndConfigs = + createColumnDefsAndConfigs(); + + this.columnDefinitions = defsAndConfigs.getColumnDefs(); + ColumnModel columnModel = createColumnModel(defsAndConfigs.getColumnConfigs()); + + refreshColumnsAndFilters(columnModel); + + viewContext.logStop(logId); + } + + private static <T> List<IColumnDefinitionUI<T>> createCustomColumnDefinitions( + List<GridCustomColumnInfo> customColumnsMetadata) + { + List<IColumnDefinitionUI<T>> defs = new ArrayList<IColumnDefinitionUI<T>>(); + for (GridCustomColumnInfo columnMetadata : customColumnsMetadata) + { + IColumnDefinitionUI<T> colDef = new GridCustomColumnDefinition<T>(columnMetadata); + defs.add(colDef); + } + return defs; + } + + private void refreshColumnsAndFilters(ColumnModel columnModel) + { + ColumnModel newColumnModel = columnModel; + GridDisplaySettings settings = tryApplyDisplaySettings(newColumnModel); + if (settings != null && settings.getColumnConfigs() != null) + { + newColumnModel = createColumnModel(settings.getColumnConfigs()); + rebuildFiltersFromIds(settings.getFilteredColumnIds()); + } else + { + filterToolbar.rebuildColumnFilters(getInitialFilters()); + } + changeColumnModel(newColumnModel, settings != null ? settings.getSortField() : null, + settings != null ? settings.getSortDir() : null); + } + + private void hideLoadingMask() + { + if (grid.isRendered() && grid.el() != null) + { + grid.el().unmask(); + } + } + + private GridDisplaySettings tryApplyDisplaySettings(ColumnModel columnModel) + { + List<IColumnDefinition<TableModelRowWithObject<T>>> initialFilters = getInitialFilters(); + return viewContext.getDisplaySettingsManager().tryApplySettings(getGridDisplayTypeID(), + columnModel, extractColumnIds(initialFilters), getGridSortInfo()); + } + + private void reconfigureGrid(ColumnModel columnModelOfVisible) + { + List<Listener<?>> sortlisteners = + new ArrayList<Listener<?>>(grid.getListeners(Events.SortChange)); + for (Listener<?> listener : sortlisteners) + { + grid.removeListener(Events.SortChange, listener); + } + + grid.reconfigure(grid.getStore(), columnModelOfVisible); + + for (Listener<?> listener : sortlisteners) + { + grid.addListener(Events.SortChange, listener); + } + } + + private void changeColumnModel(ColumnModel columnModel, String sortField, SortDir sortDir) + { + fullColumnModel = columnModel; + + int logId = log("grid reconfigure"); + ColumnModel columnModelOfVisible = trimToVisibleColumns(columnModel); + + if (sortDir != null && sortField != null) + { + pagingLoader.setSortDir(translate(sortDir)); + pagingLoader.setSortField(sortField); + } + reconfigureGrid(columnModelOfVisible); + + viewContext.logStop(logId); + registerGridSettingsChangesListener(); + // add listeners of full column model to trimmed model + List<Listener<? extends BaseEvent>> listeners = + fullColumnModel.getListeners(Events.WidthChange); + for (Listener<? extends BaseEvent> listener : listeners) + { + columnModelOfVisible.addListener(Events.WidthChange, listener); + columnModelOfVisible.addListener(Events.ColumnMove, listener); // track drag&drop + } + } + + private ColumnModel trimToVisibleColumns(ColumnModel columnModel) + { + int maxVisibleColumns = getWebClientConfiguration().getMaxVisibleColumns(); + int counter = 0; + List<ColumnConfig> columns = new ArrayList<ColumnConfig>(); + for (int i = 0, n = columnModel.getColumnCount(); i < n; i++) + { + ColumnConfig column = columnModel.getColumn(i); + if (column.isHidden() == false) + { + counter++; + if (counter <= maxVisibleColumns) + { + columns.add(column); + } else + { + column.setHidden(true); + } + } + } + if (counter > maxVisibleColumns) + { + saveColumnDisplaySettings(); // save changes made to full model + InfoConfig infoConfig = + new InfoConfig(viewContext.getMessage(Dict.VISIBLE_COLUMNS_LIMITED_TITLE), + viewContext.getMessage(Dict.VISIBLE_COLUMNS_LIMITED_MSG, + maxVisibleColumns, counter)); + infoConfig.height = 100; // a bit higher + infoConfig.display = 5000; // 5s + Info.display(infoConfig); + } + ColumnModel trimmedModel = createColumnModel(columns); + return trimmedModel; + } + + private void registerGridSettingsChangesListener() + { + viewContext.getDisplaySettingsManager().registerGridSettingsChangesListener( + getGridDisplayTypeID(), createDisplaySettingsUpdater()); + } + + // Refreshes the data, does not clear the cache. Does not change the column model. + // Default visibility so that friend classes can use -- should otherwise be considered private + void reloadData(ResultSetFetchConfig<String> resultSetFetchConfig) + { + if (pendingFetchManager.hasPendingFetch()) + { + debug("Cannot reload the data with the mode '" + resultSetFetchConfig + + "'; there is an unfinished request already: " + + pendingFetchManager.tryTopPendingFetchConfig()); + return; + } + pendingFetchManager.pushPendingFetchConfig(resultSetFetchConfig); + pagingLoader.load(0, PAGE_SIZE); + } + + private IDisplaySettingsGetter createDisplaySettingsUpdater() + { + return new IDisplaySettingsGetter() + { + public ColumnModel getColumnModel() + { + return TypedTableGrid.this.getFullColumnModel(); + } + + public List<String> getFilteredColumnIds() + { + return filterToolbar.extractFilteredColumnIds(); + } + + public Object getModifier() + { + return TypedTableGrid.this; + } + + public SortInfo getSortState() + { + return TypedTableGrid.this.getGridSortInfo(); + } + }; + } + + // returns true if some filters have changed + // Default visibility so that friend classes can use -- should otherwise be considered private + boolean rebuildFiltersFromIds(List<String> filteredColumnIds) + { + List<IColumnDefinition<TableModelRowWithObject<T>>> filteredColumns = + getColumnDefinitions(filteredColumnIds); + return filterToolbar.rebuildColumnFilters(filteredColumns); + } + + public List<IColumnDefinition<TableModelRowWithObject<T>>> getColumnDefinitions( + List<String> columnIds) + { + Map<String, IColumnDefinition<TableModelRowWithObject<T>>> colsMap = + asColumnIdMap(columnDefinitions); + List<IColumnDefinition<TableModelRowWithObject<T>>> columns = + new ArrayList<IColumnDefinition<TableModelRowWithObject<T>>>(); + for (String columnId : columnIds) + { + IColumnDefinition<TableModelRowWithObject<T>> colDef = colsMap.get(columnId); + assert colDef != null : "Cannot find a column '" + columnId; + columns.add(colDef); + } + return columns; + } + + protected final String createGridDisplayTypeID(String suffixOrNull) + { + if (displayTypeIDGenerator == null) + { + throw new IllegalStateException("Undefined display type ID generator."); + } + if (suffixOrNull == null) + { + return displayTypeIDGenerator.createID(); + } else + { + return displayTypeIDGenerator.createID(suffixOrNull); + } + } + + private IDataRefreshCallback createRefreshCallback( + IDataRefreshCallback externalRefreshCallbackOrNull) + { + IDataRefreshCallback internalCallback = createInternalPostRefreshCallback(); + if (externalRefreshCallbackOrNull == null) + { + return internalCallback; + } else + { + return mergeCallbacks(internalCallback, externalRefreshCallbackOrNull); + } + } + + private IDataRefreshCallback createInternalPostRefreshCallback() + { + return new IDataRefreshCallback() + { + public void postRefresh(boolean wasSuccessful) + { + if (customColumnsMetadataProvider.getHasChangedAndSetFalse()) + { + recreateColumnModelAndRefreshColumnsWithFilters(); + } + + updateDefaultRefreshButton(); + + if (wasSuccessful) + { + hideLoadingMask(); + pagingToolbar.updateDefaultConfigButton(true); + pagingToolbar.enableExportButton(); + } + } + }; + } + + private static IDataRefreshCallback mergeCallbacks(final IDataRefreshCallback c1, + final IDataRefreshCallback c2) + { + return new IDataRefreshCallback() + { + public void postRefresh(boolean wasSuccessful) + { + c1.postRefresh(wasSuccessful); + c2.postRefresh(wasSuccessful); + } + }; + } + + private List<IColumnDefinition<TableModelRowWithObject<T>>> getVisibleColumns( + Set<IColumnDefinition<TableModelRowWithObject<T>>> availableColumns) + { + Map<String, IColumnDefinition<TableModelRowWithObject<T>>> availableColumnsMap = + asColumnIdMap(availableColumns); + return getVisibleColumns(availableColumnsMap, fullColumnModel); + } + + private void saveCacheKey(final String newResultSetKey) + { + resultSetKeyOrNull = newResultSetKey; + debug("saving new cache key"); + } + + private void disposeCache() + { + if (resultSetKeyOrNull != null) + { + removeResultSet(resultSetKeyOrNull); + resultSetKeyOrNull = null; + } + } + + private void removeResultSet(String resultSetKey) + { + viewContext.getService().removeResultSet(resultSetKey, + new VoidAsyncCallback<Void>(viewContext)); + } + + /** + * Export always deals with data from the previous refresh operation + * + * @param allColumns whether all columns should be exported + */ + private void export(boolean allColumns) + { + export(allColumns, new ExportEntitiesCallback(viewContext)); + } + + /** + * Shows the dialog allowing to configure visibility and order of the table columns. + */ + private void configureColumnSettings() + { + assert grid != null && grid.getColumnModel() != null : "Grid must be loaded"; + ColumnSettingsConfigurer<T> columnSettingsConfigurer = + new ColumnSettingsConfigurer<T>(this, viewContext, filterToolbar, + customColumnsMetadataProvider, resultSetKeyOrNull, + pendingFetchManager.tryTopPendingFetchConfig()); + columnSettingsConfigurer.showDialog(); + } + + // Default visibility so that friend classes can use -- should otherwise be considered private + void saveColumnDisplaySettings() + { + IDisplaySettingsGetter settingsUpdater = createDisplaySettingsUpdater(); + viewContext.getDisplaySettingsManager().storeSettings(getGridDisplayTypeID(), + settingsUpdater, false); + } + + // @Private - for tests + public final void export(boolean allColumns, final AbstractAsyncCallback<String> callback) + { + final TableExportCriteria<TableModelRowWithObject<T>> exportCriteria = + createTableExportCriteria(allColumns); + + prepareExportEntities(exportCriteria, callback); + } + + // for visible columns + protected final TableExportCriteria<TableModelRowWithObject<T>> createTableExportCriteria() + { + return createTableExportCriteria(false); + } + + protected final TableExportCriteria<TableModelRowWithObject<T>> createTableExportCriteria( + boolean allColumns) + { + assert columnDefinitions != null : "refresh before exporting!"; + assert resultSetKeyOrNull != null : "refresh before exporting, resultSetKey is null!"; + + final List<IColumnDefinition<TableModelRowWithObject<T>>> columnDefs = + allColumns ? new ArrayList<IColumnDefinition<TableModelRowWithObject<T>>>( + columnDefinitions) : getVisibleColumns(columnDefinitions); + SortInfo sortInfo = getGridSortInfo(); + final TableExportCriteria<TableModelRowWithObject<T>> exportCriteria = + new TableExportCriteria<TableModelRowWithObject<T>>(resultSetKeyOrNull, sortInfo, + filterToolbar.getFilters(), columnDefs, columnDefinitions, + getGridDisplayTypeID()); + return exportCriteria; + } + + // returns info about sorting in current grid + public SortInfo getGridSortInfo() + { + ListStore<BaseEntityModel<TableModelRowWithObject<T>>> store = grid.getStore(); + return translateSortInfo(store.getSortField(), store.getSortDir()); + } + + /** @return the number of all objects cached in the browser */ + public int getTotalCount() + { + return pagingToolbar.getTotalCount(); + } + + // Default visibility so that friend classes can use -- should otherwise be considered private + void refreshColumnsSettings() + { + grid.setLoadMask(false); + grid.getView().refresh(true); + grid.setLoadMask(true); + } + + protected final void addEntityOperationsLabel() + { + pagingToolbar.addEntityOperationsLabel(); + } + + protected final void addEntityOperationsSeparator() + { + pagingToolbar.add(new SeparatorToolItem()); + } + + protected final GridCellRenderer<BaseEntityModel<?>> createMultilineStringCellRenderer() + { + return new MultilineStringCellRenderer(); + } + + @SuppressWarnings("deprecation") + protected GridCellRenderer<BaseEntityModel<?>> createInternalLinkCellRenderer() + { + // NOTE: this renderer doesn't support special rendering of deleted entities + return LinkRenderer.createLinkRenderer(); + } + + protected WebClientConfiguration getWebClientConfiguration() + { + return viewContext.getModel().getApplicationInfo().getWebClientConfiguration(); + } + + // ------- generic static helpers + + private static <T> List<String> extractColumnIds(List<IColumnDefinition<T>> columns) + { + List<String> columnsIds = new ArrayList<String>(); + for (IColumnDefinition<T> column : columns) + { + columnsIds.add(column.getIdentifier()); + } + return columnsIds; + } + + // Default visibility so that friend classes can use -- should otherwise be considered private + public static List<ColumnDataModel> createColumnsSettingsModel(ColumnModel cm, + List<String> filteredColumnsIds) + { + Set<String> filteredColumnsMap = new HashSet<String>(filteredColumnsIds); + int cols = cm.getColumnCount(); + List<ColumnDataModel> list = new ArrayList<ColumnDataModel>(); + for (int i = 0; i < cols; i++) + { + if (cm.getColumnHeader(i) == null || cm.getColumnHeader(i).equals("") || cm.isFixed(i)) + { + continue; + } + String columnId = cm.getColumnId(i); + boolean isVisible = cm.isHidden(i) == false; + boolean hasFilter = filteredColumnsMap.contains(columnId); + list.add(new ColumnDataModel(cm.getColumnHeader(i), isVisible, hasFilter, columnId)); + } + return list; + } + + private static ContentPanel createEmptyContentPanel() + { + final ContentPanel contentPanel = new ContentPanel(); + contentPanel.setBorders(false); + contentPanel.setBodyBorder(false); + contentPanel.setLayout(new FitLayout()); + return contentPanel; + } + + // creates filter and paging toolbars + private static <T> LayoutContainer createBottomToolbars(final Container<?> parentContainer, + ToolBar pagingToolbar) + { + LayoutContainer bottomToolbars = new ContainerKeeper(parentContainer); + bottomToolbars.setMonitorWindowResize(true); + bottomToolbars.setLayout(new RowLayout(com.extjs.gxt.ui.client.Style.Orientation.VERTICAL)); + // Adding paging toolbar before data are loaded fixes problems with the toolbar not visible + // if user quickly changes tab / hides section before it is layouted. On the other hand + // it is slower than adding it after requesting server for data. + bottomToolbars.add(pagingToolbar, new RowData(1, -1)); + // filter toolbar is added on request + return bottomToolbars; + } + + private static final class ContainerKeeper extends LayoutContainer + { + private final Container<?> parentContainer; + + private ContainerKeeper(Container<?> parentContainer) + { + this.parentContainer = parentContainer; + } + + @Override + protected void onWindowResize(int aWidth, int aHeight) + { + super.onWindowResize(aWidth, aHeight); + if (isVisible()) + { + this.setWidth(parentContainer.getWidth()); + } + } + } + + private Grid<BaseEntityModel<TableModelRowWithObject<T>>> createGrid( + PagingLoader<PagingLoadResult<BaseEntityModel<TableModelRowWithObject<T>>>> dataLoader, + String gridId) + { + + ListStore<BaseEntityModel<TableModelRowWithObject<T>>> listStore = + new ListStore<BaseEntityModel<TableModelRowWithObject<T>>>(dataLoader); + ColumnModel columnModel = createColumnModel(new ArrayList<ColumnConfig>()); + EditorGrid<BaseEntityModel<TableModelRowWithObject<T>>> editorGrid = + new EditorGrid<BaseEntityModel<TableModelRowWithObject<T>>>(listStore, columnModel); + editorGrid.setId(gridId); + editorGrid.setLoadMask(true); + editorGrid + .setSelectionModel(new GridSelectionModel<BaseEntityModel<TableModelRowWithObject<T>>>()); + editorGrid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + editorGrid.setView(new ExtendedGridView()); + editorGrid.setStripeRows(true); + editorGrid.setColumnReordering(true); + editorGrid.setClicksToEdit(ClicksToEdit.TWO); + editorGrid.addListener(Events.BeforeEdit, + new Listener<GridEvent<BaseEntityModel<TableModelRowWithObject<T>>>>() + { + public void handleEvent( + GridEvent<BaseEntityModel<TableModelRowWithObject<T>>> event) + { + if (viewContext.isSimpleOrEmbeddedMode()) + { + MessageBox + .info("Not Allowed", + "Sorry, table cell editing is not allowed in current viewing mode", + null); + event.setCancelled(true); + } else + { + BaseEntityModel<TableModelRowWithObject<T>> model = + event.getModel(); + String columnID = event.getProperty(); + boolean editable = isEditable(model, columnID); + if (editable == false) + { + showNonEditableTableCellMessage(model, columnID); + } + event.setCancelled(editable == false); + } + } + }); + editorGrid.addListener(Events.AfterEdit, + new Listener<GridEvent<BaseEntityModel<TableModelRowWithObject<T>>>>() + { + public void handleEvent( + GridEvent<BaseEntityModel<TableModelRowWithObject<T>>> event) + { + BaseEntityModel<TableModelRowWithObject<T>> model = event.getModel(); + String columnID = event.getProperty(); + Object value = event.getValue(); + String newValueNotNull = StringUtils.toStringEmptyIfNull(value); + String oldValueNotNull = + StringUtils.toStringEmptyIfNull(event.getStartValue()); + if (oldValueNotNull.equals(newValueNotNull)) + { + event.setCancelled(true); + } else + { + showModificationsBar(); + if (value instanceof VocabularyTerm) + { + VocabularyTerm term = (VocabularyTerm) value; + value = term.getCode(); + } + tableModificationsManager.handleEditingEvent(model, columnID, + StringUtils.toStringOrNull(value)); + } + } + }); + editorGrid.addListener(Events.SortChange, new Listener<BaseEvent>() + { + public void handleEvent(BaseEvent be) + { + saveColumnDisplaySettings(); + } + }); + + return editorGrid; + } + + /** + * Returns <code>true</code> if cell specified by model and column ID is editable. Default + * implementation false. */ - private static final int MAX_SHOWN_COLUMNS = 200; + protected boolean isEditable(BaseEntityModel<TableModelRowWithObject<T>> model, String columnID) + { + return false; + } /** - * If user selected some entities in given browser first a dialog is shown where he can select - * between showing data sets related to selected/displayed entities. Then a tab is displayed - * where these related data sets are listed.<br> - * <br> - * If no entities were selected in given browser the tab is displayed where data sets related to - * all entities displayed in the grid are listed. + * Shows a message that the table cell of specified column and row (model) isn't editable. */ - protected static final <E extends IEntityInformationHolder> void showRelatedDataSets( - final IViewContext<ICommonClientServiceAsync> viewContext, - final TypedTableGrid<E> browser) + protected void showNonEditableTableCellMessage( + BaseEntityModel<TableModelRowWithObject<T>> model, String columnID) { - final List<TableModelRowWithObject<E>> selectedEntities = browser.getSelectedBaseObjects(); - final TableExportCriteria<TableModelRowWithObject<E>> displayedEntities = - browser.createTableExportCriteria(); - if (selectedEntities.isEmpty()) - { - // no entity selected - show datasets related to all displayed - RelatedDataSetCriteria<E> relatedCriteria = - RelatedDataSetCriteria.<E> createDisplayedEntities(displayedEntities); - ShowRelatedDatasetsDialog.showRelatedDatasetsTab(viewContext, relatedCriteria); - } else + MessageBox.info("Not Editable", "Sorry, this table cell isn't editable", null); + } + + /** + * Tries to return the property of specified properties holder which is specified by the + * property column name without a prefix like <code>property-</code> but with prefix which + * distinguishes internal from externally name space. + */ + protected IEntityProperty tryGetProperty(IEntityPropertiesHolder propertiesHolder, + String propertyColumnNameWithoutPrefix) + { + String propertyTypeCode = + CodeConverter.getPropertyTypeCode(propertyColumnNameWithoutPrefix); + List<IEntityProperty> properties = propertiesHolder.getProperties(); + for (IEntityProperty property : properties) { - // > 0 entity selected - show dialog with all/selected radio - new ShowRelatedDatasetsDialog<E>(viewContext, selectedEntities, displayedEntities, - browser.getTotalCount()).show(); + if (property.getPropertyType().getCode().equals(propertyTypeCode)) + { + return property; + } } + return null; } - private final class CellListenerAndLinkGenerator implements ICellListenerAndLinkGenerator<T> + // this should be the only place where we create the grid column model. + private static ColumnModel createColumnModel(List<ColumnConfig> columConfigs) { - private final EntityKind entityKind; + return new ColumnModel(columConfigs); + } - private final TableModelColumnHeader header; + public ColumnModel getFullColumnModel() + { + return fullColumnModel; + } - private CellListenerAndLinkGenerator(EntityKind entityKind, TableModelColumnHeader header) + private static final class ExportEntitiesCallback extends AbstractAsyncCallback<String> + { + public ExportEntitiesCallback(final IViewContext<ICommonClientServiceAsync> viewContext) { - this.entityKind = entityKind; - this.header = header; + super(viewContext); } - public String tryGetLink(T entity, final ISerializableComparable value) + @Override + protected void process(final String exportDataKey) { - if (value == null || value.toString().length() == 0) + final URLMethodWithParameters methodWithParameters = + new URLMethodWithParameters( + GenericConstants.FILE_EXPORTER_DOWNLOAD_SERVLET_NAME); + methodWithParameters.addParameter(GenericConstants.EXPORT_CRITERIA_KEY_PARAMETER, + exportDataKey); + WindowUtils.openWindow(methodWithParameters.toString()); + } + + } + + // creates a map to quickly find a definition by its identifier + private static <T> Map<String, IColumnDefinition<T>> asColumnIdMap( + final Set<IColumnDefinition<T>> defs) + { + final Map<String, IColumnDefinition<T>> map = new HashMap<String, IColumnDefinition<T>>(); + for (final IColumnDefinition<T> def : defs) + { + map.put(def.getIdentifier(), def); + } + return map; + } + + /** + * @param availableColumns map of all available columns definitions. + * @param columnModel describes the visual properties of the columns. Connected with + * availableColumns by column id. + * @return list of columns definitions for those columns which are currently shown + */ + private static <T/* column definition */> List<T> getVisibleColumns( + final Map<String, T> availableColumns, final ColumnModel columnModel) + { + final List<T> selectedColumnDefs = new ArrayList<T>(); + final int columnCount = columnModel.getColumnCount(); + for (int i = 0; i < columnCount; i++) + { + if (columnModel.isHidden(i) == false) { - return null; + final String columnId = columnModel.getColumnId(i); + selectedColumnDefs.add(availableColumns.get(columnId)); } - if (value instanceof EntityTableCell) + } + return selectedColumnDefs; + } + + /** Creates callback that refreshes the grid. */ + protected final AbstractAsyncCallback<Void> createRefreshCallback( + IBrowserGridActionInvoker invoker) + { + return new RefreshCallback(viewContext, invoker); + } + + /** Callback that refreshes the grid. */ + private static final class RefreshCallback extends AbstractAsyncCallback<Void> + { + private final IBrowserGridActionInvoker invoker; + + public RefreshCallback(IViewContext<?> viewContext, IBrowserGridActionInvoker invoker) + { + super(viewContext); + this.invoker = invoker; + } + + @Override + protected void process(Void result) + { + invoker.refresh(); + } + } + + /** {@link SelectionListener} that creates a dialog with selected data items. */ + protected abstract class AbstractCreateDialogListener extends SelectionListener<ButtonEvent> + { + @Override + public void componentSelected(ButtonEvent ce) + { + List<TableModelRowWithObject<T>> data = getSelectedBaseObjects(); + IBrowserGridActionInvoker invoker = asActionInvoker(); + if (validateSelectedData(data)) { - EntityTableCell entityTableCell = (EntityTableCell) value; - if (entityTableCell.isMissing() || entityTableCell.isFake()) - { - return null; - } - String permId = entityTableCell.getPermId(); - if (entityTableCell.getEntityKind() == EntityKind.MATERIAL) - { - return LinkExtractor.tryExtract(MaterialIdentifier.tryParseIdentifier(permId)); - } else - { - return LinkExtractor.createPermlink(entityTableCell.getEntityKind(), permId); - } + createDialog(data, invoker).show(); + } + } + + /** + * If specified data is valid returns true, otherwise returns false. Dialog will be shown + * only if this method returns true. Default implementation always returns true. + */ + protected boolean validateSelectedData(List<TableModelRowWithObject<T>> data) + { + return true; + } + + protected abstract Dialog createDialog(List<TableModelRowWithObject<T>> data, + IBrowserGridActionInvoker invoker); + } + + // + // Table Modifications + // + + /** + * Apply specified modifications to the model. Should be overriden by subclasses. Default + * implementation does nothing. + */ + protected void applyModifications(BaseEntityModel<TableModelRowWithObject<T>> model, + List<IModification> modifications) + { + + } + + private class TableModificationsManager implements + ITableModificationsManager<BaseEntityModel<TableModelRowWithObject<T>>> + { + private final Map<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> modificationsByModel = + new LinkedHashMap<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>>(); + + private final Map<BaseEntityModel<TableModelRowWithObject<T>>, String> failedModifications = + new HashMap<BaseEntityModel<TableModelRowWithObject<T>>, String>(); + private int finishedModifications = 0; + + private IDelegatedAction afterSaveActionOrNull; + + // + // ITableModificationsManager + // + + // @Override + public boolean isTableDirty() + { + return isApplyModificationsComplete() == false; + } + + public void saveModifications() + { + saveModifications(null); + } + + private void setAfterSaveAction(IDelegatedAction afterSaveAction) + { + this.afterSaveActionOrNull = afterSaveAction; + } + + public void saveModifications(IDelegatedAction afterSaveAction) + { + setAfterSaveAction(afterSaveAction); + finishedModifications = 0; + for (Entry<BaseEntityModel<TableModelRowWithObject<T>>, List<IModification>> entry : modificationsByModel + .entrySet()) + { + applyModifications(entry.getKey(), entry.getValue()); } - return LinkExtractor.createPermlink(entityKind, value.toString()); } - public void handle(TableModelRowWithObject<T> rowItem, boolean specialKeyPressed) + public void cancelModifications() { - ISerializableComparable cellValue = rowItem.getValues().get(header.getIndex()); - if (cellValue instanceof EntityTableCell) + clearModifications(); + grid.getStore().rejectChanges(); + refresh(); // WORKAROUND remove this refresh after LMS-2397 is resolved + } + + public void handleEditingEvent(BaseEntityModel<TableModelRowWithObject<T>> model, + String columnID, String newValueOrNull) + { + List<IModification> modificationsForModel = modificationsByModel.get(model); + if (modificationsForModel == null) { - EntityTableCell entityTableCell = (EntityTableCell) cellValue; - String permId = entityTableCell.getPermId(); - if (entityTableCell.getEntityKind() == EntityKind.MATERIAL) - { - MaterialIdentifier materialIdentifier = - MaterialIdentifier.tryParseIdentifier(permId); - OpenEntityDetailsTabHelper.open(viewContext, materialIdentifier, - specialKeyPressed); - } else + modificationsForModel = new ArrayList<IModification>(); + modificationsByModel.put(model, modificationsForModel); + } + modificationsForModel.add(new Modification(columnID, newValueOrNull)); + } + + public AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback( + final BaseEntityModel<TableModelRowWithObject<T>> model, + final List<IModification> modifications) + { + return new AbstractAsyncCallback<EntityPropertyUpdatesResult>(viewContext) { - OpenEntityDetailsTabHelper.open(viewContext, entityTableCell.getEntityKind(), - permId, specialKeyPressed); - } + @Override + protected void process(EntityPropertyUpdatesResult result) + { + finishedModifications++; + String errorMessage = result.tryGetErrorMessage(); + if (errorMessage != null) + { + handleError(errorMessage); + } + if (isApplyModificationsComplete()) + { + onApplyModificationsComplete(); + } + } + + @Override + public void finishOnFailure(Throwable caught) + { + finishedModifications++; + handleError(caught.getMessage()); + if (isApplyModificationsComplete()) + { + onApplyModificationsComplete(); + } + } + + private void handleError(String errorMessage) + { + failedModifications.put(model, errorMessage); + } + }; + } + + // + + private boolean isApplyModificationsComplete() + { + return finishedModifications == modificationsByModel.size(); + } + + private void onApplyModificationsComplete() + { + if (failedModifications.size() > 0) + { + String failureTitle = + (failedModifications.size() == modificationsByModel.size()) ? "Operation failed" + : "Operation partly failed"; + String failureReport = createFailedModificationsReport(); + MessageBox.alert(failureTitle, failureReport, null); + refresh(); } else { - OpenEntityDetailsTabHelper.open(viewContext, entityKind, cellValue.toString(), - specialKeyPressed); + GWTUtils.displayInfo("All modifications successfully applied."); + grid.getStore().commitChanges(); // no need to refresh - everything should be valid + } + clearModifications(); + if (afterSaveActionOrNull != null) + { + afterSaveActionOrNull.execute(); } } - } - private final Map<String, ICellListenerAndLinkGenerator<T>> listenerLinkGenerators = - new HashMap<String, ICellListenerAndLinkGenerator<T>>(); - - private List<TableModelColumnHeader> headers; + private String createFailedModificationsReport() + { + assert failedModifications.size() > 0; + StringBuilder result = new StringBuilder(); + result.append("Modifications of " + failedModifications.size() + " entities failed:"); + for (String error : failedModifications.values()) + { + result.append("<br/>- " + error); + } + return result.toString(); + } - private List<IColumnDefinitionUI<TableModelRowWithObject<T>>> columnUIDefinitions; + private void clearModifications() + { + finishedModifications = 0; + failedModifications.clear(); + modificationsByModel.clear(); + hideModificationsBar(); + } - private String downloadURL; + } - private Map<String, IColumnDefinition<TableModelRowWithObject<T>>> columnDefinitions; + /** Toolbar for handling table modifications */ + static class TableModificationsToolbar extends ToolBar + { - private String currentGridDisplayTypeID; + public TableModificationsToolbar(final IMessageProvider messageProvider, + final ITableModificationsManager<?> manager) + { + add(new Label(messageProvider.getMessage(Dict.TABLE_MODIFICATIONS))); - private List<IColumnDefinitionUI<TableModelRowWithObject<T>>> visibleColDefinitions; + final AbstractImagePrototype confirmIcon = + AbstractImagePrototype.create(IMAGE_BUNDLE.getConfirmIcon()); + final AbstractImagePrototype cancelIcon = + AbstractImagePrototype.create(IMAGE_BUNDLE.getCancelIcon()); + add(new Button("Save", confirmIcon, new SelectionListener<ButtonEvent>() + { + @Override + public void componentSelected(ButtonEvent be) + { + manager.saveModifications(); + } + })); + add(new Button("Cancel", cancelIcon, new SelectionListener<ButtonEvent>() + { + @Override + public void componentSelected(ButtonEvent be) + { + manager.cancelModifications(); + } + })); + } - protected TypedTableGrid(IViewContext<ICommonClientServiceAsync> viewContext, String browserId, - boolean refreshAutomatically, IDisplayTypeIDGenerator displayTypeIDGenerator) - { - super(viewContext, browserId + GRID_POSTFIX, refreshAutomatically, displayTypeIDGenerator); - setId(browserId); } - protected TypedTableGrid(IViewContext<ICommonClientServiceAsync> viewContext, String browserId, - IDisplayTypeIDGenerator displayTypeIDGenerator) + /** + * To be subclassed if columns in the grid depend on the internal grid configuration. + * + * @return id at which grid display settings are saved. + */ + public String getGridDisplayTypeID() { - super(viewContext, browserId + GRID_POSTFIX, displayTypeIDGenerator); - setId(browserId); + return createGridDisplayTypeID(null); } protected void setDownloadURL(String downloadURL) @@ -214,17 +2450,22 @@ public abstract class TypedTableGrid<T extends Serializable> DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig, AbstractAsyncCallback<TypedTableResultSet<T>> callback); - @Override + /** + * Creates a column model with all available columns from scratch without taking user settings + * into account. + * + * @return definition of all the columns in the grid + */ protected ColumnDefsAndConfigs<TableModelRowWithObject<T>> createColumnsDefinition() { ColumnDefsAndConfigs<TableModelRowWithObject<T>> definitions = ColumnDefsAndConfigs.create(createColDefinitions(), viewContext); Set<IColumnDefinition<TableModelRowWithObject<T>>> columnDefs = definitions.getColumnDefs(); - columnDefinitions = new HashMap<String, IColumnDefinition<TableModelRowWithObject<T>>>(); + columnDefinitionsMap = new HashMap<String, IColumnDefinition<TableModelRowWithObject<T>>>(); for (IColumnDefinition<TableModelRowWithObject<T>> definition : columnDefs) { String identifier = definition.getIdentifier(); - columnDefinitions.put(identifier, definition); + columnDefinitionsMap.put(identifier, definition); } if (headers != null) { @@ -276,7 +2517,13 @@ public abstract class TypedTableGrid<T extends Serializable> } } - @Override + /** + * Initializes creation of model from received data. This is a hook method called before { + * {@link #createModel(GridRowModel)} is invoked for all rows. This implementation does nothing. + * Subclasses usually override this method by creating an instance attribute which holds a list + * of visible column definitions. This speeds up invocation of + * {@link #createModel(GridRowModel)}. + */ protected void initializeModelCreation() { Set<String> visibleColumnIds = getIDsOfVisibleColumns(); @@ -292,7 +2539,7 @@ public abstract class TypedTableGrid<T extends Serializable> } } - @Override + /** Converts specified entity into a grid row model */ protected BaseEntityModel<TableModelRowWithObject<T>> createModel( GridRowModel<TableModelRowWithObject<T>> entity) { @@ -368,7 +2615,6 @@ public abstract class TypedTableGrid<T extends Serializable> return getId() + "_" + columnID; } - @Override protected void listEntities( final DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig, final AbstractAsyncCallback<ResultSet<TableModelRowWithObject<T>>> callback) @@ -399,7 +2645,7 @@ public abstract class TypedTableGrid<T extends Serializable> currentGridDisplayTypeID = getGridDisplayTypeID(); } - @Override + /** @return should the refresh button be enabled? */ protected boolean isRefreshEnabled() { return true; @@ -412,20 +2658,21 @@ public abstract class TypedTableGrid<T extends Serializable> * set of display settings. Thus column models and filters should be refreshed before data * loading. */ - @Override protected void refresh() { String gridDisplayTypeID = getGridDisplayTypeID(); refresh(gridDisplayTypeID.equals(currentGridDisplayTypeID) == false); } - @Override + /** + * Shows the detail view for the specified entity + */ protected void showEntityViewer(TableModelRowWithObject<T> entity, boolean editMode, boolean inBackground) { } - @Override + /** @return on which fields filters should be switched on by default? */ protected List<IColumnDefinition<TableModelRowWithObject<T>>> getInitialFilters() { @@ -434,7 +2681,7 @@ public abstract class TypedTableGrid<T extends Serializable> List<String> ids = getColumnIdsOfFilters(); for (String id : ids) { - IColumnDefinition<TableModelRowWithObject<T>> definition = columnDefinitions.get(id); + IColumnDefinition<TableModelRowWithObject<T>> definition = columnDefinitionsMap.get(id); if (definition != null) { definitions.add(definition); @@ -460,7 +2707,7 @@ public abstract class TypedTableGrid<T extends Serializable> protected List<T> getContainedGridElements() { - List<TableModelRowWithObject<T>> wrappedElements = super.getGridElements(); + List<TableModelRowWithObject<T>> wrappedElements = getGridElements(); List<T> elements = new ArrayList<T>(); for (TableModelRowWithObject<T> wrappedElement : wrappedElements) { diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java deleted file mode 100644 index c2778327d27..00000000000 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractBrowserGrid.java +++ /dev/null @@ -1,2283 +0,0 @@ -/* - * Copyright 2009 ETH Zuerich, CISD - * - * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.extjs.gxt.ui.client.GXT; -import com.extjs.gxt.ui.client.Style.Orientation; -import com.extjs.gxt.ui.client.Style.SelectionMode; -import com.extjs.gxt.ui.client.data.BasePagingLoadConfig; -import com.extjs.gxt.ui.client.data.BasePagingLoadResult; -import com.extjs.gxt.ui.client.data.BasePagingLoader; -import com.extjs.gxt.ui.client.data.Loader; -import com.extjs.gxt.ui.client.data.ModelData; -import com.extjs.gxt.ui.client.data.PagingLoadConfig; -import com.extjs.gxt.ui.client.data.PagingLoadResult; -import com.extjs.gxt.ui.client.data.PagingLoader; -import com.extjs.gxt.ui.client.data.RpcProxy; -import com.extjs.gxt.ui.client.event.BaseEvent; -import com.extjs.gxt.ui.client.event.ButtonEvent; -import com.extjs.gxt.ui.client.event.Events; -import com.extjs.gxt.ui.client.event.GridEvent; -import com.extjs.gxt.ui.client.event.Listener; -import com.extjs.gxt.ui.client.event.MessageBoxEvent; -import com.extjs.gxt.ui.client.event.SelectionChangedEvent; -import com.extjs.gxt.ui.client.event.SelectionListener; -import com.extjs.gxt.ui.client.store.ListStore; -import com.extjs.gxt.ui.client.widget.Component; -import com.extjs.gxt.ui.client.widget.Container; -import com.extjs.gxt.ui.client.widget.ContentPanel; -import com.extjs.gxt.ui.client.widget.Dialog; -import com.extjs.gxt.ui.client.widget.Info; -import com.extjs.gxt.ui.client.widget.InfoConfig; -import com.extjs.gxt.ui.client.widget.Label; -import com.extjs.gxt.ui.client.widget.LayoutContainer; -import com.extjs.gxt.ui.client.widget.MessageBox; -import com.extjs.gxt.ui.client.widget.button.Button; -import com.extjs.gxt.ui.client.widget.grid.ColumnConfig; -import com.extjs.gxt.ui.client.widget.grid.ColumnModel; -import com.extjs.gxt.ui.client.widget.grid.EditorGrid; -import com.extjs.gxt.ui.client.widget.grid.EditorGrid.ClicksToEdit; -import com.extjs.gxt.ui.client.widget.grid.Grid; -import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer; -import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel; -import com.extjs.gxt.ui.client.widget.layout.FitLayout; -import com.extjs.gxt.ui.client.widget.layout.RowData; -import com.extjs.gxt.ui.client.widget.layout.RowLayout; -import com.extjs.gxt.ui.client.widget.menu.Menu; -import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar; -import com.extjs.gxt.ui.client.widget.toolbar.SeparatorToolItem; -import com.extjs.gxt.ui.client.widget.toolbar.ToolBar; -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.AbstractImagePrototype; - -import ch.systemsx.cisd.common.shared.basic.utils.StringUtils; -import ch.systemsx.cisd.openbis.generic.client.web.client.ICommonClientServiceAsync; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.IGenericImageBundle; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.VoidAsyncCallback; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.AbstractTabItemFactory; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DispatcherHelper; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager.GridDisplaySettings; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDatabaseModificationObserver; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDisplaySettingsGetter; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.IDisplayTypeIDGenerator; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.BaseEntityModel; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IClientPlugin; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IClientPluginFactory; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.MultilineStringCellRenderer; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.RealNumberRenderer; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.customcolumn.core.CustomColumnStringRenderer; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.ComponentEventLogger; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.ComponentEventLogger.EventPair; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.IColumnDefinitionUI; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.specific.GridCustomColumnDefinition; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.BrowserGridPagingToolBar.PagingToolBarButtonKind; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.expressions.filter.FilterToolbar; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityEditorTabClickListener; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.IDataRefreshCallback; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils.DisplayInfoTime; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.WindowUtils; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.lang.StringEscapeUtils; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.Constants; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DefaultResultSetConfig; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.EntityPropertyUpdatesResult; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridCustomColumnInfo; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridFilters; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridRowModels; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConfig; -import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TableExportCriteria; -import ch.systemsx.cisd.openbis.generic.shared.basic.CodeConverter; -import ch.systemsx.cisd.openbis.generic.shared.basic.GridRowModel; -import ch.systemsx.cisd.openbis.generic.shared.basic.IColumnDefinition; -import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId; -import ch.systemsx.cisd.openbis.generic.shared.basic.URLMethodWithParameters; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BasicEntityType; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ColumnSetting; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SortInfo.SortDir; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.VocabularyTerm; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebClientConfiguration; - -/** - * @author Tomasz Pylak - */ -public abstract class AbstractBrowserGrid<T/* Entity */, M extends BaseEntityModel<T>> extends - LayoutContainer implements IDatabaseModificationObserver, IDisplayTypeIDProvider, - IColumnDefinitionProvider<T> -{ - private static final IGenericImageBundle IMAGE_BUNDLE = GWT - .<IGenericImageBundle> create(IGenericImageBundle.class); - - /** - * Shows the detail view for the specified entity - */ - abstract protected void showEntityViewer(T entity, boolean editMode, boolean inBackground); - - abstract protected void listEntities(DefaultResultSetConfig<String, T> resultSetConfig, - AbstractAsyncCallback<ResultSet<T>> callback); - - /** Converts specified entity into a grid row model */ - abstract protected M createModel(GridRowModel<T> entity); - - /** - * Called when user wants to export the data. It can happen only after a previous refresh of the - * data has taken place. The export criteria has only the cache key - */ - abstract protected void prepareExportEntities(TableExportCriteria<T> exportCriteria, - AbstractAsyncCallback<String> callback); - - /** - * Creates a column model with all available columns from scratch without taking user settings - * into account. - * - * @return definition of all the columns in the grid - */ - abstract protected ColumnDefsAndConfigs<T> createColumnsDefinition(); - - /** - * Called when the user wants to refresh the data. Should be implemented by calling - * {@link #refresh(IDataRefreshCallback, boolean) at some point. } - */ - abstract protected void refresh(); - - /** @return should the refresh button be enabled? */ - abstract protected boolean isRefreshEnabled(); - - /** @return on which fields filters should be switched on by default? */ - abstract protected List<IColumnDefinition<T>> getInitialFilters(); - - /** - * To be subclassed if columns in the grid depend on the internal grid configuration. - * - * @return id at which grid display settings are saved. - */ - public String getGridDisplayTypeID() - { - return createGridDisplayTypeID(null); - } - - // -------- - - protected final IViewContext<ICommonClientServiceAsync> viewContext; - - protected final ICellListener<T> showEntityViewerLinkClickListener; - - protected final ITableModificationsManager<M> tableModificationsManager; - - // ------ private section. NOTE: it should remain unaccessible to subclasses! --------------- - - private static final int PAGE_SIZE = Constants.GRID_PAGE_SIZE; - - // set to true to see some useful debugging messages - private static final boolean DEBUG = false; - - private final PagingLoader<PagingLoadResult<M>> pagingLoader; - - private final ContentPanel contentPanel; - - private final Grid<M> grid; - - private final ColumnListener<T, M> columnListener; - - private final boolean refreshAutomatically; - - // the toolbar has the refresh and export buttons besides the paging controls - private final BrowserGridPagingToolBar pagingToolbar; - - // used to change displayed filter widgets - private final FilterToolbar<T> filterToolbar; - - private final ToolBar modificationsToolbar; - - private final IDisplayTypeIDGenerator displayTypeIDGenerator; - - // --------- private non-final fields - - // available columns definitions - private Set<IColumnDefinition<T>> columnDefinitions; - - private final CustomColumnsMetadataProvider customColumnsMetadataProvider; - - // result set key of the last refreshed data - private String resultSetKeyOrNull; - - // Keeps track of the pending fetch (only tracks 1) - private final PendingFetchManager pendingFetchManager; - - private IDataRefreshCallback refreshCallback; - - private LayoutContainer bottomToolbars; - - private ColumnModel fullColumnModel; - - protected AbstractBrowserGrid(final IViewContext<ICommonClientServiceAsync> viewContext, - String gridId, IDisplayTypeIDGenerator displayTypeIDGenerator) - { - this(viewContext, gridId, false, displayTypeIDGenerator); - } - - /** - * @param refreshAutomatically should the data be automatically loaded when the grid is rendered - * for the first time? - * @param gridId unique id of the grid which will can used by testframework to identify the grid - * and identify the callback which fills it in. - */ - protected AbstractBrowserGrid(IViewContext<ICommonClientServiceAsync> viewContext, - String gridId, boolean refreshAutomatically, - IDisplayTypeIDGenerator displayTypeIDGenerator) - { - pendingFetchManager = new PendingFetchManager(); - this.displayTypeIDGenerator = displayTypeIDGenerator; - this.viewContext = viewContext; - int logID = log("create browser grid " + gridId); - this.refreshAutomatically = refreshAutomatically; - this.pagingLoader = createPagingLoader(); - this.customColumnsMetadataProvider = new CustomColumnsMetadataProvider(); - this.grid = createGrid(pagingLoader, gridId); - // WORKAROUND - // Lazy loading of rows causes tests using experiment browser fail (selection of - // project in project tree grid doesn't work). - // Turning it off for all grids is the safest solution for our system tests framework - // and should improve GUI speed in development mode a bit. - grid.setLazyRowRender(0); - this.pagingToolbar = - new BrowserGridPagingToolBar(asActionInvoker(), viewContext, PAGE_SIZE, gridId); - pagingToolbar.bind(pagingLoader); - this.filterToolbar = - new FilterToolbar<T>(viewContext, gridId, this, createApplyFiltersDelagator()); - this.tableModificationsManager = new TableModificationsManager(); - this.modificationsToolbar = - new TableModificationsToolbar(viewContext, tableModificationsManager); - - this.contentPanel = createEmptyContentPanel(); - bottomToolbars = createBottomToolbars(contentPanel, pagingToolbar); - configureBottomToolbarSyncSize(); - contentPanel.add(grid); - contentPanel.setBottomComponent(bottomToolbars); - contentPanel.setHeaderVisible(false); - columnListener = new ColumnListener<T, M>(grid); - showEntityViewerLinkClickListener = createShowEntityViewerLinkClickListener(); - registerLinkClickListenerFor(Dict.CODE, showEntityViewerLinkClickListener); - setLayout(new FitLayout()); - add(contentPanel); - - configureLoggingBetweenEvents(logID); - - grid.addListener(Events.HeaderContextMenu, new Listener<GridEvent<ModelData>>() - { - public void handleEvent(final GridEvent<ModelData> ge) - { - Menu menu = ge.getMenu(); - int itemCount = menu.getItemCount(); - for (int i = 2; i < itemCount; i++) - { - menu.remove(menu.getItem(2)); - } - } - }); - if (viewContext.getModel().isEmbeddedMode()) - { - removeButtons(PagingToolBarButtonKind.CONFIG, PagingToolBarButtonKind.REFRESH); - } - } - - public void removeButtons(PagingToolBarButtonKind... buttonKinds) - { - pagingToolbar.removeButtons(buttonKinds); - } - - private ICellListener<T> createShowEntityViewerLinkClickListener() - { - return new ICellListener<T>() - { - public void handle(T rowItem, boolean keyPressed) - { - showEntityViewer(rowItem, false, keyPressed); - } - }; - } - - private void configureBottomToolbarSyncSize() - { - // fixes problems with: - // - no 'overflow' button when some buttons don't fit into pagingToolbar - pagingLoader.addListener(Loader.Load, new Listener<BaseEvent>() - { - public void handleEvent(BaseEvent be) - { - pagingToolbar.syncSize(); - } - }); - // - hidden paging toolbar - pagingToolbar.addListener(Events.AfterLayout, new Listener<BaseEvent>() - { - public void handleEvent(BaseEvent be) - { - contentPanel.syncSize(); - } - }); - // - bottom toolbar is not resized when new filter row appears - filterToolbar.addListener(Events.AfterLayout, new Listener<BaseEvent>() - { - public void handleEvent(BaseEvent be) - { - contentPanel.syncSize(); - } - }); - } - - private void configureLoggingBetweenEvents(int logID) - { - if (viewContext.isLoggingEnabled()) - { - ComponentEventLogger logger = new ComponentEventLogger(viewContext, getId()); - logger.prepareLoggingBetweenEvents(contentPanel, EventPair.RENDER); - logger.prepareLoggingBetweenEvents(this, EventPair.LAYOUT); - logger.prepareLoggingBetweenEvents(grid, EventPair.LAYOUT); - logger.prepareLoggingBetweenEvents(contentPanel, EventPair.LAYOUT); - logger.prepareLoggingBetweenEvents(bottomToolbars, EventPair.LAYOUT); - logger.prepareLoggingBetweenEvents(filterToolbar, EventPair.LAYOUT); - logger.prepareLoggingBetweenEvents(pagingToolbar, EventPair.LAYOUT); - viewContext.logStop(logID); - } - } - - protected int log(String message) - { - return viewContext.log(message + " [" + getId() + "]"); - } - - protected void showEntityInformationHolderViewer(IEntityInformationHolderWithPermId entity, - boolean editMode, boolean inBackground) - { - if (editMode - && OpenEntityEditorTabClickListener.forbidDeletedEntityModification(viewContext, - entity)) - { - return; - } - final EntityKind entityKind = entity.getEntityKind(); - final AbstractTabItemFactory tabView; - BasicEntityType entityType = entity.getEntityType(); - final IClientPluginFactory clientPluginFactory = - viewContext.getClientPluginFactoryProvider().getClientPluginFactory(entityKind, - entityType); - final IClientPlugin<BasicEntityType, IEntityInformationHolderWithPermId> createClientPlugin = - clientPluginFactory.createClientPlugin(entityKind); - if (editMode) - { - tabView = createClientPlugin.createEntityEditor(entity); - } else - { - tabView = createClientPlugin.createEntityViewer(entity); - } - tabView.setInBackground(inBackground); - DispatcherHelper.dispatchNaviEvent(tabView); - } - - /** Refreshes the grid without showing the loading progress bar */ - protected final void refreshGridSilently() - { - grid.setLoadMask(false); - int id = log("refresh silently"); - refresh(); - grid.setLoadMask(true); - viewContext.logStop(id); - } - - /** - * Shows/hides the load mask immediately. - * - * @param loadMask Load mask is shown if <code>true</code> otherwise it is hidden. - */ - public void setLoadMaskImmediately(boolean loadMask) - { - if (grid.isRendered()) - { - if (loadMask) - { - grid.el().mask(GXT.MESSAGES.loadMask_msg()); - } else - { - grid.el().unmask(); - } - } - - } - - /** - * Registers the specified listener for clicks on links in the specified column. - * - * @param columnID Column ID. Not case sensitive. - * @param listener Listener handle single clicks. - */ - protected final void registerLinkClickListenerFor(final String columnID, - final ICellListener<T> listener) - { - if (viewContext.isSimpleOrEmbeddedMode() == false) - { - columnListener.registerLinkClickListener(columnID, listener); - } - } - - /** - * Allows multiple selection instead of single selection. - */ - protected final void allowMultipleSelection() - { - grid.getSelectionModel().setSelectionMode(SelectionMode.MULTI); - } - - protected List<T> getGridElements() - { - List<M> models = grid.getStore().getModels(); - List<T> elements = new ArrayList<T>(); - for (M model : models) - { - elements.add(model.getBaseObject()); - } - return elements; - } - - private IDelegatedAction createApplyFiltersDelagator() - { - return new IDelegatedAction() - { - public void execute() - { - if (resultSetKeyOrNull != null && pendingFetchManager.hasNoPendingFetch()) - { - ResultSetFetchConfig<String> fetchConfig = - ResultSetFetchConfig.createFetchFromCache(resultSetKeyOrNull); - reloadData(fetchConfig); - } - } - }; - } - - /** @return this grid as a disposable component with a specified toolbar at the top. */ - protected DisposableEntityChooser<T> asDisposableWithToolbar(final IDisposableComponent toolbar) - { - final LayoutContainer container = new LayoutContainer(); - container.setLayout(new RowLayout()); - container.add(toolbar.getComponent()); - container.add(this, new RowData(1, 1)); - - return asDisposableEntityChooser(container, toolbar); - } - - /** @return this grid as a disposable component */ - protected final DisposableEntityChooser<T> asDisposableWithoutToolbar() - { - return asDisposableEntityChooser(this); - } - - /** - * @return this grid as a disposable component with a specified toolbar at the top and a tree on - * the left. - */ - protected final DisposableEntityChooser<T> asDisposableWithToolbarAndTree( - final IDisposableComponent toolbar, final Component tree, String headerOrNull) - { - // WORKAROUND: BorderLayout causes problems when rendered in a tab - // We use RowLayout here but we loose the split this way. - final LayoutContainer container = new LayoutContainer(); - container.setLayout(new RowLayout(Orientation.VERTICAL)); - container.add(toolbar.getComponent(), new RowData(1, -1)); - - final LayoutContainer subContainer = new LayoutContainer(); - subContainer.setLayout(new RowLayout(Orientation.HORIZONTAL)); - subContainer.add(tree, new RowData(300, 1)); - setHeader(headerOrNull); - subContainer.add(this, new RowData(1, 1)); - container.add(subContainer, new RowData(1, 1)); - - return asDisposableEntityChooser(container, toolbar); - } - - protected final void setHeader(String headerOrNull) - { - if (headerOrNull != null) - { - this.contentPanel.setHeaderVisible(true); - this.contentPanel.setHeading(headerOrNull); - } else - { - this.contentPanel.setHeaderVisible(false); - } - } - - protected final DisposableEntityChooser<T> asDisposableEntityChooser( - final Component mainComponent, final IDisposableComponent... disposableComponents) - { - final AbstractBrowserGrid<T, M> self = this; - return new DisposableEntityChooser<T>() - { - public T tryGetSingleSelected() - { - List<M> items = getSelectedItems(); - if (items.isEmpty()) - { - return null; - } else - { - return items.get(0).getBaseObject(); - } - } - - public void dispose() - { - debug("dispose a browser"); - self.disposeCache(); - for (IDisposableComponent disposableComponent : disposableComponents) - { - disposableComponent.dispose(); - } - } - - public Component getComponent() - { - return mainComponent; - } - - public DatabaseModificationKind[] getRelevantModifications() - { - return self.getRelevantModifications(); - } - - public void update(Set<DatabaseModificationKind> observedModifications) - { - self.update(observedModifications); - } - - }; - } - - @Override - protected void onRender(final Element parent, final int pos) - { - super.onRender(parent, pos); - if (refreshAutomatically) - { - int id = log("layout automatically"); - layout(); - viewContext.logStop(id); - id = log("refresh automatically"); - refresh(); - viewContext.logStop(id); - } - } - - private PagingLoader<PagingLoadResult<M>> createPagingLoader() - { - final RpcProxy<PagingLoadResult<M>> proxy = new RpcProxy<PagingLoadResult<M>>() - { - @Override - protected void load(Object loadConfig, AsyncCallback<PagingLoadResult<M>> callback) - { - loadData((PagingLoadConfig) loadConfig, callback); - } - }; - final BasePagingLoader<PagingLoadResult<M>> newPagingLoader = - new BasePagingLoader<PagingLoadResult<M>>(proxy); - newPagingLoader.setRemoteSort(true); - - return newPagingLoader; - } - - private void loadData(final PagingLoadConfig loadConfig, - final AsyncCallback<PagingLoadResult<M>> callback) - { - if (pendingFetchManager.hasNoPendingFetch()) - { - // this can happen when user wants to sort data - the refresh method is not called - if (resultSetKeyOrNull == null) - { - // data are not yet cached, so we ignore this call - should not really happen - return; - } - pendingFetchManager.pushPendingFetchConfig(ResultSetFetchConfig - .createFetchFromCache(resultSetKeyOrNull)); - } - GridFilters<T> filters = filterToolbar.getFilters(); - final DefaultResultSetConfig<String, T> resultSetConfig = - createPagingConfig(loadConfig, filters, getGridDisplayTypeID()); - debug("create a refresh callback " + pendingFetchManager.tryTopPendingFetchConfig()); - final ListEntitiesCallback listCallback = - new ListEntitiesCallback(viewContext, callback, resultSetConfig); - - listEntities(resultSetConfig, listCallback); - } - - // Default visibility so that friend classes can use -- should otherwise be considered private - void debug(String msg) - { - if (DEBUG) - { - String text = - "[grid: " + getGridDisplayTypeID() + ", cache: " + resultSetKeyOrNull + "] " - + msg; - System.out.println(text); - } - } - - private DefaultResultSetConfig<String, T> createPagingConfig(PagingLoadConfig loadConfig, - GridFilters<T> filters, String gridDisplayId) - { - int limit = loadConfig.getLimit(); - int offset = loadConfig.getOffset(); - com.extjs.gxt.ui.client.data.SortInfo sortInfo = loadConfig.getSortInfo(); - - DefaultResultSetConfig<String, T> resultSetConfig = new DefaultResultSetConfig<String, T>(); - resultSetConfig.setLimit(limit); - resultSetConfig.setOffset(offset); - resultSetConfig.setAvailableColumns(columnDefinitions); - SortInfo translatedSortInfo = translateSortInfo(sortInfo); - Set<String> columnIDs = getIDsOfColumnsToBeShown(); - resultSetConfig.setIDsOfPresentedColumns(columnIDs); - resultSetConfig.setSortInfo(translatedSortInfo); - resultSetConfig.setFilters(filters); - resultSetConfig.setCacheConfig(pendingFetchManager.tryTopPendingFetchConfig()); - resultSetConfig.setGridDisplayId(gridDisplayId); - resultSetConfig.setCustomColumnErrorMessageLong(viewContext.getDisplaySettingsManager() - .isDebuggingModeEnabled()); - return resultSetConfig; - } - - private Set<String> getIDsOfColumnsToBeShown() - { - Set<String> columnIDs = new HashSet<String>(); - DisplaySettingsManager manager = viewContext.getDisplaySettingsManager(); - List<ColumnSetting> columnSettings = manager.getColumnSettings(getGridDisplayTypeID()); - if (columnSettings != null) - { - for (ColumnSetting columnSetting : columnSettings) - { - if (columnSetting.isHidden() == false) - { - columnIDs.add(columnSetting.getColumnID()); - } - } - } - List<IColumnDefinition<T>> visibleColumns = getVisibleColumns(columnDefinitions); - for (IColumnDefinition<T> definition : visibleColumns) - { - columnIDs.add(definition.getIdentifier()); - } - return columnIDs; - } - - private static <T> SortInfo translateSortInfo(com.extjs.gxt.ui.client.data.SortInfo sortInfo) - { - return translateSortInfo(sortInfo.getSortField(), sortInfo.getSortDir()); - } - - private static <T> SortInfo translateSortInfo(String sortFieldId, - com.extjs.gxt.ui.client.Style.SortDir sortDir) - { - SortInfo sortInfo = new SortInfo(); - sortInfo.setSortField(sortFieldId); - sortInfo.setSortDir(translate(sortDir)); - - return sortInfo; - } - - private static SortDir translate(com.extjs.gxt.ui.client.Style.SortDir sortDir) - { - if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.ASC)) - { - return SortDir.ASC; - } else if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.DESC)) - { - return SortDir.DESC; - } else if (sortDir.equals(com.extjs.gxt.ui.client.Style.SortDir.NONE)) - { - return SortDir.NONE; - } else - { - throw new IllegalStateException("unknown sort dir: " + sortDir); - } - } - - private static com.extjs.gxt.ui.client.Style.SortDir translate(SortDir sortDir) - { - if (sortDir.equals(SortDir.ASC)) - { - return com.extjs.gxt.ui.client.Style.SortDir.ASC; - } else if (sortDir.equals(SortDir.DESC)) - { - return com.extjs.gxt.ui.client.Style.SortDir.DESC; - } else if (sortDir.equals(SortDir.NONE)) - { - return com.extjs.gxt.ui.client.Style.SortDir.NONE; - } else - { - throw new IllegalStateException("unknown sort dir: " + sortDir); - } - } - - /** @return number of rows in the grid */ - public final int getRowNumber() - { - return grid.getStore().getCount(); - } - - public final class ListEntitiesCallback extends AbstractAsyncCallback<ResultSet<T>> - { - private final AsyncCallback<PagingLoadResult<M>> delegate; - - // configuration with which the listing was called - private DefaultResultSetConfig<String, T> resultSetConfig; - - private int logID; - - private boolean reloadingPhase; - - public ListEntitiesCallback(final IViewContext<?> viewContext, - final AsyncCallback<PagingLoadResult<M>> delegate, - final DefaultResultSetConfig<String, T> resultSetConfig) - { - super(viewContext); - this.delegate = delegate; - this.resultSetConfig = resultSetConfig; - logID = log("load data"); - } - - // - // AbstractAsyncCallback - // - - @Override - public final void finishOnFailure(final Throwable caught) - { - reenableAfterFailure(); - // no need to show error message - it should be shown by DEFAULT_CALLBACK_LISTENER - caught.printStackTrace(); - delegate.onFailure(caught); - } - - public final void reenableAfterFailure() - { - grid.el().unmask(); - onComplete(false); - pagingToolbar.enable(); // somehow enabling toolbar is lost in its handleEvent() method - } - - @Override - protected void performSuccessActionOrIgnore(final IDelegatedAction successAction) - { - if (tableModificationsManager.isTableDirty()) - { - final Listener<MessageBoxEvent> listener = new Listener<MessageBoxEvent>() - { - public void handleEvent(MessageBoxEvent me) - { - if (me.getButtonClicked().getItemId().equals(Dialog.YES)) - { - tableModificationsManager.saveModifications(new IDelegatedAction() - { - public void execute() - { - // ignore this callback and refresh the table - ignore(); - reenableAfterFailure(); - refresh(); - } - }); - } else - { - tableModificationsManager.cancelModifications(); - successAction.execute(); - } - } - }; - final String title = - viewContext.getMessage(Dict.CONFIRM_SAVE_TABLE_MODIFICATIONS_DIALOG_TITLE); - final String msg = - viewContext - .getMessage(Dict.CONFIRM_SAVE_TABLE_MODIFICATIONS_DIALOG_MESSAGE); - MessageBox.confirm(title, msg, listener); - } else - { - successAction.execute(); - } - } - - @Override - protected final void process(final ResultSet<T> result) - { - viewContext.logStop(logID); - logID = log("process loaded data"); - // save the key of the result, later we can refer to the result in the cache using this - // key - String key = result.getResultSetKey(); - saveCacheKey(key); - GridRowModels<T> rowModels = result.getList(); - boolean partial = result.isPartial(); - List<GridCustomColumnInfo> customColumnMetadata = rowModels.getCustomColumnsMetadata(); - customColumnsMetadataProvider.setCustomColumnsMetadata(customColumnMetadata); - - if (reloadingPhase) - { - reloadingPhase = false; - } else if (partial) - { - reloadingPhase = true; - BasePagingLoadConfig loadConfig = new BasePagingLoadConfig(); - loadConfig.setLimit(resultSetConfig.getLimit()); - loadConfig.setOffset(resultSetConfig.getOffset()); - SortInfo sortInfo = resultSetConfig.getSortInfo(); - if (sortInfo != null) - { - String sortField = sortInfo.getSortField(); - if (sortField != null) - { - loadConfig.setSortField(sortField); - loadConfig.setSortDir(translate(sortInfo.getSortDir())); - } - } - resultSetConfig = - createPagingConfig(loadConfig, filterToolbar.getFilters(), - resultSetConfig.tryGetGridDisplayId()); - resultSetConfig.setCacheConfig(ResultSetFetchConfig - .createFetchFromCacheAndRecompute(key)); - this.reuse(); - listEntities(resultSetConfig, this); - } - // convert the result to the model data for the grid control - final List<M> models = createModels(rowModels); - final PagingLoadResult<M> loadResult = - new BasePagingLoadResult<M>(models, resultSetConfig.getOffset(), - result.getTotalLength()); - - delegate.onSuccess(loadResult); - pagingToolbar.enableExportButton(); - pagingToolbar.updateDefaultConfigButton(true); - - if (reloadingPhase == false) - { - pagingToolbar.enable(); - filterToolbar.refreshColumnFiltersDistinctValues(rowModels - .getColumnDistinctValues()); - } else - { - pagingToolbar.disableForLoadingRest(); - } - onComplete(true); - - viewContext.logStop(logID); - } - - // notify that the refresh is done - private void onComplete(boolean wasSuccessful) - { - pendingFetchManager.popPendingFetch(); - refreshCallback.postRefresh(wasSuccessful); - } - - private List<M> createModels(final GridRowModels<T> gridRowModels) - { - final List<M> result = new ArrayList<M>(); - initializeModelCreation(); - for (final GridRowModel<T> entity : gridRowModels) - { - M model = createModel(entity); - result.add(model); - } - return result; - } - - @Override - /* Note: we want to differentiate between callbacks in different subclasses of this grid. */ - public String getCallbackId() - { - return grid.getId(); - } - } - - /** - * Initializes creation of model from received data. This is a hook method called before { - * {@link AbstractBrowserGrid#createModel(GridRowModel)} is invoked for all rows. This - * implementation does nothing. Subclasses usually override this method by creating an instance - * attribute which holds a list of visible column definitions. This speeds up invocation of - * {@link AbstractBrowserGrid#createModel(GridRowModel)}. - */ - protected void initializeModelCreation() - { - } - - protected Set<String> getIDsOfVisibleColumns() - { - Set<String> visibleColumnIds = new HashSet<String>(); - for (int i = 0, n = fullColumnModel.getColumnCount(); i < n; i++) - { - ColumnConfig column = fullColumnModel.getColumn(i); - if (column.isHidden() == false) - { - visibleColumnIds.add(column.getId()); - } - } - return visibleColumnIds; - } - - // wraps this browser into the interface appropriate for the toolbar. If this class would just - // implement the interface it could be very confusing for the code reader. - protected IBrowserGridActionInvoker asActionInvoker() - { - final AbstractBrowserGrid<T, M> delegate = this; - return new IBrowserGridActionInvoker() - { - public void export(boolean allColumns) - { - delegate.export(allColumns); - } - - public void refresh() - { - int id = log("refresh in action invoker"); - delegate.refresh(); - viewContext.logStop(id); - } - - public void configure() - { - delegate.configureColumnSettings(); - } - - public void toggleFilters(boolean show) - { - if (show) - { - int logId = log("adding filters"); - delegate.showFiltersBar(); - viewContext.logStop(logId); - } else - { - int logId = log("removing filters"); - bottomToolbars.remove(filterToolbar); - bottomToolbars.layout(); - viewContext.logStop(logId); - } - } - - }; - } - - protected void showFiltersBar() - { - // always show filters under modifications - int position = bottomToolbars.getItemCount() > 1 ? 1 : 0; - bottomToolbars.insert(filterToolbar, position); - bottomToolbars.layout(); - } - - protected void showModificationsBar() - { - if (bottomToolbars.getItems().contains(modificationsToolbar) == false) - { - GWTUtils.displayInfo(viewContext.getMessage(Dict.TABLE_MODIFICATIONS_INFO_TITLE), - viewContext.getMessage(Dict.TABLE_MODIFICATIONS_INFO_TEXT), - DisplayInfoTime.LONG); - bottomToolbars.insert(modificationsToolbar, 0); - bottomToolbars.layout(); - } - } - - protected void hideModificationsBar() - { - bottomToolbars.remove(modificationsToolbar); - bottomToolbars.layout(); - } - - protected static interface ISelectedEntityInvoker<M> - { - void invoke(M selectedItem, boolean keyPressed); - } - - protected final ISelectedEntityInvoker<M> asShowEntityInvoker(final boolean editMode) - { - return new ISelectedEntityInvoker<M>() - { - public void invoke(M selectedItem, boolean keyPressed) - { - if (selectedItem != null) - { - showEntityViewer(selectedItem.getBaseObject(), editMode, keyPressed); - } - } - }; - } - - private ISelectedEntityInvoker<M> createNotImplementedInvoker() - { - return new ISelectedEntityInvoker<M>() - { - public void invoke(M selectedItem, boolean keyPressed) - { - MessageBox.alert(viewContext.getMessage(Dict.MESSAGEBOX_WARNING), - viewContext.getMessage(Dict.NOT_IMPLEMENTED), null); - } - }; - } - - /** - * @return a button which has no action but is enabled only when one entity in the grid is - * selected. Useful only for writing prototypes. - */ - protected final Button createSelectedItemDummyButton(final String title) - { - return createSelectedItemButton(title, createNotImplementedInvoker()); - } - - /** - * @return like {@link #createSelectedItemButton(String, ISelectedEntityInvoker)} with button id - * set - */ - protected final Button createSelectedItemButton(final String title, final String id, - final ISelectedEntityInvoker<M> invoker) - { - final Button button = createSelectedItemButton(title, invoker); - button.setId(id); - return button; - } - - /** - * @return a button which is enabled only when one entity in the grid is selected. When button - * is pressed, the specified invoker action is performed. - */ - protected final Button createSelectedItemButton(final String title, - final ISelectedEntityInvoker<M> invoker) - { - final Button button = new Button(title, new SelectionListener<ButtonEvent>() - { - @Override - public void componentSelected(ButtonEvent ce) - { - List<M> selectedItems = getSelectedItems(); - if (selectedItems.isEmpty() == false) - { - invoker.invoke(selectedItems.get(0), false); - } - } - }); - enableButtonOnSelectedItem(button); - return button; - } - - /** - * @return a button which is enabled only when at least one entity in the grid is selected, and - * with specified selection listener set. - */ - protected final Button createSelectedItemsButton(final String title, - SelectionListener<ButtonEvent> listener) - { - Button button = new Button(title); - button.addSelectionListener(listener); - enableButtonOnSelectedItems(button); - return button; - } - - /** adds given <var>button</var> to grid {@link PagingToolBar} */ - protected final void addButton(Button button) - { - pagingToolbar.add(button); - } - - /** - * Given <var>button</var> will be enabled only if at least one item is selected in the grid. - */ - protected final void enableButtonOnSelectedItems(final Button button) - { - button.setEnabled(false); - addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() - { - public void handleEvent(SelectionChangedEvent<ModelData> se) - { - boolean enabled = se.getSelection().size() > 0; - button.setEnabled(enabled); - } - - }); - } - - /** - * Given <var>button</var> will be enabled only if exactly one item is selected in the grid. - */ - protected final void enableButtonOnSelectedItem(final Button button) - { - button.setEnabled(false); - addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() - { - public void handleEvent(SelectionChangedEvent<ModelData> se) - { - boolean enabled = se.getSelection().size() == 1; - button.setEnabled(enabled); - } - - }); - } - - /** - * Given <var>button</var> will have title changed depending on number of items selected in the - * grid. - */ - protected final void changeButtonTitleOnSelectedItems(final Button button, - final String noSelectedItemsTitle, final String selectedItemsTitle) - { - addGridSelectionChangeListener(new Listener<SelectionChangedEvent<ModelData>>() - { - public void handleEvent(SelectionChangedEvent<ModelData> se) - { - boolean noSelected = se.getSelection().size() == 0; - button.setText(noSelected ? noSelectedItemsTitle : selectedItemsTitle); - } - - }); - } - - public void addGridSelectionChangeListener(Listener<SelectionChangedEvent<ModelData>> listener) - { - grid.getSelectionModel().addListener(Events.SelectionChange, listener); - } - - /** - * Returns all models of selected items or an empty list if nothing selected. - */ - public final List<M> getSelectedItems() - { - return grid.getSelectionModel().getSelectedItems(); - } - - /** - * Returns all base objects of selected items or an empty list if nothing selected. - */ - protected final List<T> getSelectedBaseObjects() - { - List<M> items = getSelectedItems(); - List<T> data = new ArrayList<T>(); - for (BaseEntityModel<T> item : items) - { - data.add(item.getBaseObject()); - } - return data; - } - - protected final IDelegatedAction createRefreshGridAction() - { - return new IDelegatedAction() - { - public void execute() - { - int id = log("execute refrish grid action"); - refresh(); - viewContext.logStop(id); - } - }; - } - - /** Refreshes grid and filters (resets filter selection) */ - protected final void refreshGridWithFilters() - { - // N.B. -- The order in which things are refreshed and configured is significant - - // export and config buttons are enabled when ListEntitiesCallback is complete - pagingToolbar.disableExportButton(); - pagingToolbar.updateDefaultConfigButton(false); - - // Need to reset filter fields *before* refreshing the grid so the list can - // be correctly retrieved - filterToolbar.resetFilterFields(); - filterToolbar.resetFilterSelectionWithoutApply(); - - int id = log("refresh grid with filters"); - refresh(); - viewContext.logStop(id); - - // Need to refresh the filter toolbar *after* refreshing the grid, because it - // has a dependency on information from the grid that gets updated with the refresh - filterToolbar.refresh(); - } - - protected final void updateDefaultRefreshButton() - { - boolean isEnabled = isRefreshEnabled(); - pagingToolbar.updateDefaultRefreshButton(isEnabled); - } - - /** - * Refreshes the grid. - * <p> - * Note that, doing so, the result set associated on the server side will be removed. - * </p> - */ - protected final void refresh(boolean refreshColumnsDefinition) - { - refresh(null, refreshColumnsDefinition); - } - - /** - * @param externalRefreshCallbackOrNull external class can define it's own refresh callback - * method. It will be merged with the internal one. - */ - protected final void refresh(final IDataRefreshCallback externalRefreshCallbackOrNull, - boolean refreshColumnsDefinition) - { - int id = log("refresh (refreshColumnsDefinition=" + refreshColumnsDefinition + ")"); - pagingToolbar.updateDefaultRefreshButton(false); - debug("clean cache for refresh"); - this.refreshCallback = createRefreshCallback(externalRefreshCallbackOrNull); - if (columnDefinitions == null || refreshColumnsDefinition) - { - recreateColumnModelAndRefreshColumnsWithFilters(); - } - reloadData(createDisposeAndRefreshFetchMode()); - viewContext.logStop(id); - } - - private ResultSetFetchConfig<String> createDisposeAndRefreshFetchMode() - { - if (resultSetKeyOrNull != null) - { - return ResultSetFetchConfig.createClearComputeAndCache(resultSetKeyOrNull); - } else - { - return ResultSetFetchConfig.createComputeAndCache(); - } - } - - private ColumnDefsAndConfigs<T> createColumnDefsAndConfigs() - { - ColumnDefsAndConfigs<T> defsAndConfigs = createColumnsDefinition(); - // add custom columns - List<GridCustomColumnInfo> customColumnsMetadata = - customColumnsMetadataProvider.getCustomColumnsMetadata(); - if (customColumnsMetadata.size() > 0) - { - List<IColumnDefinitionUI<T>> customColumnsDefs = - createCustomColumnDefinitions(customColumnsMetadata); - defsAndConfigs.addColumns(customColumnsDefs, viewContext); - - for (GridCustomColumnInfo gridCustomColumnInfo : customColumnsMetadata) - { - - DataTypeCode columnType = gridCustomColumnInfo.getDataType(); - GridCellRenderer<BaseEntityModel<?>> columnRenderer = null; - - if (DataTypeCode.REAL.equals(columnType)) - { - columnRenderer = - new RealNumberRenderer(viewContext.getDisplaySettingsManager() - .getRealNumberFormatingParameters()); - - } else if (DataTypeCode.VARCHAR.equals(columnType)) - { - columnRenderer = new CustomColumnStringRenderer(); - } - - if (columnRenderer != null) - { - defsAndConfigs.setGridCellRendererFor(gridCustomColumnInfo.getCode(), - columnRenderer); - } - } - } - - return defsAndConfigs; - } - - protected final void recreateColumnModelAndRefreshColumnsWithFilters() - { - int logId = log("recreateColumnModelAndRefreshColumnsWithFilters"); - - ColumnDefsAndConfigs<T> defsAndConfigs = createColumnDefsAndConfigs(); - - this.columnDefinitions = defsAndConfigs.getColumnDefs(); - ColumnModel columnModel = createColumnModel(defsAndConfigs.getColumnConfigs()); - - refreshColumnsAndFilters(columnModel); - - viewContext.logStop(logId); - } - - private static <T> List<IColumnDefinitionUI<T>> createCustomColumnDefinitions( - List<GridCustomColumnInfo> customColumnsMetadata) - { - List<IColumnDefinitionUI<T>> defs = new ArrayList<IColumnDefinitionUI<T>>(); - for (GridCustomColumnInfo columnMetadata : customColumnsMetadata) - { - IColumnDefinitionUI<T> colDef = new GridCustomColumnDefinition<T>(columnMetadata); - defs.add(colDef); - } - return defs; - } - - private void refreshColumnsAndFilters(ColumnModel columnModel) - { - ColumnModel newColumnModel = columnModel; - GridDisplaySettings settings = tryApplyDisplaySettings(newColumnModel); - if (settings != null && settings.getColumnConfigs() != null) - { - newColumnModel = createColumnModel(settings.getColumnConfigs()); - rebuildFiltersFromIds(settings.getFilteredColumnIds()); - } else - { - filterToolbar.rebuildColumnFilters(getInitialFilters()); - } - changeColumnModel(newColumnModel, settings != null ? settings.getSortField() : null, - settings != null ? settings.getSortDir() : null); - } - - private void hideLoadingMask() - { - if (grid.isRendered() && grid.el() != null) - { - grid.el().unmask(); - } - } - - private GridDisplaySettings tryApplyDisplaySettings(ColumnModel columnModel) - { - List<IColumnDefinition<T>> initialFilters = getInitialFilters(); - return viewContext.getDisplaySettingsManager().tryApplySettings(getGridDisplayTypeID(), - columnModel, extractColumnIds(initialFilters), getGridSortInfo()); - } - - private void reconfigureGrid(ColumnModel columnModelOfVisible) - { - List<Listener<?>> sortlisteners = - new ArrayList<Listener<?>>(grid.getListeners(Events.SortChange)); - for (Listener<?> listener : sortlisteners) - { - grid.removeListener(Events.SortChange, listener); - } - - grid.reconfigure(grid.getStore(), columnModelOfVisible); - - for (Listener<?> listener : sortlisteners) - { - grid.addListener(Events.SortChange, listener); - } - } - - private void changeColumnModel(ColumnModel columnModel, String sortField, SortDir sortDir) - { - fullColumnModel = columnModel; - - int logId = log("grid reconfigure"); - ColumnModel columnModelOfVisible = trimToVisibleColumns(columnModel); - - if (sortDir != null && sortField != null) - { - pagingLoader.setSortDir(translate(sortDir)); - pagingLoader.setSortField(sortField); - } - reconfigureGrid(columnModelOfVisible); - - viewContext.logStop(logId); - registerGridSettingsChangesListener(); - // add listeners of full column model to trimmed model - List<Listener<? extends BaseEvent>> listeners = - fullColumnModel.getListeners(Events.WidthChange); - for (Listener<? extends BaseEvent> listener : listeners) - { - columnModelOfVisible.addListener(Events.WidthChange, listener); - columnModelOfVisible.addListener(Events.ColumnMove, listener); // track drag&drop - } - } - - private ColumnModel trimToVisibleColumns(ColumnModel columnModel) - { - int maxVisibleColumns = getWebClientConfiguration().getMaxVisibleColumns(); - int counter = 0; - List<ColumnConfig> columns = new ArrayList<ColumnConfig>(); - for (int i = 0, n = columnModel.getColumnCount(); i < n; i++) - { - ColumnConfig column = columnModel.getColumn(i); - if (column.isHidden() == false) - { - counter++; - if (counter <= maxVisibleColumns) - { - columns.add(column); - } else - { - column.setHidden(true); - } - } - } - if (counter > maxVisibleColumns) - { - saveColumnDisplaySettings(); // save changes made to full model - InfoConfig infoConfig = - new InfoConfig(viewContext.getMessage(Dict.VISIBLE_COLUMNS_LIMITED_TITLE), - viewContext.getMessage(Dict.VISIBLE_COLUMNS_LIMITED_MSG, - maxVisibleColumns, counter)); - infoConfig.height = 100; // a bit higher - infoConfig.display = 5000; // 5s - Info.display(infoConfig); - } - ColumnModel trimmedModel = createColumnModel(columns); - return trimmedModel; - } - - private void registerGridSettingsChangesListener() - { - viewContext.getDisplaySettingsManager().registerGridSettingsChangesListener( - getGridDisplayTypeID(), createDisplaySettingsUpdater()); - } - - // Refreshes the data, does not clear the cache. Does not change the column model. - // Default visibility so that friend classes can use -- should otherwise be considered private - void reloadData(ResultSetFetchConfig<String> resultSetFetchConfig) - { - if (pendingFetchManager.hasPendingFetch()) - { - debug("Cannot reload the data with the mode '" + resultSetFetchConfig - + "'; there is an unfinished request already: " - + pendingFetchManager.tryTopPendingFetchConfig()); - return; - } - pendingFetchManager.pushPendingFetchConfig(resultSetFetchConfig); - pagingLoader.load(0, PAGE_SIZE); - } - - private IDisplaySettingsGetter createDisplaySettingsUpdater() - { - return new IDisplaySettingsGetter() - { - public ColumnModel getColumnModel() - { - return AbstractBrowserGrid.this.getFullColumnModel(); - } - - public List<String> getFilteredColumnIds() - { - return filterToolbar.extractFilteredColumnIds(); - } - - public Object getModifier() - { - return AbstractBrowserGrid.this; - } - - public SortInfo getSortState() - { - return AbstractBrowserGrid.this.getGridSortInfo(); - } - }; - } - - // returns true if some filters have changed - // Default visibility so that friend classes can use -- should otherwise be considered private - boolean rebuildFiltersFromIds(List<String> filteredColumnIds) - { - List<IColumnDefinition<T>> filteredColumns = getColumnDefinitions(filteredColumnIds); - return filterToolbar.rebuildColumnFilters(filteredColumns); - } - - public List<IColumnDefinition<T>> getColumnDefinitions(List<String> columnIds) - { - Map<String, IColumnDefinition<T>> colsMap = asColumnIdMap(columnDefinitions); - List<IColumnDefinition<T>> columns = new ArrayList<IColumnDefinition<T>>(); - for (String columnId : columnIds) - { - IColumnDefinition<T> colDef = colsMap.get(columnId); - assert colDef != null : "Cannot find a column '" + columnId; - columns.add(colDef); - } - return columns; - } - - protected final String createGridDisplayTypeID(String suffixOrNull) - { - if (displayTypeIDGenerator == null) - { - throw new IllegalStateException("Undefined display type ID generator."); - } - if (suffixOrNull == null) - { - return displayTypeIDGenerator.createID(); - } else - { - return displayTypeIDGenerator.createID(suffixOrNull); - } - } - - private IDataRefreshCallback createRefreshCallback( - IDataRefreshCallback externalRefreshCallbackOrNull) - { - IDataRefreshCallback internalCallback = createInternalPostRefreshCallback(); - if (externalRefreshCallbackOrNull == null) - { - return internalCallback; - } else - { - return mergeCallbacks(internalCallback, externalRefreshCallbackOrNull); - } - } - - private IDataRefreshCallback createInternalPostRefreshCallback() - { - return new IDataRefreshCallback() - { - public void postRefresh(boolean wasSuccessful) - { - if (customColumnsMetadataProvider.getHasChangedAndSetFalse()) - { - recreateColumnModelAndRefreshColumnsWithFilters(); - } - - updateDefaultRefreshButton(); - - if (wasSuccessful) - { - hideLoadingMask(); - pagingToolbar.updateDefaultConfigButton(true); - pagingToolbar.enableExportButton(); - } - } - }; - } - - private static IDataRefreshCallback mergeCallbacks(final IDataRefreshCallback c1, - final IDataRefreshCallback c2) - { - return new IDataRefreshCallback() - { - public void postRefresh(boolean wasSuccessful) - { - c1.postRefresh(wasSuccessful); - c2.postRefresh(wasSuccessful); - } - }; - } - - private List<IColumnDefinition<T>> getVisibleColumns(Set<IColumnDefinition<T>> availableColumns) - { - Map<String, IColumnDefinition<T>> availableColumnsMap = asColumnIdMap(availableColumns); - return getVisibleColumns(availableColumnsMap, fullColumnModel); - } - - private void saveCacheKey(final String newResultSetKey) - { - resultSetKeyOrNull = newResultSetKey; - debug("saving new cache key"); - } - - private void disposeCache() - { - if (resultSetKeyOrNull != null) - { - removeResultSet(resultSetKeyOrNull); - resultSetKeyOrNull = null; - } - } - - private void removeResultSet(String resultSetKey) - { - viewContext.getService().removeResultSet(resultSetKey, - new VoidAsyncCallback<Void>(viewContext)); - } - - /** - * Export always deals with data from the previous refresh operation - * - * @param allColumns whether all columns should be exported - */ - private void export(boolean allColumns) - { - export(allColumns, new ExportEntitiesCallback(viewContext)); - } - - /** - * Shows the dialog allowing to configure visibility and order of the table columns. - */ - private void configureColumnSettings() - { - assert grid != null && grid.getColumnModel() != null : "Grid must be loaded"; - ColumnSettingsConfigurer<T, M> columnSettingsConfigurer = - new ColumnSettingsConfigurer<T, M>(this, viewContext, filterToolbar, - customColumnsMetadataProvider, resultSetKeyOrNull, - pendingFetchManager.tryTopPendingFetchConfig()); - columnSettingsConfigurer.showDialog(); - } - - // Default visibility so that friend classes can use -- should otherwise be considered private - void saveColumnDisplaySettings() - { - IDisplaySettingsGetter settingsUpdater = createDisplaySettingsUpdater(); - viewContext.getDisplaySettingsManager().storeSettings(getGridDisplayTypeID(), - settingsUpdater, false); - } - - // @Private - for tests - public final void export(boolean allColumns, final AbstractAsyncCallback<String> callback) - { - final TableExportCriteria<T> exportCriteria = createTableExportCriteria(allColumns); - - prepareExportEntities(exportCriteria, callback); - } - - // for visible columns - protected final TableExportCriteria<T> createTableExportCriteria() - { - return createTableExportCriteria(false); - } - - protected final TableExportCriteria<T> createTableExportCriteria(boolean allColumns) - { - assert columnDefinitions != null : "refresh before exporting!"; - assert resultSetKeyOrNull != null : "refresh before exporting, resultSetKey is null!"; - - final List<IColumnDefinition<T>> columnDefs = - allColumns ? new ArrayList<IColumnDefinition<T>>(columnDefinitions) - : getVisibleColumns(columnDefinitions); - SortInfo sortInfo = getGridSortInfo(); - final TableExportCriteria<T> exportCriteria = - new TableExportCriteria<T>(resultSetKeyOrNull, sortInfo, - filterToolbar.getFilters(), columnDefs, columnDefinitions, - getGridDisplayTypeID()); - return exportCriteria; - } - - // returns info about sorting in current grid - SortInfo getGridSortInfo() - { - ListStore<M> store = grid.getStore(); - return translateSortInfo(store.getSortField(), store.getSortDir()); - } - - /** @return the number of all objects cached in the browser */ - public int getTotalCount() - { - return pagingToolbar.getTotalCount(); - } - - // Default visibility so that friend classes can use -- should otherwise be considered private - void refreshColumnsSettings() - { - grid.setLoadMask(false); - grid.getView().refresh(true); - grid.setLoadMask(true); - } - - protected final void addEntityOperationsLabel() - { - pagingToolbar.addEntityOperationsLabel(); - } - - protected final void addEntityOperationsSeparator() - { - pagingToolbar.add(new SeparatorToolItem()); - } - - protected final GridCellRenderer<BaseEntityModel<?>> createMultilineStringCellRenderer() - { - return new MultilineStringCellRenderer(); - } - - @SuppressWarnings("deprecation") - protected GridCellRenderer<BaseEntityModel<?>> createInternalLinkCellRenderer() - { - // NOTE: this renderer doesn't support special rendering of deleted entities - return LinkRenderer.createLinkRenderer(); - } - - protected WebClientConfiguration getWebClientConfiguration() - { - return viewContext.getModel().getApplicationInfo().getWebClientConfiguration(); - } - - // ------- generic static helpers - - private static <T> List<String> extractColumnIds(List<IColumnDefinition<T>> columns) - { - List<String> columnsIds = new ArrayList<String>(); - for (IColumnDefinition<T> column : columns) - { - columnsIds.add(column.getIdentifier()); - } - return columnsIds; - } - - // Default visibility so that friend classes can use -- should otherwise be considered private - static List<ColumnDataModel> createColumnsSettingsModel(ColumnModel cm, - List<String> filteredColumnsIds) - { - Set<String> filteredColumnsMap = new HashSet<String>(filteredColumnsIds); - int cols = cm.getColumnCount(); - List<ColumnDataModel> list = new ArrayList<ColumnDataModel>(); - for (int i = 0; i < cols; i++) - { - if (cm.getColumnHeader(i) == null || cm.getColumnHeader(i).equals("") || cm.isFixed(i)) - { - continue; - } - String columnId = cm.getColumnId(i); - boolean isVisible = cm.isHidden(i) == false; - boolean hasFilter = filteredColumnsMap.contains(columnId); - list.add(new ColumnDataModel(cm.getColumnHeader(i), isVisible, hasFilter, columnId)); - } - return list; - } - - private static ContentPanel createEmptyContentPanel() - { - final ContentPanel contentPanel = new ContentPanel(); - contentPanel.setBorders(false); - contentPanel.setBodyBorder(false); - contentPanel.setLayout(new FitLayout()); - return contentPanel; - } - - // creates filter and paging toolbars - private static <T> LayoutContainer createBottomToolbars(final Container<?> parentContainer, - ToolBar pagingToolbar) - { - LayoutContainer bottomToolbars = new ContainerKeeper(parentContainer); - bottomToolbars.setMonitorWindowResize(true); - bottomToolbars.setLayout(new RowLayout(com.extjs.gxt.ui.client.Style.Orientation.VERTICAL)); - // Adding paging toolbar before data are loaded fixes problems with the toolbar not visible - // if user quickly changes tab / hides section before it is layouted. On the other hand - // it is slower than adding it after requesting server for data. - bottomToolbars.add(pagingToolbar, new RowData(1, -1)); - // filter toolbar is added on request - return bottomToolbars; - } - - private static final class ContainerKeeper extends LayoutContainer - { - private final Container<?> parentContainer; - - private ContainerKeeper(Container<?> parentContainer) - { - this.parentContainer = parentContainer; - } - - @Override - protected void onWindowResize(int aWidth, int aHeight) - { - super.onWindowResize(aWidth, aHeight); - if (isVisible()) - { - this.setWidth(parentContainer.getWidth()); - } - } - } - - private Grid<M> createGrid(PagingLoader<PagingLoadResult<M>> dataLoader, String gridId) - { - - ListStore<M> listStore = new ListStore<M>(dataLoader); - ColumnModel columnModel = createColumnModel(new ArrayList<ColumnConfig>()); - EditorGrid<M> editorGrid = new EditorGrid<M>(listStore, columnModel); - editorGrid.setId(gridId); - editorGrid.setLoadMask(true); - editorGrid.setSelectionModel(new GridSelectionModel<M>()); - editorGrid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); - editorGrid.setView(new ExtendedGridView()); - editorGrid.setStripeRows(true); - editorGrid.setColumnReordering(true); - editorGrid.setClicksToEdit(ClicksToEdit.TWO); - editorGrid.addListener(Events.BeforeEdit, new Listener<GridEvent<M>>() - { - public void handleEvent(GridEvent<M> event) - { - if (viewContext.isSimpleOrEmbeddedMode()) - { - MessageBox.info("Not Allowed", - "Sorry, table cell editing is not allowed in current viewing mode", - null); - event.setCancelled(true); - } else - { - M model = event.getModel(); - String columnID = event.getProperty(); - boolean editable = isEditable(model, columnID); - if (editable == false) - { - showNonEditableTableCellMessage(model, columnID); - } - event.setCancelled(editable == false); - } - } - }); - editorGrid.addListener(Events.AfterEdit, new Listener<GridEvent<M>>() - { - public void handleEvent(GridEvent<M> event) - { - M model = event.getModel(); - String columnID = event.getProperty(); - Object value = event.getValue(); - String newValueNotNull = StringUtils.toStringEmptyIfNull(value); - String oldValueNotNull = StringUtils.toStringEmptyIfNull(event.getStartValue()); - if (oldValueNotNull.equals(newValueNotNull)) - { - event.setCancelled(true); - } else - { - showModificationsBar(); - if (value instanceof VocabularyTerm) - { - VocabularyTerm term = (VocabularyTerm) value; - value = term.getCode(); - } - tableModificationsManager.handleEditingEvent(model, columnID, - StringUtils.toStringOrNull(value)); - } - } - }); - editorGrid.addListener(Events.SortChange, new Listener<BaseEvent>() - { - public void handleEvent(BaseEvent be) - { - saveColumnDisplaySettings(); - } - }); - - return editorGrid; - } - - /** - * Returns <code>true</code> if cell specified by model and column ID is editable. Default - * implementation false. - */ - protected boolean isEditable(M model, String columnID) - { - return false; - } - - /** - * Shows a message that the table cell of specified column and row (model) isn't editable. - */ - protected void showNonEditableTableCellMessage(M model, String columnID) - { - MessageBox.info("Not Editable", "Sorry, this table cell isn't editable", null); - } - - /** - * Tries to return the property of specified properties holder which is specified by the - * property column name without a prefix like <code>property-</code> but with prefix which - * distinguishes internal from externally name space. - */ - protected IEntityProperty tryGetProperty(IEntityPropertiesHolder propertiesHolder, - String propertyColumnNameWithoutPrefix) - { - String propertyTypeCode = - CodeConverter.getPropertyTypeCode(propertyColumnNameWithoutPrefix); - List<IEntityProperty> properties = propertiesHolder.getProperties(); - for (IEntityProperty property : properties) - { - if (property.getPropertyType().getCode().equals(propertyTypeCode)) - { - return property; - } - } - return null; - } - - // this should be the only place where we create the grid column model. - private static ColumnModel createColumnModel(List<ColumnConfig> columConfigs) - { - return new ColumnModel(columConfigs); - } - - protected ColumnModel getFullColumnModel() - { - return fullColumnModel; - } - - private static final class ExportEntitiesCallback extends AbstractAsyncCallback<String> - { - public ExportEntitiesCallback(final IViewContext<ICommonClientServiceAsync> viewContext) - { - super(viewContext); - } - - @Override - protected void process(final String exportDataKey) - { - final URLMethodWithParameters methodWithParameters = - new URLMethodWithParameters( - GenericConstants.FILE_EXPORTER_DOWNLOAD_SERVLET_NAME); - methodWithParameters.addParameter(GenericConstants.EXPORT_CRITERIA_KEY_PARAMETER, - exportDataKey); - WindowUtils.openWindow(methodWithParameters.toString()); - } - - } - - // creates a map to quickly find a definition by its identifier - private static <T> Map<String, IColumnDefinition<T>> asColumnIdMap( - final Set<IColumnDefinition<T>> defs) - { - final Map<String, IColumnDefinition<T>> map = new HashMap<String, IColumnDefinition<T>>(); - for (final IColumnDefinition<T> def : defs) - { - map.put(def.getIdentifier(), def); - } - return map; - } - - /** - * @param availableColumns map of all available columns definitions. - * @param columnModel describes the visual properties of the columns. Connected with - * availableColumns by column id. - * @return list of columns definitions for those columns which are currently shown - */ - private static <T/* column definition */> List<T> getVisibleColumns( - final Map<String, T> availableColumns, final ColumnModel columnModel) - { - final List<T> selectedColumnDefs = new ArrayList<T>(); - final int columnCount = columnModel.getColumnCount(); - for (int i = 0; i < columnCount; i++) - { - if (columnModel.isHidden(i) == false) - { - final String columnId = columnModel.getColumnId(i); - selectedColumnDefs.add(availableColumns.get(columnId)); - } - } - return selectedColumnDefs; - } - - /** Creates callback that refreshes the grid. */ - protected final AbstractAsyncCallback<Void> createRefreshCallback( - IBrowserGridActionInvoker invoker) - { - return new RefreshCallback(viewContext, invoker); - } - - /** Callback that refreshes the grid. */ - private static final class RefreshCallback extends AbstractAsyncCallback<Void> - { - private final IBrowserGridActionInvoker invoker; - - public RefreshCallback(IViewContext<?> viewContext, IBrowserGridActionInvoker invoker) - { - super(viewContext); - this.invoker = invoker; - } - - @Override - protected void process(Void result) - { - invoker.refresh(); - } - } - - /** {@link SelectionListener} that creates a dialog with selected data items. */ - protected abstract class AbstractCreateDialogListener extends SelectionListener<ButtonEvent> - { - @Override - public void componentSelected(ButtonEvent ce) - { - List<T> data = getSelectedBaseObjects(); - IBrowserGridActionInvoker invoker = asActionInvoker(); - if (validateSelectedData(data)) - { - createDialog(data, invoker).show(); - } - } - - /** - * If specified data is valid returns true, otherwise returns false. Dialog will be shown - * only if this method returns true. Default implementation always returns true. - */ - protected boolean validateSelectedData(List<T> data) - { - return true; - } - - protected abstract Dialog createDialog(List<T> data, IBrowserGridActionInvoker invoker); - } - - // - // Table Modifications - // - - /** - * Apply specified modifications to the model. Should be overriden by subclasses. Default - * implementation does nothing. - */ - protected void applyModifications(M model, List<IModification> modifications) - { - - } - - /** Manager of table modifications */ - interface ITableModificationsManager<M extends ModelData> - { - /** @return <code>true</code> iff there are any uncommitted modifications */ - boolean isTableDirty(); - - /** save all modifications made in the table to the DB */ - void saveModifications(); - - /** save all modifications made in the table to the DB and call the after save action */ - void saveModifications(IDelegatedAction afterSaveAction); - - /** cancel all modifications made in the table */ - void cancelModifications(); - - /** handle cell editing event */ - void handleEditingEvent(M model, String columnID, String stringOrNull); - - /** @return callback for given modifications made to specified model. */ - AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback(final M model, - final List<IModification> modifications); - } - - protected interface IModification - { - String getColumnID(); - - String tryGetNewValue(); - } - - private static class Modification implements IModification - { - private final String columnID; - - private final String newValueOrNull; - - public Modification(String columnID, String newValueOrNull) - { - super(); - this.columnID = columnID; - this.newValueOrNull = - newValueOrNull == null ? null : StringEscapeUtils.unescapeHtml(newValueOrNull); - } - - public String getColumnID() - { - return columnID; - } - - public String tryGetNewValue() - { - return newValueOrNull; - } - } - - private class TableModificationsManager implements ITableModificationsManager<M> - { - private final Map<M, List<IModification>> modificationsByModel = - new LinkedHashMap<M, List<IModification>>(); - - private final Map<M, String> failedModifications = new HashMap<M, String>(); - - private int finishedModifications = 0; - - private IDelegatedAction afterSaveActionOrNull; - - // - // ITableModificationsManager - // - - // @Override - public boolean isTableDirty() - { - return isApplyModificationsComplete() == false; - } - - public void saveModifications() - { - saveModifications(null); - } - - private void setAfterSaveAction(IDelegatedAction afterSaveAction) - { - this.afterSaveActionOrNull = afterSaveAction; - } - - public void saveModifications(IDelegatedAction afterSaveAction) - { - setAfterSaveAction(afterSaveAction); - finishedModifications = 0; - for (Entry<M, List<IModification>> entry : modificationsByModel.entrySet()) - { - applyModifications(entry.getKey(), entry.getValue()); - } - } - - public void cancelModifications() - { - clearModifications(); - grid.getStore().rejectChanges(); - refresh(); // WORKAROUND remove this refresh after LMS-2397 is resolved - } - - public void handleEditingEvent(M model, String columnID, String newValueOrNull) - { - List<IModification> modificationsForModel = modificationsByModel.get(model); - if (modificationsForModel == null) - { - modificationsForModel = new ArrayList<IModification>(); - modificationsByModel.put(model, modificationsForModel); - } - modificationsForModel.add(new Modification(columnID, newValueOrNull)); - } - - public AsyncCallback<EntityPropertyUpdatesResult> createApplyModificationsCallback( - final M model, final List<IModification> modifications) - { - return new AbstractAsyncCallback<EntityPropertyUpdatesResult>(viewContext) - { - @Override - protected void process(EntityPropertyUpdatesResult result) - { - finishedModifications++; - String errorMessage = result.tryGetErrorMessage(); - if (errorMessage != null) - { - handleError(errorMessage); - } - if (isApplyModificationsComplete()) - { - onApplyModificationsComplete(); - } - } - - @Override - public void finishOnFailure(Throwable caught) - { - finishedModifications++; - handleError(caught.getMessage()); - if (isApplyModificationsComplete()) - { - onApplyModificationsComplete(); - } - } - - private void handleError(String errorMessage) - { - failedModifications.put(model, errorMessage); - } - }; - } - - // - - private boolean isApplyModificationsComplete() - { - return finishedModifications == modificationsByModel.size(); - } - - private void onApplyModificationsComplete() - { - if (failedModifications.size() > 0) - { - String failureTitle = - (failedModifications.size() == modificationsByModel.size()) ? "Operation failed" - : "Operation partly failed"; - String failureReport = createFailedModificationsReport(); - MessageBox.alert(failureTitle, failureReport, null); - refresh(); - } else - { - GWTUtils.displayInfo("All modifications successfully applied."); - grid.getStore().commitChanges(); // no need to refresh - everything should be valid - } - clearModifications(); - if (afterSaveActionOrNull != null) - { - afterSaveActionOrNull.execute(); - } - } - - private String createFailedModificationsReport() - { - assert failedModifications.size() > 0; - StringBuilder result = new StringBuilder(); - result.append("Modifications of " + failedModifications.size() + " entities failed:"); - for (String error : failedModifications.values()) - { - result.append("<br/>- " + error); - } - return result.toString(); - } - - private void clearModifications() - { - finishedModifications = 0; - failedModifications.clear(); - modificationsByModel.clear(); - hideModificationsBar(); - } - - } - - /** Toolbar for handling table modifications */ - static class TableModificationsToolbar extends ToolBar - { - - public TableModificationsToolbar(final IMessageProvider messageProvider, - final ITableModificationsManager<?> manager) - { - add(new Label(messageProvider.getMessage(Dict.TABLE_MODIFICATIONS))); - - final AbstractImagePrototype confirmIcon = - AbstractImagePrototype.create(IMAGE_BUNDLE.getConfirmIcon()); - final AbstractImagePrototype cancelIcon = - AbstractImagePrototype.create(IMAGE_BUNDLE.getCancelIcon()); - add(new Button("Save", confirmIcon, new SelectionListener<ButtonEvent>() - { - @Override - public void componentSelected(ButtonEvent be) - { - manager.saveModifications(); - } - })); - add(new Button("Cancel", cancelIcon, new SelectionListener<ButtonEvent>() - { - @Override - public void componentSelected(ButtonEvent be) - { - manager.cancelModifications(); - } - })); - } - - } - -} diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/BrowserGridPagingToolBar.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/BrowserGridPagingToolBar.java index 6ca167983d4..e5ad449fb1d 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/BrowserGridPagingToolBar.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/BrowserGridPagingToolBar.java @@ -217,12 +217,12 @@ public final class BrowserGridPagingToolBar extends PagingToolBar disable(); } - protected final void updateDefaultRefreshButton(boolean isEnabled) + public final void updateDefaultRefreshButton(boolean isEnabled) { updateRefreshButton(refreshButton, isEnabled, messageProvider); } - protected final void updateDefaultConfigButton(boolean isEnabled) + public final void updateDefaultConfigButton(boolean isEnabled) { updateConfigButton(configButton, isEnabled, messageProvider); } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/CustomColumnsMetadataProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/CustomColumnsMetadataProvider.java index 00b1f45afc0..7f25604c1f4 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/CustomColumnsMetadataProvider.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/CustomColumnsMetadataProvider.java @@ -28,7 +28,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.GridCustomColumnIn * * @author Tomasz Pylak */ -class CustomColumnsMetadataProvider +public class CustomColumnsMetadataProvider { // what custom columns are present in the grid. Used to build column definitions. private List<GridCustomColumnInfo> customColumnsMetadata = diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ExtendedGridView.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ExtendedGridView.java index 65802c784f1..be9fb62c8b7 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ExtendedGridView.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/ExtendedGridView.java @@ -21,7 +21,7 @@ import com.google.gwt.user.client.Element; * * @author Izabela Adamczyk */ -class ExtendedGridView extends GridView +public class ExtendedGridView extends GridView { @Override protected ColumnHeader newColumnHeader() diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/PendingFetchManager.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/PendingFetchManager.java index e1de0a17bbb..4ac57765c2c 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/PendingFetchManager.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/PendingFetchManager.java @@ -23,7 +23,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSetFetchConf * * @author Chandrasekhar Ramakrishnan */ -class PendingFetchManager +public class PendingFetchManager { // The current pending fetch, or null if there is none. private ResultSetFetchConfig<String> pendingFetchConfigOrNull = null; diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractGridDataRefreshCallback.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractGridDataRefreshCallback.java index 8c579ec04fd..9c01cf8b0c4 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractGridDataRefreshCallback.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/AbstractGridDataRefreshCallback.java @@ -16,26 +16,27 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.BaseEntityModel; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.AbstractBrowserGrid; +import java.io.Serializable; + +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.TypedTableGrid; /** - * Abstract {@link IDataRefreshCallback} implementation for refreshing {@link AbstractBrowserGrid}. + * Abstract {@link IDataRefreshCallback} implementation for refreshing {@link TypedTableGrid}. * * @author Piotr Buczek */ -public abstract class AbstractGridDataRefreshCallback<T/* Entity */> implements - IDataRefreshCallback +public abstract class AbstractGridDataRefreshCallback<T extends Serializable/* Entity */> + implements IDataRefreshCallback { - private AbstractBrowserGrid<T, BaseEntityModel<T>> grid; + private TypedTableGrid<T> grid; - public AbstractBrowserGrid<T, BaseEntityModel<T>> getGrid() + public TypedTableGrid<T> getGrid() { assert grid != null : "grid not set!"; return grid; } - public void setGrid(AbstractBrowserGrid<T, BaseEntityModel<T>> grid) + public void setGrid(TypedTableGrid<T> grid) { this.grid = grid; } diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AuthorizationManagementConsolTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AuthorizationManagementConsolTest.java index 387bf760395..3b2428f37d8 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AuthorizationManagementConsolTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AuthorizationManagementConsolTest.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application; import com.google.gwt.user.client.rpc.AsyncCallback; import ch.systemsx.cisd.openbis.generic.client.web.client.application.menu.TopMenu.ActionMenuKind; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.TypedTableGrid; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.CheckGroupTable; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.CheckPersonTable; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.CheckRoleAssignmentTable; @@ -27,7 +28,6 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.Cre import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.FillRoleAssignmentForm; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.OpenRoleAssignmentDialog; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.amc.RoleAssignmentRow; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.AbstractBrowserGrid; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.PersonGridColumnIDs; import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SpaceGridColumnIDs; import ch.systemsx.cisd.openbis.generic.client.web.client.testframework.AbstractGWTTestCase; @@ -114,7 +114,7 @@ public class AuthorizationManagementConsolTest extends AbstractGWTTestCase FailureExpectation failureExpectation = new FailureExpectation( - (Class<? extends AsyncCallback<?>>) AbstractBrowserGrid.ListEntitiesCallback.class) + (Class<? extends AsyncCallback<?>>) TypedTableGrid.ListEntitiesCallback.class) .with("Authorization failure: None of method roles '[INSTANCE_ADMIN]' " + "could be found in roles of user 'o'."); diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/SampleBrowserTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/SampleBrowserTest.java index 63341ce1850..846c40f32c3 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/SampleBrowserTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/SampleBrowserTest.java @@ -17,11 +17,11 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application; import ch.systemsx.cisd.openbis.generic.client.web.client.application.menu.TopMenu.ActionMenuKind; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.ColumnSettingsConfigurer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.InvokeActionMenu; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.Login; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.Logout; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.SpaceSelectionWidget; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnSettingsConfigurer; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.sample.CheckSampleTable; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.sample.ExportSamplesTestCommand; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.sample.ListSamples; diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/util/GridTestUtils.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/util/GridTestUtils.java index a1030977338..97a4e3fedc1 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/util/GridTestUtils.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/util/GridTestUtils.java @@ -25,9 +25,9 @@ import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.widget.button.Button; import com.extjs.gxt.ui.client.widget.grid.Grid; +import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.TypedTableGrid; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.EntityPropertyColDef; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.specific.data.DataSetPropertyColDef; -import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.AbstractBrowserGrid; import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.BrowserGridPagingToolBar; import ch.systemsx.cisd.openbis.generic.client.web.client.testframework.GWTTestUtil; import ch.systemsx.cisd.openbis.generic.client.web.client.testframework.TestUtil; @@ -35,7 +35,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample; /** - * Utility methods to test subclasses of {@link AbstractBrowserGrid} + * Utility methods to test subclasses of {@link TypedTableGrid} * * @author Tomasz Pylak */ -- GitLab