Java tutorial
/* * 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 java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ElementRemote; import com.google.gwt.dom.client.LocalDom; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; 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.DragEndEvent; import com.google.gwt.event.dom.client.DragEndHandler; import com.google.gwt.event.dom.client.DragEnterEvent; import com.google.gwt.event.dom.client.DragEnterHandler; import com.google.gwt.event.dom.client.DragEvent; import com.google.gwt.event.dom.client.DragHandler; import com.google.gwt.event.dom.client.DragLeaveEvent; import com.google.gwt.event.dom.client.DragLeaveHandler; import com.google.gwt.event.dom.client.DragOverEvent; import com.google.gwt.event.dom.client.DragOverHandler; import com.google.gwt.event.dom.client.DragStartEvent; import com.google.gwt.event.dom.client.DragStartHandler; import com.google.gwt.event.dom.client.DropEvent; import com.google.gwt.event.dom.client.DropHandler; import com.google.gwt.event.dom.client.HasAllDragAndDropHandlers; 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.safehtml.shared.annotations.IsSafeHtml; import com.google.gwt.user.client.DOM; 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; /** * 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, HasAllDragAndDropHandlers, HasClickHandlers, HasDoubleClickHandlers { private static final HTMLTableImpl impl = GWT.create(HTMLTableImpl.class); /** * 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()); } public HandlerRegistration addDragEndHandler(DragEndHandler handler) { return addBitlessDomHandler(handler, DragEndEvent.getType()); } public HandlerRegistration addDragEnterHandler(DragEnterHandler handler) { return addBitlessDomHandler(handler, DragEnterEvent.getType()); } public HandlerRegistration addDragHandler(DragHandler handler) { return addBitlessDomHandler(handler, DragEvent.getType()); } public HandlerRegistration addDragLeaveHandler(DragLeaveHandler handler) { return addBitlessDomHandler(handler, DragLeaveEvent.getType()); } public HandlerRegistration addDragOverHandler(DragOverHandler handler) { return addBitlessDomHandler(handler, DragOverEvent.getType()); } public HandlerRegistration addDragStartHandler(DragStartHandler handler) { return addBitlessDomHandler(handler, DragStartEvent.getType()); } public HandlerRegistration addDropHandler(DropHandler handler) { return addBitlessDomHandler(handler, DropEvent.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 tableElem.getPropertyInt("cellPadding"); } /** * Gets the amount of spacing that is added around all cells. * * @return the cell spacing, in pixels */ public int getCellSpacing() { return tableElem.getPropertyInt("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 cellFormatter.getElement(row, column).getInnerHTML(); } /** * 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 e.getInnerText(); } /** * 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.getParent(elem).removeChild(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) { tableElem.setPropertyString("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) { tableElem.setPropertyInt("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) { tableElem.setPropertyInt("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 safe html contents * @throws IndexOutOfBoundsException */ public void setHTML(int row, int column, SafeHtml html) { setHTML(row, column, html.asString()); } /** * 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, @IsSafeHtml String html) { prepareCell(row, column); Element td = cleanCell(row, column, html == null); if (html != null) { td.setInnerHTML(html); } } /** * 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) { td.setInnerText(text); } } /** * Overloaded version for IsWidget. * * @see #setWidget(int,int,Widget) */ public void setWidget(int row, int column, IsWidget widget) { this.setWidget(row, column, asWidgetOrNull(widget)); } /** * 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, or null to clear the cell * @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); // Removes any existing widget. Element td = cleanCell(row, column, true); if (widget != null) { widget.removeFromParent(); // Logical attach. widgetMap.put(widget); // Physical attach. DOM.appendChild(td, widget.getElement()); adopt(widget); } } /** * 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); } } /** * 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 DOM.asOld(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 int getDOMCellCount(Element tableBody, int row) { Element rowElement = impl.getRows(tableBody).get(row); return impl.getCells(rowElement).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); } @Deprecated protected int getDOMRowCount(Element tbody) { return impl.getRows(tbody).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 (td.getPropertyString("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 DOM.asOld(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) { td.setInnerHTML(""); } 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); tr.removeChild(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); } bodyElem.removeChild(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; } void addCells(Element tbody, int row, int num) { com.google.gwt.dom.client.Element rowElem = impl.getRows(tbody).get(row); for (int i = 0; i < num; i++) { TableCellElement tdElement = Document.get().createTDElement(); rowElem.appendChild(tdElement); } } /** * 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 DOM.asOld(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 DOM.asOld(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); elem.setPropertyString("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); elem.setPropertyString("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); getCellElement(bodyElem, row, column).getStyle().setProperty("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); getCellElement(bodyElem, row, column).setPropertyString("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"; getElement(row, column).getStyle().setProperty("whiteSpace", wrapValue); } /** * Get a cell's element. * * @param tbody * the table element * @param row * the row of the cell * @param col * the column of the cell * @return the element */ private Element getCellElement(Element tbody, int row, int col) { Element rowObj = impl.getRows(tbody).get(row); ElementArray<Element> cells = impl.getCells(rowObj); return cells.get(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); } /** * 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 DOM.asOld(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 elem.getAttribute(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); elem.setAttribute(attrName, value); } } /** * 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 DOM.asOld(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 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 column, boolean visible) { Element e = ensureColumn(column); UIObject.setVisible(e, visible); } /** * 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) { ensureColumn(column).setPropertyString("width", width); } 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")); } } /** * 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()); } } } } /** * 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 DOM.asOld(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) { ensureElement(row).getStyle().setProperty("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 DOM.asOld(getRow(bodyElem, row)); } /** * @deprecated Call and override {@link #getRow(Element, int)} instead. */ protected Element getRow(Element tbody, int row) { return DOM.asOld(impl.getRows(tbody).get(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); elem.setAttribute(attrName, value); } } /** * IE specific implementation for accessing the Table DOM. see: issue 6938 */ @SuppressWarnings("unused") // used due to rebinding private static class HTMLTableIEImpl extends HTMLTableStandardImpl { native JsArray<ElementRemote> getCells0(ElementRemote row) /*-{ return row.children; }-*/; native JsArray<ElementRemote> getRows0(ElementRemote tbody) /*-{ return tbody.children; }-*/; } /** * Interface to access {@link HTMLTable}'s DOM. */ private interface HTMLTableImpl { ElementArray<Element> getCells(Element row); ElementArray<Element> getRows(Element tbody); } /** * Standard implementation for accessing the Table DOM. */ private static class HTMLTableStandardImpl implements HTMLTableImpl { @Override public ElementArray<Element> getCells(Element row) { return new ElementArray<Element>((List) ((TableRowElement) row).provideChildNodeList()); } @Override public ElementArray<Element> getRows(Element tbody) { return new ElementArray<Element>((List) ((TableSectionElement) tbody).provideChildNodeList()); } native JsArray<ElementRemote> getCells0(ElementRemote row) /*-{ return row.cells; }-*/; native JsArray<ElementRemote> getRows0(ElementRemote tbody) /*-{ return tbody.rows; }-*/; } static class ElementArray<T extends Node> { private JsArray<ElementRemote> jsArray; private NodeList<? extends Element> nodeList; private List<? extends Element> jvmList; public ElementArray(JsArray<ElementRemote> elements) { this.jsArray = elements; } public ElementArray(List<? extends Element> jvmList) { this.jvmList = jvmList; } public ElementArray(NodeList<? extends Element> nodeList) { this.nodeList = nodeList; } public Element get(int idx) { if (jvmList != null) { return jvmList.get(idx); } return nodeList != null ? nodeList.getItem(idx) : LocalDom.nodeFor(jsArray.get(idx)); } public int length() { if (jvmList != null) { return jvmList.size(); } return nodeList != null ? nodeList.getLength() : jsArray.length(); } } }