| /* |
| * 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. |
| } |
| } |