diff options
author | Henrik Paul <henrik@vaadin.com> | 2013-11-25 12:32:52 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-11-27 11:57:58 +0000 |
commit | 42461bfebfe43d1f24948d72f29c1dd38a8d7d1a (patch) | |
tree | 685232693d911336fb490cb7fd2895faf70bfb27 | |
parent | 27959ff3e6095eae3977fab384845293b2903a59 (diff) | |
download | vaadin-framework-42461bfebfe43d1f24948d72f29c1dd38a8d7d1a.tar.gz vaadin-framework-42461bfebfe43d1f24948d72f29c1dd38a8d7d1a.zip |
Add support for colspan in Escalator (#12645)
Change-Id: I252fe949537154bce7e09d42f434bce20ec28928
7 files changed, 236 insertions, 18 deletions
diff --git a/client/src/com/vaadin/client/ui/grid/Cell.java b/client/src/com/vaadin/client/ui/grid/Cell.java index b06ad582b2..09bc5da344 100644 --- a/client/src/com/vaadin/client/ui/grid/Cell.java +++ b/client/src/com/vaadin/client/ui/grid/Cell.java @@ -47,9 +47,27 @@ public interface Cell { * update the class names of the element, add inline styles and freely * modify the contents. * <p> - * Avoid modifying the dimensions or positioning of the cell element. + * Avoid modifying the dimensions, positioning or colspan of the cell + * element. * * @return The root element for this cell. Never <code>null</code>. */ public Element getElement(); + + /** + * Sets the column span of the cell. + * <p> + * This will overwrite any possible "colspan" attribute in the current + * element (i.e. the object returned by {@link #getElement()}). This will + * also handle internal bookkeeping, skip the rendering of any affected + * adjacent cells, and make sure that the current cell's dimensions are + * handled correctly. + * + * @param numberOfCells + * the number of cells to span to the right, or <code>1</code> to + * unset any column spans + * @throws IllegalArgumentException + * if <code>numberOfCells < 1</code> + */ + public void setColSpan(int numberOfCells) throws IllegalArgumentException; }
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java index 431b8715ad..2f5f28871b 100644 --- a/client/src/com/vaadin/client/ui/grid/Escalator.java +++ b/client/src/com/vaadin/client/ui/grid/Escalator.java @@ -520,7 +520,7 @@ public class Escalator extends Widget { } private static final int ROW_HEIGHT_PX = 20; - private static final int COLUMN_WIDTH_PX = 100; + static final int COLUMN_WIDTH_PX = 100; /** An inner class that handles all logic related to scrolling. */ private class Scroller extends JsniWorkaround { @@ -1257,12 +1257,21 @@ public class Escalator extends Widget { // this needs to be after the scroll position adjustment above. scroller.recalculateScrollbarsForVirtualViewport(); + /* + * Because we might remove columns where affected by colspans, it's + * easiest to simply redraw everything when columns are modified. + * + * Yes, this is a TODO [[optimize]]. + */ + if (getRowCount() > 0 + && getColumnConfiguration().getColumnCount() > 0) { + refreshRows(0, getRowCount()); + } } protected void paintInsertColumns(final int offset, final int numberOfColumns, boolean frozen) { final NodeList<Node> childNodes = root.getChildNodes(); - final int topVisualRowLogicalIndex = getTopVisualRowLogicalIndex(); for (int row = 0; row < childNodes.getLength(); row++) { final Element tr = getTrByVisualIndex(row); @@ -1280,8 +1289,6 @@ public class Escalator extends Widget { cellElem, referenceCell); } - refreshRow(tr, topVisualRowLogicalIndex + row); - /* * TODO [[optimize]] [[colwidth]]: When this method is updated * to measure things instead of using hardcoded values, it would @@ -1308,6 +1315,17 @@ public class Escalator extends Widget { .setScrollPos((int) (scroller.lastScrollLeft + numberOfColumns * COLUMN_WIDTH_PX)); } + + /* + * Because we might insert columns where affected by colspans, it's + * easiest to simply redraw everything when columns are modified. + * + * Yes, this is a TODO [[optimize]]. + */ + if (getRowCount() > 0 + && getColumnConfiguration().getColumnCount() > 1) { + refreshRows(0, getRowCount()); + } } public void setColumnFrozen(int column, boolean frozen) { @@ -2216,6 +2234,9 @@ public class Escalator extends Widget { private Range convertToVisual(final Range logicalRange) { if (logicalRange.isEmpty()) { return logicalRange; + } else if (visualRowOrder.isEmpty()) { + // empty range + return Range.withLength(0, 0); } /* diff --git a/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java b/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java index 9a48dbffd8..283517fcc4 100644 --- a/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java +++ b/client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java @@ -16,7 +16,6 @@ package com.vaadin.client.ui.grid; -import java.util.List; /** * A functional interface that allows client code to define how a certain row in @@ -37,7 +36,8 @@ public interface EscalatorUpdater { /** An {@link EscalatorUpdater} that doesn't render anything. */ public static final EscalatorUpdater NULL = new EscalatorUpdater() { @Override - public void updateCells(final Row row, final List<Cell> cellsToUpdate) { + public void updateCells(final Row row, + final Iterable<Cell> cellsToUpdate) { // NOOP } }; @@ -62,5 +62,5 @@ public interface EscalatorUpdater { * You should neither store nor reuse the reference to the list, * nor to the individual cells */ - public void updateCells(Row row, List<Cell> cellsToUpdate); + public void updateCells(Row row, Iterable<Cell> cellsToUpdate); } diff --git a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java b/client/src/com/vaadin/client/ui/grid/FlyweightCell.java index 88f05fafe5..08c27fa859 100644 --- a/client/src/com/vaadin/client/ui/grid/FlyweightCell.java +++ b/client/src/com/vaadin/client/ui/grid/FlyweightCell.java @@ -15,7 +15,12 @@ */ package com.vaadin.client.ui.grid; +import java.util.List; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.Element; +import com.vaadin.client.ui.grid.FlyweightRow.CellIterator; /** * An internal implementation of the {@link Cell} interface. @@ -30,8 +35,11 @@ import com.google.gwt.user.client.Element; * @see FlyweightRow#removeCells(int, int) */ class FlyweightCell implements Cell { + private static final String COLSPAN_ATTR = "colSpan"; + private final int column; private final FlyweightRow row; + private CellIterator currentIterator = null; public FlyweightCell(final FlyweightRow row, final int column) { this.row = row; @@ -40,11 +48,13 @@ class FlyweightCell implements Cell { @Override public int getRow() { + assertSetup(); return row.getRow(); } @Override public int getColumn() { + assertSetup(); return column; } @@ -53,4 +63,87 @@ class FlyweightCell implements Cell { return (Element) row.getElement().getChild(column); } + void setup(final CellIterator cellIterator) { + currentIterator = cellIterator; + + final Element e = getElement(); + e.setPropertyInt(COLSPAN_ATTR, 1); + e.getStyle().setWidth(Escalator.COLUMN_WIDTH_PX, Unit.PX); + e.getStyle().clearDisplay(); + } + + /** + * Tear down the state of the Cell. + * <p> + * This is an internal check method, to prevent retrieving uninitialized + * data by calling {@link #getRow()}, {@link #getColumn()} or + * {@link #getElement()} at an improper time. + * <p> + * This should only be used with asserts (" + * <code>assert flyweightCell.teardown()</code> ") so that the code is never + * run when asserts aren't enabled. + * + * @return always <code>true</code> + * @see FlyweightRow#teardown() + */ + boolean teardown() { + currentIterator = null; + return true; + } + + /** + * Asserts that the flyweight cell has properly been set up before trying to + * access any of its data. + */ + private void assertSetup() { + assert currentIterator != null : "FlyweightCell was not properly " + + "initialized. This is either a bug in Grid/Escalator " + + "or a Cell reference has been stored and reused " + + "inappropriately."; + } + + @Override + public void setColSpan(final int numberOfCells) { + /*- + * This will default to 1 if unset, as per DOM specifications: + * http://www.w3.org/TR/html5/tabular-data.html#attributes-common-to-td-and-th-elements + */ + final int prevColSpan = getElement().getPropertyInt(COLSPAN_ATTR); + if (numberOfCells == 1 && prevColSpan == 1) { + return; + } + + getElement().setPropertyInt(COLSPAN_ATTR, numberOfCells); + adjustCellWidthForSpan(numberOfCells); + hideOrRevealAdjacentCellElements(numberOfCells, prevColSpan); + currentIterator.setSkipNext(numberOfCells - 1); + } + + private void adjustCellWidthForSpan(final int numberOfCells) { + final List<FlyweightCell> cellsToTheRight = currentIterator + .rawPeekNext(numberOfCells - 1); + final int widthsOfColumnsToTheRight = cellsToTheRight.size() + * Escalator.COLUMN_WIDTH_PX; + final int selfWidth = Escalator.COLUMN_WIDTH_PX; + getElement().getStyle().setWidth(selfWidth + widthsOfColumnsToTheRight, + Unit.PX); + } + + private void hideOrRevealAdjacentCellElements(final int numberOfCells, + final int prevColSpan) { + final int affectedCellsNumber = Math.max(prevColSpan, numberOfCells); + final List<FlyweightCell> affectedCells = currentIterator + .rawPeekNext(affectedCellsNumber - 1); + if (prevColSpan < numberOfCells) { + for (int i = 0; i < affectedCells.size(); i++) { + affectedCells.get(prevColSpan + i - 1).getElement().getStyle() + .setDisplay(Display.NONE); + } + } else if (prevColSpan > numberOfCells) { + for (int i = 0; i < affectedCells.size(); i++) { + affectedCells.get(numberOfCells + i - 1).getElement() + .getStyle().clearDisplay(); + } + } + } } diff --git a/client/src/com/vaadin/client/ui/grid/FlyweightRow.java b/client/src/com/vaadin/client/ui/grid/FlyweightRow.java index dae2758527..fc1c977052 100644 --- a/client/src/com/vaadin/client/ui/grid/FlyweightRow.java +++ b/client/src/com/vaadin/client/ui/grid/FlyweightRow.java @@ -16,7 +16,8 @@ package com.vaadin.client.ui.grid; import java.util.ArrayList; -import java.util.Collections; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import com.google.gwt.dom.client.Node; @@ -33,12 +34,77 @@ import com.google.gwt.user.client.Element; * @see Escalator.AbstractRowContainer#refreshRow(Node, int) */ class FlyweightRow implements Row { + + static class CellIterator implements Iterator<Cell> { + /** A defensive copy of the cells in the current row. */ + private final ArrayList<FlyweightCell> cells; + private int cursor = 0; + private int skipNext = 0; + + public CellIterator(final Collection<FlyweightCell> cells) { + this.cells = new ArrayList<FlyweightCell>(cells); + } + + @Override + public boolean hasNext() { + return cursor + skipNext < cells.size(); + } + + @Override + public FlyweightCell next() { + // if we needed to skip some cells since the last invocation. + for (int i = 0; i < skipNext; i++) { + cells.remove(cursor); + } + skipNext = 0; + + final FlyweightCell cell = cells.get(cursor++); + cell.setup(this); + return cell; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove cells via iterator"); + } + + /** + * Sets the number of cells to skip when {@link #next()} is called the + * next time. Cell hiding is also handled eagerly in this method. + * + * @param colspan + * the number of cells to skip on next invocation of + * {@link #next()} + */ + public void setSkipNext(final int colspan) { + assert colspan > 0 : "Number of cells didn't make sense: " + + colspan; + skipNext = colspan; + } + + /** + * Gets the next <code>n</code> cells in the iterator, ignoring any + * possibly spanned cells. + * + * @param n + * the number of next cells to retrieve + * @return A list of next <code>n</code> cells, or less if there aren't + * enough cells to retrieve + */ + public List<FlyweightCell> rawPeekNext(final int n) { + final int from = Math.min(cursor, cells.size()); + final int to = Math.min(cursor + n, cells.size()); + return cells.subList(from, to); + } + } + private static final int BLANK = Integer.MIN_VALUE; private int row; private Element element; private final Escalator escalator; - private final List<Cell> cells = new ArrayList<Cell>(); + private final List<FlyweightCell> cells = new ArrayList<FlyweightCell>(); public FlyweightRow(final Escalator escalator) { this.escalator = escalator; @@ -70,6 +136,9 @@ class FlyweightRow implements Row { boolean teardown() { element = null; row = BLANK; + for (final FlyweightCell cell : cells) { + assert cell.teardown(); + } return true; } @@ -116,9 +185,14 @@ class FlyweightRow implements Row { * @see #setup(Element, int) * @see #teardown() */ - List<Cell> getCells() { + Iterable<Cell> getCells() { assertSetup(); - return Collections.unmodifiableList(cells); + return new Iterable<Cell>() { + @Override + public Iterator<Cell> iterator() { + return new CellIterator(cells); + } + }; } /** diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index 90c8b60474..66f37c7620 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -304,7 +304,7 @@ public class Grid<T> extends Composite { public abstract boolean firstRowIsVisible(); @Override - public void updateCells(Row row, List<Cell> cellsToUpdate) { + public void updateCells(Row row, Iterable<Cell> cellsToUpdate) { int rowIndex; if (inverted) { @@ -423,7 +423,7 @@ public class Grid<T> extends Composite { return new EscalatorUpdater() { @Override - public void updateCells(Row row, List<Cell> cellsToUpdate) { + public void updateCells(Row row, Iterable<Cell> cellsToUpdate) { int rowIndex = row.getRow(); if (dataSource == null) { setCellsLoading(cellsToUpdate); @@ -443,7 +443,7 @@ public class Grid<T> extends Composite { } } - private void setCellsLoading(List<Cell> cellsToUpdate) { + private void setCellsLoading(Iterable<Cell> cellsToUpdate) { for (Cell cell : cellsToUpdate) { cell.getElement().setInnerText("..."); } diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java index e9ee461fb9..18732549c0 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java @@ -42,8 +42,12 @@ public class VTestGrid extends Composite { return new EscalatorUpdater() { @Override public void updateCells(final Row row, - final List<Cell> cellsToUpdate) { + final Iterable<Cell> cellsToUpdate) { for (final Cell cell : cellsToUpdate) { + if (cell.getColumn() % 3 == 0) { + cell.setColSpan(2); + } + final Integer columnName = columns .get(cell.getColumn()); cell.getElement().setInnerText("Header " + columnName); @@ -56,8 +60,12 @@ public class VTestGrid extends Composite { return new EscalatorUpdater() { @Override public void updateCells(final Row row, - final List<Cell> cellsToUpdate) { + final Iterable<Cell> cellsToUpdate) { for (final Cell cell : cellsToUpdate) { + if (cell.getColumn() % 3 == 1) { + cell.setColSpan(2); + } + final Integer columnName = columns .get(cell.getColumn()); cell.getElement().setInnerText("Footer " + columnName); @@ -83,6 +91,10 @@ public class VTestGrid extends Composite { "Row " + cell.getRow() + ": " + cellInfo); } + if (cell.getColumn() % 3 == cell.getRow() % 3) { + cell.setColSpan(3); + } + final double c = i * .1; final int r = (int) ((Math.cos(c) + 1) * 128); final int g = (int) ((Math.cos(c / Math.PI) + 1) * 128); @@ -102,7 +114,7 @@ public class VTestGrid extends Composite { @Override public void updateCells(final Row row, - final List<Cell> cellsToUpdate) { + final Iterable<Cell> cellsToUpdate) { for (final Cell cell : cellsToUpdate) { renderCell(cell); } |