From e4e2328a3a78d652cd09ef8293f233d31d899415 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Tue, 27 Jun 2017 15:42:01 +0300 Subject: [PATCH] Add presentation value providers for Grid (#9553) This patch changes Grid Columns, so they can have different value and presentation types. A presentation provider can be given when setting the renderer for a column. This provider takes the value of the column on a row and chooses what to present for this value. Using this approach it is easier to have an editor for the actual backing data instead of the presentation of it. Fixes #8656 Resolves #9588 --- all/src/main/templates/release-notes.html | 2 + .../components/components-grid.asciidoc | 29 +++ server/src/main/java/com/vaadin/ui/Grid.java | 188 +++++++++++++++--- .../grid/GridColumnPresentation.java | 99 +++++++++ .../grid/GridColumnPresentationTest.java | 27 +++ 5 files changed, 318 insertions(+), 27 deletions(-) create mode 100644 uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnPresentation.java create mode 100644 uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnPresentationTest.java diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index 0652dbd58a..46891fe8b2 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -108,6 +108,8 @@
  • Tooltip styles for ContentMode.PREFORMATTED have been changed in all built-in themes to use the application font and allow long lines to wrap to multiple lines.
  • Grid.Column now extends AbstractExtension instead of AbstractGridExtension to hide data generator specific API.
  • DataCommunicator, DataKeyMapper and KeyMapper public APIs have some minor changes for better bean identification.
  • +
  • Grid.createColumn now has one more parameter presentationProvider.
  • +

    For incompatible or behaviour-altering changes in 8.0, please see 8.0 release notes

    diff --git a/documentation/components/components-grid.asciidoc b/documentation/components/components-grid.asciidoc index 5a9cef230b..6d73da72df 100644 --- a/documentation/components/components-grid.asciidoc +++ b/documentation/components/components-grid.asciidoc @@ -613,6 +613,7 @@ grid.addColumn(person -> { }, new ComponentRenderer()); ---- + [[components.grid.renderer.custom]] === Custom Renderers @@ -896,6 +897,34 @@ You can modify the error message by implementing a custom [interfacename]#EditorErrorGenerator# with for the [classname]#Editor#. +[[components.grid.presentation.provider]] +=== Presentation Value Providers + +By default, a renderer displays the column value. If you want to edit an +internal value (such as an address object) but show a simpler representation +when not editing a row, a presentation value provider can be used. + +A presentation value provider converts the value of a cell (obtained with a +value provider, and used by the editor) to a different representation to be +shown by renderers when the cell is not being edited. A custom renderer can +optionally be used for the presentation values. + +In the following example, we demonstrate one way to use a simplified +presentation of an address column while allowing editing the full address: + +[source, java] +---- +Column column = grid.addColumn(Person::getAddress); +// alternatively, the presentation provider can be given as an extra parameter +// to addColumn() +column.setRenderer( + address -> address.getCity() + " " + address.getCountry(), + new TextRenderer()); +column.setCaption("Address"); +column.setEditorComponent(new AddressField(), Person::setAddress); +---- + + //// // Not supported in 8 [[components.grid.scrolling]] diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 58f834cafd..6e11989757 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -822,6 +822,7 @@ public class Grid extends AbstractListing implements HasComponents, public static class Column extends AbstractExtension { private final ValueProvider valueProvider; + private ValueProvider presentationProvider; private SortOrderProvider sortOrderProvider = direction -> { String id = getId(); @@ -846,22 +847,11 @@ public class Grid extends AbstractListing implements HasComponents, assert communicationId != null : "No communication ID set for column " + state.caption; - @SuppressWarnings("unchecked") - Renderer renderer = (Renderer) state.renderer; - JsonObject obj = getDataObject(jsonObject, DataCommunicatorConstants.DATA); - V providerValue = valueProvider.apply(item); - - // Make Grid track components. - if (renderer instanceof ComponentRenderer - && providerValue instanceof Component) { - addComponent(item, (Component) providerValue); - } - JsonValue rendererValue = renderer.encode(providerValue); - - obj.put(communicationId, rendererValue); + obj.put(communicationId, generateRendererValue(item, + presentationProvider, state.renderer)); String style = styleGenerator.apply(item); if (style != null && !style.isEmpty()) { @@ -906,19 +896,48 @@ public class Grid extends AbstractListing implements HasComponents, * the function to get values from items, not * null * @param renderer - * the type of value, not null + * the value renderer, not null */ protected Column(ValueProvider valueProvider, Renderer renderer) { + this(valueProvider, ValueProvider.identity(), renderer); + } + + /** + * Constructs a new Column configuration with given renderer and value + * provider. + *

    + * For a more complete explanation on presentation provider, see + * {@link #setRenderer(ValueProvider, Renderer)}. + * + * @param valueProvider + * the function to get values from items, not + * null + * @param presentationProvider + * the function to get presentations from the value of this + * column, not null. For more details, see {@link #setRenderer(ValueProvider, Renderer)} + * @param renderer + * the presentation renderer, not null + * @param

    + * the presentation type + * + * @since 8.1 + */ + protected

    Column(ValueProvider valueProvider, + ValueProvider presentationProvider, + Renderer renderer) { Objects.requireNonNull(valueProvider, "Value provider can't be null"); + Objects.requireNonNull(presentationProvider, + "Presentation provider can't be null"); Objects.requireNonNull(renderer, "Renderer can't be null"); ColumnState state = getState(); this.valueProvider = valueProvider; - state.renderer = renderer; + this.presentationProvider = presentationProvider; + state.renderer = renderer; state.caption = ""; // Add the renderer as a child extension of this extension, thus @@ -926,7 +945,7 @@ public class Grid extends AbstractListing implements HasComponents, // removed addExtension(renderer); - Class valueType = renderer.getPresentationType(); + Class valueType = renderer.getPresentationType(); if (Comparable.class.isAssignableFrom(valueType)) { comparator = (a, b) -> compareComparables( @@ -1008,6 +1027,20 @@ public class Grid extends AbstractListing implements HasComponents, } } + @SuppressWarnings("unchecked") + private

    JsonValue generateRendererValue(T item, + ValueProvider presentationProvider, Connector renderer) { + P presentationValue = presentationProvider + .apply(valueProvider.apply(item)); + + // Make Grid track components. + if (renderer instanceof ComponentRenderer + && presentationValue instanceof Component) { + addComponent(item, (Component) presentationValue); + } + return ((Renderer

    ) renderer).encode(presentationValue); + } + private void addComponent(T item, Component component) { if (activeComponents.containsKey(item)) { if (activeComponents.get(item).equals(component)) { @@ -1887,7 +1920,39 @@ public class Grid extends AbstractListing implements HasComponents, * @since 8.0.3 */ public Column setRenderer(Renderer renderer) { - Objects.requireNonNull(renderer, "Renderer can't be null"); + return setRenderer(ValueProvider.identity(), renderer); + } + + /** + * Sets the Renderer for this Column. Setting the renderer will cause + * all currently available row data to be recreated and sent to the + * client. + *

    + * The presentation provider is a method that takes the value of this + * column on a single row, and maps that to a value that the renderer + * accepts. This feature can be used for storing a complex value in a + * column for editing, but providing a simplified presentation for the + * user when not editing. + * + * @param presentationProvider + * the function to get presentations from the value of this + * column, not {@code null} + * @param renderer + * the new renderer, not {@code null} + * + * @param

    + * the presentation type + * + * @return this column + * + * @since 8.1 + */ + public

    Column setRenderer( + ValueProvider presentationProvider, + Renderer renderer) { + Objects.requireNonNull(renderer, "Renderer can not be null"); + Objects.requireNonNull(presentationProvider, + "Presentation provider can not be null"); // Remove old renderer Connector oldRenderer = getState().renderer; @@ -1898,6 +1963,7 @@ public class Grid extends AbstractListing implements HasComponents, // Set new renderer getState().renderer = renderer; addExtension(renderer); + this.presentationProvider = presentationProvider; // Trigger redraw getGrid().getDataCommunicator().reset(); @@ -1911,8 +1977,8 @@ public class Grid extends AbstractListing implements HasComponents, * @return the renderer * @since 8.1 */ - public Renderer getRenderer() { - return (Renderer) getState().renderer; + public Renderer getRenderer() { + return (Renderer) getState().renderer; } /** @@ -1921,6 +1987,7 @@ public class Grid extends AbstractListing implements HasComponents, * @return the grid that this column belongs to, or null if * this column has not yet been associated with any grid */ + @SuppressWarnings("unchecked") protected Grid getGrid() { return (Grid) getParent(); } @@ -2500,7 +2567,7 @@ public class Grid extends AbstractListing implements HasComponents, * @param valueProvider * the value provider * @param renderer - * the column value class + * the column value renderer * @param * the column value type * @@ -2510,8 +2577,67 @@ public class Grid extends AbstractListing implements HasComponents, */ public Column addColumn(ValueProvider valueProvider, AbstractRenderer renderer) { + return addColumn(valueProvider, ValueProvider.identity(), renderer); + } + + /** + * Adds a new column to this {@link Grid} with value provider and + * presentation provider. + *

    + * Note: The presentation type for this method is set to be + * String. To use any custom renderer with the presentation provider, use + * {@link #addColumn(ValueProvider, ValueProvider, AbstractRenderer)}. + * + * @param valueProvider + * the value provider + * @param presentationProvider + * the value presentation provider + * @param + * the column value type + * + * @see #addColumn(ValueProvider, ValueProvider, AbstractRenderer) + * + * @return the new column + * @since 8.1 + */ + public Column addColumn(ValueProvider valueProvider, + ValueProvider presentationProvider) { + return addColumn(valueProvider, presentationProvider, + new TextRenderer()); + } + + /** + * Adds a new column to this {@link Grid} with value provider, presentation + * provider and typed renderer. + * + *

    + * The presentation provider is a method that takes the value from the value + * provider, and maps that to a value that the renderer accepts. This + * feature can be used for storing a complex value in a column for editing, + * but providing a simplified presentation for the user when not editing. + * + * @param valueProvider + * the value provider + * @param presentationProvider + * the value presentation provider + * @param renderer + * the column value renderer + * @param + * the column value type + * @param

    + * the column presentation type + * + * @return the new column + * + * @see AbstractRenderer + * @since 8.1 + */ + public Column addColumn(ValueProvider valueProvider, + ValueProvider presentationProvider, + AbstractRenderer renderer) { String generatedIdentifier = getGeneratedIdentifier(); - Column column = createColumn(valueProvider, renderer); + Column column = createColumn(valueProvider, presentationProvider, + renderer); addColumn(generatedIdentifier, column); return column; } @@ -2536,21 +2662,28 @@ public class Grid extends AbstractListing implements HasComponents, } /** - * Creates a column instance from a value provider and a renderer. + * Creates a column instance from a value provider, presentation provider + * and a renderer. * * @param valueProvider * the value provider + * @param presentationProvider + * the presentation provider * @param renderer * the renderer * @return a new column instance * @param * the column value type + * @param

    + * the column presentation type * - * @since 8.0.3 + * @since 8.1 */ - protected Column createColumn(ValueProvider valueProvider, - AbstractRenderer renderer) { - return new Column<>(valueProvider, renderer); + protected Column createColumn( + ValueProvider valueProvider, + ValueProvider presentationProvider, + AbstractRenderer renderer) { + return new Column<>(valueProvider, presentationProvider, renderer); } private void addColumn(String identifier, Column column) { @@ -4019,7 +4152,8 @@ public class Grid extends AbstractListing implements HasComponents, column = addColumn(id); } else { DeclarativeValueProvider provider = new DeclarativeValueProvider<>(); - column = createColumn(provider, new HtmlRenderer()); + column = createColumn(provider, ValueProvider.identity(), + new HtmlRenderer()); addColumn(getGeneratedIdentifier(), column); if (id != null) { column.setId(id); diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnPresentation.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnPresentation.java new file mode 100644 index 0000000000..2f099982a6 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridColumnPresentation.java @@ -0,0 +1,99 @@ +package com.vaadin.tests.components.grid; + +import com.vaadin.data.HasValue; +import com.vaadin.server.ErrorMessage; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.Registration; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.data.bean.Address; +import com.vaadin.tests.data.bean.Person; +import com.vaadin.ui.Composite; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Label; +import com.vaadin.ui.renderers.TextRenderer; + +/** + * An example for using a different value and presentation types in a Grid + * column. + */ +public class GridColumnPresentation extends AbstractTestUI { + + /** + * Dummy HasValue for Address. + */ + private static class AddressField extends Composite + implements HasValue

    { + + Address address; + private Label label; + + public AddressField() { + super(); + + label = new Label(); + setCompositionRoot(label); + } + + @Override + public void setValue(Address value) { + Address oldAddress = address; + address = value; + label.setValue(String.valueOf(address)); + fireEvent(new ValueChangeEvent<>(this, oldAddress, false)); + } + + @Override + public Address getValue() { + return address; + } + + @Override + public Registration addValueChangeListener( + ValueChangeListener
    listener) { + return addListener(ValueChangeEvent.class, listener, + ValueChangeListener.VALUE_CHANGE_METHOD); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly(); + } + + @Override + public void setReadOnly(boolean readOnly) { + super.setReadOnly(readOnly); + } + + @Override + public boolean isRequiredIndicatorVisible() { + return super.isRequiredIndicatorVisible(); + } + + @Override + public void setRequiredIndicatorVisible(boolean visible) { + super.setRequiredIndicatorVisible(visible); + } + + @Override + public void setComponentError(ErrorMessage componentError) { + label.setComponentError(componentError); + } + } + + @Override + protected void setup(VaadinRequest request) { + Grid personGrid = new Grid<>(); + personGrid.setItems(Person.createTestPerson1(), + Person.createTestPerson2()); + personGrid.addColumn(Person::getAddress) + .setRenderer( + address -> address.getCity() + " " + + address.getCountry().name(), + new TextRenderer()) + .setCaption("Address") + .setEditorComponent(new AddressField(), Person::setAddress); + personGrid.getEditor().setEnabled(true); + addComponent(personGrid); + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnPresentationTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnPresentationTest.java new file mode 100644 index 0000000000..79233dc50d --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridColumnPresentationTest.java @@ -0,0 +1,27 @@ +package com.vaadin.tests.components.grid; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.SingleBrowserTest; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +@TestCategory("grid") +public class GridColumnPresentationTest extends SingleBrowserTest { + + @Test + public void presenterAndEditor() { + openTestURL(); + GridElement grid = $(GridElement.class).get(0); + assertEquals("Turku FINLAND", grid.getCell(0, 0).getText()); + assertEquals("Amsterdam NETHERLANDS", grid.getCell(1, 0).getText()); + //Activate editor + GridElement.GridCellElement cell = grid.getCell(1, 0); + cell.doubleClick(); + + assertEquals("Address [streetAddress=Red street, postalCode=12, city=Amsterdam, country=Netherlands]", + grid.getEditor().getField(0).getText()); + + } +} -- 2.39.5