blob: 65c38536e1f8065220433341b8452bbc410a7e55 [file] [log] [blame]
/*
* Copyright 2007 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.client.ui;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.impl.ElementMapperImpl;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* HTMLTable contains the common table algorithms for
* {@link com.google.gwt.user.client.ui.Grid} and
* {@link com.google.gwt.user.client.ui.FlexTable}.
* <p>
* <img class='gallery' src='doc-files/Table.png'/>
* </p>
*/
@SuppressWarnings("deprecation")
public abstract class HTMLTable extends Panel implements SourcesTableEvents,
HasClickHandlers, HasDoubleClickHandlers {
/**
* Return value for {@link HTMLTable#getCellForEvent}.
*/
public class Cell {
private final int rowIndex;
private final int cellIndex;
/**
* Creates a cell.
*
* @param rowIndex the cell's row
* @param cellIndex the cell's index
*/
protected Cell(int rowIndex, int cellIndex) {
this.cellIndex = cellIndex;
this.rowIndex = rowIndex;
}
/**
* Gets the cell index.
*
* @return the cell index
*/
public int getCellIndex() {
return cellIndex;
}
/**
* Gets the cell's element.
*
* @return the cell's element.
*/
public Element getElement() {
return getCellFormatter().getElement(rowIndex, cellIndex);
}
/**
* Get row index.
*
* @return the row index
*/
public int getRowIndex() {
return rowIndex;
}
}
/**
* This class contains methods used to format a table's cells.
*/
public class CellFormatter {
/**
* Adds a style to the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @param styleName the style name to be added
* @see UIObject#addStyleName(String)
*/
public void addStyleName(int row, int column, String styleName) {
prepareCell(row, column);
Element td = getCellElement(bodyElem, row, column);
UIObject.setStyleName(td, styleName, true);
}
/**
* Gets the TD element representing the specified cell.
*
* @param row the row of the cell to be retrieved
* @param column the column of the cell to be retrieved
* @return the column's TD element
* @throws IndexOutOfBoundsException
*/
public Element getElement(int row, int column) {
checkCellBounds(row, column);
return getCellElement(bodyElem, row, column);
}
/**
* Gets the style of a specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @see UIObject#getStyleName()
* @return returns the style name
* @throws IndexOutOfBoundsException
*/
public String getStyleName(int row, int column) {
return UIObject.getStyleName(getElement(row, column));
}
/**
* Gets the primary style of a specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @see UIObject#getStylePrimaryName()
* @return returns the style name
* @throws IndexOutOfBoundsException
*/
public String getStylePrimaryName(int row, int column) {
return UIObject.getStylePrimaryName(getElement(row, column));
}
/**
* Determines whether or not this cell is visible.
*
* @param row the row of the cell whose visibility is to be set
* @param column the column of the cell whose visibility is to be set
* @return <code>true</code> if the object is visible
*/
public boolean isVisible(int row, int column) {
Element e = getElement(row, column);
return UIObject.isVisible(e);
}
/**
* Removes a style from the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @param styleName the style name to be removed
* @see UIObject#removeStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void removeStyleName(int row, int column, String styleName) {
checkCellBounds(row, column);
Element td = getCellElement(bodyElem, row, column);
UIObject.setStyleName(td, styleName, false);
}
/**
* Sets the horizontal and vertical alignment of the specified cell's
* contents.
*
* @param row the row of the cell whose alignment is to be set
* @param column the column of the cell whose alignment is to be set
* @param hAlign the cell's new horizontal alignment as specified in
* {@link HasHorizontalAlignment}
* @param vAlign the cell's new vertical alignment as specified in
* {@link HasVerticalAlignment}
* @throws IndexOutOfBoundsException
*/
public void setAlignment(int row, int column,
HorizontalAlignmentConstant hAlign, VerticalAlignmentConstant vAlign) {
setHorizontalAlignment(row, column, hAlign);
setVerticalAlignment(row, column, vAlign);
}
/**
* Sets the height of the specified cell.
*
* @param row the row of the cell whose height is to be set
* @param column the column of the cell whose height is to be set
* @param height the cell's new height, in CSS units
* @throws IndexOutOfBoundsException
*/
public void setHeight(int row, int column, String height) {
prepareCell(row, column);
Element elem = getCellElement(bodyElem, row, column);
DOM.setElementProperty(elem, "height", height);
}
/**
* Sets the horizontal alignment of the specified cell.
*
* @param row the row of the cell whose alignment is to be set
* @param column the column of the cell whose alignment is to be set
* @param align the cell's new horizontal alignment as specified in
* {@link HasHorizontalAlignment}.
* @throws IndexOutOfBoundsException
*/
public void setHorizontalAlignment(int row, int column,
HorizontalAlignmentConstant align) {
prepareCell(row, column);
Element elem = getCellElement(bodyElem, row, column);
DOM.setElementProperty(elem, "align", align.getTextAlignString());
}
/**
* Sets the style name associated with the specified cell.
*
* @param row the row of the cell whose style name is to be set
* @param column the column of the cell whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void setStyleName(int row, int column, String styleName) {
prepareCell(row, column);
UIObject.setStyleName(getCellElement(bodyElem, row, column), styleName);
}
/**
* Sets the primary style name associated with the specified cell.
*
* @param row the row of the cell whose style name is to be set
* @param column the column of the cell whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStylePrimaryName(String)
* @throws IndexOutOfBoundsException
*/
public void setStylePrimaryName(int row, int column, String styleName) {
UIObject.setStylePrimaryName(getCellElement(bodyElem, row, column),
styleName);
}
/**
* Sets the vertical alignment of the specified cell.
*
* @param row the row of the cell whose alignment is to be set
* @param column the column of the cell whose alignment is to be set
* @param align the cell's new vertical alignment as specified in
* {@link HasVerticalAlignment}.
* @throws IndexOutOfBoundsException
*/
public void setVerticalAlignment(int row, int column,
VerticalAlignmentConstant align) {
prepareCell(row, column);
DOM.setStyleAttribute(getCellElement(bodyElem, row, column),
"verticalAlign", align.getVerticalAlignString());
}
/**
* Sets whether this cell is visible via the display style property. The
* other cells in the row will all shift left to fill the cell's space. So,
* for example a table with (0,1,2) will become (1,2) if cell 1 is hidden.
*
* @param row the row of the cell whose visibility is to be set
* @param column the column of the cell whose visibility is to be set
* @param visible <code>true</code> to show the cell, <code>false</code> to
* hide it
*/
public void setVisible(int row, int column, boolean visible) {
Element e = ensureElement(row, column);
UIObject.setVisible(e, visible);
}
/**
* Sets the width of the specified cell.
*
* @param row the row of the cell whose width is to be set
* @param column the column of the cell whose width is to be set
* @param width the cell's new width, in CSS units
* @throws IndexOutOfBoundsException
*/
public void setWidth(int row, int column, String width) {
// Give the subclass a chance to prepare the cell.
prepareCell(row, column);
DOM.setElementProperty(getCellElement(bodyElem, row, column), "width",
width);
}
/**
* Sets whether the specified cell will allow word wrapping of its contents.
*
* @param row the row of the cell whose word-wrap is to be set
* @param column the column of the cell whose word-wrap is to be set
* @param wrap <code>false </code> to disable word wrapping in this cell
* @throws IndexOutOfBoundsException
*/
public void setWordWrap(int row, int column, boolean wrap) {
prepareCell(row, column);
String wrapValue = wrap ? "" : "nowrap";
DOM.setStyleAttribute(getElement(row, column), "whiteSpace", wrapValue);
}
/**
* Gets the element associated with a cell. If it does not exist and the
* subtype allows creation of elements, creates it.
*
* @param row the cell's row
* @param column the cell's column
* @return the cell's element
* @throws IndexOutOfBoundsException
*/
protected Element ensureElement(int row, int column) {
prepareCell(row, column);
return getCellElement(bodyElem, row, column);
}
/**
* Convenience methods to get an attribute on a cell.
*
* @param row cell's row
* @param column cell's column
* @param attr attribute to get
* @return the attribute's value
* @throws IndexOutOfBoundsException
*/
protected String getAttr(int row, int column, String attr) {
Element elem = getElement(row, column);
return DOM.getElementAttribute(elem, attr);
}
/**
* Convenience methods to set an attribute on a cell.
*
* @param row cell's row
* @param column cell's column
* @param attrName attribute to set
* @param value value to set
* @throws IndexOutOfBoundsException
*/
protected void setAttr(int row, int column, String attrName, String value) {
Element elem = ensureElement(row, column);
DOM.setElementAttribute(elem, attrName, value);
}
/**
* Native method to get a cell's element.
*
* @param table the table element
* @param row the row of the cell
* @param col the column of the cell
* @return the element
*/
private native Element getCellElement(Element table, int row, int col) /*-{
return table.rows[row].cells[col];
}-*/;
/**
* Gets the TD element representing the specified cell unsafely (meaning
* that it doesn't ensure that <code>row</code> and <code>column</code> are
* valid).
*
* @param row the row of the cell to be retrieved
* @param column the column of the cell to be retrieved
* @return the cell's TD element
*/
private Element getRawElement(int row, int column) {
return getCellElement(bodyElem, row, column);
}
}
/**
* This class contains methods used to format a table's columns. It is limited
* by the support cross-browser HTML support for column formatting.
*/
public class ColumnFormatter {
protected Element columnGroup;
/**
* Adds a style to the specified column.
*
* @param col the col to which the style will be added
* @param styleName the style name to be added
* @see UIObject#addStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void addStyleName(int col, String styleName) {
UIObject.setStyleName(ensureColumn(col), styleName, true);
}
/**
* Get the col element for the column.
*
* @param column the column index
* @return the col element
*/
public Element getElement(int column) {
return ensureColumn(column);
}
/**
* Gets the style of the specified column.
*
* @param column the column to be queried
* @return the style name
* @see UIObject#getStyleName()
* @throws IndexOutOfBoundsException
*/
public String getStyleName(int column) {
return UIObject.getStyleName(ensureColumn(column));
}
/**
* Gets the primary style of the specified column.
*
* @param column the column to be queried
* @return the style name
* @see UIObject#getStylePrimaryName()
* @throws IndexOutOfBoundsException
*/
public String getStylePrimaryName(int column) {
return UIObject.getStylePrimaryName(ensureColumn(column));
}
/**
* Removes a style from the specified column.
*
* @param column the column from which the style will be removed
* @param styleName the style name to be removed
* @see UIObject#removeStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void removeStyleName(int column, String styleName) {
UIObject.setStyleName(ensureColumn(column), styleName, false);
}
/**
* Sets the style name associated with the specified column.
*
* @param column the column whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void setStyleName(int column, String styleName) {
UIObject.setStyleName(ensureColumn(column), styleName);
}
/**
* Sets the primary style name associated with the specified column.
*
* @param column the column whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStylePrimaryName(String)
* @throws IndexOutOfBoundsException
*/
public void setStylePrimaryName(int column, String styleName) {
UIObject.setStylePrimaryName(ensureColumn(column), styleName);
}
/**
* Sets the width of the specified column.
*
* @param column the column of the cell whose width is to be set
* @param width the cell's new width, in percentage or pixel units
* @throws IndexOutOfBoundsException
*/
public void setWidth(int column, String width) {
DOM.setElementProperty(ensureColumn(column), "width", width);
}
/**
* Resize the column group element.
*
* @param columns the number of columns
* @param growOnly true to only grow, false to shrink if needed
*/
void resizeColumnGroup(int columns, boolean growOnly) {
// The colgroup should always have at least one element. See
// prepareColumnGroup() for more details.
columns = Math.max(columns, 1);
int num = columnGroup.getChildCount();
if (num < columns) {
for (int i = num; i < columns; i++) {
columnGroup.appendChild(Document.get().createColElement());
}
} else if (!growOnly && num > columns) {
for (int i = num; i > columns; i--) {
columnGroup.removeChild(columnGroup.getLastChild());
}
}
}
private Element ensureColumn(int col) {
prepareColumn(col);
prepareColumnGroup();
resizeColumnGroup(col + 1, true);
return columnGroup.getChild(col).cast();
}
/**
* Prepare the colgroup tag for the first time, guaranteeing that it exists
* and has at least one col tag in it. This method corrects a Mozilla issue
* where the col tag will affect the wrong column if a col tag doesn't exist
* when the element is attached to the page.
*/
private void prepareColumnGroup() {
if (columnGroup == null) {
columnGroup = DOM.createElement("colgroup");
DOM.insertChild(tableElem, columnGroup, 0);
DOM.appendChild(columnGroup, DOM.createElement("col"));
}
}
}
/**
* This class contains methods used to format a table's rows.
*/
public class RowFormatter {
/**
* Adds a style to the specified row.
*
* @param row the row to which the style will be added
* @param styleName the style name to be added
* @see UIObject#addStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void addStyleName(int row, String styleName) {
UIObject.setStyleName(ensureElement(row), styleName, true);
}
/**
* Gets the TR element representing the specified row.
*
* @param row the row whose TR element is to be retrieved
* @return the row's TR element
* @throws IndexOutOfBoundsException
*/
public Element getElement(int row) {
checkRowBounds(row);
return getRow(bodyElem, row);
}
/**
* Gets the style of the specified row.
*
* @param row the row to be queried
* @return the style name
* @see UIObject#getStyleName()
* @throws IndexOutOfBoundsException
*/
public String getStyleName(int row) {
return UIObject.getStyleName(getElement(row));
}
/**
* Gets the primary style of the specified row.
*
* @param row the row to be queried
* @return the style name
* @see UIObject#getStylePrimaryName()
* @throws IndexOutOfBoundsException
*/
public String getStylePrimaryName(int row) {
return UIObject.getStylePrimaryName(getElement(row));
}
/**
* Determines whether or not this row is visible via the display style
* attribute.
*
* @param row the row whose visibility is to be set
* @return <code>true</code> if the row is visible
*/
public boolean isVisible(int row) {
Element e = getElement(row);
return UIObject.isVisible(e);
}
/**
* Removes a style from the specified row.
*
* @param row the row from which the style will be removed
* @param styleName the style name to be removed
* @see UIObject#removeStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void removeStyleName(int row, String styleName) {
UIObject.setStyleName(ensureElement(row), styleName, false);
}
/**
* Sets the style name associated with the specified row.
*
* @param row the row whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStyleName(String)
* @throws IndexOutOfBoundsException
*/
public void setStyleName(int row, String styleName) {
UIObject.setStyleName(ensureElement(row), styleName);
}
/**
* Sets the primary style name associated with the specified row.
*
* @param row the row whose style name is to be set
* @param styleName the new style name
* @see UIObject#setStylePrimaryName(String)
* @throws IndexOutOfBoundsException
*/
public void setStylePrimaryName(int row, String styleName) {
UIObject.setStylePrimaryName(ensureElement(row), styleName);
}
/**
* Sets the vertical alignment of the specified row.
*
* @param row the row whose alignment is to be set
* @param align the row's new vertical alignment as specified in
* {@link HasVerticalAlignment}
* @throws IndexOutOfBoundsException
*/
public void setVerticalAlign(int row, VerticalAlignmentConstant align) {
DOM.setStyleAttribute(ensureElement(row), "verticalAlign",
align.getVerticalAlignString());
}
/**
* Sets whether this row is visible.
*
* @param row the row whose visibility is to be set
* @param visible <code>true</code> to show the row, <code>false</code> to
* hide it
*/
public void setVisible(int row, boolean visible) {
Element e = ensureElement(row);
UIObject.setVisible(e, visible);
}
/**
* Ensure the TR element representing the specified row exists for
* subclasses that allow dynamic addition of elements.
*
* @param row the row whose TR element is to be retrieved
* @return the row's TR element
* @throws IndexOutOfBoundsException
*/
protected Element ensureElement(int row) {
prepareRow(row);
return getRow(bodyElem, row);
}
protected native Element getRow(Element elem, int row)/*-{
return elem.rows[row];
}-*/;
/**
* Convenience methods to set an attribute on a row.
*
* @param row cell's row
* @param attrName attribute to set
* @param value value to set
* @throws IndexOutOfBoundsException
*/
protected void setAttr(int row, String attrName, String value) {
Element elem = ensureElement(row);
DOM.setElementAttribute(elem, attrName, value);
}
}
/**
* Table's body.
*/
private final Element bodyElem;
/**
* Current cell formatter.
*/
private CellFormatter cellFormatter;
/**
* Column Formatter.
*/
private ColumnFormatter columnFormatter;
/**
* Current row formatter.
*/
private RowFormatter rowFormatter;
/**
* Table element.
*/
private final Element tableElem;
private ElementMapperImpl<Widget> widgetMap = new ElementMapperImpl<Widget>();
/**
* Create a new empty HTML Table.
*/
public HTMLTable() {
tableElem = DOM.createTable();
bodyElem = DOM.createTBody();
DOM.appendChild(tableElem, bodyElem);
setElement(tableElem);
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
return addDomHandler(handler, DoubleClickEvent.getType());
}
/**
* Adds a listener to the current table.
*
* @param listener listener to add
* @deprecated add a click handler instead and use
* {@link HTMLTable#getCellForEvent(ClickEvent)} to get the cell
* information (remember to check for a null return value)
*/
@Deprecated
public void addTableListener(TableListener listener) {
ListenerWrapper.WrappedTableListener.add(this, listener);
}
/**
* Removes all widgets from this table, but does not remove other HTML or text
* contents of cells.
*/
@Override
public void clear() {
clear(false);
}
/**
* Removes all widgets from this table, optionally clearing the inner HTML of
* each cell. Note that this method does not remove any cells or rows.
*
* @param clearInnerHTML should the cell's inner html be cleared?
*/
public void clear(boolean clearInnerHTML) {
for (int row = 0; row < getRowCount(); ++row) {
for (int col = 0; col < getCellCount(row); ++col) {
cleanCell(row, col, clearInnerHTML);
}
}
}
/**
* Clears the cell at the given row and column. If it contains a Widget, it
* will be removed from the table. If not, its contents will simply be
* cleared.
*
* @param row the widget's row
* @param column the widget's column
* @return true if a widget was removed
* @throws IndexOutOfBoundsException
*/
public boolean clearCell(int row, int column) {
Element td = getCellFormatter().getElement(row, column);
return internalClearCell(td, true);
}
/**
* Gets the number of cells in a given row.
*
* @param row the row whose cells are to be counted
* @return the number of cells present in the row
*/
public abstract int getCellCount(int row);
/**
* Given a click event, return the Cell that was clicked, or null if the event
* did not hit this table. The cell can also be null if the click event does
* not occur on a specific cell.
*
* @param event A click event of indeterminate origin
* @return The appropriate cell, or null
*/
public Cell getCellForEvent(ClickEvent event) {
Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
if (td == null) {
return null;
}
int row = TableRowElement.as(td.getParentElement()).getSectionRowIndex();
int column = TableCellElement.as(td).getCellIndex();
return new Cell(row, column);
}
/**
* Gets the {@link CellFormatter} associated with this table. Use casting to
* get subclass-specific functionality
*
* @return this table's cell formatter
*/
public CellFormatter getCellFormatter() {
return cellFormatter;
}
/**
* Gets the amount of padding that is added around all cells.
*
* @return the cell padding, in pixels
*/
public int getCellPadding() {
return DOM.getElementPropertyInt(tableElem, "cellPadding");
}
/**
* Gets the amount of spacing that is added around all cells.
*
* @return the cell spacing, in pixels
*/
public int getCellSpacing() {
return DOM.getElementPropertyInt(tableElem, "cellSpacing");
}
/**
* Gets the column formatter.
*
* @return the column formatter
*/
public ColumnFormatter getColumnFormatter() {
return columnFormatter;
}
/**
* Gets the HTML contents of the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @return the cell's HTML contents
* @throws IndexOutOfBoundsException
*/
public String getHTML(int row, int column) {
return DOM.getInnerHTML(cellFormatter.getElement(row, column));
}
/**
* Gets the number of rows present in this table.
*
* @return the table's row count
*/
public abstract int getRowCount();
/**
* Gets the RowFormatter associated with this table.
*
* @return the table's row formatter
*/
public RowFormatter getRowFormatter() {
return rowFormatter;
}
/**
* Gets the text within the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @return the cell's text contents
* @throws IndexOutOfBoundsException
*/
public String getText(int row, int column) {
checkCellBounds(row, column);
Element e = cellFormatter.getElement(row, column);
return DOM.getInnerText(e);
}
/**
* Gets the widget in the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @return the widget in the specified cell, or <code>null</code> if none is
* present
* @throws IndexOutOfBoundsException
*/
public Widget getWidget(int row, int column) {
checkCellBounds(row, column);
return getWidgetImpl(row, column);
}
/**
* Determines whether the specified cell exists.
*
* @param row the cell's row
* @param column the cell's column
* @return <code>true</code> if the specified cell exists
*/
public boolean isCellPresent(int row, int column) {
if ((row >= getRowCount()) || (row < 0)) {
return false;
}
if ((column < 0) || (column >= getCellCount(row))) {
return false;
} else {
return true;
}
}
/**
* Returns an iterator containing all the widgets in this table.
*
* @return the iterator
*/
public Iterator<Widget> iterator() {
return new Iterator<Widget>() {
final ArrayList<Widget> widgetList = widgetMap.getObjectList();
int lastIndex = -1;
int nextIndex = -1;
{
findNext();
}
public boolean hasNext() {
return nextIndex < widgetList.size();
}
public Widget next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Widget result = widgetList.get(nextIndex);
lastIndex = nextIndex;
findNext();
return result;
}
public void remove() {
if (lastIndex < 0) {
throw new IllegalStateException();
}
Widget w = widgetList.get(lastIndex);
assert (w.getParent() instanceof HTMLTable);
w.removeFromParent();
lastIndex = -1;
}
private void findNext() {
while (++nextIndex < widgetList.size()) {
if (widgetList.get(nextIndex) != null) {
return;
}
}
}
};
}
/**
* Remove the specified widget from the table.
*
* @param widget widget to remove
* @return was the widget removed from the table.
*/
@Override
public boolean remove(Widget widget) {
// Validate.
if (widget.getParent() != this) {
return false;
}
// Orphan.
try {
orphan(widget);
} finally {
// Physical detach.
Element elem = widget.getElement();
DOM.removeChild(DOM.getParent(elem), elem);
// Logical detach.
widgetMap.removeByElement(elem);
}
return true;
}
/**
* Removes the specified table listener.
*
* @param listener listener to remove
*
* @deprecated Use the {@link HandlerRegistration#removeHandler}
* method on the object returned by an add*Handler method instead
*/
@Deprecated
public void removeTableListener(TableListener listener) {
ListenerWrapper.WrappedTableListener.remove(this, listener);
}
/**
* Sets the width of the table's border. This border is displayed around all
* cells in the table.
*
* @param width the width of the border, in pixels
*/
public void setBorderWidth(int width) {
DOM.setElementProperty(tableElem, "border", "" + width);
}
/**
* Sets the amount of padding to be added around all cells.
*
* @param padding the cell padding, in pixels
*/
public void setCellPadding(int padding) {
DOM.setElementPropertyInt(tableElem, "cellPadding", padding);
}
/**
* Sets the amount of spacing to be added around all cells.
*
* @param spacing the cell spacing, in pixels
*/
public void setCellSpacing(int spacing) {
DOM.setElementPropertyInt(tableElem, "cellSpacing", spacing);
}
/**
* Sets the HTML contents of the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @param html the cell's HTML contents
* @throws IndexOutOfBoundsException
*/
public void setHTML(int row, int column, String html) {
prepareCell(row, column);
Element td = cleanCell(row, column, html == null);
if (html != null) {
DOM.setInnerHTML(td, html);
}
}
/**
* Sets the HTML contents of the specified cell.
*
* @param row the cell's row
* @param column the cell's column
* @param html the cell's safe html contents
* @throws IndexOutOfBoundsException
*/
public void setHTML(int row, int column, SafeHtml html) {
setHTML(row, column, html.asString());
}
/**
* Sets the text within the specified cell.
*
* @param row the cell's row
* @param column cell's column
* @param text the cell's text contents
* @throws IndexOutOfBoundsException
*/
public void setText(int row, int column, String text) {
prepareCell(row, column);
Element td;
td = cleanCell(row, column, text == null);
if (text != null) {
DOM.setInnerText(td, text);
}
}
/**
* Sets the widget within the specified cell.
* <p>
* Inherited implementations may either throw IndexOutOfBounds exception if
* the cell does not exist, or allocate a new cell to store the content.
* </p>
* <p>
* FlexTable will automatically allocate the cell at the correct location and
* then set the widget. Grid will set the widget if and only if the cell is
* within the Grid's bounding box.
* </p>
*
* @param widget The widget to be added
* @param row the cell's row
* @param column the cell's column
* @throws IndexOutOfBoundsException
*/
public void setWidget(int row, int column, Widget widget) {
prepareCell(row, column);
if (widget != null) {
widget.removeFromParent();
// Removes any existing widget.
Element td = cleanCell(row, column, true);
// Logical attach.
widgetMap.put(widget);
// Physical attach.
DOM.appendChild(td, widget.getElement());
adopt(widget);
}
}
/**
* Bounds checks that the cell exists at the specified location.
*
* @param row cell's row
* @param column cell's column
* @throws IndexOutOfBoundsException
*/
protected void checkCellBounds(int row, int column) {
checkRowBounds(row);
if (column < 0) {
throw new IndexOutOfBoundsException("Column " + column
+ " must be non-negative: " + column);
}
int cellSize = getCellCount(row);
if (cellSize <= column) {
throw new IndexOutOfBoundsException("Column index: " + column
+ ", Column size: " + getCellCount(row));
}
}
/**
* Checks that the row is within the correct bounds.
*
* @param row row index to check
* @throws IndexOutOfBoundsException
*/
protected void checkRowBounds(int row) {
int rowSize = getRowCount();
if ((row >= rowSize) || (row < 0)) {
throw new IndexOutOfBoundsException("Row index: " + row + ", Row size: "
+ rowSize);
}
}
/**
* Creates a new cell. Override this method if the cell should have initial
* contents.
*
* @return the newly created TD
*/
protected Element createCell() {
return DOM.createTD();
}
/**
* Gets the table's TBODY element.
*
* @return the TBODY element
*/
protected Element getBodyElement() {
return bodyElem;
}
/**
* Directly ask the underlying DOM what the cell count on the given row is.
*
* @param tableBody the element
* @param row the row
* @return number of columns in the row
*/
protected native int getDOMCellCount(Element tableBody, int row) /*-{
return tableBody.rows[row].cells.length;
}-*/;
/**
* Directly ask the underlying DOM what the cell count on the given row is.
*
* @param row the row
* @return number of columns in the row
*/
protected int getDOMCellCount(int row) {
return getDOMCellCount(bodyElem, row);
}
/**
* Directly ask the underlying DOM what the row count is.
*
* @return Returns the number of rows in the table
*/
protected int getDOMRowCount() {
return getDOMRowCount(bodyElem);
}
protected native int getDOMRowCount(Element elem) /*-{
return elem.rows.length;
}-*/;
/**
* Determines the TD associated with the specified event.
*
* @param event the event to be queried
* @return the TD associated with the event, or <code>null</code> if none is
* found.
*/
protected Element getEventTargetCell(Event event) {
Element td = DOM.eventGetTarget(event);
for (; td != null; td = DOM.getParent(td)) {
// If it's a TD, it might be the one we're looking for.
if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
// Make sure it's directly a part of this table before returning
// it.
Element tr = DOM.getParent(td);
Element body = DOM.getParent(tr);
if (body == bodyElem) {
return td;
}
}
// If we run into this table's body, we're out of options.
if (td == bodyElem) {
return null;
}
}
return null;
}
/**
* Inserts a new cell into the specified row.
*
* @param row the row into which the new cell will be inserted
* @param column the column before which the cell will be inserted
* @throws IndexOutOfBoundsException
*/
protected void insertCell(int row, int column) {
Element tr = rowFormatter.getRow(bodyElem, row);
Element td = createCell();
DOM.insertChild(tr, td, column);
}
/**
* Inserts a number of cells before the specified cell.
*
* @param row the row into which the new cells will be inserted
* @param column the column before which the new cells will be inserted
* @param count number of cells to be inserted
* @throws IndexOutOfBoundsException
*/
protected void insertCells(int row, int column, int count) {
Element tr = rowFormatter.getRow(bodyElem, row);
for (int i = column; i < column + count; i++) {
Element td = createCell();
DOM.insertChild(tr, td, i);
}
}
/**
* Inserts a new row into the table.
*
* @param beforeRow the index before which the new row will be inserted
* @return the index of the newly-created row
* @throws IndexOutOfBoundsException
*/
protected int insertRow(int beforeRow) {
// Specifically allow the row count as an insert position.
if (beforeRow != getRowCount()) {
checkRowBounds(beforeRow);
}
Element tr = DOM.createTR();
DOM.insertChild(bodyElem, tr, beforeRow);
return beforeRow;
}
/**
* Does actual clearing, used by clearCell and cleanCell. All HTMLTable
* methods should use internalClearCell rather than clearCell, as clearCell
* may be overridden in subclasses to format an empty cell.
*
* @param td element to clear
* @param clearInnerHTML should the cell's inner html be cleared?
* @return returns whether a widget was cleared
*/
protected boolean internalClearCell(Element td, boolean clearInnerHTML) {
Element maybeChild = DOM.getFirstChild(td);
Widget widget = null;
if (maybeChild != null) {
widget = widgetMap.get(maybeChild);
}
if (widget != null) {
// If there is a widget, remove it.
remove(widget);
return true;
} else {
// Otherwise, simply clear whatever text and/or HTML may be there.
if (clearInnerHTML) {
DOM.setInnerHTML(td, "");
}
return false;
}
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-(row)#-(cell)# = the cell at the given row and cell index.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
int rowCount = getRowCount();
for (int row = 0; row < rowCount; row++) {
int cellCount = getCellCount(row);
for (int cell = 0; cell < cellCount; cell++) {
Element cellElem = cellFormatter.getRawElement(row, cell);
ensureDebugId(cellElem, baseID, row + "-" + cell);
}
}
}
/**
* Subclasses must implement this method. It allows them to decide what to do
* just before a cell is accessed. If the cell already exists, this method
* must do nothing. Otherwise, a subclass must either ensure that the cell
* exists or throw an {@link IndexOutOfBoundsException}.
*
* @param row the cell's row
* @param column the cell's column
*/
protected abstract void prepareCell(int row, int column);
/**
* Subclasses can implement this method. It allows them to decide what to do
* just before a column is accessed. For classes, such as
* <code>FlexTable</code>, that do not have a concept of a global column
* length can ignore this method.
*
* @param column the cell's column
* @throws IndexOutOfBoundsException
*/
protected void prepareColumn(int column) {
// Ensure that the indices are not negative.
if (column < 0) {
throw new IndexOutOfBoundsException(
"Cannot access a column with a negative index: " + column);
}
}
/**
* Subclasses must implement this method. If the row already exists, this
* method must do nothing. Otherwise, a subclass must either ensure that the
* row exists or throw an {@link IndexOutOfBoundsException}.
*
* @param row the cell's row
*/
protected abstract void prepareRow(int row);
/**
* Removes the specified cell from the table.
*
* @param row the row of the cell to remove
* @param column the column of cell to remove
* @throws IndexOutOfBoundsException
*/
protected void removeCell(int row, int column) {
checkCellBounds(row, column);
Element td = cleanCell(row, column, false);
Element tr = rowFormatter.getRow(bodyElem, row);
DOM.removeChild(tr, td);
}
/**
* Removes the specified row from the table.
*
* @param row the index of the row to be removed
* @throws IndexOutOfBoundsException
*/
protected void removeRow(int row) {
int columnCount = getCellCount(row);
for (int column = 0; column < columnCount; ++column) {
cleanCell(row, column, false);
}
DOM.removeChild(bodyElem, rowFormatter.getRow(bodyElem, row));
}
/**
* Sets the table's CellFormatter.
*
* @param cellFormatter the table's cell formatter
*/
protected void setCellFormatter(CellFormatter cellFormatter) {
this.cellFormatter = cellFormatter;
}
protected void setColumnFormatter(ColumnFormatter formatter) {
// Copy the columnGroup element to the new formatter so we don't create a
// second colgroup element.
if (columnFormatter != null) {
formatter.columnGroup = columnFormatter.columnGroup;
}
columnFormatter = formatter;
columnFormatter.prepareColumnGroup();
}
/**
* Sets the table's RowFormatter.
*
* @param rowFormatter the table's row formatter
*/
protected void setRowFormatter(RowFormatter rowFormatter) {
this.rowFormatter = rowFormatter;
}
/**
* Removes any widgets, text, and HTML within the cell. This method assumes
* that the requested cell already exists.
*
* @param row the cell's row
* @param column the cell's column
* @param clearInnerHTML should the cell's inner html be cleared?
* @return element that has been cleaned
*/
private Element cleanCell(int row, int column, boolean clearInnerHTML) {
// Clear whatever is in the cell.
Element td = getCellFormatter().getRawElement(row, column);
internalClearCell(td, clearInnerHTML);
return td;
}
/**
* Gets the Widget associated with the given cell.
*
* @param row the cell's row
* @param column the cell's column
* @return the widget
*/
private Widget getWidgetImpl(int row, int column) {
Element e = cellFormatter.getRawElement(row, column);
Element child = DOM.getFirstChild(e);
if (child == null) {
return null;
} else {
return widgetMap.get(child);
}
}
}