]> source.dussan.org Git - vaadin-framework.git/commitdiff
Server-side renderer groundwork (#13334)
authorJohannes Dahlström <johannesd@vaadin.com>
Fri, 13 Jun 2014 14:08:07 +0000 (17:08 +0300)
committerHenrik Paul <henrik@vaadin.com>
Wed, 25 Jun 2014 13:31:48 +0000 (13:31 +0000)
Change-Id: I12a0fec1b0e49cfb481b0a7c8b6a40a4d43db7f5

13 files changed:
client/src/com/vaadin/client/ui/grid/Grid.java
client/src/com/vaadin/client/ui/grid/GridConnector.java
client/src/com/vaadin/client/ui/grid/renderers/AbstractRendererConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/grid/renderers/TextRendererConnector.java [new file with mode: 0644]
server/src/com/vaadin/data/RpcDataProviderExtension.java
server/src/com/vaadin/ui/components/grid/Grid.java
server/src/com/vaadin/ui/components/grid/GridColumn.java
server/src/com/vaadin/ui/components/grid/Renderer.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/grid/renderers/AbstractRenderer.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/grid/renderers/TextRenderer.java [new file with mode: 0644]
server/tests/src/com/vaadin/tests/server/component/grid/RendererTest.java [new file with mode: 0644]
shared/src/com/vaadin/shared/data/DataProviderRpc.java
shared/src/com/vaadin/shared/ui/grid/GridColumnState.java

index 0e226936387f7ad6c7978395cba0ac7f9d946ba7..dfcda40b045373a65ff201dcc3d1fe7be9df28dd 100644 (file)
@@ -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;
index 8685180d386cf6866e6a47ed8d439db70e8768bc..ab304a921480b41ae8de09bc66665300eb62d12a 100644 (file)
@@ -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 (file)
index 0000000..236fdbe
--- /dev/null
@@ -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 (file)
index 0000000..76fce7b
--- /dev/null
@@ -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;
+    }
+}
index 507b2d0f22f90d67f2b6453ffa4622afd346338e..79121af6b0b5c71ed8a18f23c335a7e3d0701984 100644 (file)
@@ -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);
+    }
 }
index 7f3e3440c7e3437f3b6a6874803ef2f2013d7ee7..0d7e7999785f7de3b49f5d5d8071e48567adcae7 100644 (file)
@@ -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);
+    }
 }
index 76b17bda41808b762ce9eaa445c1670d0dd252c6..fd504fdae3ba05828bf8071b1d094adcf1d6f521 100644 (file)
@@ -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 (file)
index 0000000..f3d502e
--- /dev/null
@@ -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 (file)
index 0000000..d5631c6
--- /dev/null
@@ -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 (file)
index 0000000..28aae27
--- /dev/null
@@ -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 (file)
index 0000000..1ea501f
--- /dev/null
@@ -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());
+    }
+}
index 79e3f17f8db7b96cdb37be2a6c005572f0d72b04..a2f85159ba03ae0af9258c7c8c38207635214216 100644 (file)
@@ -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
index 0301c5ead2ea8b20e4b49acf5723307109289e56..581d4d436559f567c57520b77ced72a9d8162c4b 100644 (file)
@@ -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;
 }