From 12bb89d64b3ba975701600aefdcec06dc4530086 Mon Sep 17 00:00:00 2001 From: Jonatan Kronqvist Date: Fri, 19 Aug 2011 07:16:01 +0000 Subject: [PATCH] Implemented row generation for Table and TreeTable #6720# svn changeset:20495/svn branch:6.7 --- WebContent/VAADIN/themes/base/table/table.css | 4 + .../chameleon/components/table/table.css | 5 + .../VAADIN/themes/liferay/table/table.css | 9 + .../VAADIN/themes/reindeer/table/table.css | 9 + WebContent/VAADIN/themes/runo/table/table.css | 7 + .../terminal/gwt/client/ui/VScrollTable.java | 360 ++++++++++++------ .../terminal/gwt/client/ui/VTreeTable.java | 82 +++- src/com/vaadin/ui/Table.java | 224 +++++++++-- .../tests/components/table/RowGenerators.java | 59 +++ .../components/table/TableGeneratedRows.html | 107 ++++++ .../vaadin/tests/components/table/Tables.java | 65 ++++ .../treetable/TreeTableGeneratedRows.html | 182 +++++++++ 12 files changed, 950 insertions(+), 163 deletions(-) create mode 100644 tests/src/com/vaadin/tests/components/table/RowGenerators.java create mode 100644 tests/src/com/vaadin/tests/components/table/TableGeneratedRows.html create mode 100644 tests/src/com/vaadin/tests/components/treetable/TreeTableGeneratedRows.html diff --git a/WebContent/VAADIN/themes/base/table/table.css b/WebContent/VAADIN/themes/base/table/table.css index c618c07587..a5611e809f 100644 --- a/WebContent/VAADIN/themes/base/table/table.css +++ b/WebContent/VAADIN/themes/base/table/table.css @@ -143,6 +143,10 @@ cursor: pointer; } +.v-table-generated-row { + background: #efefef; +} + .v-table-body-noselection .v-table-row, .v-table-body-noselection .v-table-row-odd { cursor: default; diff --git a/WebContent/VAADIN/themes/chameleon/components/table/table.css b/WebContent/VAADIN/themes/chameleon/components/table/table.css index ae1ada0793..0974f7588b 100644 --- a/WebContent/VAADIN/themes/chameleon/components/table/table.css +++ b/WebContent/VAADIN/themes/chameleon/components/table/table.css @@ -38,6 +38,11 @@ div.v-table-focus-slot-left { margin: 0; } +.v-table-generated-row { + background: #c9c9c9; + } + + div.v-table-focus-slot-right { background: transparent; border-right: 2px solid #b3b3b3; diff --git a/WebContent/VAADIN/themes/liferay/table/table.css b/WebContent/VAADIN/themes/liferay/table/table.css index 50b5ed9182..020bac882b 100644 --- a/WebContent/VAADIN/themes/liferay/table/table.css +++ b/WebContent/VAADIN/themes/liferay/table/table.css @@ -96,6 +96,15 @@ background: #eef0f2; } +.v-table-generated-row { + color: #336699; + font-weight: bold; + font-size: 11px; + padding-left: 0px; + padding-top: 6px; + background: #c0c2c5; +} + .v-table .v-selected { background-color: #5B677D; color: #FFF; diff --git a/WebContent/VAADIN/themes/reindeer/table/table.css b/WebContent/VAADIN/themes/reindeer/table/table.css index 84c423bec9..461f4642b3 100644 --- a/WebContent/VAADIN/themes/reindeer/table/table.css +++ b/WebContent/VAADIN/themes/reindeer/table/table.css @@ -121,6 +121,15 @@ .v-table-row-odd { background: #eff0f1; } +.v-table-generated-row { + background: #dcdee0; + text-transform: uppercase; + font-size: 10px; + font-weight: bold; + color: #222; + text-shadow: #f3f5f8 0 1px 0; + line-height: normal; +} .v-table-cell-content:last-child { border-right-color: transparent; } diff --git a/WebContent/VAADIN/themes/runo/table/table.css b/WebContent/VAADIN/themes/runo/table/table.css index 7a7de1962b..2b2bcbce05 100644 --- a/WebContent/VAADIN/themes/runo/table/table.css +++ b/WebContent/VAADIN/themes/runo/table/table.css @@ -67,6 +67,13 @@ tr.v-table-row-odd:hover { .v-table-body-noselection .v-table-row-odd:hover { background-color: #f6f7f7; } +.v-table-generated-row { + color: #393a3c; + font-size: 15px; + padding: 9px 2px 9px 0; + text-shadow: #ffffff 0 1px 0; + background: #e7e9ea; +} .v-table tr.v-selected { background: #57a7ed; color: #fff; diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index fb51919b10..f0db3e55b2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -1679,9 +1679,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, /** * Run only once when component is attached and received its initial - * content. This function : * Syncs headers and bodys "natural widths and - * saves the values. * Sets proper width and height * Makes deferred request - * to get some cache rows + * content. This function: + * + * * Syncs headers and bodys "natural widths and saves the values. + * + * * Sets proper width and height + * + * * Makes deferred request to get some cache rows */ private void sizeInit() { /* @@ -3968,22 +3972,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, */ private VScrollTableRow prepareRow(UIDL uidl) { final VScrollTableRow row = createRow(uidl, aligns); - final int cells = DOM.getChildCount(row.getElement()); - for (int i = 0; i < cells; i++) { - final Element cell = DOM.getChild(row.getElement(), i); - int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); - if (w < 0) { - w = 0; - } - cell.getFirstChildElement().getStyle() - .setPropertyPx("width", w); - cell.getStyle().setPropertyPx("width", w); - } + row.initCellWidths(); return row; } protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { - return new VScrollTableRow(uidl, aligns); + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VScrollTableGeneratedRow(uidl, aligns2); + } + return new VScrollTableRow(uidl, aligns2); } private void addRowBeforeFirstRendered(VScrollTableRow row) { @@ -4187,16 +4185,19 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, */ public int getColWidth(int columnIndex) { if (tBodyMeasurementsDone) { - NodeList rows = tBodyElement.getRows(); - if (rows.getLength() == 0) { + if (renderedRows.isEmpty()) { // no rows yet rendered return 0; - } else { - com.google.gwt.dom.client.Element wrapperdiv = rows - .getItem(0).getCells().getItem(columnIndex) - .getFirstChildElement(); - return wrapperdiv.getOffsetWidth(); } + for (Widget row : renderedRows) { + if (!(row instanceof VScrollTableGeneratedRow)) { + TableRowElement tr = row.getElement().cast(); + Element wrapperdiv = tr.getCells().getItem(columnIndex) + .getFirstChildElement().cast(); + return wrapperdiv.getOffsetWidth(); + } + } + return 0; } else { return 0; } @@ -4216,14 +4217,22 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * @param w */ public void setColWidth(int colIndex, int w) { - NodeList rows2 = tBodyElement.getRows(); - final int rows = rows2.getLength(); - for (int i = 0; i < rows; i++) { - TableRowElement row = rows2.getItem(i); - TableCellElement cell = row.getCells().getItem(colIndex); - cell.getFirstChildElement().getStyle() - .setPropertyPx("width", w); - cell.getStyle().setPropertyPx("width", w); + for (Widget row : renderedRows) { + TableRowElement tr = row.getElement().cast(); + TableCellElement cell = tr.getCells().getItem(colIndex); + boolean spanned = false; + if (row instanceof VScrollTableGeneratedRow) { + spanned = ((VScrollTableGeneratedRow) row).isSpanColumns(); + } + if (!spanned) { + cell.getFirstChildElement().getStyle() + .setPropertyPx("width", w); + cell.getStyle().setPropertyPx("width", w); + } else if (colIndex == 0) { + cell.getFirstChildElement().getStyle() + .clearProperty("width"); + cell.getStyle().clearProperty("width"); + } } } @@ -4323,8 +4332,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, protected final int rowKey; private List pendingComponentPaints; - private String[] actionKeys = null; - private final TableRowElement rowElement; + protected String[] actionKeys = null; + protected final TableRowElement rowElement; private boolean mDown; private int index; private Event touchStart; @@ -4345,6 +4354,120 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS); } + public VScrollTableRow(UIDL uidl, char[] aligns) { + this(uidl.getIntAttribute("key")); + + /* + * Rendering the rows as hidden improves Firefox and Safari + * performance drastically. + */ + getElement().getStyle().setProperty("visibility", "hidden"); + + String rowStyle = uidl.getStringAttribute("rowstyle"); + if (rowStyle != null) { + addStyleName(CLASSNAME + "-row-" + rowStyle); + } + + String rowDescription = uidl.getStringAttribute("rowdescr"); + if (rowDescription != null && !rowDescription.equals("")) { + TooltipInfo info = new TooltipInfo(rowDescription); + client.registerTooltip(VScrollTable.this, rowElement, info); + } else { + // Remove possibly previously set tooltip + client.registerTooltip(VScrollTable.this, rowElement, null); + } + + tHead.getColumnAlignments(); + int col = 0; + int visibleColumnIndex = -1; + + // row header + if (showRowHeaders) { + boolean sorted = tHead.getHeaderCell(col).isSorted(); + addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], + "rowheader", true, sorted); + visibleColumnIndex++; + } + + if (uidl.hasAttribute("al")) { + actionKeys = uidl.getStringArrayAttribute("al"); + } + + addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex); + + if (uidl.hasAttribute("selected") && !isSelected()) { + toggleSelection(); + } + } + + /** + * Add a dummy row, used for measurements if Table is empty. + */ + public VScrollTableRow() { + this(0); + addStyleName(CLASSNAME + "-row"); + addCell(null, "_", 'b', "", true, false); + } + + protected void initCellWidths() { + final int cells = DOM.getChildCount(getElement()); + for (int i = 0; i < cells; i++) { + final Element cell = DOM.getChild(getElement(), i); + int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); + if (w < 0) { + w = 0; + } + cell.getFirstChildElement().getStyle() + .setPropertyPx("width", w); + cell.getStyle().setPropertyPx("width", w); + } + } + + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + final Iterator cells = uidl.getChildIterator(); + while (cells.hasNext()) { + final Object cell = cells.next(); + visibleColumnIndex++; + + String columnId = visibleColOrder[visibleColumnIndex]; + + String style = ""; + if (uidl.hasAttribute("style-" + columnId)) { + style = uidl.getStringAttribute("style-" + columnId); + } + + String description = null; + if (uidl.hasAttribute("descr-" + columnId)) { + description = uidl.getStringAttribute("descr-" + + columnId); + } + + boolean sorted = tHead.getHeaderCell(col).isSorted(); + if (cell instanceof String) { + addCell(uidl, cell.toString(), aligns[col++], style, + isRenderCellsAsHtml(), sorted, description); + } else { + final Paintable cellContent = client + .getPaintable((UIDL) cell); + + addCell(uidl, (Widget) cellContent, aligns[col++], + style, sorted); + paintComponent(cellContent, (UIDL) cell); + } + } + } + + /** + * Overriding this and returning true causes all text cells to be + * rendered as HTML. + * + * @return always returns false in the default implementation + */ + protected boolean isRenderCellsAsHtml() { + return false; + } + /** * Detects whether row is visible in tables viewport. * @@ -4403,7 +4526,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return index; } - private void paintComponent(Paintable p, UIDL uidl) { + protected void paintComponent(Paintable p, UIDL uidl) { if (isAttached()) { p.updateFromUIDL(uidl, client); } else { @@ -4435,90 +4558,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, return String.valueOf(rowKey); } - public VScrollTableRow(UIDL uidl, char[] aligns) { - this(uidl.getIntAttribute("key")); - - /* - * Rendering the rows as hidden improves Firefox and Safari - * performance drastically. - */ - getElement().getStyle().setProperty("visibility", "hidden"); - - String rowStyle = uidl.getStringAttribute("rowstyle"); - if (rowStyle != null) { - addStyleName(CLASSNAME + "-row-" + rowStyle); - } - - String rowDescription = uidl.getStringAttribute("rowdescr"); - if (rowDescription != null && !rowDescription.equals("")) { - TooltipInfo info = new TooltipInfo(rowDescription); - client.registerTooltip(VScrollTable.this, rowElement, info); - } else { - // Remove possibly previously set tooltip - client.registerTooltip(VScrollTable.this, rowElement, null); - } - - tHead.getColumnAlignments(); - int col = 0; - int visibleColumnIndex = -1; - - // row header - if (showRowHeaders) { - boolean sorted = tHead.getHeaderCell(col).isSorted(); - addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], - "rowheader", true, sorted); - visibleColumnIndex++; - } - - if (uidl.hasAttribute("al")) { - actionKeys = uidl.getStringArrayAttribute("al"); - } - - final Iterator cells = uidl.getChildIterator(); - while (cells.hasNext()) { - final Object cell = cells.next(); - visibleColumnIndex++; - - String columnId = visibleColOrder[visibleColumnIndex]; - - String style = ""; - if (uidl.hasAttribute("style-" + columnId)) { - style = uidl.getStringAttribute("style-" + columnId); - } - - String description = null; - if (uidl.hasAttribute("descr-" + columnId)) { - description = uidl.getStringAttribute("descr-" - + columnId); - } - - boolean sorted = tHead.getHeaderCell(col).isSorted(); - if (cell instanceof String) { - addCell(uidl, cell.toString(), aligns[col++], style, - false, sorted, description); - } else { - final Paintable cellContent = client - .getPaintable((UIDL) cell); - - addCell(uidl, (Widget) cellContent, aligns[col++], - style, sorted); - paintComponent(cellContent, (UIDL) cell); - } - } - if (uidl.hasAttribute("selected") && !isSelected()) { - toggleSelection(); - } - } - - /** - * Add a dummy row, used for measurements if Table is empty. - */ - public VScrollTableRow() { - this(0); - addStyleName(CLASSNAME + "-row"); - addCell(null, "_", 'b', "", true, false); - } - public void addCell(UIDL rowUidl, String text, char align, String style, boolean textIsHTML, boolean sorted) { addCell(rowUidl, text, align, style, textIsHTML, sorted, null); @@ -4528,7 +4567,14 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, String style, boolean textIsHTML, boolean sorted, String description) { // String only content is optimized by not using Label widget - final Element td = DOM.createTD(); + final TableCellElement td = DOM.createTD().cast(); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + + protected void initCellWithText(String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, final TableCellElement td) { final Element container = DOM.createDiv(); String className = CLASSNAME + "-cell-content"; if (style != null && !style.equals("")) { @@ -4570,7 +4616,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, public void addCell(UIDL rowUidl, Widget w, char align, String style, boolean sorted) { - final Element td = DOM.createTD(); + final TableCellElement td = DOM.createTD().cast(); + initCellWithWidget(w, align, style, sorted, td); + } + + protected void initCellWithWidget(Widget w, char align, + String style, boolean sorted, final TableCellElement td) { final Element container = DOM.createDiv(); String className = CLASSNAME + "-cell-content"; if (style != null && !style.equals("")) { @@ -5223,7 +5274,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, }; } - private int getColIndexOf(Widget child) { + protected int getColIndexOf(Widget child) { com.google.gwt.dom.client.Element widgetCell = child .getElement().getParentElement().getParentElement(); NodeList cells = rowElement.getCells(); @@ -5269,6 +5320,75 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } } + protected class VScrollTableGeneratedRow extends VScrollTableRow { + + private boolean spanColumns; + private boolean renderAsHtml; + + public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + @Override + protected void initCellWidths() { + if (!spanColumns) { + super.initCellWidths(); + } + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected boolean isRenderCellsAsHtml() { + return renderAsHtml; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + renderAsHtml = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", renderAsHtml, false, null, colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + } + /** * Ensure the component has a focus. * diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java index a3b7008e77..b21a5dfdce 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java @@ -15,6 +15,7 @@ import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; @@ -122,6 +123,10 @@ public class VTreeTable extends VScrollTable { @Override protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VTreeTableGeneratedRow(uidl, aligns2); + } return new VTreeTableRow(uidl, aligns2); } @@ -133,7 +138,7 @@ public class VTreeTable extends VScrollTable { private boolean open; private int depth; private boolean canHaveChildren; - private Widget widgetInHierarchyColumn; + protected Widget widgetInHierarchyColumn; public VTreeTableRow(UIDL uidl, char[] aligns2) { super(uidl, aligns2); @@ -149,7 +154,7 @@ public class VTreeTable extends VScrollTable { addTreeSpacer(rowUidl); } - private boolean addTreeSpacer(UIDL rowUidl) { + protected boolean addTreeSpacer(UIDL rowUidl) { if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { Element container = (Element) getElement().getLastChild() .getFirstChild(); @@ -265,6 +270,79 @@ public class VTreeTable extends VScrollTable { } + protected class VTreeTableGeneratedRow extends VTreeTableRow { + + private boolean spanColumns; + private boolean renderAsHtml; + + public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + @Override + protected void initCellWidths() { + if (!spanColumns) { + super.initCellWidths(); + } + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected boolean isRenderCellsAsHtml() { + return renderAsHtml; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + renderAsHtml = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", renderAsHtml, false, null, colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + addTreeSpacer(rowUidl); + } + } + private int getIdentWidth() { return identWidth; } diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 5f0fd07277..09625d88d6 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -109,7 +109,9 @@ public class Table extends AbstractSelect implements Action.Container, protected static final int CELL_ITEMID = 3; - protected static final int CELL_FIRSTCOL = 4; + protected static final int CELL_GENERATED_ROW = 4; + + protected static final int CELL_FIRSTCOL = 5; /** * Left column alignment. This is the default behaviour. @@ -400,6 +402,8 @@ public class Table extends AbstractSelect implements Action.Container, private boolean rowCacheInvalidated; + private RowGenerator rowGenerator = null; + /* Table constructors */ /** @@ -1533,61 +1537,79 @@ public class Table extends AbstractSelect implements Action.Container, cells[CELL_ICON][i] = getItemIcon(id); } + GeneratedRow generatedRow = rowGenerator != null ? rowGenerator + .generateRow(this, id) : null; + cells[CELL_GENERATED_ROW][i] = generatedRow; + for (int j = 0; j < cols; j++) { if (isColumnCollapsed(colids[j])) { continue; } Property p = null; Object value = ""; - boolean isGenerated = columnGenerators.containsKey(colids[j]); + boolean isGeneratedRow = generatedRow != null; + boolean isGeneratedColumn = columnGenerators + .containsKey(colids[j]); + boolean isGenerated = isGeneratedRow || isGeneratedColumn; if (!isGenerated) { p = getContainerProperty(id, colids[j]); } - // check in current pageBuffer already has row - int index = firstIndex + i; - if (p != null || isGenerated) { - if (index < firstIndexNotInCache - && index >= pageBufferFirstIndex) { - // we have data already in our cache, - // recycle it instead of fetching it via - // getValue/getPropertyValue + if (isGeneratedRow) { + if (generatedRow.isSpanColumns() && j > 0) { + value = null; + } else if (generatedRow.getText().length > j) { + value = generatedRow.getText()[j]; + } + } else { + // check in current pageBuffer already has row + int index = firstIndex + i; + if (p != null || isGenerated) { int indexInOldBuffer = index - pageBufferFirstIndex; - value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer]; - if (!isGenerated && iscomponent[j] - || !(value instanceof Component)) { - listenProperty(p, oldListenedProperties); - } - } else { - if (isGenerated) { - ColumnGenerator cg = columnGenerators - .get(colids[j]); - value = cg.generateCell(this, id, colids[j]); - if (value != null && !(value instanceof Component) - && !(value instanceof String)) { - // Avoid errors if a generator returns something - // other than a Component or a String - value = value.toString(); - } - } else if (iscomponent[j]) { - value = p.getValue(); - listenProperty(p, oldListenedProperties); - } else if (p != null) { - value = getPropertyValue(id, colids[j], p); - /* - * If returned value is Component (via fieldfactory - * or overridden getPropertyValue) we excpect it to - * listen property value changes. Otherwise if - * property emits value change events, table will - * start to listen them and refresh content when - * needed. - */ - if (!(value instanceof Component)) { + if (index < firstIndexNotInCache + && index >= pageBufferFirstIndex + && pageBuffer[CELL_GENERATED_ROW][indexInOldBuffer] == null) { + // we have data already in our cache, + // recycle it instead of fetching it via + // getValue/getPropertyValue + value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer]; + if (!isGeneratedColumn && iscomponent[j] + || !(value instanceof Component)) { listenProperty(p, oldListenedProperties); } } else { - value = getPropertyValue(id, colids[j], null); + if (isGeneratedColumn) { + ColumnGenerator cg = columnGenerators + .get(colids[j]); + value = cg.generateCell(this, id, colids[j]); + if (value != null + && !(value instanceof Component) + && !(value instanceof String)) { + // Avoid errors if a generator returns + // something + // other than a Component or a String + value = value.toString(); + } + } else if (iscomponent[j]) { + value = p.getValue(); + listenProperty(p, oldListenedProperties); + } else if (p != null) { + value = getPropertyValue(id, colids[j], p); + /* + * If returned value is Component (via + * fieldfactory or overridden getPropertyValue) + * we excpect it to listen property value + * changes. Otherwise if property emits value + * change events, table will start to listen + * them and refresh content when needed. + */ + if (!(value instanceof Component)) { + listenProperty(p, oldListenedProperties); + } + } else { + value = getPropertyValue(id, colids[j], null); + } } } } @@ -2919,6 +2941,7 @@ public class Table extends AbstractSelect implements Action.Container, paintRowIcon(target, cells, indexInRowbuffer); paintRowHeader(target, cells, indexInRowbuffer); + paintGeneratedRowInfo(target, cells, indexInRowbuffer); target.addAttribute("key", Integer.parseInt(cells[CELL_KEY][indexInRowbuffer].toString())); @@ -2959,6 +2982,15 @@ public class Table extends AbstractSelect implements Action.Container, paintRowAttributes(target, itemId); } + private void paintGeneratedRowInfo(PaintTarget target, Object[][] cells, + int indexInRowBuffer) throws PaintException { + GeneratedRow generatedRow = (GeneratedRow) cells[CELL_GENERATED_ROW][indexInRowBuffer]; + if (generatedRow != null) { + target.addAttribute("gen_html", generatedRow.isRenderAsHtml()); + target.addAttribute("gen_span", generatedRow.isSpanColumns()); + } + } + protected void paintRowHeader(PaintTarget target, Object[][] cells, int indexInRowbuffer) throws PaintException { if (rowHeadersAreEnabled()) { @@ -4604,4 +4636,114 @@ public class Table extends AbstractSelect implements Action.Container, return itemDescriptionGenerator; } + public interface RowGenerator { + /** + * Called for every row that is painted in the Table. Returning a + * GeneratedRow object will cause the row to be painted based on the + * contents of the GeneratedRow. A generated row is by default styled + * similarly to a header or footer row. + *

+ * The GeneratedRow data object contains the text that should be + * rendered in the row. The itemId in the container thus works only as a + * placeholder. + *

+ * If GeneratedRow.setSpanColumns(true) is used, there will be one + * String spanning all columns (use setText("Spanning text")). Otherwise + * you can define one String per visible column. + *

+ * If GeneratedRow.setRenderAsHtml(true) is used, the strings can + * contain HTML markup, otherwise all strings will be rendered as text + * (the default). + *

+ * A "v-table-generated-row" CSS class is added to all generated rows. + * For custom styling of a generated row you can combine a RowGenerator + * with a CellStyleGenerator. + *

+ * + * @param table + * The Table that is being painted + * @param itemId + * The itemId for the row + * @return A GeneratedRow describing how the row should be painted or + * null to paint the row with the contents from the container + */ + public GeneratedRow generateRow(Table table, Object itemId); + } + + public static class GeneratedRow { + private boolean renderAsHtml = false; + private boolean spanColumns = false; + private String[] text = null; + + /** + * Creates a new generated row. If only one string is passed in, columns + * are automatically spanned. + * + * @param text + */ + public GeneratedRow(String... text) { + setRenderAsHtml(false); + setSpanColumns(text.length == 1); + setText(text); + } + + /** + * Pass one String if spanColumns is used, one String for each visible + * column otherwise + */ + public void setText(String... text) { + this.text = text; + } + + protected String[] getText() { + return text; + } + + protected boolean isRenderAsHtml() { + return renderAsHtml; + } + + /** + * If set to true, all strings passed to {@link #setText(String...)} + * will be rendered as HTML. + * + * @param renderAsHtml + */ + public void setRenderAsHtml(boolean renderAsHtml) { + this.renderAsHtml = renderAsHtml; + } + + protected boolean isSpanColumns() { + return spanColumns; + } + + /** + * If set to true, only one string will be rendered, spanning the entire + * row. + * + * @param spanColumns + */ + public void setSpanColumns(boolean spanColumns) { + this.spanColumns = spanColumns; + } + } + + /** + * Assigns a row generator to the table. The row generator will be able to + * replace rows in the table when it is rendered. + * + * @param generator + * the new row generator + */ + public void setRowGenerator(RowGenerator generator) { + rowGenerator = generator; + refreshRenderedCells(); + } + + /** + * @return the current row generator + */ + public RowGenerator getRowGenerator() { + return rowGenerator; + } } diff --git a/tests/src/com/vaadin/tests/components/table/RowGenerators.java b/tests/src/com/vaadin/tests/components/table/RowGenerators.java new file mode 100644 index 0000000000..1ddb03c316 --- /dev/null +++ b/tests/src/com/vaadin/tests/components/table/RowGenerators.java @@ -0,0 +1,59 @@ +package com.vaadin.tests.components.table; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Table; +import com.vaadin.ui.Table.GeneratedRow; +import com.vaadin.ui.Table.RowGenerator; + +public class RowGenerators extends TestBase implements RowGenerator { + + @Override + protected void setup() { + Table table = new Table(); + table.setContainerDataSource(filledContainer()); + table.setRowGenerator(this); + addComponent(table); + } + + private Container filledContainer() { + IndexedContainer c = new IndexedContainer(); + c.addContainerProperty("Property 1", String.class, ""); + c.addContainerProperty("Property 2", String.class, ""); + c.addContainerProperty("Property 3", String.class, ""); + c.addContainerProperty("Property 4", String.class, ""); + for (int ix = 0; ix < 500; ix++) { + Item i = c.addItem(ix); + i.getItemProperty("Property 1").setValue("Item " + ix + ",1"); + i.getItemProperty("Property 2").setValue("Item " + ix + ",2"); + i.getItemProperty("Property 3").setValue("Item " + ix + ",3"); + i.getItemProperty("Property 4").setValue("Item " + ix + ",4"); + } + return c; + } + + public GeneratedRow generateRow(Table table, Object itemId) { + if ((Integer) itemId % 5 == 0) { + if ((Integer) itemId % 10 == 0) { + return new GeneratedRow( + "foobarbazoof very extremely long, most definitely will span."); + } else { + return new GeneratedRow("foo", "bar", "baz", "oof"); + } + } + return null; + } + + @Override + protected String getDescription() { + return "Row generators should replace every fifth row in the table"; + } + + @Override + protected Integer getTicketNumber() { + return 6720; + } + +} diff --git a/tests/src/com/vaadin/tests/components/table/TableGeneratedRows.html b/tests/src/com/vaadin/tests/components/table/TableGeneratedRows.html new file mode 100644 index 0000000000..50b91c53ba --- /dev/null +++ b/tests/src/com/vaadin/tests/components/table/TableGeneratedRows.html @@ -0,0 +1,107 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.components.table.Tables?restartApplication
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item046,13
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item842,5
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item682,3
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[2]/VMenuBar[0]#item156,4
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item029,3
screenCapturespanned
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item030,5
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item027,4
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item847,13
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item680,2
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[2]/VMenuBar[0]#item280,8
screenCapturenospan
mouseClickvaadin=runcomvaadintestscomponentstableTables::PID_Smenu#item036,12
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[0]/VMenuBar[0]#item847,5
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[1]/VMenuBar[0]#item695,7
mouseClickvaadin=runcomvaadintestscomponentstableTables::Root/VOverlay[2]/VMenuBar[0]#item367,12
screenCapturehtml
+ + diff --git a/tests/src/com/vaadin/tests/components/table/Tables.java b/tests/src/com/vaadin/tests/components/table/Tables.java index 1f55cf5389..621ef60abc 100644 --- a/tests/src/com/vaadin/tests/components/table/Tables.java +++ b/tests/src/com/vaadin/tests/components/table/Tables.java @@ -20,8 +20,10 @@ import com.vaadin.ui.Table.ColumnResizeEvent; import com.vaadin.ui.Table.ColumnResizeListener; import com.vaadin.ui.Table.FooterClickEvent; import com.vaadin.ui.Table.FooterClickListener; +import com.vaadin.ui.Table.GeneratedRow; import com.vaadin.ui.Table.HeaderClickEvent; import com.vaadin.ui.Table.HeaderClickListener; +import com.vaadin.ui.Table.RowGenerator; public class Tables extends AbstractSelectTestCase implements ItemClickListener, HeaderClickListener, FooterClickListener, @@ -308,6 +310,48 @@ public class Tables extends AbstractSelectTestCase } }; + private class GeneratedRowInfo { + + private final int nth; + private final String[] text; + private final boolean isHtml; + + public GeneratedRowInfo(int nth, boolean isHtml, String... text) { + this.nth = nth; + this.isHtml = isHtml; + this.text = text; + } + + public boolean appliesTo(Object itemId) { + int ix = Integer.valueOf(itemId.toString().substring(5)); + return ix % nth == 0; + } + } + + private Command rowGeneratorCommand = new Command() { + + public void execute(T c, final GeneratedRowInfo generatedRowInfo, + Object data) { + if (generatedRowInfo == null) { + c.setRowGenerator(null); + } else { + c.setRowGenerator(new RowGenerator() { + + public GeneratedRow generateRow(Table table, Object itemId) { + if (generatedRowInfo.appliesTo(itemId)) { + GeneratedRow generatedRow = new GeneratedRow( + generatedRowInfo.text); + generatedRow + .setRenderAsHtml(generatedRowInfo.isHtml); + return generatedRow; + } + return null; + } + }); + } + } + }; + private Command setSortEnabledCommand = new Command() { public void execute(T c, Boolean value, Object data) { @@ -348,6 +392,7 @@ public class Tables extends AbstractSelectTestCase createColumnHeaderMode(CATEGORY_FEATURES); createAddGeneratedColumnAction(CATEGORY_FEATURES); createCellStyleAction(CATEGORY_FEATURES); + createGeneratedRowAction(CATEGORY_FEATURES); createBooleanAction("Sort enabled", CATEGORY_FEATURES, true, setSortEnabledCommand); @@ -401,6 +446,26 @@ public class Tables extends AbstractSelectTestCase "None", cellStyleCommand, true); } + private void createGeneratedRowAction(String categoryFeatures) { + LinkedHashMap options = new LinkedHashMap(); + options.put("None", null); + options.put("Every fifth row, spanned", new GeneratedRowInfo(5, false, + "foobarbaz this is a long one that should span.")); + int props = getComponent().getContainerPropertyIds().size(); + String[] text = new String[props]; + for (int ix = 0; ix < props; ix++) { + text[ix] = "foo" + ix; + } + options.put("Every tenth row, no spanning", new GeneratedRowInfo(10, + false, text)); + options.put( + "Every eight row, spanned, html formatted", + new GeneratedRowInfo(8, true, + "foo bar baz")); + createSelectAction("Row generator", categoryFeatures, options, "None", + rowGeneratorCommand, true); + } + private void createColumnHeaderMode(String category) { LinkedHashMap columnHeaderModeOptions = new LinkedHashMap(); columnHeaderModeOptions.put("Hidden", Table.COLUMN_HEADER_MODE_HIDDEN); diff --git a/tests/src/com/vaadin/tests/components/treetable/TreeTableGeneratedRows.html b/tests/src/com/vaadin/tests/components/treetable/TreeTableGeneratedRows.html new file mode 100644 index 0000000000..6419a8c251 --- /dev/null +++ b/tests/src/com/vaadin/tests/components/treetable/TreeTableGeneratedRows.html @@ -0,0 +1,182 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.components.treetable.TreeTableTest?restartApplication=
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item030,7
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item536,4
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item175,6
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item1334,10
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item039,9
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item543,2
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item345,4
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item1041,8
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item010,9
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item235,7
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item232,8
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item354,7
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item020,4
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item842,5
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item692,5
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item165,8
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[4]/domChild[0]/domChild[0]/domChild[0]12,8
screenCapturespanned
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[4]/domChild[0]/domChild[0]/domChild[0]12,4
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item022,2
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item844,0
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item687,3
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item268,12
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[9]/domChild[0]/domChild[0]/domChild[0]11,6
screenCapturenospan
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[9]/domChild[0]/domChild[0]/domChild[0]11,6
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_Smenu#item07,12
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[0]/VMenuBar[0]#item824,1
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[1]/VMenuBar[0]#item693,8
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::Root/VOverlay[2]/VMenuBar[0]#item361,6
mouseClickvaadin=runcomvaadintestscomponentstreetableTreeTableTest::PID_StestComponent/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[7]/domChild[0]/domChild[0]/domChild[0]12,7
screenCapturehtml
+ + -- 2.39.5