final NodeList<Node> childNodes = root.getChildNodes();
for (int row = 0; row < childNodes.getLength(); row++) {
- final int rowHeight = getDefaultRowHeight();
final Element tr = getTrByVisualIndex(row);
-
- Node referenceCell;
- if (offset != 0) {
- referenceCell = tr.getChild(offset - 1);
- } else {
- referenceCell = null;
- }
-
- for (int col = offset; col < offset + numberOfColumns; col++) {
- final int colWidth = columnConfiguration
- .getColumnWidthActual(col);
- final Element cellElem = createCellElement(rowHeight,
- colWidth);
- referenceCell = insertAfterReferenceAndUpdateIt(tr,
- cellElem, referenceCell);
- }
+ paintInsertCells(tr, row, offset, numberOfColumns);
}
reapplyRowWidths();
}
}
+ /**
+ * Inserts new cell elements into a single row element, invoking
+ * {@link #getEscalatorUpdater()}
+ * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and
+ * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before
+ * and after inserting the cells, respectively.
+ * <p>
+ * Precondition: The row must be already attached to the DOM and the
+ * FlyweightCell instances corresponding to the new columns added to
+ * {@code flyweightRow}.
+ *
+ * @param tr
+ * the row in which to insert the cells
+ * @param logicalRowIndex
+ * the index of the row
+ * @param offset
+ * the index of the first cell
+ * @param numberOfCells
+ * the number of cells to insert
+ */
+ private void paintInsertCells(final Element tr, int logicalRowIndex,
+ final int offset, final int numberOfCells) {
+
+ assert Document.get().isOrHasChild(tr) : "The row must be attached to the document";
+
+ flyweightRow.setup(tr, logicalRowIndex,
+ columnConfiguration.getCalculatedColumnWidths());
+
+ Iterable<FlyweightCell> cells = flyweightRow.getUninitializedCells(
+ offset, numberOfCells);
+
+ final int rowHeight = getDefaultRowHeight();
+ for (FlyweightCell cell : cells) {
+ final int colWidth = columnConfiguration
+ .getColumnWidthActual(cell.getColumn());
+ final Element cellElem = createCellElement(rowHeight, colWidth);
+ cell.setElement(cellElem);
+ }
+
+ getEscalatorUpdater().preAttach(flyweightRow, cells);
+
+ Node referenceCell;
+ if (offset != 0) {
+ referenceCell = tr.getChild(offset - 1);
+ } else {
+ referenceCell = null;
+ }
+ for (FlyweightCell cell : cells) {
+ referenceCell = insertAfterReferenceAndUpdateIt(tr,
+ cell.getElement(), referenceCell);
+ }
+
+ getEscalatorUpdater().postAttach(flyweightRow, cells);
+
+ assert flyweightRow.teardown();
+ }
+
public void setColumnFrozen(int column, boolean frozen) {
final NodeList<Node> childNodes = root.getChildNodes();
package com.vaadin.client.ui.grid;
/**
- * A functional interface that allows client code to define how a certain row in
- * Escalator will be displayed. The contents of an escalator's header, body and
- * footer are rendered by their respective updaters.
+ * An interface that allows client code to define how a certain row in Escalator
+ * will be displayed. The contents of an escalator's header, body and footer are
+ * rendered by their respective updaters.
* <p>
* The updater is responsible for internally handling all remote communication,
* should the displayed data need to be fetched remotely.
private final int column;
private final FlyweightRow row;
+ private Element element = null;
private CellIterator currentIterator = null;
private final Escalator escalator;
* or a <code>TH</code> element.
*/
public Element getElement() {
- return (Element) row.getElement().getChild(column);
+ return element;
}
- void setup(final CellIterator cellIterator) {
- currentIterator = cellIterator;
+ /**
+ * Sets the DOM element for this FlyweightCell, either a <code>TD</code> or
+ * a <code>TH</code>. This method should only be called when
+ * {@code getElement() == null}. It is the caller's responsibility to
+ * actually insert the given element to the document when needed.
+ *
+ * @param element
+ * the element corresponding to this FlyweightCell
+ */
+ void setElement(Element element) {
+ assert element != null;
+ // When asserts are enabled, teardown() resets the element to null
+ // so this won't fire simply due to cell reuse
+ assert this.element == null : "Cell element can only be set once";
+ this.element = element;
+ }
+
+ void setup(final CellIterator iterator) {
+ currentIterator = iterator;
- final Element e = getElement();
- e.setPropertyInt(COLSPAN_ATTR, 1);
- e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX);
- e.getStyle().clearDisplay();
+ if (iterator.areCellsInitialized()) {
+ final Element e = (Element) row.getElement().getChild(column);
+ e.setPropertyInt(COLSPAN_ATTR, 1);
+ e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX);
+ e.getStyle().clearDisplay();
+ setElement(e);
+ }
}
/**
*/
boolean teardown() {
currentIterator = null;
+ element = null;
return true;
}
static class CellIterator implements Iterator<FlyweightCell> {
/** A defensive copy of the cells in the current row. */
private final ArrayList<FlyweightCell> cells;
+ private final boolean initialized;
private int cursor = 0;
private int skipNext = 0;
- public CellIterator(final Collection<FlyweightCell> cells) {
+ /**
+ * Creates a new iterator of initialized flyweight cells. A cell is
+ * initialized if it has a corresponding
+ * {@link FlyweightCell#getElement() DOM element} attached to the row
+ * element.
+ *
+ * @param cells
+ * the collection of cells to iterate
+ */
+ public static CellIterator initialized(
+ final Collection<FlyweightCell> cells) {
+ return new CellIterator(cells, true);
+ }
+
+ /**
+ * Creates a new iterator of uninitialized flyweight cells. A cell is
+ * uninitialized if it does not have a corresponding
+ * {@link FlyweightCell#getElement() DOM element} attached to the row
+ * element.
+ *
+ * @param cells
+ * the collection of cells to iterate
+ */
+ public static CellIterator uninitialized(
+ final Collection<FlyweightCell> cells) {
+ return new CellIterator(cells, false);
+ }
+
+ private CellIterator(final Collection<FlyweightCell> cells,
+ final boolean initialized) {
this.cells = new ArrayList<FlyweightCell>(cells);
+ this.initialized = initialized;
}
@Override
final int to = Math.min(cursor + n, cells.size());
return cells.subList(from, to);
}
+
+ public boolean areCellsInitialized() {
+ return initialized;
+ }
}
private static final int BLANK = Integer.MIN_VALUE;
}
/**
- * Get flyweight cells for the client code to render.
+ * Returns flyweight cells for the client code to render.
+ *
+ * @return an iterable of flyweight cells
*
- * @return a list of {@link FlyweightCell FlyweightCells}. They are
- * generified into {@link Cell Cells}, because Java's generics
- * system isn't expressive enough.
* @see #setup(Element, int, int[])
* @see #teardown()
*/
return new Iterable<FlyweightCell>() {
@Override
public Iterator<FlyweightCell> iterator() {
- return new CellIterator(cells);
+ return CellIterator.initialized(cells);
+ }
+ };
+ }
+
+ /**
+ * Returns a subsequence of uninitialized flyweight cells. Uninitialized
+ * cells do not have {@link FlyweightCell#getElement() elements} associated.
+ * Note that FlyweightRow does not keep track of whether cells in actuality
+ * have corresponding DOM elements or not; it is the caller's responsibility
+ * to invoke this method with correct parameters.
+ * <p>
+ * Precondition: the range [offset, offset + numberOfCells) must be valid
+ *
+ * @param offset
+ * the index of the first cell to return
+ * @param numberOfCells
+ * the number of cells to return
+ * @return an iterable of flyweight cells
+ */
+ Iterable<FlyweightCell> getUninitializedCells(final int offset,
+ final int numberOfCells) {
+ assertSetup();
+ assert offset >= 0 && offset + numberOfCells <= cells.size() : "Invalid range of cells";
+ return new Iterable<FlyweightCell>() {
+ @Override
+ public Iterator<FlyweightCell> iterator() {
+ return CellIterator.uninitialized(cells.subList(offset, offset
+ + numberOfCells));
}
};
}