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
new file mode 100644
index 0000000000000000000000000000000000000000..53d9d09142476324769cf79a513a9ef7760852b1
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGrid.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2010 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+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.IViewContext;
+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.renderer.RealNumberRenderer;
+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.grid.AbstractBrowserGrid;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.ColumnDefsAndConfigs;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.DefaultResultSetConfig;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.TypedTableResultSet;
+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.dto.DataTypeCode;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public abstract class TypedTableGrid<T extends IsSerializable>
+        extends
+        AbstractBrowserGrid<TableModelRowWithObject<T>, BaseEntityModel<TableModelRowWithObject<T>>>
+{
+    private List<TableModelColumnHeader> headers;
+
+    public TypedTableGrid(IViewContext<ICommonClientServiceAsync> viewContext, String gridId,
+            boolean refreshAutomatically, IDisplayTypeIDGenerator displayTypeIDGenerator)
+    {
+        super(viewContext, gridId, refreshAutomatically, displayTypeIDGenerator);
+    }
+
+    public TypedTableGrid(IViewContext<ICommonClientServiceAsync> viewContext, String gridId,
+            IDisplayTypeIDGenerator displayTypeIDGenerator)
+    {
+        super(viewContext, gridId, displayTypeIDGenerator);
+    }
+    
+    /**
+     * Lists table rows. Implementations of this method usually call a server method.
+     */
+    protected abstract void listTableRows(
+            IResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig,
+            AsyncCallback<TypedTableResultSet<T>> callback);
+
+    @Override
+    protected ColumnDefsAndConfigs<TableModelRowWithObject<T>> createColumnsDefinition()
+    {
+        ColumnDefsAndConfigs<TableModelRowWithObject<T>> definitions =
+                ColumnDefsAndConfigs.create(createColDefinitions());
+        if (headers != null)
+        {
+            RealNumberRenderer realNumberRenderer =
+                    new RealNumberRenderer(viewContext.getDisplaySettingsManager()
+                            .getRealNumberFormatingParameters());
+            for (TableModelColumnHeader header : headers)
+            {
+                if (header.getDataType() == DataTypeCode.REAL)
+                {
+                    definitions.setGridCellRendererFor(header.getId(), realNumberRenderer);
+                }
+            }
+        }
+        return definitions;
+    }
+
+    @Override
+    protected BaseEntityModel<TableModelRowWithObject<T>> createModel(
+            GridRowModel<TableModelRowWithObject<T>> entity)
+    {
+        return new BaseEntityModel<TableModelRowWithObject<T>>(entity, createColDefinitions());
+    }
+
+
+    private List<IColumnDefinitionUI<TableModelRowWithObject<T>>> createColDefinitions()
+    {
+        List<IColumnDefinitionUI<TableModelRowWithObject<T>>> list =
+                new ArrayList<IColumnDefinitionUI<TableModelRowWithObject<T>>>();
+        if (headers != null)
+        {
+            for (TableModelColumnHeader header : headers)
+            {
+                String title = header.getTitle();
+                if (title == null)
+                {
+                    title = viewContext.getMessage(header.getId());
+                }
+                list.add(new TypedTableGridColumnDefinitionUI<T>(header, title));
+            }
+        }
+        return list;
+    }
+
+    @Override
+    protected void listEntities(
+            DefaultResultSetConfig<String, TableModelRowWithObject<T>> resultSetConfig,
+            final AbstractAsyncCallback<ResultSet<TableModelRowWithObject<T>>> callback)
+    {
+        AbstractAsyncCallback<TypedTableResultSet<T>> extendedCallback =
+                new AbstractAsyncCallback<TypedTableResultSet<T>>(viewContext)
+                    {
+                        @Override
+                        protected void process(TypedTableResultSet<T> result)
+                        {
+                            headers = result.getHeaders();
+                            callback.onSuccess(result.getResultSet());
+                            recreateColumnModelAndRefreshColumnsWithFilters();
+                        }
+
+                        @Override
+                        public void finishOnFailure(Throwable caught)
+                        {
+                            callback.finishOnFailure(caught);
+                        }
+                    };
+        listTableRows(resultSetConfig, extendedCallback);
+    }
+
+    @Override
+    protected boolean isRefreshEnabled()
+    {
+        return true;
+    }
+    
+    @Override
+    protected void refresh()
+    {
+        refresh(false);
+    }
+
+    @Override
+    protected void showEntityViewer(TableModelRowWithObject<T> entity, boolean editMode, boolean active)
+    {
+    }
+
+    @Override
+    protected List<IColumnDefinition<TableModelRowWithObject<T>>> getInitialFilters()
+    {
+        return Collections.emptyList();
+    }
+    
+    public DatabaseModificationKind[] getRelevantModifications()
+    {
+        return new DatabaseModificationKind[] {};
+    }
+
+    public void update(Set<DatabaseModificationKind> observedModifications)
+    {
+        refreshGridSilently();
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGridColumnDefinitionUI.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGridColumnDefinitionUI.java
new file mode 100644
index 0000000000000000000000000000000000000000..65b0b764def74542a03aacdb0606126188b23935
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TypedTableGridColumnDefinitionUI.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 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;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+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.TypedTableGridColumnDefinition;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TypedTableGridColumnDefinitionUI<T extends IsSerializable> extends
+        TypedTableGridColumnDefinition<T> implements IColumnDefinitionUI<TableModelRowWithObject<T>>
+{
+    public TypedTableGridColumnDefinitionUI(TableModelColumnHeader header, String title)
+    {
+        super(header, title);
+    }
+
+    // GWT only
+    @SuppressWarnings("unused")
+    private TypedTableGridColumnDefinitionUI()
+    {
+        this(null, null);
+    }
+    
+    public int getWidth()
+    {
+        return header.getDefaultColumnWidth();
+    }
+
+    public boolean isHidden()
+    {
+        return false;
+    }
+
+    public boolean isLink()
+    {
+        return false;
+    }
+
+    public boolean isNumeric()
+    {
+        DataTypeCode type = header.getDataType();
+        return type == DataTypeCode.INTEGER || type == DataTypeCode.REAL;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/columns/specific/TypedTableGridColumnDefinition.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/columns/specific/TypedTableGridColumnDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fa1fc06bc0ebf36cf6d2aad4b6eb787b2329469
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/columns/specific/TypedTableGridColumnDefinition.java
@@ -0,0 +1,59 @@
+package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.specific;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+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.dto.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TypedTableGridColumnDefinition<T extends IsSerializable> implements
+        IColumnDefinition<TableModelRowWithObject<T>>
+{
+    protected TableModelColumnHeader header;
+
+    private String title;
+
+    public TypedTableGridColumnDefinition(TableModelColumnHeader header, String title)
+    {
+        this.header = header;
+        this.title = title;
+    }
+    
+
+    // GWT only
+    @SuppressWarnings("unused")
+    private TypedTableGridColumnDefinition()
+    {
+    }
+
+    public String getHeader()
+    {
+        return title;
+    }
+
+    public String getIdentifier()
+    {
+        return header.getId();
+    }
+
+    public String tryToGetProperty(String key)
+    {
+        return null;
+    }
+
+    public String getValue(GridRowModel<TableModelRowWithObject<T>> rowModel)
+    {
+        return rowModel.getOriginalObject().getValues().get(header.getIndex()).toString();
+    }
+
+    public Comparable<?> tryGetComparableValue(GridRowModel<TableModelRowWithObject<T>> rowModel)
+    {
+        return rowModel.getOriginalObject().getValues().get(header.getIndex());
+    }
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/TypedTableResultSet.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/TypedTableResultSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..738da1791a278274e2a7a391862a3b72c6f4d5c0
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/TypedTableResultSet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 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.dto;
+
+import java.util.List;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TypedTableResultSet<T extends IsSerializable> implements IsSerializable
+{
+    private ResultSet<TableModelRowWithObject<T>> resultSet;
+    private List<TableModelColumnHeader> headers;
+
+    public TypedTableResultSet(ResultSet<TableModelRowWithObject<T>> resultSet, List<TableModelColumnHeader> headers)
+    {
+        this.resultSet = resultSet;
+        this.headers = headers;
+    }
+    
+    // GWT only
+    @SuppressWarnings("unused")
+    private TypedTableResultSet()
+    {
+    }
+
+    public final ResultSet<TableModelRowWithObject<T>> getResultSet()
+    {
+        return resultSet;
+    }
+
+    public final List<TableModelColumnHeader> getHeaders()
+    {
+        return headers;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/ITableModelProvider.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/ITableModelProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..2551e7d8b680c8fbbc0ff54542ec9edcee6971f2
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/ITableModelProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.server;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TypedTableModel;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface ITableModelProvider<T extends IsSerializable>
+{
+    public TypedTableModel<T> getTableModel();
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/TypedTableModelBuilder.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/TypedTableModelBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..87d5f80ebd2d17c9ce48886b960601d562d518fd
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/util/TypedTableModelBuilder.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2010 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.server.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ISerializableComparable;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.StringTableCell;
+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;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TypedTableModelBuilder<T extends IsSerializable>
+{
+    private static final class Column
+    {
+        private final TableModelColumnHeader header;
+        private final List<ISerializableComparable> values = new ArrayList<ISerializableComparable>();
+
+        Column(TableModelColumnHeader header)
+        {
+            this.header = header;
+        }
+        
+        public TableModelColumnHeader getHeader()
+        {
+            return header;
+        }
+        
+        public ISerializableComparable getValue(int index)
+        {
+            return values.get(index);
+        }
+
+        public void insertValueAt(int index, ISerializableComparable value)
+        {
+            while (index > values.size())
+            {
+                values.add(new StringTableCell(""));
+            }
+            values.add(index, value);
+        }
+    }
+    
+    private final Map<String, Column> columns = new HashMap<String, Column>();
+    
+    private final List<T> rowObjects = new ArrayList<T>();
+    
+    public TypedTableModel<T> getModel()
+    {
+        List<Column> cols = new ArrayList<Column>(columns.values());
+        Collections.sort(cols, new Comparator<Column>()
+            {
+                public int compare(Column c1, Column c2)
+                {
+                    return c1.getHeader().getIndex() - c2.getHeader().getIndex();
+                }
+            });
+        List<TableModelColumnHeader> headers = new ArrayList<TableModelColumnHeader>();
+        for (Column column : cols)
+        {
+            headers.add(column.getHeader());
+        }
+        List<TableModelRowWithObject<T>> rows = new ArrayList<TableModelRowWithObject<T>>();
+        for (int i = 0, n = rowObjects.size(); i < n; i++)
+        {
+            T object = rowObjects.get(i);
+            List<ISerializableComparable> rowValues = new ArrayList<ISerializableComparable>(headers.size());
+            for (int j = 0, m = headers.size(); j < m; j++)
+            {
+                rowValues.add(null);
+            }
+            for (int j = 0, m = headers.size(); j < m; j++)
+            {
+                int index = headers.get(j).getIndex();
+                rowValues.set(index, cols.get(index).getValue(i));
+            }
+            rows.add(new TableModelRowWithObject<T>(object, rowValues));
+        }
+        return new TypedTableModel<T>(headers, rows);
+    }
+
+    public void addColumn(String id)
+    {
+        addColumn(null, id);
+    }
+    
+    public void addColumn(String titleOrNull, String id)
+    {
+        Column column = createColumn(titleOrNull, id);
+        Column oldColumn = columns.put(id, column);
+        if (oldColumn != null)
+        {
+            throw new IllegalArgumentException("There is already a column with id '" + id + "'.");
+        }
+    }
+
+    public void addStringValueToColumn(String id, String value)
+    {
+        addValueToColumn(null, id, new StringTableCell(value));
+    }
+
+    public void addStringValueToColumn(String title, String id, String value)
+    {
+        addValueToColumn(title, id, new StringTableCell(value));
+    }
+    
+    public void addValueToColumn(String titleOrNull, String id, ISerializableComparable value)
+    {
+        getOrCreateColumn(titleOrNull, id).insertValueAt(rowObjects.size() - 1, value);
+    }
+    
+    private Column getOrCreateColumn(String titleOrNull, String id)
+    {
+        Column column = columns.get(id);
+        if (column == null)
+        {
+            column = createColumn(titleOrNull, id);
+            columns.put(id, column);
+        }
+        return column;
+    }
+
+    private Column createColumn(String titleOrNull, String id)
+    {
+        TableModelColumnHeader header = new TableModelColumnHeader(titleOrNull, id, columns.size());
+        return new Column(header);
+    }
+
+    public void addRow(T object)
+    {
+        rowObjects.add(object);
+    }
+    
+}
+
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java
index 64e8155b7666e07f72e865854f0492886293fde1..8f44f8376c777a8a03c48a172a3c6579e90bcbdd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java
@@ -46,8 +46,7 @@ public class TableModelRow implements IsSerializable, Serializable
     // ---------------------------
 
     // GWT only
-    @SuppressWarnings("unused")
-    private TableModelRow()
+    protected TableModelRow()
     {
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRowWithObject.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRowWithObject.java
new file mode 100644
index 0000000000000000000000000000000000000000..75a5fe72f9e39503c98df8f9bf745b1a2231bd5c
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRowWithObject.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.shared.basic.dto;
+
+import java.util.List;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TableModelRowWithObject<T extends IsSerializable> extends TableModelRow
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+    
+    private T objectOrNull;
+
+    public TableModelRowWithObject(T objectOrNull, List<ISerializableComparable> values)
+    {
+        super(values);
+        this.objectOrNull = objectOrNull;
+    }
+    
+    // GWT only
+    @SuppressWarnings("unused")
+    private TableModelRowWithObject()
+    {
+        
+    }
+
+    public T getObjectOrNull()
+    {
+        return objectOrNull;
+    }
+    
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TypedTableModel.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TypedTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ccf702279db205ede1b6d84d1948bc220e3600f
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TypedTableModel.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 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.shared.basic.dto;
+
+import java.util.List;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class TypedTableModel<T extends IsSerializable>
+{
+    private final List<TableModelColumnHeader> header;
+    private final List<TableModelRowWithObject<T>> rows;
+
+    public TypedTableModel(List<TableModelColumnHeader> header, List<TableModelRowWithObject<T>> rows)
+    {
+        this.header = header;
+        this.rows = rows;
+    }
+
+    public final List<TableModelColumnHeader> getHeader()
+    {
+        return header;
+    }
+
+    public final List<TableModelRowWithObject<T>> getRows()
+    {
+        return rows;
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
index 7eb89f700a03b9fca66415c6931b5663601bc889..1861596ed0d714f77e8a96df1e9bd4a9bdf2f6f0 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
@@ -26,12 +26,14 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet;
 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.client.web.client.exception.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericTableRow;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.LibraryRegistrationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateContent;
@@ -84,6 +86,10 @@ public interface IScreeningClientService extends IClientService
             DefaultResultSetConfig<String, WellContent> gridCriteria,
             PlateMaterialsSearchCriteria materialCriteria);
 
+    public TypedTableResultSet<WellContent> listPlateWells2(
+            IResultSetConfig<String, TableModelRowWithObject<WellContent>> gridCriteria,
+            PlateMaterialsSearchCriteria materialCriteria);
+
     /**
      * Like {@link ICommonClientService#prepareExportSamples(TableExportCriteria)}, but for
      * WellContent.
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
index bac854bcfef2a718e2b847ffc40103a01b1e1d17..11537b3e416027ab27ecc5312d8598ea63e512a4 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
@@ -27,11 +27,13 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet;
 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.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericTableRow;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.LibraryRegistrationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateContent;
@@ -71,6 +73,13 @@ public interface IScreeningClientServiceAsync extends IClientServiceAsync
             PlateMaterialsSearchCriteria materialCriteria,
             AsyncCallback<ResultSet<WellContent>> callback);
 
+    /**
+     * @see IScreeningClientService#listPlateWells2(IResultSetConfig, PlateMaterialsSearchCriteria)
+     */
+    public void listPlateWells2(IResultSetConfig<String, TableModelRowWithObject<WellContent>> gridCriteria,
+            PlateMaterialsSearchCriteria materialCriteria,
+            AsyncCallback<TypedTableResultSet<WellContent>> callback);
+    
     /**
      * @see IScreeningClientService#prepareExportPlateLocations(TableExportCriteria)
      */
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/PlateMaterialReviewer2.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/PlateMaterialReviewer2.java
new file mode 100644
index 0000000000000000000000000000000000000000..3825a17950819a7bfaa296dcd8c8e41c25d8454e
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/PlateMaterialReviewer2.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2010 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.plugin.screening.client.web.client.application.detailviewers;
+
+import com.extjs.gxt.ui.client.Style.Orientation;
+import com.extjs.gxt.ui.client.event.BaseEvent;
+import com.extjs.gxt.ui.client.event.Events;
+import com.extjs.gxt.ui.client.event.Listener;
+import com.extjs.gxt.ui.client.widget.Component;
+import com.extjs.gxt.ui.client.widget.Label;
+import com.extjs.gxt.ui.client.widget.LayoutContainer;
+import com.extjs.gxt.ui.client.widget.form.Radio;
+import com.extjs.gxt.ui.client.widget.form.RadioGroup;
+import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.GenericConstants;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.TypedTableGrid;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.field.ExperimentChooserField;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.field.IChosenEntityListener;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.field.ExperimentChooserField.ExperimentChooserFieldAdaptor;
+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.ICellListener;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisposableComponent;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
+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.IEntityInformationHolderWithIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.IScreeningClientServiceAsync;
+import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.ClientPluginFactory;
+import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.DisplayTypeIDGenerator;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ExperimentReference;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateMaterialsSearchCriteria;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateMaterialsSearchCriteria.ExperimentSearchCriteria;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateMaterialsSearchCriteria.MaterialSearchCriteria;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateMaterialsSearchCriteria.SingleExperimentSearchCriteria;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class PlateMaterialReviewer2 extends TypedTableGrid<WellContent>
+{
+    public static final String BROWSER_ID =
+            GenericConstants.ID_PREFIX + "PlateMaterialReviewer2Grid";
+    public static final String GRID_ID = BROWSER_ID + "-grid";
+    private static final String ALL_EXPERIMENTS_TEXT = "All experiments";
+    private static final String CHOOSE_ONE_EXPERIMENT_TEXT = "Choose one experiment...";
+    
+    public static IDisposableComponent create(
+            IViewContext<IScreeningClientServiceAsync> viewContext,
+            IEntityInformationHolderWithIdentifier experiment, String[] materialItemList,
+            String[] materialTypeCodes, boolean exactMatchOnly)
+    {
+        ExperimentSearchCriteria experimentCriteria =
+                ExperimentSearchCriteria.createExperiment(experiment.getId(),
+                        experiment.getIdentifier());
+        MaterialSearchCriteria materialCriteria =
+                MaterialSearchCriteria.createCodesCriteria(materialItemList, materialTypeCodes,
+                        exactMatchOnly);
+        return create(viewContext, experimentCriteria, materialCriteria);
+    }
+
+    public static IDisposableComponent create(
+            IViewContext<IScreeningClientServiceAsync> viewContext,
+            ExperimentSearchCriteria experimentCriteriaOrNull, TechId materialId)
+    {
+        return create(viewContext, experimentCriteriaOrNull,
+                MaterialSearchCriteria.createIdCriteria(materialId));
+    }
+
+    private static IDisposableComponent create(
+            IViewContext<IScreeningClientServiceAsync> viewContext,
+            ExperimentSearchCriteria experimentCriteriaOrNull,
+            MaterialSearchCriteria materialCriteria)
+    {
+        PlateMaterialReviewer2 reviewer =
+                new PlateMaterialReviewer2(viewContext, experimentCriteriaOrNull, materialCriteria);
+        return reviewer.asDisposableWithToolbar(reviewer.createToolbar());
+    }
+
+    private final IViewContext<IScreeningClientServiceAsync> viewContext;
+
+    private final MaterialSearchCriteria materialCriteria;
+
+    private ExperimentSearchCriteria experimentCriteriaOrNull;
+    
+    private SingleExperimentSearchCriteria singleExperimentChooserStateOrNull;
+    
+    private ChannelComboBox channelChooser;
+
+    private PlateMaterialReviewer2(IViewContext<IScreeningClientServiceAsync> viewContext,
+            ExperimentSearchCriteria experimentCriteriaOrNull,
+            MaterialSearchCriteria materialCriteria)
+    {
+        super(viewContext.getCommonViewContext(), GRID_ID, experimentCriteriaOrNull != null,
+                DisplayTypeIDGenerator.PLATE_MATERIAL_REVIEWER);
+        this.viewContext = viewContext;
+        this.experimentCriteriaOrNull = experimentCriteriaOrNull;
+        this.materialCriteria = materialCriteria;
+        setId(BROWSER_ID);
+        channelChooser = new ChannelComboBox();
+        registerClickListeners();
+    }
+
+    private ToolBar createToolbar()
+    {
+        ToolBar toolbar = new ToolBar();
+        toolbar.add(createExperimentChooser());
+        toolbar.add(new Label("Channel:"));
+        toolbar.add(channelChooser);
+        return toolbar;
+    }
+
+    private Component createExperimentChooser()
+    {
+        LayoutContainer container = new LayoutContainer();
+        container.setWidth(400);
+
+        ExperimentChooserFieldAdaptor singleExperimentChooser = createSingleExperimentChooser();
+        RadioGroup experimentRadioChooser = createExperimentRadio(singleExperimentChooser);
+
+        container.add(experimentRadioChooser);
+        container.add(singleExperimentChooser.getField());
+        return container;
+    }
+
+    private ExperimentChooserFieldAdaptor createSingleExperimentChooser()
+    {
+        ExperimentChooserFieldAdaptor experimentChooser =
+                ExperimentChooserField.create("", true, null, viewContext.getCommonViewContext());
+        final ExperimentChooserField chooserField = experimentChooser.getChooserField();
+        chooserField.addChosenEntityListener(new IChosenEntityListener<Experiment>()
+            {
+                public void entityChosen(Experiment experiment)
+                {
+                    if (experiment != null)
+                    {
+                        chooseSingleExperiment(chooserField, experiment);
+                    }
+                }
+            });
+
+        chooserField.setEditable(false);
+        if (experimentCriteriaOrNull != null && experimentCriteriaOrNull.tryGetExperiment() != null)
+        {
+            updateSingleExperimentChooser(chooserField, experimentCriteriaOrNull.tryGetExperiment());
+        } else
+        {
+            // we search in all experiments or single experiment has not been chosen
+            this.singleExperimentChooserStateOrNull = null;
+            chooserField.reset();
+        }
+        if (experimentCriteriaOrNull == null || experimentCriteriaOrNull.tryGetExperiment() != null)
+        {
+            chooserField.setEmptyText(CHOOSE_ONE_EXPERIMENT_TEXT);
+        } else
+        {
+            chooserField.setEmptyText(ALL_EXPERIMENTS_TEXT);
+        }
+        return experimentChooser;
+    }
+
+    private void chooseSingleExperiment(final ExperimentChooserField chooserField,
+            Experiment experiment)
+    {
+        SingleExperimentSearchCriteria singleExperiment =
+                new SingleExperimentSearchCriteria(experiment.getId(), experiment.getIdentifier());
+        updateSingleExperimentChooser(chooserField, singleExperiment);
+        this.experimentCriteriaOrNull =
+                ExperimentSearchCriteria.createExperiment(singleExperiment);
+        refresh();
+    }
+
+    private void updateSingleExperimentChooser(ExperimentChooserField chooserField,
+            SingleExperimentSearchCriteria singleExperiment)
+    {
+        this.singleExperimentChooserStateOrNull = singleExperiment;
+        chooserField.updateValue(new ExperimentIdentifier(singleExperiment.getExperimentIdentifier()));
+    }
+
+    private boolean isAllExperimentsChoosen()
+    {
+        return experimentCriteriaOrNull != null
+                && experimentCriteriaOrNull.tryGetExperiment() == null;
+    }
+
+    private RadioGroup createExperimentRadio(
+            final ExperimentChooserFieldAdaptor singleExperimentChooser)
+    {
+        final RadioGroup experimentRadio = new RadioGroup();
+        experimentRadio.setSelectionRequired(true);
+        experimentRadio.setOrientation(Orientation.HORIZONTAL);
+
+        final Radio allExps = new Radio();
+        allExps.setBoxLabel(ALL_EXPERIMENTS_TEXT);
+        experimentRadio.add(allExps);
+
+        final Radio oneExps = new Radio();
+        oneExps.setBoxLabel("Single experiment");
+        experimentRadio.add(oneExps);
+
+        experimentRadio.setValue(isAllExperimentsChoosen() ? allExps : oneExps);
+        experimentRadio.setAutoHeight(true);
+        experimentRadio.addListener(Events.Change, new Listener<BaseEvent>()
+            {
+                public void handleEvent(BaseEvent be)
+                {
+                    if (allExps.getValue())
+                    {
+                        singleExperimentChooser.getChooserField().setEnabled(false);
+                        singleExperimentChooser.getChooserField()
+                                .setEmptyText(ALL_EXPERIMENTS_TEXT);
+                        experimentCriteriaOrNull = ExperimentSearchCriteria.createAllExperiments();
+                        refresh();
+                    } else
+                    {
+                        singleExperimentChooser.getChooserField().setEmptyText(
+                                CHOOSE_ONE_EXPERIMENT_TEXT);
+
+                        singleExperimentChooser.getChooserField().setEnabled(true);
+                        if (singleExperimentChooserStateOrNull == null)
+                        {
+                            experimentCriteriaOrNull = null;
+                        } else
+                        {
+                            experimentCriteriaOrNull =
+                                    ExperimentSearchCriteria
+                                            .createExperiment(singleExperimentChooserStateOrNull);
+                            refresh();
+                        }
+                    }
+                }
+            });
+        return experimentRadio;
+    }
+    
+    private void registerClickListeners()
+    {
+        registerLinkClickListenerFor("WELL_CONTENT_MATERIAL",
+                new ICellListener<TableModelRowWithObject<WellContent>>()
+                    {
+                        public void handle(TableModelRowWithObject<WellContent> wellContent, boolean specialKeyPressed)
+                        {
+                            Material contentMaterial = wellContent.getObjectOrNull().getMaterialContent();
+                            ExperimentReference experiment = wellContent.getObjectOrNull().getExperiment();
+                            ExperimentSearchCriteria experimentCriteria =
+                                    ExperimentSearchCriteria.createExperiment(experiment.getId(),
+                                            experiment.getExperimentIdentifier());
+
+                            ClientPluginFactory.openPlateLocationsMaterialViewer(contentMaterial,
+                                    experimentCriteria, viewContext);
+                        }
+                    });
+    }
+
+
+    @Override
+    protected ColumnDefsAndConfigs<TableModelRowWithObject<WellContent>> createColumnsDefinition()
+    {
+        ColumnDefsAndConfigs<TableModelRowWithObject<WellContent>> columnDefs = super.createColumnsDefinition();
+        columnDefs.setGridCellRendererFor("WELL_CONTENT_MATERIAL", LinkRenderer
+                .createLinkRenderer());
+
+        return columnDefs;
+    }
+
+    @Override
+    protected void listTableRows(
+            IResultSetConfig<String, TableModelRowWithObject<WellContent>> resultSetConfig,
+            AsyncCallback<TypedTableResultSet<WellContent>> callback)
+    {
+        assert experimentCriteriaOrNull != null : "experiment not specified";
+
+        PlateMaterialsSearchCriteria searchCriteria =
+                new PlateMaterialsSearchCriteria(experimentCriteriaOrNull, materialCriteria);
+        viewContext.getService().listPlateWells2(resultSetConfig, searchCriteria, callback);
+    }
+
+    @Override
+    protected void prepareExportEntities(
+            TableExportCriteria<TableModelRowWithObject<WellContent>> exportCriteria,
+            AbstractAsyncCallback<String> callback)
+    {
+        // TODO Auto-generated method stub
+        
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
index 24acd99e1b71079194fc56a0b36b89397ad26ccc..4a5b43973a475cd38849a9fb4effc53327026f4c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
@@ -39,6 +39,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.IResultSetConfig;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ListMaterialDisplayCriteria;
 import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ResultSet;
 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.client.web.client.exception.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.client.web.server.AbstractClientService;
 import ch.systemsx.cisd.openbis.generic.client.web.server.UploadedFilesBean;
@@ -52,6 +53,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericTableColumnHeade
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.GenericTableRow;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
 import ch.systemsx.cisd.openbis.plugin.generic.shared.IGenericServer;
@@ -187,6 +189,24 @@ public final class ScreeningClientService extends AbstractClientService implemen
         }
     }
 
+    public TypedTableResultSet<WellContent> listPlateWells2(
+            IResultSetConfig<String, TableModelRowWithObject<WellContent>> gridCriteria,
+            PlateMaterialsSearchCriteria materialCriteria)
+    {
+        final WellContentProvider provider =
+                new WellContentProvider(server, getSessionToken(), materialCriteria);
+        ResultSet<TableModelRowWithObject<WellContent>> resultSet =
+                listEntities(gridCriteria,
+                        new IOriginalDataProvider<TableModelRowWithObject<WellContent>>()
+                            {
+                                public List<TableModelRowWithObject<WellContent>> getOriginalData()
+                                {
+                                    return provider.getTableModel().getRows();
+                                }
+                            });
+        return new TypedTableResultSet<WellContent>(resultSet, provider.getTableModel().getHeader());
+    }
+
     public String prepareExportPlateLocations(TableExportCriteria<WellContent> criteria)
             throws ch.systemsx.cisd.openbis.generic.client.web.client.exception.UserFailureException
     {
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/WellContentProvider.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/WellContentProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..739103405d20597c12588eda61f2c660bdfa0c78
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/WellContentProvider.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010 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.plugin.screening.client.web.server;
+
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.client.web.server.ITableModelProvider;
+import ch.systemsx.cisd.openbis.generic.server.util.TypedTableModelBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TypedTableModel;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.IScreeningServer;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateMaterialsSearchCriteria;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class WellContentProvider implements ITableModelProvider<WellContent>
+{
+    static final String WELL_CONTENT_MATERIAL_ID = "WELL_CONTENT_MATERIAL";
+    static final String WELL_CONTENT_MATERIAL_TYPE_ID = "WELL_CONTENT_MATERIAL_TYPE";
+    static final String WELL_CONTENT_PROPERTY_ID_PREFIX = "WELL_CONTENT_PROPERTY-";
+    
+    private final IScreeningServer server;
+    private final String sessionToken;
+    private final PlateMaterialsSearchCriteria materialCriteria;
+    private TypedTableModel<WellContent> model;
+
+    WellContentProvider(IScreeningServer server, String sessionToken, PlateMaterialsSearchCriteria materialCriteria)
+    {
+        this.server = server;
+        this.sessionToken = sessionToken;
+        this.materialCriteria = materialCriteria;
+    }
+
+    public TypedTableModel<WellContent> getTableModel()
+    {
+        if (model == null)
+        {
+            TypedTableModelBuilder<WellContent> builder = new TypedTableModelBuilder<WellContent>();
+            builder.addColumn(WELL_CONTENT_MATERIAL_ID);
+            List<WellContent> wells = server.listPlateWells(sessionToken, materialCriteria);
+            for (WellContent well : wells)
+            {
+                builder.addRow(well);
+                String value = well.getMaterialContent().getCode();
+                builder.addStringValueToColumn(WELL_CONTENT_MATERIAL_ID, value);
+                List<IEntityProperty> properties = well.getMaterialContent().getProperties();
+                for (IEntityProperty property : properties)
+                {
+                    PropertyType propertyType = property.getPropertyType();
+                    String code = propertyType.getCode();
+                    builder.addStringValueToColumn(propertyType.getLabel(),
+                            WELL_CONTENT_PROPERTY_ID_PREFIX + code, property.tryGetAsString());
+                }
+            }
+            model = builder.getModel();
+        }
+        return model;
+    }
+}