]> source.dussan.org Git - vaadin-framework.git/commitdiff
Client-side Grid header/footer rewrite: add add/remove rows support (#13334)
authorJohannes Dahlström <johannesd@vaadin.com>
Tue, 15 Jul 2014 17:44:59 +0000 (20:44 +0300)
committerJohannes Dahlström <johannesd@vaadin.com>
Tue, 22 Jul 2014 09:10:57 +0000 (09:10 +0000)
Currently supported:
* Adding and removal of header and footer rows
* Header is single-row by default
* Footer is zero-row by default
* Text captions

TODO:
* Column spanning
* HTML content
* Widget content
* Component content
* Sorting/Indicators
* Server side API
* Shared state handling

Change-Id: I54b5062f31e38e872ca64394dfa02f866a1af202

client/src/com/vaadin/client/ui/grid/Grid.java
client/src/com/vaadin/client/ui/grid/GridFooter.java
client/src/com/vaadin/client/ui/grid/GridHeader.java
client/src/com/vaadin/client/ui/grid/GridStaticSection.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridFooterTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridHeaderTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java [new file with mode: 0644]
uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeatures.java

index 4465cd007090fc8db4fcc2794add8e9626453033..18c1cbae016cc5647a6545c300526ad59e57b1ce 100644 (file)
@@ -45,7 +45,6 @@ import com.vaadin.client.Util;
 import com.vaadin.client.data.DataChangeHandler;
 import com.vaadin.client.data.DataSource;
 import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.grid.GridStaticSection.StaticRow;
 import com.vaadin.client.ui.grid.renderers.ComplexRenderer;
 import com.vaadin.client.ui.grid.renderers.TextRenderer;
 import com.vaadin.client.ui.grid.renderers.WidgetRenderer;
@@ -1311,7 +1310,8 @@ public class Grid<T> extends Composite implements
 
         @Override
         public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
-            StaticRow<?> gridRow = section.getRow(row.getRow());
+            GridStaticSection.StaticRow<?> gridRow = section.getRow(row
+                    .getRow());
 
             final List<Integer> columnIndices = getVisibleColumnIndices();
 
@@ -1365,8 +1365,10 @@ public class Grid<T> extends Composite implements
         escalator.getBody().setEscalatorUpdater(createBodyUpdater());
         escalator.getFooter().setEscalatorUpdater(createFooterUpdater());
 
-        refreshHeader();
-        refreshFooter();
+        header.setGrid(this);
+        header.appendRow();
+
+        footer.setGrid(this);
 
         setSelectionMode(SelectionMode.SINGLE);
 
@@ -1574,7 +1576,7 @@ public class Grid<T> extends Composite implements
             GridStaticSection<?> section) {
 
         // Add or Remove rows on demand
-        int rowDiff = section.getRows().size() - rows.getRowCount();
+        int rowDiff = section.getRowCount() - rows.getRowCount();
         if (rowDiff > 0) {
             rows.insertRows(0, rowDiff);
         } else if (rowDiff < 0) {
index dc0f0054a25bc5688741cfb674062d6a72e8b721..7f478f7d29780b1f2cfe691401cd13a0813d72fb 100644 (file)
@@ -15,9 +15,6 @@
  */
 package com.vaadin.client.ui.grid;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * Represents the footer section of a Grid. The footer is always empty.
  * 
@@ -38,8 +35,7 @@ public class GridFooter extends GridStaticSection<GridFooter.FooterRow> {
      * A single row in a grid Footer section.
      * 
      */
-    public static class FooterRow extends
-            GridStaticSection.StaticRow<FooterCell> {
+    public class FooterRow extends GridStaticSection.StaticRow<FooterCell> {
 
         @Override
         protected FooterCell createCell() {
@@ -51,7 +47,7 @@ public class GridFooter extends GridStaticSection<GridFooter.FooterRow> {
      * A single cell in a grid Footer row. Has a textual caption.
      * 
      */
-    public static class FooterCell extends GridStaticSection.StaticCell {
+    public class FooterCell extends GridStaticSection.StaticCell {
     }
 
     @Override
@@ -60,7 +56,7 @@ public class GridFooter extends GridStaticSection<GridFooter.FooterRow> {
     }
 
     @Override
-    protected List<FooterRow> createRowList() {
-        return Collections.emptyList();
+    protected void refreshGrid() {
+        getGrid().refreshFooter();
     }
 }
index c23c848b8d2ef8001ee9016270ff7160495c1edb..a2207c49c7dbc6dbdde5ced4947a9f5d9e9bbdfa 100644 (file)
@@ -15,9 +15,6 @@
  */
 package com.vaadin.client.ui.grid;
 
-import java.util.Arrays;
-import java.util.List;
-
 /**
  * Represents the header section of a Grid. A header consists of a single header
  * row containing a header cell for each column. Each cell has a simple textual
@@ -42,8 +39,7 @@ public class GridHeader extends GridStaticSection<GridHeader.HeaderRow> {
      * A single row in a grid header section.
      * 
      */
-    public static class HeaderRow extends
-            GridStaticSection.StaticRow<HeaderCell> {
+    public class HeaderRow extends GridStaticSection.StaticRow<HeaderCell> {
 
         @Override
         protected HeaderCell createCell() {
@@ -55,7 +51,7 @@ public class GridHeader extends GridStaticSection<GridHeader.HeaderRow> {
      * A single cell in a grid header row. Has a textual caption.
      * 
      */
-    public static class HeaderCell extends GridStaticSection.StaticCell {
+    public class HeaderCell extends GridStaticSection.StaticCell {
     }
 
     @Override
@@ -64,7 +60,7 @@ public class GridHeader extends GridStaticSection<GridHeader.HeaderRow> {
     }
 
     @Override
-    protected List<HeaderRow> createRowList() {
-        return Arrays.asList(createRow());
+    protected void refreshGrid() {
+        getGrid().refreshHeader();
     }
 }
index 3273c2dfa2aa9fa1a7d7c8d5f543512a0c646a51..5b4523ab76393cfba08edf0d26f85221d0311595 100644 (file)
@@ -18,7 +18,6 @@ package com.vaadin.client.ui.grid;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.vaadin.client.ui.grid.GridStaticSection.StaticRow;
 import com.vaadin.client.ui.grid.renderers.TextRenderer;
 
 /**
@@ -29,7 +28,7 @@ import com.vaadin.client.ui.grid.renderers.TextRenderer;
  * @param <ROWTYPE>
  *            the type of the rows in the section
  */
-abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
+abstract class GridStaticSection<ROWTYPE extends GridStaticSection.StaticRow<?>> {
 
     /**
      * A header or footer cell. Has a simple textual caption.
@@ -42,6 +41,8 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
 
         private String text = "";
 
+        private GridStaticSection<?> section;
+
         /**
          * Sets the text displayed in this cell.
          *
@@ -50,6 +51,7 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
          */
         public void setText(String text) {
             this.text = text;
+            section.refreshGrid();
         }
 
         /**
@@ -60,6 +62,16 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
         public String getText() {
             return text;
         }
+
+        protected GridStaticSection<?> getSection() {
+            assert section != null;
+            return section;
+        }
+
+        protected void setSection(GridStaticSection<?> section) {
+            this.section = section;
+        }
+
     }
 
     /**
@@ -74,6 +86,8 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
 
         private Renderer<String> renderer = new TextRenderer();
 
+        private GridStaticSection<?> section;
+
         /**
          * Returns the cell at the given position in this row.
          * 
@@ -88,7 +102,9 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
         }
 
         protected void addCell(int index) {
-            cells.add(index, createCell());
+            CELLTYPE cell = createCell();
+            cell.setSection(getSection());
+            cells.add(index, cell);
         }
 
         protected void removeCell(int index) {
@@ -100,16 +116,109 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
         }
 
         protected abstract CELLTYPE createCell();
+
+        protected GridStaticSection<?> getSection() {
+            return section;
+        }
+
+        protected void setSection(GridStaticSection<?> section) {
+            this.section = section;
+        }
+    }
+
+    private Grid<?> grid;
+
+    private List<ROWTYPE> rows = new ArrayList<ROWTYPE>();
+
+    /**
+     * Creates and returns a new instance of the row type.
+     * 
+     * @return the created row
+     */
+    protected abstract ROWTYPE createRow();
+
+    /**
+     * Informs the grid that this section should be re-rendered.
+     */
+    protected abstract void refreshGrid();
+
+    /**
+     * Inserts a new row at the given position.
+     * 
+     * @param index
+     *            the position at which to insert the row
+     * @return the new row
+     * 
+     * @throws IndexOutOfBoundsException
+     *             if the index is out of bounds
+     */
+    public ROWTYPE addRow(int index) {
+        ROWTYPE row = createRow();
+        row.setSection(this);
+        for (int i = 0; i < getGrid().getColumnCount(); ++i) {
+            row.addCell(i);
+        }
+        rows.add(index, row);
+        refreshGrid();
+        return row;
+    }
+
+    /**
+     * Adds a new row at the top of this section.
+     * 
+     * @return the new row
+     */
+    public ROWTYPE prependRow() {
+        return addRow(0);
+    }
+
+    /**
+     * Adds a new row at the bottom of this section.
+     * 
+     * @return the new row
+     */
+    public ROWTYPE appendRow() {
+        return addRow(rows.size());
+    }
+
+    /**
+     * Removes the row at the given position.
+     * 
+     * @param index
+     *            the position of the row
+     * 
+     * @throws IndexOutOfBoundsException
+     *             if the index is out of bounds
+     */
+    public void removeRow(int index) {
+        rows.remove(index);
+        refreshGrid();
     }
 
-    private List<ROWTYPE> rows = createRowList();
+    /**
+     * Removes the given row from the section.
+     *
+     * @param row
+     *            the row to be removed
+     * 
+     * @throws IllegalArgumentException
+     *             if the row does not exist in this section
+     */
+    public void removeRow(ROWTYPE row) {
+        if (!rows.remove(row)) {
+            throw new IllegalArgumentException(
+                    "Section does not contain the given row");
+        }
+        refreshGrid();
+    }
 
     /**
-     * Returns the row at the given position in this section.
+     * Returns the row at the given position.
      * 
      * @param index
      *            the position of the row
-     * @return the row at the index
+     * @return the row with the given index
+     * 
      * @throws IndexOutOfBoundsException
      *             if the index is out of bounds
      */
@@ -117,25 +226,37 @@ abstract class GridStaticSection<ROWTYPE extends StaticRow<?>> {
         return rows.get(index);
     }
 
+    /**
+     * Returns the number of rows in this section.
+     * 
+     * @return the number of rows
+     */
+    public int getRowCount() {
+        return rows.size();
+    }
+
     protected List<ROWTYPE> getRows() {
         return rows;
     }
 
     protected void addColumn(GridColumn<?, ?> column, int index) {
-        for (ROWTYPE row : getRows()) {
+        for (ROWTYPE row : rows) {
             row.addCell(index);
         }
     }
 
     protected void removeColumn(int index) {
-        for (ROWTYPE row : getRows()) {
+        for (ROWTYPE row : rows) {
             row.removeCell(index);
         }
     }
 
-    protected List<ROWTYPE> createRowList() {
-        return new ArrayList<ROWTYPE>();
+    protected void setGrid(Grid<?> grid) {
+        this.grid = grid;
     }
 
-    protected abstract ROWTYPE createRow();
+    protected Grid<?> getGrid() {
+        assert grid != null;
+        return grid;
+    }
 }
index e126994f3433173b4f264731c9e0ea2f42301311..80110ddc81bbd52b6450c7ba56892ce1f726f539 100644 (file)
@@ -17,20 +17,58 @@ package com.vaadin.tests.components.grid.basicfeatures;
 
 import static org.junit.Assert.assertEquals;
 
-import java.util.List;
-
 import org.junit.Test;
 
-import com.vaadin.testbench.TestBenchElement;
-
-public class GridFooterTest extends GridBasicClientFeaturesTest {
+public class GridFooterTest extends GridStaticSectionTest {
 
     @Test
     public void testFooterVisibility() throws Exception {
         openTestURL();
 
         // Footer should have zero rows by default
-        List<TestBenchElement> cells = getGridFooterRowCells();
-        assertEquals(0, cells.size());
+        assertEquals(0, getGridFooterRowCells().size());
+    }
+
+    @Test
+    public void testAddRows() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Footer", "Append row");
+
+        assertFooterCount(1);
+        assertFooterTexts(0, 0);
+
+        selectMenuPath("Component", "Footer", "Prepend row");
+
+        assertFooterCount(2);
+        assertFooterTexts(1, 0);
+        assertFooterTexts(0, 1);
+
+        selectMenuPath("Component", "Footer", "Append row");
+
+        assertFooterCount(3);
+        assertFooterTexts(1, 0);
+        assertFooterTexts(0, 1);
+        assertFooterTexts(2, 2);
+    }
+
+    @Test
+    public void testRemoveRows() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Footer", "Prepend row");
+        selectMenuPath("Component", "Footer", "Append row");
+
+        selectMenuPath("Component", "Footer", "Remove top row");
+
+        assertFooterCount(1);
+        assertFooterTexts(1, 0);
+
+        selectMenuPath("Component", "Footer", "Remove bottom row");
+        assertFooterCount(0);
+    }
+
+    private void assertFooterCount(int count) {
+        assertEquals("footer count", count, getGridElement().getFooterCount());
     }
 }
index 716e3b30fcc38ed56095c06373917d13f621e452..c1bc4cdd73f01e919edf2d72ab46dbd4af82f564 100644 (file)
@@ -23,28 +23,21 @@ import org.junit.Test;
 
 import com.vaadin.testbench.TestBenchElement;
 
-public class GridHeaderTest extends GridBasicClientFeaturesTest {
+public class GridHeaderTest extends GridStaticSectionTest {
 
     @Test
     public void testHeaderVisibility() throws Exception {
         openTestURL();
 
         // Column headers should be visible by default
-        List<TestBenchElement> cells = getGridHeaderRowCells();
-        assertEquals(GridBasicFeatures.COLUMNS, cells.size());
+        assertEquals(GridBasicFeatures.COLUMNS, getGridHeaderRowCells().size());
     }
 
     @Test
     public void testHeaderCaptions() throws Exception {
         openTestURL();
 
-        List<TestBenchElement> cells = getGridHeaderRowCells();
-
-        int i = 0;
-        for (TestBenchElement cell : cells) {
-            assertText("Column " + i, cell);
-            i++;
-        }
+        assertHeaderTexts(0, 0);
     }
 
     @Test
@@ -57,23 +50,66 @@ public class GridHeaderTest extends GridBasicClientFeaturesTest {
         List<TestBenchElement> cells = getGridHeaderRowCells();
         assertEquals(GridBasicFeatures.COLUMNS - 2, cells.size());
 
-        assertText("Column 0", cells.get(0));
-        assertText("Column 2", cells.get(1));
-        assertText("Column 4", cells.get(2));
+        assertText("Header (0,0)", cells.get(0));
+        assertText("Header (0,2)", cells.get(1));
+        assertText("Header (0,4)", cells.get(2));
 
         selectMenuPath("Component", "Columns", "Column 3", "Visible");
 
         cells = getGridHeaderRowCells();
         assertEquals(GridBasicFeatures.COLUMNS - 1, cells.size());
 
-        assertText("Column 0", cells.get(0));
-        assertText("Column 2", cells.get(1));
-        assertText("Column 3", cells.get(2));
-        assertText("Column 4", cells.get(3));
+        assertText("Header (0,0)", cells.get(0));
+        assertText("Header (0,2)", cells.get(1));
+        assertText("Header (0,3)", cells.get(2));
+        assertText("Header (0,4)", cells.get(3));
+    }
+
+    @Test
+    public void testAddRows() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Header", "Append row");
+
+        assertHeaderCount(2);
+        assertHeaderTexts(0, 0);
+        assertHeaderTexts(1, 1);
+
+        selectMenuPath("Component", "Header", "Prepend row");
+
+        assertHeaderCount(3);
+        assertHeaderTexts(2, 0);
+        assertHeaderTexts(0, 1);
+        assertHeaderTexts(1, 2);
+
+        selectMenuPath("Component", "Header", "Append row");
+
+        assertHeaderCount(4);
+        assertHeaderTexts(2, 0);
+        assertHeaderTexts(0, 1);
+        assertHeaderTexts(1, 2);
+        assertHeaderTexts(3, 3);
+    }
+
+    @Test
+    public void testRemoveRows() throws Exception {
+        openTestURL();
+
+        selectMenuPath("Component", "Header", "Prepend row");
+        selectMenuPath("Component", "Header", "Append row");
+
+        selectMenuPath("Component", "Header", "Remove top row");
+
+        assertHeaderCount(2);
+        assertHeaderTexts(0, 0);
+        assertHeaderTexts(2, 1);
+
+        selectMenuPath("Component", "Header", "Remove bottom row");
+        assertHeaderCount(1);
+        assertHeaderTexts(0, 0);
     }
 
-    private static void assertText(String text, TestBenchElement e) {
-        // TBE.getText returns "" if the element is scrolled out of view
-        assertEquals(text, e.getAttribute("innerHTML"));
+    private void assertHeaderCount(int count) {
+        assertEquals("header count", count, getGridElement().getHeaderCount());
     }
 }
diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridStaticSectionTest.java
new file mode 100644 (file)
index 0000000..8f6739e
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.components.grid.basicfeatures;
+
+import static org.junit.Assert.assertEquals;
+
+import com.vaadin.testbench.TestBenchElement;
+
+/**
+ * Abstract base class for header and footer tests.
+ * 
+ * @since
+ * @author Vaadin Ltd
+ */
+public abstract class GridStaticSectionTest extends GridBasicClientFeaturesTest {
+
+    protected void assertHeaderTexts(int headerId, int rowIndex) {
+        int i = 0;
+        for (TestBenchElement cell : getGridElement().getHeaderCells(rowIndex)) {
+            assertText(String.format("Header (%d,%d)", headerId, i), cell);
+            i++;
+        }
+        assertEquals("number of header columns", GridBasicFeatures.COLUMNS, i);
+    }
+
+    protected void assertFooterTexts(int footerId, int rowIndex) {
+        int i = 0;
+        for (TestBenchElement cell : getGridElement().getFooterCells(rowIndex)) {
+            assertText(String.format("Footer (%d,%d)", footerId, i), cell);
+            i++;
+        }
+        assertEquals("number of footer columns", GridBasicFeatures.COLUMNS, i);
+    }
+
+    protected static void assertText(String text, TestBenchElement e) {
+        // TBE.getText returns "" if the element is scrolled out of view
+        assertEquals(text, e.getAttribute("innerHTML"));
+    }
+}
index 182a5bfa2955de4efca8084e24141dec253a642f..8564b149d8fd096ca8e4d9b354cd0426f88cbac2 100644 (file)
@@ -25,7 +25,10 @@ import com.vaadin.client.ui.grid.FlyweightCell;
 import com.vaadin.client.ui.grid.Grid;
 import com.vaadin.client.ui.grid.Grid.SelectionMode;
 import com.vaadin.client.ui.grid.GridColumn;
-import com.vaadin.client.ui.grid.GridHeader.HeaderCell;
+import com.vaadin.client.ui.grid.GridFooter;
+import com.vaadin.client.ui.grid.GridFooter.FooterRow;
+import com.vaadin.client.ui.grid.GridHeader;
+import com.vaadin.client.ui.grid.GridHeader.HeaderRow;
 import com.vaadin.client.ui.grid.Renderer;
 import com.vaadin.client.ui.grid.datasources.ListDataSource;
 import com.vaadin.client.ui.grid.renderers.DateRenderer;
@@ -193,12 +196,7 @@ public class GridBasicClientFeatures extends
             });
         }
 
-        // Set captions to column headers
-
-        for (int i = 0; i < COLUMNS; ++i) {
-            HeaderCell cell = grid.getHeader().getRow(0).getCell(i);
-            cell.setText("Column " + i);
-        }
+        setHeaderTexts(grid.getHeader().getRow(0));
 
         //
         // Populate the menu
@@ -206,6 +204,8 @@ public class GridBasicClientFeatures extends
 
         createStateMenu();
         createColumnsMenu();
+        createHeaderMenu();
+        createFooterMenu();
 
         grid.getElement().getStyle().setZIndex(0);
         add(grid);
@@ -250,6 +250,81 @@ public class GridBasicClientFeatures extends
         }
     }
 
+    private int headerCounter = 0;
+    private int footerCounter = 0;
+
+    private void setHeaderTexts(HeaderRow row) {
+        for (int i = 0; i < COLUMNS; ++i) {
+            row.getCell(i).setText("Header (" + headerCounter + "," + i + ")");
+        }
+        headerCounter++;
+    }
+
+    private void setFooterTexts(FooterRow row) {
+        for (int i = 0; i < COLUMNS; ++i) {
+            row.getCell(i).setText("Footer (" + footerCounter + "," + i + ")");
+        }
+        footerCounter++;
+    }
+
+    private void createHeaderMenu() {
+        final GridHeader header = grid.getHeader();
+        addMenuCommand("Prepend row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                setHeaderTexts(header.prependRow());
+            }
+        }, "Component", "Header");
+        addMenuCommand("Append row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                setHeaderTexts(header.appendRow());
+            }
+        }, "Component", "Header");
+        addMenuCommand("Remove top row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                header.removeRow(0);
+            }
+        }, "Component", "Header");
+        addMenuCommand("Remove bottom row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                header.removeRow(header.getRowCount() - 1);
+            }
+        }, "Component", "Header");
+    }
+
+    private void createFooterMenu() {
+
+        final GridFooter footer = grid.getFooter();
+
+        addMenuCommand("Prepend row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                setFooterTexts(footer.prependRow());
+            }
+        }, "Component", "Footer");
+        addMenuCommand("Append row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                setFooterTexts(footer.appendRow());
+            }
+        }, "Component", "Footer");
+        addMenuCommand("Remove top row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                footer.removeRow(0);
+            }
+        }, "Component", "Footer");
+        addMenuCommand("Remove bottom row", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                footer.removeRow(footer.getRowCount() - 1);
+            }
+        }, "Component", "Footer");
+    }
+
     /**
      * Creates a a renderer for a {@link Renderers}
      */