blob: 0e4fde0f8b8d9be64003841a9884a23c9b2dbb34 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* 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.google.gwt.user.cellview.client;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.HasCell;
import com.google.gwt.dom.builder.shared.ElementBuilderBase;
import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
import com.google.gwt.dom.builder.shared.HtmlTableSectionBuilder;
import com.google.gwt.dom.builder.shared.TableRowBuilder;
import com.google.gwt.dom.builder.shared.TableSectionBuilder;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Builder used to construct a CellTable.
*
* @param <T> the row data type
*/
public abstract class AbstractCellTableBuilder<T> implements CellTableBuilder<T> {
/**
* The attribute used to indicate that an element contains a cell.
*/
private static final String CELL_ATTRIBUTE = "__gwt_cell";
/**
* The attribute used to specify the logical row index.
*/
private static final String ROW_ATTRIBUTE = "__gwt_row";
/**
* The attribute used to specify the subrow within a logical row value.
*/
private static final String SUBROW_ATTRIBUTE = "__gwt_subrow";
protected final AbstractCellTable<T> cellTable;
/**
* A mapping of unique cell IDs to the cell.
*/
private final Map<String, HasCell<T, ?>> idToCellMap = new HashMap<String, HasCell<T, ?>>();
private final Map<HasCell<T, ?>, String> cellToIdMap = new HashMap<HasCell<T, ?>, String>();
private HtmlTableSectionBuilder tbody;
private int rowIndex;
private int subrowIndex;
private Object rowValueKey;
/**
* Construct a new table builder.
*
* @param cellTable the table this builder will build rows for
*/
public AbstractCellTableBuilder(AbstractCellTable<T> cellTable) {
this.cellTable = cellTable;
}
/**
* Build zero or more table rows for the specified row value.
*
* @param rowValue the value for the row to render
* @param absRowIndex the absolute row index
*/
@Override
public final void buildRow(T rowValue, int absRowIndex) {
setRowInfo(absRowIndex, rowValue);
buildRowImpl(rowValue, absRowIndex);
}
/**
* Create the context for a column based on the current table building state.
*
* @param column the column id
* @return the context that contains the column index, row/subrow indexes, and the row value key
*/
public final Context createContext(int column) {
return new Context(rowIndex, column, rowValueKey, subrowIndex);
}
/**
* Finish the building and get the {@link TableSectionBuilder} containing the children.
*/
@Override
public final TableSectionBuilder finish() {
// End dangling elements.
while (tbody.getDepth() > 0) {
tbody.endTBody();
}
return tbody;
}
/**
* Return the column containing an element.
*
* @param context the context for the element
* @param rowValue the value for the row corresponding to the element
* @param elem the element that the column contains
* @return the immediate column containing the element
*/
@Override
public final HasCell<T, ?> getColumn(Context context, T rowValue, Element elem) {
return getColumn(elem);
}
/**
* Return all the columns that this table builder has renderred.
*/
@Override
public final Collection<HasCell<T, ?>> getColumns() {
return idToCellMap.values();
}
/**
* Get the index of the row value from the associated {@link TableRowElement}.
*
* @param row the row element
* @return the row value index
*/
@Override
public final int getRowValueIndex(TableRowElement row) {
try {
return Integer.parseInt(row.getAttribute(ROW_ATTRIBUTE));
} catch (NumberFormatException e) {
// The attribute doesn't exist. Maybe the user is overriding
// renderRowValues().
return row.getSectionRowIndex() + cellTable.getPageStart();
}
}
/**
* Get the index of the subrow value from the associated
* {@link TableRowElement}. The sub row value starts at 0 for the first row
* that represents a row value.
*
* @param row the row element
* @return the subrow value index, or 0 if not found
*/
@Override
public final int getSubrowValueIndex(TableRowElement row) {
try {
return Integer.parseInt(row.getAttribute(SUBROW_ATTRIBUTE));
} catch (NumberFormatException e) {
// The attribute doesn't exist. Maybe the user is overriding
// renderRowValues() in {@link AbstractCellTable}.
return 0;
}
}
/**
* Return if an element contains a cell. This may be faster to execute than {@link getColumn}.
*
* @param elem the element of interest
*/
@Override
public final boolean isColumn(Element elem) {
return getCellId(elem) != null;
}
/**
* Render the cell into an {@link ElementBuilderBase}.
*
* @param builder the {@link ElementBuilderBase} that cell contents append to
* @param context the context for the element
* @param column the column containing the cell
* @param rowValue the value for the row corresponding to the element
*/
public final <C> void renderCell(ElementBuilderBase<?> builder, Context context,
HasCell<T, C> column, T rowValue) {
// Generate a unique ID for the cell.
String cellId = cellToIdMap.get(column);
if (cellId == null) {
cellId = "cell-" + Document.get().createUniqueId();
idToCellMap.put(cellId, column);
cellToIdMap.put(column, cellId);
}
builder.attribute(CELL_ATTRIBUTE, cellId);
// Render the cell into the builder.
SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
if (column instanceof Column) {
/*
* If the HasCell is a Column, let it render the Cell itself. This is
* here for legacy support.
*/
Column<T, C> theColumn = (Column<T, C>) column;
theColumn.render(context, rowValue, cellBuilder);
} else {
column.getCell().render(context, column.getValue(rowValue), cellBuilder);
}
builder.html(cellBuilder.toSafeHtml());
}
/**
*
*/
/**
* Start building rows. Reset the internal table section builder. If the table builder is going
* to re-build all rows, the internal the maps associating the cells and ids will be cleared.
*
* @param isRebuildingAllRows is this start intended for rebuilding all rows
*/
@Override
public final void start(boolean isRebuildingAllRows) {
/*
* TODO(jlabanca): Test with DomBuilder.
*
* DOM manipulation is sometimes faster than String concatenation and
* innerHTML, but not when mixing the two. Cells render as HTML strings,
* so its faster to render the entire table as a string.
*/
tbody = HtmlBuilderFactory.get().createTBodyBuilder();
if (isRebuildingAllRows) {
cellToIdMap.clear();
idToCellMap.clear();
}
}
/**
* Start a row and return the {@link TableRowBuilder} for this row.
*/
public final TableRowBuilder startRow() {
// End any dangling rows.
while (tbody.getDepth() > 1) {
tbody.end();
}
// Verify the depth.
if (tbody.getDepth() < 1) {
throw new IllegalStateException(
"Cannot start a row. Did you call TableRowBuilder.end() too many times?");
}
// Start the next row.
TableRowBuilder row = tbody.startTR();
row.attribute(ROW_ATTRIBUTE, rowIndex);
row.attribute(SUBROW_ATTRIBUTE, subrowIndex);
subrowIndex++;
return row;
}
/**
* Build zero or more table rows for the specified row value.
*
* @param rowValue the value for the row to render
* @param absRowIndex the absolute row index
*/
protected abstract void buildRowImpl(T rowValue, int absRowIndex);
/**
* Check if an element is the parent of a rendered cell.
*
* @param elem the element to check
* @return the cellId if a cell parent, null if not
*/
private String getCellId(Element elem) {
if (elem == null) {
return null;
}
String cellId = elem.getAttribute(CELL_ATTRIBUTE);
return (cellId == null) || (cellId.length() == 0) ? null : cellId;
}
/**
* Return the column containing an element.
*
* @param elem the elm that the column contains
* @return the column containing the element.
*/
private HasCell<T, ?> getColumn(Element elem) {
String cellId = getCellId(elem);
return (cellId == null) ? null : idToCellMap.get(cellId);
}
/**
* Set the information for the current row to build.
*
* @param rowIndex the index of the row
* @param rowValue the value of this row
*/
private void setRowInfo(int rowIndex, T rowValue) {
this.rowIndex = rowIndex;
this.rowValueKey = cellTable.getValueKey(rowValue);
this.subrowIndex = 0; // Reset the subrow.
}
}