diff options
author | Johannes Dahlström <johannesd@vaadin.com> | 2014-06-13 17:08:07 +0300 |
---|---|---|
committer | Henrik Paul <henrik@vaadin.com> | 2014-06-25 13:31:48 +0000 |
commit | 10f7eb04e354a072a04af362f95f831f0656abf7 (patch) | |
tree | f15636982640893be728e893fff4862b224bdc4a | |
parent | 7a518fa8a1431789b30b53feded52ea1dac35c9e (diff) | |
download | vaadin-framework-10f7eb04e354a072a04af362f95f831f0656abf7.tar.gz vaadin-framework-10f7eb04e354a072a04af362f95f831f0656abf7.zip |
Server-side renderer groundwork (#13334)
Change-Id: I12a0fec1b0e49cfb481b0a7c8b6a40a4d43db7f5
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; } |