From: Leif Åstrand Date: Mon, 5 Sep 2016 09:20:36 +0000 (+0300) Subject: Reimplement basic Grid renderers X-Git-Tag: 8.0.0.alpha1~19 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3d3f12d92bb840a66ed6caaf992e0fe7bb3c1e77;p=vaadin-framework.git Reimplement basic Grid renderers Change-Id: Ic855143d23cc7735d00130806df45b6579ba0d5c --- diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/AbstractGridRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/AbstractGridRendererConnector.java new file mode 100644 index 0000000000..96bfc3b26d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/AbstractGridRendererConnector.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.client.connectors.AbstractRendererConnector; + +/** + * 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.client.renderers.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. + * + * @param + * the presentation type of the renderer + * + * @since 7.4 + * @author Vaadin Ltd + */ +public abstract class AbstractGridRendererConnector + extends AbstractRendererConnector { + // getRowKey and getColumnId will be needed here later on +} diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java index 4b61b62dc7..49bd492406 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java @@ -17,6 +17,7 @@ package com.vaadin.client.connectors.grid; import com.vaadin.client.ServerConnector; import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.connectors.AbstractRendererConnector; import com.vaadin.client.extensions.AbstractExtensionConnector; import com.vaadin.client.widgets.Grid.Column; import com.vaadin.shared.data.DataCommunicatorConstants; @@ -35,7 +36,7 @@ import elemental.json.JsonValue; @Connect(com.vaadin.ui.Grid.Column.class) public class ColumnConnector extends AbstractExtensionConnector { - private Column column; + private Column column; /* This parent is needed because it's no longer available in onUnregister */ private GridConnector parent; @@ -43,15 +44,30 @@ public class ColumnConnector extends AbstractExtensionConnector { @Override protected void extend(ServerConnector target) { parent = getParent(); - column = new Column() { + String columnId = getState().id; + column = new Column() { @Override - public JsonValue getValue(JsonObject row) { - return row.getObject(DataCommunicatorConstants.DATA) - .get(getState().id); + public Object getValue(JsonObject row) { + final JsonObject rowData = row + .getObject(DataCommunicatorConstants.DATA); + + if (rowData.hasKey(columnId)) { + final JsonValue columnValue = rowData.get(columnId); + + return getRendererConnector().decode(columnValue); + } + + return null; } }; - getParent().addColumn(column, getState().id); + column.setRenderer(getRendererConnector().getRenderer()); + getParent().addColumn(column, columnId); + } + + @SuppressWarnings("unchecked") + private AbstractRendererConnector getRendererConnector() { + return (AbstractRendererConnector) getState().renderer; } @OnStateChange("caption") diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/DateRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/DateRendererConnector.java new file mode 100644 index 0000000000..da22c77548 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/DateRendererConnector.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.shared.ui.Connect; + +/** + * A connector for {@link com.vaadin.ui.renderers.DateRenderer DateRenderer}. + *

+ * The server-side Renderer operates on dates, but the data is serialized as a + * string, and displayed as-is on the client side. This is to be able to support + * the server's locale. + * + * @since 7.4 + * @author Vaadin Ltd + */ +@Connect(com.vaadin.ui.renderers.DateRenderer.class) +public class DateRendererConnector extends TextRendererConnector { + // No implementation needed +} diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/NumberRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/NumberRendererConnector.java new file mode 100644 index 0000000000..dd5d4fae40 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/NumberRendererConnector.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.shared.ui.Connect; + +/** + * A connector for {@link com.vaadin.ui.renderers.NumberRenderer + * NumberRenderer}. + *

+ * The server-side Renderer operates on numbers, but the data is serialized as a + * string, and displayed as-is on the client side. This is to be able to support + * the server's locale. + * + * @since 7.4 + * @author Vaadin Ltd + */ +@Connect(com.vaadin.ui.renderers.NumberRenderer.class) +public class NumberRendererConnector extends TextRendererConnector { + // no implementation needed +} diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/ProgressBarRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/ProgressBarRendererConnector.java new file mode 100644 index 0000000000..a5f6bec479 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/ProgressBarRendererConnector.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.client.renderers.ProgressBarRenderer; +import com.vaadin.shared.ui.Connect; + +/** + * A connector for {@link ProgressBarRenderer}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +@Connect(com.vaadin.ui.renderers.ProgressBarRenderer.class) +public class ProgressBarRendererConnector + extends AbstractGridRendererConnector { + + @Override + public ProgressBarRenderer getRenderer() { + return (ProgressBarRenderer) super.getRenderer(); + } +} diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/TextRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/TextRendererConnector.java new file mode 100644 index 0000000000..4a368a975d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/TextRendererConnector.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.client.renderers.TextRenderer; +import com.vaadin.shared.ui.Connect; + +/** + * A connector for {@link TextRenderer}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +@Connect(com.vaadin.ui.renderers.TextRenderer.class) +public class TextRendererConnector + extends AbstractGridRendererConnector { + + @Override + public TextRenderer getRenderer() { + return (TextRenderer) super.getRenderer(); + } +} diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/UnsafeHtmlRendererConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/UnsafeHtmlRendererConnector.java new file mode 100644 index 0000000000..42c9a64bd4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/UnsafeHtmlRendererConnector.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 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.connectors.grid; + +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.RendererCellReference; +import com.vaadin.shared.ui.Connect; + +/** + * A connector for {@link UnsafeHtmlRenderer} + * + * @since 7.4 + * @author Vaadin Ltd + */ +@Connect(com.vaadin.ui.renderers.HtmlRenderer.class) +public class UnsafeHtmlRendererConnector + extends AbstractGridRendererConnector { + + public static class UnsafeHtmlRenderer implements Renderer { + @Override + public void render(RendererCellReference cell, String data) { + cell.getElement().setInnerHTML(data); + } + } + + @Override + public UnsafeHtmlRenderer getRenderer() { + return (UnsafeHtmlRenderer) super.getRenderer(); + } +} diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 387cd22275..22b5ef8072 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -46,9 +46,12 @@ import com.vaadin.shared.ui.grid.GridConstants.Section; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.HeightMode; +import com.vaadin.ui.renderers.Renderer; +import com.vaadin.ui.renderers.TextRenderer; import elemental.json.Json; import elemental.json.JsonObject; +import elemental.json.JsonValue; /** * A grid component for displaying tabular data. @@ -305,49 +308,99 @@ public class Grid extends AbstractListing> public static class Column extends AbstractExtension implements DataGenerator { - private Function valueProvider; + private final Function valueProvider; + private Function>> sortOrderProvider; private Comparator comparator; /** - * Constructs a new Column configuration with given header caption and - * value provider. + * Constructs a new Column configuration with given header caption, + * renderer and value provider. * * @param caption * the header caption - * @param valueType - * the type of value * @param valueProvider * the function to get values from items + * @param renderer + * the type of value */ - protected Column(String caption, Class valueType, - Function valueProvider) { + protected Column(String caption, Function valueProvider, + Renderer renderer) { Objects.requireNonNull(caption, "Header caption can't be null"); Objects.requireNonNull(valueProvider, "Value provider can't be null"); - Objects.requireNonNull(valueType, "Value type can't be null"); + Objects.requireNonNull(renderer, "Renderer can't be null"); + + ColumnState state = getState(); this.valueProvider = valueProvider; - getState().caption = caption; + state.renderer = renderer; + + state.caption = caption; sortOrderProvider = d -> Stream.of(); + // Add the renderer as a child extension of this extension, thus + // ensuring the renderer will be unregistered when this column is + // removed + addExtension(renderer); + + Class valueType = renderer.getPresentationType(); + if (Comparable.class.isAssignableFrom(valueType)) { comparator = (a, b) -> { @SuppressWarnings("unchecked") Comparable comp = (Comparable) valueProvider.apply(a); return comp.compareTo(valueProvider.apply(b)); }; - getState().sortable = true; + state.sortable = true; + } else if (Number.class.isAssignableFrom(valueType)) { + /* + * Value type will be Number whenever using NumberRenderer. + * Provide explicit comparison support in this case even though + * Number itself isn't Comparable. + */ + comparator = (a, b) -> { + return compareNumbers((Number) valueProvider.apply(a), + (Number) valueProvider.apply(b)); + }; + state.sortable = true; } else { - getState().sortable = false; + state.sortable = false; + } + } + + @SuppressWarnings("unchecked") + private static int compareNumbers(Number a, Number b) { + assert a.getClass() == b.getClass(); + + // Most Number implementations are Comparable + if (a instanceof Comparable && a.getClass().isInstance(b)) { + return ((Comparable) a).compareTo(b); + } else if (a.equals(b)) { + return 0; + } else { + // Fall back to comparing based on potentially truncated values + int compare = Long.compare(a.longValue(), b.longValue()); + if (compare == 0) { + // This might still produce 0 even though the values are not + // equals, but there's nothing more we can do about that + compare = Double.compare(a.doubleValue(), b.doubleValue()); + } + return compare; } } @Override public void generateData(T data, JsonObject jsonObject) { - assert getState( - false).id != null : "No communication ID set for column " - + getState(false).caption; + ColumnState state = getState(false); + + String communicationId = state.id; + + assert communicationId != null : "No communication ID set for column " + + state.caption; + + @SuppressWarnings("unchecked") + Renderer renderer = (Renderer) state.renderer; if (!jsonObject.hasKey(DataCommunicatorConstants.DATA)) { jsonObject.put(DataCommunicatorConstants.DATA, @@ -355,9 +408,12 @@ public class Grid extends AbstractListing> } JsonObject obj = jsonObject .getObject(DataCommunicatorConstants.DATA); - // Since we dont' have renderers yet, use a dummy toString for - // data. - obj.put(getState(false).id, valueProvider.apply(data).toString()); + + V providerValue = valueProvider.apply(data); + + JsonValue rendererValue = renderer.encode(providerValue); + + obj.put(communicationId, rendererValue); } @Override @@ -538,23 +594,23 @@ public class Grid extends AbstractListing> } /** - * Adds a new column to this {@link Grid} with given header caption and - * value provider. + * Adds a new column to this {@link Grid} with given header caption, + * renderer and value provider. * * @param caption * the header caption - * @param valueType - * the column value class * @param valueProvider * the value provider + * @param renderer + * the column value class * @param * the column value type * * @return the new column */ - public Column addColumn(String caption, Class valueType, - Function valueProvider) { - Column c = new Column<>(caption, valueType, valueProvider); + public Column addColumn(String caption, Function valueProvider, + Renderer renderer) { + Column c = new Column<>(caption, valueProvider, renderer); c.extend(this); c.setId(columnKeys.key(c)); @@ -564,6 +620,22 @@ public class Grid extends AbstractListing> return c; } + /** + * Adds a new text column to this {@link Grid} with given header caption + * string value provider. The column will use a {@link TextRenderer}. + * + * @param caption + * the header caption + * @param valueProvider + * the value provider + * + * @return the new column + */ + public Column addColumn(String caption, + Function valueProvider) { + return addColumn(caption, valueProvider, new TextRenderer()); + } + /** * Removes the given column from this {@link Grid}. * diff --git a/server/src/main/java/com/vaadin/ui/renderers/AbstractRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/AbstractRenderer.java new file mode 100644 index 0000000000..4dcf911515 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/AbstractRenderer.java @@ -0,0 +1,139 @@ +/* + * Copyright 2000-2016 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.renderers; + +import java.util.Objects; + +import com.vaadin.server.AbstractClientConnector; +import com.vaadin.server.AbstractExtension; +import com.vaadin.server.JsonCodec; +import com.vaadin.ui.Grid.Column; + +import elemental.json.JsonValue; + +/** + * An abstract base class for server-side + * {@link com.vaadin.ui.renderers.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 version. + * + * @param + * the type this renderer knows how to present + */ +public abstract class AbstractRenderer extends AbstractExtension + implements Renderer { + private final Class presentationType; + + private final String nullRepresentation; + + /** + * Creates a new renderer with the given presentation type and null + * representation. + * + * @param presentationType + * the data type that this renderer displays, not + * null + * @param nullRepresentation + * a string that will be sent to the client instead of a regular + * value in case the actual cell value is null. May + * be null. + */ + protected AbstractRenderer(Class presentationType, + String nullRepresentation) { + Objects.requireNonNull(presentationType, + "Presentation type cannot be null"); + this.presentationType = presentationType; + this.nullRepresentation = nullRepresentation; + } + + /** + * Creates a new renderer with the given presentation type. No null + * representation will be used. + * + * @param presentationType + * the data type that this renderer displays, not + * null + */ + protected AbstractRenderer(Class presentationType) { + this(presentationType, null); + } + + /** + * This method is inherited from AbstractExtension but should never be + * called directly with an AbstractRenderer. + */ + @Deprecated + @Override + @SuppressWarnings("rawtypes") + protected Class getSupportedParentType() { + return Column.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 getPresentationType() { + return presentationType; + } + + @Override + public JsonValue encode(T value) { + if (value == null) { + return encode(getNullRepresentation(), String.class); + } else { + return encode(value, getPresentationType()); + } + } + + /** + * Null representation for the renderer + * + * @return a textual representation of {@code null} + */ + protected String getNullRepresentation() { + return nullRepresentation; + } + + /** + * Encodes the given value to JSON. + *

+ * This is a helper method that can be invoked by an {@link #encode(Object) + * encode(T)} override if serializing a value of type other than + * {@link #getPresentationType() the presentation type} is desired. For + * instance, a {@code Renderer} could first turn a date value into a + * formatted string and return {@code encode(dateString, String.class)}. + * + * @param value + * the value to be encoded + * @param type + * the type of the value + * @return a JSON representation of the given value + */ + protected JsonValue encode(U value, Class type) { + return JsonCodec + .encode(value, null, type, getUI().getConnectorTracker()) + .getEncodedValue(); + } +} \ No newline at end of file diff --git a/server/src/main/java/com/vaadin/ui/renderers/DateRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/DateRenderer.java new file mode 100644 index 0000000000..dd33ce064c --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/DateRenderer.java @@ -0,0 +1,238 @@ +/* + * Copyright 2000-2016 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.renderers; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; + +import elemental.json.JsonValue; + +/** + * A renderer for presenting date values. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DateRenderer extends AbstractRenderer { + private final Locale locale; + private final String formatString; + private final DateFormat dateFormat; + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the {@link Date#toString()} + * representation for the default locale. + */ + public DateRenderer() { + this(Locale.getDefault(), ""); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the {@link Date#toString()} + * representation for the given locale. + * + * @param locale + * the locale in which to present dates + * @throws IllegalArgumentException + * if {@code locale} is {@code null} + */ + public DateRenderer(Locale locale) throws IllegalArgumentException { + this("%s", locale, ""); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the {@link Date#toString()} + * representation for the given locale. + * + * @param locale + * the locale in which to present dates + * @param nullRepresentation + * the textual representation of {@code null} value + * @throws IllegalArgumentException + * if {@code locale} is {@code null} + */ + public DateRenderer(Locale locale, String nullRepresentation) + throws IllegalArgumentException { + this("%s", locale, nullRepresentation); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the given string format, as + * displayed in the default locale. + * + * @param formatString + * the format string with which to format the date + * @throws IllegalArgumentException + * if {@code formatString} is {@code null} + * @see Format + * String Syntax + */ + public DateRenderer(String formatString) throws IllegalArgumentException { + this(formatString, ""); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the given string format, as + * displayed in the default locale. + * + * @param formatString + * the format string with which to format the date + * @param nullRepresentation + * the textual representation of {@code null} value + * @throws IllegalArgumentException + * if {@code formatString} is {@code null} + * @see Format + * String Syntax + */ + public DateRenderer(String formatString, String nullRepresentation) + throws IllegalArgumentException { + this(formatString, Locale.getDefault(), nullRepresentation); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the given string format, as + * displayed in the given locale. + * + * @param formatString + * the format string to format the date with + * @param locale + * the locale to use + * @throws IllegalArgumentException + * if either argument is {@code null} + * @see Format + * String Syntax + */ + public DateRenderer(String formatString, Locale locale) + throws IllegalArgumentException { + this(formatString, locale, ""); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with the given string format, as + * displayed in the given locale. + * + * @param formatString + * the format string to format the date with + * @param locale + * the locale to use + * @param nullRepresentation + * the textual representation of {@code null} value + * @throws IllegalArgumentException + * if either argument is {@code null} + * @see Format + * String Syntax + */ + public DateRenderer(String formatString, Locale locale, + String nullRepresentation) throws IllegalArgumentException { + super(Date.class, nullRepresentation); + + if (formatString == null) { + throw new IllegalArgumentException("format string may not be null"); + } + + if (locale == null) { + throw new IllegalArgumentException("locale may not be null"); + } + + this.locale = locale; + this.formatString = formatString; + dateFormat = null; + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with he given date format. + * + * @param dateFormat + * the date format to use when rendering dates + * @throws IllegalArgumentException + * if {@code dateFormat} is {@code null} + */ + public DateRenderer(DateFormat dateFormat) throws IllegalArgumentException { + this(dateFormat, ""); + } + + /** + * Creates a new date renderer. + *

+ * The renderer is configured to render with he given date format. + * + * @param dateFormat + * the date format to use when rendering dates + * @throws IllegalArgumentException + * if {@code dateFormat} is {@code null} + */ + public DateRenderer(DateFormat dateFormat, String nullRepresentation) + throws IllegalArgumentException { + super(Date.class, nullRepresentation); + if (dateFormat == null) { + throw new IllegalArgumentException("date format may not be null"); + } + + locale = null; + formatString = null; + this.dateFormat = dateFormat; + } + + @Override + public String getNullRepresentation() { + return super.getNullRepresentation(); + } + + @Override + public JsonValue encode(Date value) { + String dateString; + if (value == null) { + dateString = getNullRepresentation(); + } else if (dateFormat != null) { + dateString = dateFormat.format(value); + } else { + dateString = String.format(locale, formatString, value); + } + return encode(dateString, String.class); + } + + @Override + public String toString() { + final String fieldInfo; + if (dateFormat != null) { + fieldInfo = "dateFormat: " + dateFormat.toString(); + } else { + fieldInfo = "locale: " + locale + ", formatString: " + formatString; + } + + return String.format("%s [%s]", getClass().getSimpleName(), fieldInfo); + } +} diff --git a/server/src/main/java/com/vaadin/ui/renderers/HtmlRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/HtmlRenderer.java new file mode 100644 index 0000000000..80fcdc18c4 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/HtmlRenderer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2016 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.renderers; + +/** + * A renderer for presenting HTML content. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class HtmlRenderer extends AbstractRenderer { + /** + * Creates a new HTML renderer. + * + * @param nullRepresentation + * the html representation of {@code null} value + */ + public HtmlRenderer(String nullRepresentation) { + super(String.class, nullRepresentation); + } + + /** + * Creates a new HTML renderer. + */ + public HtmlRenderer() { + this(""); + } + + @Override + public String getNullRepresentation() { + return super.getNullRepresentation(); + } +} diff --git a/server/src/main/java/com/vaadin/ui/renderers/NumberRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/NumberRenderer.java new file mode 100644 index 0000000000..02e6ce68fd --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/NumberRenderer.java @@ -0,0 +1,205 @@ +/* + * Copyright 2000-2016 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.renderers; + +import java.text.NumberFormat; +import java.util.Locale; + +import elemental.json.JsonValue; + +/** + * A renderer for presenting number values. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class NumberRenderer extends AbstractRenderer { + private final Locale locale; + private final NumberFormat numberFormat; + private final String formatString; + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render with the number's natural string + * representation in the default locale. + */ + public NumberRenderer() { + this(Locale.getDefault()); + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render the number as defined with the given + * number format. + * + * @param numberFormat + * the number format with which to display numbers + * @throws IllegalArgumentException + * if {@code numberFormat} is {@code null} + */ + public NumberRenderer(NumberFormat numberFormat) { + this(numberFormat, ""); + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render the number as defined with the given + * number format. + * + * @param numberFormat + * the number format with which to display numbers + * @param nullRepresentation + * the textual representation of {@code null} value + * @throws IllegalArgumentException + * if {@code numberFormat} is {@code null} + */ + public NumberRenderer(NumberFormat numberFormat, String nullRepresentation) + throws IllegalArgumentException { + super(Number.class, nullRepresentation); + + if (numberFormat == null) { + throw new IllegalArgumentException("Number format may not be null"); + } + + locale = null; + this.numberFormat = numberFormat; + formatString = null; + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render with the number's natural string + * representation in the given locale. + * + * @param locale + * the locale in which to display numbers + * @throws IllegalArgumentException + * if {@code locale} is {@code null} + */ + public NumberRenderer(Locale locale) throws IllegalArgumentException { + this("%s", locale); + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render with the number's natural string + * representation in the given locale. + * + * @param formatString + * the format string with which to format the number + * @param locale + * the locale in which to display numbers + * @throws IllegalArgumentException + * if {@code locale} is {@code null} + */ + public NumberRenderer(String formatString, Locale locale) + throws IllegalArgumentException { + this(formatString, locale, ""); // This will call #toString() during + // formatting + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render with the given format string in the + * default locale. + * + * @param formatString + * the format string with which to format the number + * @throws IllegalArgumentException + * if {@code formatString} is {@code null} + * @see Format + * String Syntax + */ + public NumberRenderer(String formatString) throws IllegalArgumentException { + this(formatString, Locale.getDefault(), ""); + } + + /** + * Creates a new number renderer. + *

+ * The renderer is configured to render with the given format string in the + * given locale. + * + * @param formatString + * the format string with which to format the number + * @param locale + * the locale in which to present numbers + * @throws IllegalArgumentException + * if either argument is {@code null} + * @see Format + * String Syntax + */ + public NumberRenderer(String formatString, Locale locale, + String nullRepresentation) { + super(Number.class, nullRepresentation); + + if (formatString == null) { + throw new IllegalArgumentException("Format string may not be null"); + } + + if (locale == null) { + throw new IllegalArgumentException("Locale may not be null"); + } + + this.locale = locale; + numberFormat = null; + this.formatString = formatString; + } + + @Override + public JsonValue encode(Number value) { + String stringValue; + if (value == null) { + stringValue = getNullRepresentation(); + } else if (formatString != null && locale != null) { + stringValue = String.format(locale, formatString, value); + } else if (numberFormat != null) { + stringValue = numberFormat.format(value); + } else { + throw new IllegalStateException(String.format( + "Internal bug: " + "%s is in an illegal state: " + + "[locale: %s, numberFormat: %s, formatString: %s]", + getClass().getSimpleName(), locale, numberFormat, + formatString)); + } + return encode(stringValue, String.class); + } + + @Override + public String toString() { + final String fieldInfo; + if (numberFormat != null) { + fieldInfo = "numberFormat: " + numberFormat.toString(); + } else { + fieldInfo = "locale: " + locale + ", formatString: " + formatString; + } + + return String.format("%s [%s]", getClass().getSimpleName(), fieldInfo); + } + + @Override + public String getNullRepresentation() { + return super.getNullRepresentation(); + } +} diff --git a/server/src/main/java/com/vaadin/ui/renderers/ProgressBarRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/ProgressBarRenderer.java new file mode 100644 index 0000000000..7d05959484 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/ProgressBarRenderer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2016 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.renderers; + +import elemental.json.JsonValue; + +/** + * A renderer that represents double values between 0 and 1 as a graphical + * progress bar. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class ProgressBarRenderer extends AbstractRenderer { + + /** + * Creates a new text renderer + */ + public ProgressBarRenderer() { + super(Double.class, null); + } + + @Override + public JsonValue encode(Double value) { + if (value != null) { + value = Math.max(Math.min(value, 1), 0); + } else { + value = 0d; + } + return super.encode(value); + } +} diff --git a/server/src/main/java/com/vaadin/ui/renderers/Renderer.java b/server/src/main/java/com/vaadin/ui/renderers/Renderer.java new file mode 100644 index 0000000000..28582d4e8d --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/Renderer.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2016 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.renderers; + +import com.vaadin.server.ClientConnector; +import com.vaadin.server.Extension; + +import elemental.json.JsonValue; + +/** + * A ClientConnector for controlling client-side + * {@link com.vaadin.client.renderers.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 + * the type this renderer knows how to present + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface Renderer extends Extension { + + /** + * Returns the class literal corresponding to the presentation type T. + * + * @return the class literal of T + */ + Class getPresentationType(); + + /** + * Encodes the given value into a {@link JsonValue}. + * + * @param value + * the value to encode + * @return a JSON representation of the given value + */ + JsonValue 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/main/java/com/vaadin/ui/renderers/TextRenderer.java b/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java new file mode 100644 index 0000000000..07f09b88f2 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/renderers/TextRenderer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2016 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.renderers; + +/** + * A renderer for presenting simple plain-text string values. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class TextRenderer extends AbstractRenderer { + + /** + * Creates a new text renderer + */ + public TextRenderer() { + this(""); + } + + /** + * Creates a new text renderer + * + * @param nullRepresentation + * the textual representation of {@code null} value + */ + public TextRenderer(String nullRepresentation) { + super(String.class, nullRepresentation); + } + + @Override + public String getNullRepresentation() { + return super.getNullRepresentation(); + } +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java index c426ffe4f6..3dc6dad770 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java @@ -17,7 +17,7 @@ public class GridTest { @Before public void setUp() { grid = new Grid<>(); - grid.addColumn("foo", String.class, Function.identity()); + grid.addColumn("foo", Function.identity()); } @Test diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java index bc04c4acb2..63a93cac15 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java @@ -15,6 +15,7 @@ */ package com.vaadin.shared.ui.grid; +import com.vaadin.shared.Connector; import com.vaadin.shared.communication.SharedState; public class ColumnState extends SharedState { @@ -22,4 +23,6 @@ public class ColumnState extends SharedState { public String caption; public String id; public boolean sortable; + + public Connector renderer; } diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java index b78e5272fe..a47d8b3e9e 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java @@ -1,7 +1,6 @@ package com.vaadin.tests.components.grid.basics; import java.text.DecimalFormat; -import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -25,6 +24,10 @@ import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.renderers.DateRenderer; +import com.vaadin.ui.renderers.HtmlRenderer; +import com.vaadin.ui.renderers.NumberRenderer; +import com.vaadin.ui.renderers.ProgressBarRenderer; @Widgetset("com.vaadin.DefaultWidgetSet") public class GridBasics extends AbstractTestUIWithLog { @@ -113,16 +116,20 @@ public class GridBasics extends AbstractTestUIWithLog { grid = new Grid<>(); grid.setItems(data); - grid.addColumn("Column 0", String.class, + grid.addColumn("Column 0", dataObj -> "(" + dataObj.getRowNumber() + ", 0)"); - grid.addColumn("Column 1", String.class, + grid.addColumn("Column 1", dataObj -> "(" + dataObj.getRowNumber() + ", 1)"); - grid.addColumn("Row Number", Integer.class, DataObject::getRowNumber); - grid.addColumn("Date", Date.class, DataObject::getDate); - grid.addColumn("HTML String", String.class, DataObject::getHtmlString); - grid.addColumn("Big Random", Integer.class, DataObject::getBigRandom); - grid.addColumn("Small Random", Integer.class, - DataObject::getSmallRandom); + + grid.addColumn("Row Number", DataObject::getRowNumber, + new NumberRenderer()); + grid.addColumn("Date", DataObject::getDate, new DateRenderer()); + grid.addColumn("HTML String", DataObject::getHtmlString, + new HtmlRenderer()); + grid.addColumn("Big Random", DataObject::getBigRandom, + new NumberRenderer()); + grid.addColumn("Small Random", data -> data.getSmallRandom() / 5d, + new ProgressBarRenderer()); ((SingleSelection) grid.getSelectionModel()) .addSelectionListener(e -> log("Selected: " + e.getValue())); diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java index 267567bb53..88f2eb0b20 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java @@ -5,13 +5,13 @@ import org.junit.Test; public class GridContentTest extends GridBasicsTest { - @Test(expected = AssertionError.class) + @Test public void testHtmlRenderer() { DataObject first = getTestData().findFirst().orElse(null); Assert.assertEquals("Text content should match row number", first.getRowNumber().toString(), - getGridElement().getCell(0, 2).getText()); + getGridElement().getCell(0, 4).getText()); Assert.assertEquals("HTML content did not match", first.getHtmlString(), - getGridElement().getCell(0, 2).getAttribute("innerHTML")); + getGridElement().getCell(0, 4).getAttribute("innerHTML")); } }