aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2013-11-25 12:32:52 +0200
committerVaadin Code Review <review@vaadin.com>2013-11-27 11:57:58 +0000
commit42461bfebfe43d1f24948d72f29c1dd38a8d7d1a (patch)
tree685232693d911336fb490cb7fd2895faf70bfb27
parent27959ff3e6095eae3977fab384845293b2903a59 (diff)
downloadvaadin-framework-42461bfebfe43d1f24948d72f29c1dd38a8d7d1a.tar.gz
vaadin-framework-42461bfebfe43d1f24948d72f29c1dd38a8d7d1a.zip
Add support for colspan in Escalator (#12645)
Change-Id: I252fe949537154bce7e09d42f434bce20ec28928
-rw-r--r--client/src/com/vaadin/client/ui/grid/Cell.java20
-rw-r--r--client/src/com/vaadin/client/ui/grid/Escalator.java29
-rw-r--r--client/src/com/vaadin/client/ui/grid/EscalatorUpdater.java6
-rw-r--r--client/src/com/vaadin/client/ui/grid/FlyweightCell.java93
-rw-r--r--client/src/com/vaadin/client/ui/grid/FlyweightRow.java82
-rw-r--r--client/src/com/vaadin/client/ui/grid/Grid.java6
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/grid/VTestGrid.java18
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 &lt; 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);
}