aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2014-06-13 17:08:07 +0300
committerHenrik Paul <henrik@vaadin.com>2014-06-25 13:31:48 +0000
commit10f7eb04e354a072a04af362f95f831f0656abf7 (patch)
treef15636982640893be728e893fff4862b224bdc4a
parent7a518fa8a1431789b30b53feded52ea1dac35c9e (diff)
downloadvaadin-framework-10f7eb04e354a072a04af362f95f831f0656abf7.tar.gz
vaadin-framework-10f7eb04e354a072a04af362f95f831f0656abf7.zip
Server-side renderer groundwork (#13334)
Change-Id: I12a0fec1b0e49cfb481b0a7c8b6a40a4d43db7f5
-rw-r--r--client/src/com/vaadin/client/ui/grid/Grid.java13
-rw-r--r--client/src/com/vaadin/client/ui/grid/GridConnector.java30
-rw-r--r--client/src/com/vaadin/client/ui/grid/renderers/AbstractRendererConnector.java126
-rw-r--r--client/src/com/vaadin/client/ui/grid/renderers/TextRendererConnector.java43
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java65
-rw-r--r--server/src/com/vaadin/ui/components/grid/Grid.java11
-rw-r--r--server/src/com/vaadin/ui/components/grid/GridColumn.java182
-rw-r--r--server/src/com/vaadin/ui/components/grid/Renderer.java71
-rw-r--r--server/src/com/vaadin/ui/components/grid/renderers/AbstractRenderer.java69
-rw-r--r--server/src/com/vaadin/ui/components/grid/renderers/TextRenderer.java34
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/grid/RendererTest.java201
-rw-r--r--shared/src/com/vaadin/shared/data/DataProviderRpc.java2
-rw-r--r--shared/src/com/vaadin/shared/ui/grid/GridColumnState.java3
13 files changed, 831 insertions, 19 deletions
diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java
index 0e22693638..dfcda40b04 100644
--- a/client/src/com/vaadin/client/ui/grid/Grid.java
+++ b/client/src/com/vaadin/client/ui/grid/Grid.java
@@ -515,12 +515,7 @@ public class Grid<T> extends Composite implements
}
/**
- * Returns the data that should be rendered into the cell. By default
- * returning Strings and Widgets are supported. If the return type is a
- * String then it will be treated as preformatted text.
- * <p>
- * To support other types you will need to pass a custom renderer to the
- * column via the column constructor.
+ * Returns the data that should be rendered into the cell.
*
* @param row
* The row object that provides the cell content.
@@ -530,11 +525,9 @@ public class Grid<T> extends Composite implements
public abstract C getValue(T row);
/**
- * The renderer to render the cell width. By default renders the data as
- * a String or adds the widget into the cell if the column type is of
- * widget type.
+ * Returns the renderer used to render the cells of this column.
*
- * @return The renderer to render the cell content with
+ * @return the renderer to render the cell content with
*/
public Renderer<? super C> getRenderer() {
return bodyRenderer;
diff --git a/client/src/com/vaadin/client/ui/grid/GridConnector.java b/client/src/com/vaadin/client/ui/grid/GridConnector.java
index 8685180d38..ab304a9214 100644
--- a/client/src/com/vaadin/client/ui/grid/GridConnector.java
+++ b/client/src/com/vaadin/client/ui/grid/GridConnector.java
@@ -26,7 +26,7 @@ import java.util.Set;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.grid.renderers.TextRenderer;
+import com.vaadin.client.ui.grid.renderers.AbstractRendererConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.grid.ColumnGroupRowState;
import com.vaadin.shared.ui.grid.ColumnGroupState;
@@ -53,16 +53,31 @@ public class GridConnector extends AbstractComponentConnector {
private final String id;
- public CustomGridColumn(String id) {
- super(new TextRenderer());
+ private AbstractRendererConnector<String> rendererConnector;
+
+ public CustomGridColumn(String id,
+ AbstractRendererConnector<String> rendererConnector) {
+ super(rendererConnector.getRenderer());
+ this.rendererConnector = rendererConnector;
this.id = id;
}
@Override
public String getValue(String[] obj) {
+ // TODO this should invoke AbstractRendererConnector.decode
return obj[resolveCurrentIndexFromState()];
}
+ /*
+ * Only used to check that the renderer connector will not change during
+ * the column lifetime.
+ *
+ * TODO remove once support for changing renderers is implemented
+ */
+ private AbstractRendererConnector<String> getRendererConnector() {
+ return rendererConnector;
+ }
+
private int resolveCurrentIndexFromState() {
List<GridColumnState> columns = getState().columns;
int numColumns = columns.size();
@@ -195,6 +210,12 @@ public class GridConnector extends AbstractComponentConnector {
GridColumn<?, String[]> column = getWidget().getColumn(columnIndex);
GridColumnState columnState = getState().columns.get(columnIndex);
updateColumnFromState(column, columnState);
+
+ if (columnState.rendererConnector != ((CustomGridColumn) column)
+ .getRendererConnector()) {
+ throw new UnsupportedOperationException(
+ "Changing column renderer after initialization is currently unsupported");
+ }
}
/**
@@ -205,7 +226,8 @@ public class GridConnector extends AbstractComponentConnector {
*/
private void addColumnFromStateChangeEvent(int columnIndex) {
GridColumnState state = getState().columns.get(columnIndex);
- CustomGridColumn column = new CustomGridColumn(state.id);
+ CustomGridColumn column = new CustomGridColumn(state.id,
+ ((AbstractRendererConnector<String>) state.rendererConnector));
columnIdToColumn.put(state.id, column);
// Adds a column to grid, and registers Grid with the column.
diff --git a/client/src/com/vaadin/client/ui/grid/renderers/AbstractRendererConnector.java b/client/src/com/vaadin/client/ui/grid/renderers/AbstractRendererConnector.java
new file mode 100644
index 0000000000..236fdbe9f6
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/renderers/AbstractRendererConnector.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.grid.renderers;
+
+import com.google.gwt.json.client.JSONValue;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.communication.JsonDecoder;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Type;
+import com.vaadin.client.metadata.TypeData;
+import com.vaadin.client.ui.grid.Renderer;
+
+/**
+ * An abstract base class for renderer connectors. A renderer connector is used
+ * to link a client-side {@link Renderer} to a server-side
+ * {@link com.vaadin.ui.components.grid.Renderer Renderer}. As a connector, it
+ * can use the regular Vaadin RPC and shared state mechanism to pass additional
+ * state and information between the client and the server. This base class
+ * itself only uses the basic
+ * {@link com.vaadin.shared.communication.SharedState SharedState} and no RPC
+ * interfaces.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractRendererConnector<T> extends
+ AbstractExtensionConnector {
+
+ private Renderer<T> renderer = null;
+
+ /**
+ * Returns the renderer associated with this renderer connector.
+ * <p>
+ * A subclass of AbstractRendererConnector should override this method as
+ * shown below. The framework uses
+ * {@link com.google.gwt.core.client.GWT#create(Class) GWT.create(Class)} to
+ * create a renderer based on the return type of the overridden method, but
+ * only if {@link #createRenderer()} is not overridden as well:
+ *
+ * <pre>
+ * public MyRenderer getRenderer() {
+ * return (MyRenderer) super.getRenderer();
+ * }
+ * </pre>
+ *
+ * @return the renderer bound to this connector
+ */
+ public Renderer<T> getRenderer() {
+ if (renderer == null) {
+ renderer = createRenderer();
+ }
+ return renderer;
+ }
+
+ /**
+ * Creates a new Renderer instance associated with this renderer connector.
+ * <p>
+ * You should typically not override this method since the framework by
+ * default generates an implementation that uses {@link GWT#create(Class)}
+ * to create a renderer of the same type as returned by the most specific
+ * override of {@link #getRenderer()}. If you do override the method, you
+ * can't call <code>super.createRenderer()</code> since the metadata needed
+ * for that implementation is not generated if there's an override of the
+ * method.
+ *
+ * @return a new renderer to be used with this connector
+ */
+ protected Renderer<T> createRenderer() {
+ // TODO generate type data
+ Type type = TypeData.getType(getClass());
+ try {
+ Type rendererType = type.getMethod("getRenderer").getReturnType();
+ @SuppressWarnings("unchecked")
+ Renderer<T> instance = (Renderer<T>) rendererType.createInstance();
+ return instance;
+ } catch (NoDataException e) {
+ throw new IllegalStateException(
+ "Default implementation of createRenderer() does not work for "
+ + Util.getSimpleName(this)
+ + ". This might be caused by explicitely using "
+ + "super.createRenderer() or some unspecified "
+ + "problem with the widgetset compilation.", e);
+ }
+ }
+
+ /**
+ * Decodes the given JSON value into a value of type T so it can be passed
+ * to the {@link #getRenderer() renderer}.
+ *
+ * TODO This method is currently not called from anywhere
+ *
+ * @param value
+ * the value to decode
+ * @return the decoded value of {@code value}
+ */
+ public T decode(JSONValue value) {
+ @SuppressWarnings("unchecked")
+ T decodedValue = (T) JsonDecoder.decodeValue(new Type(getType()),
+ value, null, getConnection());
+ return decodedValue;
+ }
+
+ // TODO generate data type
+ protected abstract Class<T> getType();
+
+ @Override
+ @Deprecated
+ protected void extend(ServerConnector target) {
+ // NOOP
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/grid/renderers/TextRendererConnector.java b/client/src/com/vaadin/client/ui/grid/renderers/TextRendererConnector.java
new file mode 100644
index 0000000000..76fce7b4b6
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/renderers/TextRendererConnector.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.grid.renderers;
+
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * A connector for {@link TextRenderer}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Connect(com.vaadin.ui.components.grid.renderers.TextRenderer.class)
+public class TextRendererConnector extends AbstractRendererConnector<String> {
+
+ @Override
+ public TextRenderer getRenderer() {
+ return (TextRenderer) super.getRenderer();
+ }
+
+ @Override
+ public TextRenderer createRenderer() {
+ return new TextRenderer();
+ }
+
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+}
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index 507b2d0f22..79121af6b0 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import com.vaadin.data.Container.Indexed;
@@ -31,18 +32,19 @@ import com.vaadin.data.Container.Indexed.ItemRemoveEvent;
import com.vaadin.data.Container.ItemSetChangeEvent;
import com.vaadin.data.Container.ItemSetChangeListener;
import com.vaadin.data.Container.ItemSetChangeNotifier;
-import com.vaadin.data.Container.PropertySetChangeListener;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.data.util.converter.Converter;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.ClientConnector;
import com.vaadin.shared.data.DataProviderRpc;
import com.vaadin.shared.data.DataProviderState;
import com.vaadin.shared.data.DataRequestRpc;
import com.vaadin.shared.ui.grid.Range;
-import com.vaadin.ui.Component;
import com.vaadin.ui.components.grid.Grid;
+import com.vaadin.ui.components.grid.GridColumn;
+import com.vaadin.ui.components.grid.Renderer;
/**
* Provides Vaadin server-side container data source to a
@@ -397,10 +399,17 @@ public class RpcDataProviderExtension extends AbstractExtension {
String[] row = new String[propertyIds.size()];
int i = 0;
+ final Grid grid = getGrid();
for (Object propertyId : propertyIds) {
- Object value = item.getItemProperty(propertyId).getValue();
- String stringValue = String.valueOf(value);
- row[i++] = stringValue;
+ GridColumn column = grid.getColumn(propertyId);
+
+ Object propertyValue = item.getItemProperty(propertyId).getValue();
+ Object encodedValue = encodeValue(propertyValue,
+ column.getRenderer(), column.getConverter(),
+ grid.getLocale());
+
+ // TODO Drop string conversion once client supports Objects
+ row[i++] = String.valueOf(encodedValue);
}
return row;
}
@@ -531,4 +540,50 @@ public class RpcDataProviderExtension extends AbstractExtension {
public void propertiesAdded(HashSet<Object> addedPropertyIds) {
activeRowHandler.propertiesAdded(addedPropertyIds);
}
+
+ protected Grid getGrid() {
+ return (Grid) getParent();
+ }
+
+ /**
+ * Converts and encodes the given data model property value using the given
+ * converter and renderer. This method is public only for testing purposes.
+ *
+ * @param renderer
+ * the renderer to use
+ * @param converter
+ * the converter to use
+ * @param modelValue
+ * the value to convert and encode
+ * @param locale
+ * the locale to use in conversion
+ * @return an encoded value ready to be sent to the client
+ */
+ public static <T> Object encodeValue(Object modelValue,
+ Renderer<T> renderer, Converter<?, ?> converter, Locale locale) {
+ Class<T> presentationType = renderer.getPresentationType();
+ T presentationValue;
+
+ if (converter == null) {
+ try {
+ presentationValue = presentationType.cast(modelValue);
+ } catch (ClassCastException e) {
+ throw new Converter.ConversionException(
+ "Unable to convert value of type "
+ + modelValue.getClass().getName()
+ + " to presentation type "
+ + presentationType.getName()
+ + ". No converter is set and the types are not compatible.");
+ }
+ } else {
+ assert presentationType.isAssignableFrom(converter
+ .getPresentationType());
+ @SuppressWarnings("unchecked")
+ Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
+ presentationValue = safeConverter.convertToPresentation(modelValue,
+ safeConverter.getPresentationType(), locale);
+ }
+
+ return renderer.encode(presentationValue);
+ }
}
diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java
index 7f3e3440c7..0d7e799978 100644
--- a/server/src/com/vaadin/ui/components/grid/Grid.java
+++ b/server/src/com/vaadin/ui/components/grid/Grid.java
@@ -150,6 +150,7 @@ public class Grid extends AbstractComponent implements SelectionChangeNotifier {
GridColumn column = columns.remove(columnId);
columnKeys.remove(columnId);
getState().columns.remove(column.getState());
+ removeExtension(column.getRenderer());
}
datasourceExtension.propertiesRemoved(removedColumns);
@@ -930,4 +931,14 @@ public class Grid extends AbstractComponent implements SelectionChangeNotifier {
public void setSelectionCheckboxes(boolean value) {
getState().selectionCheckboxes = value;
}
+
+ /**
+ * Adds a renderer to this grid's connector hierarchy.
+ *
+ * @param renderer
+ * the renderer to add
+ */
+ void addRenderer(Renderer<?> renderer) {
+ addExtension(renderer);
+ }
}
diff --git a/server/src/com/vaadin/ui/components/grid/GridColumn.java b/server/src/com/vaadin/ui/components/grid/GridColumn.java
index 76b17bda41..fd504fdae3 100644
--- a/server/src/com/vaadin/ui/components/grid/GridColumn.java
+++ b/server/src/com/vaadin/ui/components/grid/GridColumn.java
@@ -18,7 +18,12 @@ package com.vaadin.ui.components.grid;
import java.io.Serializable;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.ConverterUtil;
+import com.vaadin.server.VaadinSession;
import com.vaadin.shared.ui.grid.GridColumnState;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.components.grid.renderers.TextRenderer;
/**
* A column in the grid. Can be obtained by calling
@@ -39,6 +44,15 @@ public class GridColumn implements Serializable {
*/
private final Grid grid;
+ private Converter<?, Object> converter;
+
+ /**
+ * A check for allowing the {@link #GridColumn(Grid, GridColumnState)
+ * constructor} to call {@link #setConverter(Converter)} with a
+ * <code>null</code>, even if model and renderer aren't compatible.
+ */
+ private boolean isFirstConverterAssignment = true;
+
/**
* Internally used constructor.
*
@@ -50,6 +64,7 @@ public class GridColumn implements Serializable {
GridColumn(Grid grid, GridColumnState state) {
this.grid = grid;
this.state = state;
+ internalSetRenderer(new TextRenderer());
}
/**
@@ -213,4 +228,171 @@ public class GridColumn implements Serializable {
checkColumnIsAttached();
grid.setLastFrozenColumn(this);
}
+
+ /**
+ * Sets the renderer for this column.
+ * <p>
+ * If a suitable converter isn't defined explicitly, the session converter
+ * factory is used to find a compatible converter.
+ *
+ * @param renderer
+ * the renderer to use
+ * @throws IllegalArgumentException
+ * if no compatible converter could be found
+ * @see VaadinSession#getConverterFactory()
+ * @see ConverterUtil#getConverter(Class, Class, VaadinSession)
+ * @see #setConverter(Converter)
+ */
+ public void setRenderer(Renderer<?> renderer) {
+ if (!internalSetRenderer(renderer)) {
+ throw new IllegalArgumentException(
+ "Could not find a converter for converting from the model type "
+ + getModelType()
+ + " to the renderer presentation type "
+ + renderer.getPresentationType());
+ }
+ }
+
+ /**
+ * Sets the renderer for this column and the converter used to convert from
+ * the property value type to the renderer presentation type.
+ *
+ * @param renderer
+ * the renderer to use, cannot be null
+ * @param converter
+ * the converter to use
+ *
+ * @throws IllegalArgumentException
+ * if the renderer is already associated with a grid column
+ */
+ public <T> void setRenderer(Renderer<T> renderer,
+ Converter<? extends T, ?> converter) {
+ if (renderer.getParent() != null) {
+ throw new IllegalArgumentException(
+ "Cannot set a renderer that is already connected to a grid column");
+ }
+
+ if (getRenderer() != null) {
+ grid.removeExtension(getRenderer());
+ }
+
+ grid.addRenderer(renderer);
+ state.rendererConnector = renderer;
+ setConverter(converter);
+ }
+
+ /**
+ * Sets the converter used to convert from the property value type to the
+ * renderer presentation type.
+ *
+ * @param converter
+ * the converter to use, or {@code null} to not use any
+ * converters
+ * @throws IllegalArgumentException
+ * if the types are not compatible
+ */
+ public void setConverter(Converter<?, ?> converter)
+ throws IllegalArgumentException {
+ Class<?> modelType = getModelType();
+ if (converter != null) {
+ if (!converter.getModelType().isAssignableFrom(modelType)) {
+ throw new IllegalArgumentException("The converter model type "
+ + converter.getModelType()
+ + " is not compatible with the property type "
+ + modelType);
+
+ } else if (!getRenderer().getPresentationType().isAssignableFrom(
+ converter.getPresentationType())) {
+ throw new IllegalArgumentException(
+ "The converter presentation type "
+ + converter.getPresentationType()
+ + " is not compatible with the renderer presentation type "
+ + getRenderer().getPresentationType());
+ }
+ }
+
+ else {
+ /*
+ * Since the converter is null (i.e. will be removed), we need to
+ * know that the renderer and model are compatible. If not, we can't
+ * allow for this to happen.
+ *
+ * The constructor is allowed to call this method with null without
+ * any compatibility checks, therefore we have a special case for
+ * it.
+ */
+
+ Class<?> rendererPresentationType = getRenderer()
+ .getPresentationType();
+ if (!isFirstConverterAssignment
+ && !rendererPresentationType.isAssignableFrom(modelType)) {
+ throw new IllegalArgumentException("Cannot remove converter, "
+ + "as renderer's presentation type "
+ + rendererPresentationType.getName() + " and column's "
+ + "model " + modelType.getName() + " type aren't "
+ + "directly with each other");
+ }
+ }
+
+ isFirstConverterAssignment = false;
+
+ @SuppressWarnings("unchecked")
+ Converter<?, Object> castConverter = (Converter<?, Object>) converter;
+ this.converter = castConverter;
+ }
+
+ /**
+ * Returns the renderer instance used by this column.
+ *
+ * @return the renderer
+ */
+ public Renderer<?> getRenderer() {
+ return (Renderer<?>) getState().rendererConnector;
+ }
+
+ /**
+ * Returns the converter instance used by this column.
+ *
+ * @return the converter
+ */
+ public Converter<?, ?> getConverter() {
+ return converter;
+ }
+
+ private <T> boolean internalSetRenderer(Renderer<T> renderer) {
+
+ Converter<? extends T, ?> converter;
+ if (isCompatibleWithProperty(renderer, getConverter())) {
+ // Use the existing converter (possibly none) if types compatible
+ converter = (Converter<? extends T, ?>) getConverter();
+ } else {
+ converter = ConverterUtil.getConverter(
+ renderer.getPresentationType(), getModelType(),
+ getSession());
+ }
+ setRenderer(renderer, converter);
+ return isCompatibleWithProperty(renderer, converter);
+ }
+
+ private VaadinSession getSession() {
+ UI ui = grid.getUI();
+ return ui != null ? ui.getSession() : null;
+ }
+
+ private boolean isCompatibleWithProperty(Renderer<?> renderer,
+ Converter<?, ?> converter) {
+ Class<?> type;
+ if (converter == null) {
+ type = getModelType();
+ } else {
+ type = converter.getPresentationType();
+ }
+ return renderer.getPresentationType().isAssignableFrom(type);
+ }
+
+ private Class<?> getModelType() {
+ return grid.getContainerDatasource().getType(
+ grid.getPropertyIdByColumnId(state.id));
+ }
+
}
diff --git a/server/src/com/vaadin/ui/components/grid/Renderer.java b/server/src/com/vaadin/ui/components/grid/Renderer.java
new file mode 100644
index 0000000000..f3d502eb85
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/Renderer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.grid;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.Extension;
+
+/**
+ * A ClientConnector for controlling client-side
+ * {@link com.vaadin.client.ui.grid.Renderer Grid renderers}. Renderers
+ * currently extend the Extension interface, but this fact should be regarded as
+ * an implementation detail and subject to change in a future major or minor
+ * Vaadin revision.
+ *
+ * @param <T>
+ * the type this renderer knows how to present
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public interface Renderer<T> extends Extension {
+
+ /**
+ * Returns the class literal corresponding to the presentation type T.
+ *
+ * @return the class literal of T
+ */
+ Class<T> getPresentationType();
+
+ /**
+ * Encodes the given value into a form that can be transferred to the
+ * client. The type of the returned value must be one of the types that are
+ * accepted by <a href=
+ * "http://www.json.org/javadoc/org/json/JSONObject.html#put%28java.lang.String,%20java.lang.Object%29"
+ * >{@code org.json.JSONObject#put(String, Object)}</a>.
+ *
+ * @param value
+ * the value to encode
+ * @return an encoded form of the given value
+ */
+ Object encode(T value);
+
+ /**
+ * This method is inherited from Extension but should never be called
+ * directly with a Renderer.
+ */
+ @Override
+ @Deprecated
+ void remove();
+
+ /**
+ * This method is inherited from Extension but should never be called
+ * directly with a Renderer.
+ */
+ @Override
+ @Deprecated
+ void setParent(ClientConnector parent);
+}
diff --git a/server/src/com/vaadin/ui/components/grid/renderers/AbstractRenderer.java b/server/src/com/vaadin/ui/components/grid/renderers/AbstractRenderer.java
new file mode 100644
index 0000000000..d5631c6b60
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/renderers/AbstractRenderer.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.grid.renderers;
+
+import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.ui.components.grid.Grid;
+import com.vaadin.ui.components.grid.Renderer;
+
+/**
+ * An abstract base class for server-side Grid renderers.
+ * {@link com.vaadin.client.ui.grid.Renderer Grid renderers}. This class
+ * currently extends the AbstractExtension superclass, but this fact should be
+ * regarded as an implementation detail and subject to change in a future major
+ * or minor Vaadin revision.
+ *
+ * @param <T>
+ * the type this renderer knows how to present
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractRenderer<T> extends AbstractExtension implements
+ Renderer<T> {
+
+ private final Class<T> presentationType;
+
+ protected AbstractRenderer(Class<T> presentationType) {
+ this.presentationType = presentationType;
+ }
+
+ /**
+ * This method is inherited from AbstractExtension but should never be
+ * called directly with an AbstractRenderer.
+ */
+ @Deprecated
+ @Override
+ protected Class<Grid> getSupportedParentType() {
+ return Grid.class;
+ }
+
+ /**
+ * This method is inherited from AbstractExtension but should never be
+ * called directly with an AbstractRenderer.
+ */
+ @Deprecated
+ @Override
+ protected void extend(AbstractClientConnector target) {
+ super.extend(target);
+ }
+
+ @Override
+ public Class<T> getPresentationType() {
+ return presentationType;
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/renderers/TextRenderer.java b/server/src/com/vaadin/ui/components/grid/renderers/TextRenderer.java
new file mode 100644
index 0000000000..28aae27085
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/renderers/TextRenderer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.grid.renderers;
+
+/**
+ * A renderer for presenting simple plain-text string values.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class TextRenderer extends AbstractRenderer<String> {
+
+ public TextRenderer() {
+ super(String.class);
+ }
+
+ @Override
+ public Object encode(String value) {
+ return value;
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/RendererTest.java b/server/tests/src/com/vaadin/tests/server/component/grid/RendererTest.java
new file mode 100644
index 0000000000..1ea501ff01
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/grid/RendererTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.grid;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Locale;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.RpcDataProviderExtension;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.Converter.ConversionException;
+import com.vaadin.data.util.converter.StringToIntegerConverter;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.components.grid.Grid;
+import com.vaadin.ui.components.grid.GridColumn;
+import com.vaadin.ui.components.grid.renderers.TextRenderer;
+
+/**
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class RendererTest {
+
+ private static class TestBean {
+ int i = 42;
+ }
+
+ private static class ExtendedBean extends TestBean {
+ float f = 3.14f;
+ }
+
+ private static class TestRenderer extends TextRenderer {
+ @Override
+ public Object encode(String value) {
+ return "renderer(" + super.encode(value) + ")";
+ }
+ }
+
+ private static class TestConverter implements Converter<String, TestBean> {
+
+ @Override
+ public TestBean convertToModel(String value,
+ Class<? extends TestBean> targetType, Locale locale)
+ throws ConversionException {
+ return null;
+ }
+
+ @Override
+ public String convertToPresentation(TestBean value,
+ Class<? extends String> targetType, Locale locale)
+ throws ConversionException {
+ if (value instanceof ExtendedBean) {
+ return "ExtendedBean(" + value.i + ", "
+ + ((ExtendedBean) value).f + ")";
+ } else {
+ return "TestBean(" + value.i + ")";
+ }
+ }
+
+ @Override
+ public Class<TestBean> getModelType() {
+ return TestBean.class;
+ }
+
+ @Override
+ public Class<String> getPresentationType() {
+ return String.class;
+ }
+ }
+
+ private Grid grid;
+
+ private GridColumn foo;
+ private GridColumn bar;
+ private GridColumn baz;
+ private GridColumn bah;
+
+ @Before
+ public void setUp() {
+ VaadinSession.setCurrent(new VaadinSession(null));
+
+ IndexedContainer c = new IndexedContainer();
+
+ c.addContainerProperty("foo", Integer.class, 0);
+ c.addContainerProperty("bar", String.class, "");
+ c.addContainerProperty("baz", TestBean.class, null);
+ c.addContainerProperty("bah", ExtendedBean.class, null);
+
+ Object id = c.addItem();
+ Item item = c.getItem(id);
+ item.getItemProperty("foo").setValue(123);
+ item.getItemProperty("bar").setValue("321");
+ item.getItemProperty("baz").setValue(new TestBean());
+ item.getItemProperty("bah").setValue(new ExtendedBean());
+
+ grid = new Grid(c);
+
+ foo = grid.getColumn("foo");
+ bar = grid.getColumn("bar");
+ baz = grid.getColumn("baz");
+ bah = grid.getColumn("bah");
+ }
+
+ @Test
+ public void testDefaultRendererAndConverter() throws Exception {
+ assertSame(TextRenderer.class, foo.getRenderer().getClass());
+ assertSame(StringToIntegerConverter.class, foo.getConverter()
+ .getClass());
+
+ assertSame(TextRenderer.class, bar.getRenderer().getClass());
+ // String->String; converter not needed
+ assertNull(bar.getConverter());
+
+ assertSame(TextRenderer.class, baz.getRenderer().getClass());
+ // MyBean->String; converter not found
+ assertNull(baz.getConverter());
+ }
+
+ @Test
+ public void testFindCompatibleConverter() throws Exception {
+ foo.setRenderer(renderer());
+ assertSame(StringToIntegerConverter.class, foo.getConverter()
+ .getClass());
+
+ bar.setRenderer(renderer());
+ assertNull(bar.getConverter());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCannotFindConverter() {
+ baz.setRenderer(renderer());
+ }
+
+ @Test
+ public void testExplicitConverter() throws Exception {
+ baz.setRenderer(renderer(), converter());
+ bah.setRenderer(renderer(), converter());
+ }
+
+ @Test
+ public void testEncoding() throws Exception {
+ assertEquals("42", render(foo, 42));
+ foo.setRenderer(renderer());
+ assertEquals("renderer(42)", render(foo, 42));
+
+ assertEquals("2.72", render(bar, "2.72"));
+ bar.setRenderer(new TestRenderer());
+ assertEquals("renderer(2.72)", render(bar, "2.72"));
+ }
+
+ @Test(expected = ConversionException.class)
+ public void testEncodingWithoutConverter() throws Exception {
+ render(baz, new TestBean());
+ }
+
+ @Test
+ public void testBeanEncoding() throws Exception {
+ baz.setRenderer(renderer(), converter());
+ bah.setRenderer(renderer(), converter());
+
+ assertEquals("renderer(TestBean(42))", render(baz, new TestBean()));
+ assertEquals("renderer(ExtendedBean(42, 3.14))",
+ render(baz, new ExtendedBean()));
+
+ assertEquals("renderer(ExtendedBean(42, 3.14))",
+ render(bah, new ExtendedBean()));
+ }
+
+ private TestConverter converter() {
+ return new TestConverter();
+ }
+
+ private TestRenderer renderer() {
+ return new TestRenderer();
+ }
+
+ private Object render(GridColumn column, Object value) {
+ return RpcDataProviderExtension.encodeValue(value,
+ column.getRenderer(), column.getConverter(), grid.getLocale());
+ }
+}
diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
index 79e3f17f8d..a2f85159ba 100644
--- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java
+++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java
@@ -30,6 +30,8 @@ public interface DataProviderRpc extends ClientRpc {
/**
* Sends updated row data to a client.
+ * <p>
+ * TODO rowData should be List<Object[]>
*
* @param firstRowIndex
* the index of the first updated row
diff --git a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java
index 0301c5ead2..581d4d4365 100644
--- a/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java
+++ b/shared/src/com/vaadin/shared/ui/grid/GridColumnState.java
@@ -17,6 +17,8 @@ package com.vaadin.shared.ui.grid;
import java.io.Serializable;
+import com.vaadin.shared.Connector;
+
/**
* Column state DTO for transferring column properties from the server to the
* client
@@ -52,4 +54,5 @@ public class GridColumnState implements Serializable {
*/
public int width = 100;
+ public Connector rendererConnector;
}