com.google.gwt.user.cellview.client.CellTable Maven / Gradle / Ivy
/*
* Copyright 2010 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;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.IconCellDecorator;
import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style.TableLayout;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableColElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A tabular view that supports paging and columns.
*
*
*
Columns
The {@link Column} class defines the {@link Cell} used to
* render a column. Implement {@link Column#getValue(Object)} to retrieve the
* field value from the row object that will be rendered in the {@link Cell}.
*
*
*
*
Headers and Footers
A {@link Header} can be placed at the top
* (header) or bottom (footer) of the {@link CellTable}. You can specify a
* header as text using {@link #addColumn(Column, String)}, or you can create a
* custom {@link Header} that can change with the value of the cells, such as a
* column total. The {@link Header} will be rendered every time the row data
* changes or the table is redrawn. If you pass the same header instance (==)
* into adjacent columns, the header will span the columns.
*
*
*
*
Examples
*
* - Trivial example
* - {@example com.google.gwt.examples.cellview.CellTableExample}
* - FieldUpdater example
* - {@example com.google.gwt.examples.cellview.CellTableFieldUpdaterExample}
* - Key provider example
* - {@example com.google.gwt.examples.view.KeyProviderExample}
*
*
*
* @param the data type of each row
*/
public class CellTable extends AbstractHasData {
/**
* Resources that match the GWT standard style theme.
*/
public interface BasicResources extends Resources {
/**
* The styles used in this widget.
*/
@Source(BasicStyle.DEFAULT_CSS)
BasicStyle cellTableStyle();
}
/**
* A ClientBundle that provides images for this widget.
*/
public interface Resources extends ClientBundle {
/**
* The background used for footer cells.
*/
@Source("cellTableHeaderBackground.png")
@ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
ImageResource cellTableFooterBackground();
/**
* The background used for header cells.
*/
@ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
ImageResource cellTableHeaderBackground();
/**
* The loading indicator used while the table is waiting for data.
*/
@ImageOptions(flipRtl = true)
ImageResource cellTableLoading();
/**
* The background used for selected cells.
*/
@Source("cellListSelectedBackground.png")
@ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
ImageResource cellTableSelectedBackground();
/**
* Icon used when a column is sorted in ascending order.
*/
@Source("sortAscending.png")
@ImageOptions(flipRtl = true)
ImageResource cellTableSortAscending();
/**
* Icon used when a column is sorted in descending order.
*/
@Source("sortDescending.png")
@ImageOptions(flipRtl = true)
ImageResource cellTableSortDescending();
/**
* The styles used in this widget.
*/
@Source(Style.DEFAULT_CSS)
Style cellTableStyle();
}
/**
* Styles used by this widget.
*/
@ImportedWithPrefix("gwt-CellTable")
public interface Style extends CssResource {
/**
* The path to the default CSS styles used by this resource.
*/
String DEFAULT_CSS = "com/google/gwt/user/cellview/client/CellTable.css";
/**
* Applied to every cell.
*/
String cellTableCell();
/**
* Applied to even rows.
*/
String cellTableEvenRow();
/**
* Applied to cells in even rows.
*/
String cellTableEvenRowCell();
/**
* Applied to the first column.
*/
String cellTableFirstColumn();
/**
* Applied to the first column footers.
*/
String cellTableFirstColumnFooter();
/**
* Applied to the first column headers.
*/
String cellTableFirstColumnHeader();
/**
* Applied to footers cells.
*/
String cellTableFooter();
/**
* Applied to headers cells.
*/
String cellTableHeader();
/**
* Applied to the hovered row.
*/
String cellTableHoveredRow();
/**
* Applied to the cells in the hovered row.
*/
String cellTableHoveredRowCell();
/**
* Applied to the keyboard selected cell.
*/
String cellTableKeyboardSelectedCell();
/**
* Applied to the keyboard selected row.
*/
String cellTableKeyboardSelectedRow();
/**
* Applied to the cells in the keyboard selected row.
*/
String cellTableKeyboardSelectedRowCell();
/**
* Applied to the last column.
*/
String cellTableLastColumn();
/**
* Applied to the last column footers.
*/
String cellTableLastColumnFooter();
/**
* Applied to the last column headers.
*/
String cellTableLastColumnHeader();
/**
* Applied to the loading indicator.
*/
String cellTableLoading();
/**
* Applied to odd rows.
*/
String cellTableOddRow();
/**
* Applied to cells in odd rows.
*/
String cellTableOddRowCell();
/**
* Applied to selected rows.
*/
String cellTableSelectedRow();
/**
* Applied to cells in selected rows.
*/
String cellTableSelectedRowCell();
/**
* Applied to header cells that are sortable.
*/
String cellTableSortableHeader();
/**
* Applied to header cells that are sorted in ascending order.
*/
String cellTableSortedHeaderAscending();
/**
* Applied to header cells that are sorted in descending order.
*/
String cellTableSortedHeaderDescending();
/**
* Applied to the table.
*/
String cellTableWidget();
}
/**
* Styles used by {@link BasicResources}.
*/
@ImportedWithPrefix("gwt-CellTable")
interface BasicStyle extends Style {
/**
* The path to the default CSS styles used by this resource.
*/
String DEFAULT_CSS = "com/google/gwt/user/cellview/client/CellTableBasic.css";
}
interface Template extends SafeHtmlTemplates {
@Template("{0}")
SafeHtml div(SafeHtml contents);
@Template("{1}")
SafeHtml divFocusable(int tabIndex, SafeHtml contents);
@Template("{2}")
SafeHtml divFocusableWithKey(int tabIndex, char accessKey, SafeHtml contents);
@Template("{0}
")
SafeHtml tbody(SafeHtml rowHtml);
@Template("{1} ")
SafeHtml td(String classes, SafeHtml contents);
@Template("{3} ")
SafeHtml tdBothAlign(String classes, String hAlign, String vAlign,
SafeHtml contents);
@Template("{2} ")
SafeHtml tdHorizontalAlign(String classes, String hAlign, SafeHtml contents);
@Template("{2} ")
SafeHtml tdVerticalAlign(String classes, String vAlign, SafeHtml contents);
@Template("{0}
")
SafeHtml tfoot(SafeHtml rowHtml);
@Template("{2} ")
SafeHtml th(int colspan, String classes, SafeHtml contents);
@Template("{0}
")
SafeHtml thead(SafeHtml rowHtml);
@Template("{1} ")
SafeHtml tr(String classes, SafeHtml contents);
}
/**
* Implementation of {@link CellTable}.
*/
private static class Impl {
private final com.google.gwt.user.client.Element tmpElem = Document.get().createDivElement().cast();
/**
* Convert the rowHtml into Elements wrapped by the specified table section.
*
* @param table the {@link CellTable}
* @param sectionTag the table section tag
* @param rowHtml the Html for the rows
* @return the section element
*/
protected TableSectionElement convertToSectionElement(CellTable> table,
String sectionTag, SafeHtml rowHtml) {
// Attach an event listener so we can catch synchronous load events from
// cached images.
DOM.setEventListener(tmpElem, table);
// Render the rows into a table.
// IE doesn't support innerHtml on a TableSection or Table element, so we
// generate the entire table.
sectionTag = sectionTag.toLowerCase();
if ("tbody".equals(sectionTag)) {
tmpElem.setInnerHTML(template.tbody(rowHtml).asString());
} else if ("thead".equals(sectionTag)) {
tmpElem.setInnerHTML(template.thead(rowHtml).asString());
} else if ("tfoot".equals(sectionTag)) {
tmpElem.setInnerHTML(template.tfoot(rowHtml).asString());
} else {
throw new IllegalArgumentException("Invalid table section tag: "
+ sectionTag);
}
TableElement tableElem = tmpElem.getFirstChildElement().cast();
// Detach the event listener.
DOM.setEventListener(tmpElem, null);
// Get the section out of the table.
if ("tbody".equals(sectionTag)) {
return tableElem.getTBodies().getItem(0);
} else if ("thead".equals(sectionTag)) {
return tableElem.getTHead();
} else if ("tfoot".equals(sectionTag)) {
return tableElem.getTFoot();
} else {
throw new IllegalArgumentException("Invalid table section tag: "
+ sectionTag);
}
}
/**
* Render a table section in the table.
*
* @param table the {@link CellTable}
* @param section the {@link TableSectionElement} to replace
* @param html the html to render
*/
protected void replaceAllRows(CellTable> table,
TableSectionElement section, SafeHtml html) {
// If the widget is not attached, attach an event listener so we can catch
// synchronous load events from cached images.
if (!table.isAttached()) {
DOM.setEventListener(table.getElement(), table);
}
// Render the html.
section.setInnerHTML(html.asString());
// Detach the event listener.
if (!table.isAttached()) {
DOM.setEventListener(table.getElement(), null);
}
}
}
/**
* Implementation of {@link CellTable} used by IE.
*/
@SuppressWarnings("unused")
private static class ImplTrident extends Impl {
/**
* IE doesn't support innerHTML on tbody, nor does it support removing or
* replacing a tbody. The only solution is to remove and replace the rows
* themselves.
*/
@Override
protected void replaceAllRows(CellTable> table,
TableSectionElement section, SafeHtml html) {
// Remove all children.
Element child = section.getFirstChildElement();
while (child != null) {
Element next = child.getNextSiblingElement();
section.removeChild(child);
child = next;
}
// Add new child elements.
TableSectionElement newSection = convertToSectionElement(table,
section.getTagName(), html);
child = newSection.getFirstChildElement();
while (child != null) {
Element next = child.getNextSiblingElement();
section.appendChild(child);
child = next;
}
}
}
/**
* The default page size.
*/
private static final int DEFAULT_PAGESIZE = 15;
private static Resources DEFAULT_RESOURCES;
/**
* The table specific {@link Impl}.
*/
private static Impl TABLE_IMPL;
private static Template template;
private static Resources getDefaultResources() {
if (DEFAULT_RESOURCES == null) {
DEFAULT_RESOURCES = GWT.create(Resources.class);
}
return DEFAULT_RESOURCES;
}
/**
* Create the default loading indicator using the loading image in the
* specified {@link Resources}.
*
* @param resources the resources
* @return a widget loading indicator
*/
private static Widget createDefaultLoadingIndicator(Resources resources) {
ImageResource loadingImg = resources.cellTableLoading();
return (loadingImg == null) ? null
: new Image(resources.cellTableLoading());
}
final TableColElement colgroup;
private boolean cellIsEditing;
private final List> columns = new ArrayList>();
private final Map, String> columnWidths = new HashMap, String>();
/**
* Indicates that at least one column depends on selection.
*/
private boolean dependsOnSelection;
private final SimplePanel emptyTableWidgetContainer = new SimplePanel();
private final List> footers = new ArrayList>();
/**
* Indicates that at least one column handles selection.
*/
private boolean handlesSelection;
private final List> headers = new ArrayList>();
private TableRowElement hoveringRow;
/**
* Indicates that at least one column is interactive.
*/
private boolean isInteractive;
private int keyboardSelectedColumn = 0;
private final SimplePanel loadingIndicatorContainer = new SimplePanel();
/**
* A {@link DeckPanel} to hold widgets associated with various loading states.
*/
private final DeckPanel messagesPanel = new DeckPanel();
private final Resources resources;
private RowStyles rowStyles;
private IconCellDecorator sortAscDecorator;
private IconCellDecorator sortDescDecorator;
private final ColumnSortList sortList;
private final Style style;
private final TableElement table;
private final TableSectionElement tbody;
private final TableSectionElement tbodyLoading;
private final TableCellElement tbodyLoadingCell;
private final TableSectionElement tfoot;
private final TableSectionElement thead;
private boolean updatingSortList;
/**
* Constructs a table with a default page size of 15.
*/
public CellTable() {
this(DEFAULT_PAGESIZE);
}
/**
* Constructs a table with the given page size.
*
* @param pageSize the page size
*/
public CellTable(final int pageSize) {
this(pageSize, getDefaultResources(), null);
}
/**
* Constructs a table with a default page size of 15, and the given
* {@link ProvidesKey key provider}.
*
* @param keyProvider an instance of ProvidesKey, or null if the record
* object should act as its own key
*/
public CellTable(ProvidesKey keyProvider) {
this(DEFAULT_PAGESIZE, getDefaultResources(), keyProvider);
}
/**
* Constructs a table with the given page size with the specified
* {@link Resources}.
*
* @param pageSize the page size
* @param resources the resources to use for this widget
*/
public CellTable(final int pageSize, Resources resources) {
this(pageSize, resources, null);
}
/**
* Constructs a table with the given page size and the given
* {@link ProvidesKey key provider}.
*
* @param pageSize the page size
* @param keyProvider an instance of ProvidesKey, or null if the record
* object should act as its own key
*/
public CellTable(final int pageSize, ProvidesKey keyProvider) {
this(pageSize, getDefaultResources(), keyProvider);
}
/**
* Constructs a table with the given page size, the specified
* {@link Resources}, and the given key provider.
*
* @param pageSize the page size
* @param resources the resources to use for this widget
* @param keyProvider an instance of ProvidesKey, or null if the record
* object should act as its own key
*/
public CellTable(final int pageSize, Resources resources,
ProvidesKey keyProvider) {
this(pageSize, resources, keyProvider,
createDefaultLoadingIndicator(resources));
}
/**
* Constructs a table with the specified page size, {@link Resources}, key
* provider, and loading indicator.
*
* @param pageSize the page size
* @param resources the resources to use for this widget
* @param keyProvider an instance of ProvidesKey, or null if the record
* object should act as its own key
* @param loadingIndicator the widget to use as a loading indicator, or null
* to disable
*/
public CellTable(final int pageSize, Resources resources,
ProvidesKey keyProvider, Widget loadingIndicator) {
super(Document.get().createTableElement(), pageSize, keyProvider);
if (TABLE_IMPL == null) {
TABLE_IMPL = GWT.create(Impl.class);
}
if (template == null) {
template = GWT.create(Template.class);
}
this.resources = resources;
this.style = resources.cellTableStyle();
this.style.ensureInjected();
// Create the ColumnSortList and delegate.
sortList = new ColumnSortList(new ColumnSortList.Delegate() {
public void onModification() {
if (!updatingSortList) {
createHeaders(false);
}
}
});
table = getElement().cast();
table.setCellSpacing(0);
colgroup = Document.get().createColGroupElement();
table.appendChild(colgroup);
thead = table.createTHead();
// Some browsers create a tbody automatically, others do not.
if (table.getTBodies().getLength() > 0) {
tbody = table.getTBodies().getItem(0);
} else {
tbody = Document.get().createTBodyElement();
table.appendChild(tbody);
}
table.appendChild(tbodyLoading = Document.get().createTBodyElement());
tfoot = table.createTFoot();
setStyleName(this.style.cellTableWidget());
// Attach the messages panel.
{
tbodyLoadingCell = Document.get().createTDElement();
TableRowElement tr = Document.get().createTRElement();
tbodyLoading.appendChild(tr);
tr.appendChild(tbodyLoadingCell);
tbodyLoadingCell.setAlign("center");
tbodyLoadingCell.appendChild(messagesPanel.getElement());
adopt(messagesPanel);
messagesPanel.add(emptyTableWidgetContainer);
messagesPanel.add(loadingIndicatorContainer);
loadingIndicatorContainer.setStyleName(style.cellTableLoading());
}
// Set the loading indicator.
setLoadingIndicator(loadingIndicator); // Can be null.
// Sink events.
Set eventTypes = new HashSet();
eventTypes.add("mouseover");
eventTypes.add("mouseout");
CellBasedWidgetImpl.get().sinkEvents(this, eventTypes);
}
/**
* Adds a column to the end of the table.
*
* @param col the column to be added
*/
public void addColumn(Column col) {
insertColumn(getColumnCount(), col);
}
/**
* Adds a column to the end of the table with an associated header.
*
* @param col the column to be added
* @param header the associated {@link Header}
*/
public void addColumn(Column col, Header> header) {
insertColumn(getColumnCount(), col, header);
}
/**
* Adds a column to the end of the table with an associated header and footer.
*
* @param col the column to be added
* @param header the associated {@link Header}
* @param footer the associated footer (as a {@link Header} object)
*/
public void addColumn(Column col, Header> header, Header> footer) {
insertColumn(getColumnCount(), col, header, footer);
}
/**
* Adds a column to the end of the table with an associated String header.
*
* @param col the column to be added
* @param headerString the associated header text, as a String
*/
public void addColumn(Column col, String headerString) {
insertColumn(getColumnCount(), col, headerString);
}
/**
* Adds a column to the end of the table with an associated {@link SafeHtml}
* header.
*
* @param col the column to be added
* @param headerHtml the associated header text, as safe HTML
*/
public void addColumn(Column col, SafeHtml headerHtml) {
insertColumn(getColumnCount(), col, headerHtml);
}
/**
* Adds a column to the end of the table with an associated String header and
* footer.
*
* @param col the column to be added
* @param headerString the associated header text, as a String
* @param footerString the associated footer text, as a String
*/
public void addColumn(Column col, String headerString,
String footerString) {
insertColumn(getColumnCount(), col, headerString, footerString);
}
/**
* Adds a column to the end of the table with an associated {@link SafeHtml}
* header and footer.
*
* @param col the column to be added
* @param headerHtml the associated header text, as safe HTML
* @param footerHtml the associated footer text, as safe HTML
*/
public void addColumn(Column col, SafeHtml headerHtml,
SafeHtml footerHtml) {
insertColumn(getColumnCount(), col, headerHtml, footerHtml);
}
/**
* Add a handler to handle {@link ColumnSortEvent}s.
*
* @param handler the {@link ColumnSortEvent.Handler} to add
* @return a {@link HandlerRegistration} to remove the handler
*/
public HandlerRegistration addColumnSortHandler(
ColumnSortEvent.Handler handler) {
return addHandler(handler, ColumnSortEvent.getType());
}
/**
* Add a style name to the {@link TableColElement} at the specified index,
* creating it if necessary.
*
* @param index the column index
* @param styleName the style name to add
*/
public void addColumnStyleName(int index, String styleName) {
ensureTableColElement(index).addClassName(styleName);
}
/**
* Clear the width of the specified {@link Column}.
*
* @param column the column
*/
public void clearColumnWidth(Column column) {
columnWidths.remove(column);
refreshColumnWidths();
}
/**
* Return the height of the table body.
*
* @return an int representing the body height
*/
public int getBodyHeight() {
int height = getClientHeight(tbody);
return height;
}
/**
* Get the column at the specified index.
*
* @param col the index of the column to retrieve
* @return the {@link Column} at the index
*/
public Column getColumn(int col) {
checkColumnBounds(col);
return columns.get(col);
}
/**
* Get the number of columns in the table.
*
* @return the column count
*/
public int getColumnCount() {
return columns.size();
}
/**
* Get the index of the specified column.
*
* @param column the column to search for
* @return the index of the column, or -1 if not found
*/
public int getColumnIndex(Column column) {
return columns.indexOf(column);
}
/**
* Get the {@link ColumnSortList} that specifies which columns are sorted.
* Modifications to the {@link ColumnSortList} will be reflected in the table
* header.
*
* @return the {@link ColumnSortList}
*/
public ColumnSortList getColumnSortList() {
return sortList;
}
/**
* Get the widget displayed when the table has no rows.
*
* @return the empty table widget
*/
public Widget getEmptyTableWidget() {
return emptyTableWidgetContainer.getWidget();
}
/**
* Return the height of the table header.
*
* @return an int representing the header height
*/
public int getHeaderHeight() {
int height = getClientHeight(thead);
return height;
}
/**
* Get the widget displayed when the data is loading.
*
* @return the loading indicator
*/
public Widget getLoadingIndicator() {
return loadingIndicatorContainer.getWidget();
}
/**
* Get the {@link TableRowElement} for the specified row. If the row element
* has not been created, null is returned.
*
* @param row the row index
* @return the row element, or null if it doesn't exists
* @throws IndexOutOfBoundsException if the row index is outside of the
* current page
*/
public TableRowElement getRowElement(int row) {
getPresenter().flush();
checkRowBounds(row);
NodeList rows = tbody.getRows();
return rows.getLength() > row ? rows.getItem(row) : null;
}
/**
* Inserts a column into the table at the specified index.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
*/
public void insertColumn(int beforeIndex, Column col) {
insertColumn(beforeIndex, col, (Header>) null, (Header>) null);
}
/**
* Inserts a column into the table at the specified index with an associated
* header.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param header the associated {@link Header}
*/
public void insertColumn(int beforeIndex, Column col, Header> header) {
insertColumn(beforeIndex, col, header, null);
}
/**
* Inserts a column into the table at the specified index with an associated
* header and footer.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param header the associated {@link Header}
* @param footer the associated footer (as a {@link Header} object)
* @throws IndexOutOfBoundsException if the index is out of range
*/
public void insertColumn(int beforeIndex, Column col, Header> header,
Header> footer) {
// Allow insert at the end.
if (beforeIndex != getColumnCount()) {
checkColumnBounds(beforeIndex);
}
headers.add(beforeIndex, header);
footers.add(beforeIndex, footer);
columns.add(beforeIndex, col);
boolean wasinteractive = isInteractive;
updateDependsOnSelection();
// Move the keyboard selected column if the current column is not
// interactive.
if (!wasinteractive && isInteractive) {
keyboardSelectedColumn = beforeIndex;
}
// Sink events used by the new column.
Set consumedEvents = new HashSet();
{
Set cellEvents = col.getCell().getConsumedEvents();
if (cellEvents != null) {
consumedEvents.addAll(cellEvents);
}
}
if (header != null) {
Set headerEvents = header.getCell().getConsumedEvents();
if (headerEvents != null) {
consumedEvents.addAll(headerEvents);
}
}
if (footer != null) {
Set footerEvents = footer.getCell().getConsumedEvents();
if (footerEvents != null) {
consumedEvents.addAll(footerEvents);
}
}
CellBasedWidgetImpl.get().sinkEvents(this, consumedEvents);
redraw();
}
/**
* Inserts a column into the table at the specified index with an associated
* String header.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param headerString the associated header text, as a String
*/
public void insertColumn(int beforeIndex, Column col,
String headerString) {
insertColumn(beforeIndex, col, new TextHeader(headerString), null);
}
/**
* Inserts a column into the table at the specified index with an associated
* {@link SafeHtml} header.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param headerHtml the associated header text, as safe HTML
*/
public void insertColumn(int beforeIndex, Column col,
SafeHtml headerHtml) {
insertColumn(beforeIndex, col, new SafeHtmlHeader(headerHtml), null);
}
/**
* Inserts a column into the table at the specified index with an associated
* String header and footer.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param headerString the associated header text, as a String
* @param footerString the associated footer text, as a String
*/
public void insertColumn(int beforeIndex, Column col,
String headerString, String footerString) {
insertColumn(beforeIndex, col, new TextHeader(headerString),
new TextHeader(footerString));
}
/**
* Inserts a column into the table at the specified index with an associated
* {@link SafeHtml} header and footer.
*
* @param beforeIndex the index to insert the column
* @param col the column to be added
* @param headerHtml the associated header text, as safe HTML
* @param footerHtml the associated footer text, as safe HTML
*/
public void insertColumn(int beforeIndex, Column col,
SafeHtml headerHtml, SafeHtml footerHtml) {
insertColumn(beforeIndex, col, new SafeHtmlHeader(headerHtml),
new SafeHtmlHeader(footerHtml));
}
@Override
public void redraw() {
refreshColumnWidths();
super.redraw();
}
/**
* Redraw the table's footers.
*/
public void redrawFooters() {
createHeaders(true);
}
/**
* Redraw the table's headers.
*/
public void redrawHeaders() {
createHeaders(false);
}
/**
* Remove a column.
*
* @param col the column to remove
*/
public void removeColumn(Column col) {
int index = columns.indexOf(col);
if (index < 0) {
throw new IllegalArgumentException(
"The specified column is not part of this table.");
}
removeColumn(index);
}
/**
* Remove a column.
*
* @param index the column index
*/
public void removeColumn(int index) {
if (index < 0 || index >= columns.size()) {
throw new IndexOutOfBoundsException(
"The specified column index is out of bounds.");
}
columns.remove(index);
headers.remove(index);
footers.remove(index);
updateDependsOnSelection();
// Find an interactive column. Stick with 0 if no column is interactive.
if (index <= keyboardSelectedColumn) {
keyboardSelectedColumn = 0;
if (isInteractive) {
for (int i = 0; i < columns.size(); i++) {
if (isColumnInteractive(columns.get(i))) {
keyboardSelectedColumn = i;
break;
}
}
}
}
// Redraw the table asynchronously.
redraw();
// We don't unsink events because other handlers or user code may have sunk
// them intentionally.
}
/**
* Remove a style from the {@link TableColElement} at the specified index.
*
* @param index the column index
* @param styleName the style name to remove
*/
public void removeColumnStyleName(int index, String styleName) {
if (index >= colgroup.getChildCount()) {
return;
}
ensureTableColElement(index).removeClassName(styleName);
}
/**
* Set the width of a {@link Column}. The layout behavior depends on whether
* or not the table is using fixed layout.
*
* @param column the column
* @param width the width of the column
* @see #setTableLayoutFixed(boolean)
*/
public void setColumnWidth(Column column, String width) {
columnWidths.put(column, width);
refreshColumnWidths();
}
/**
* Set the width of a {@link Column}. The layout behavior depends on whether
* or not the table is using fixed layout.
*
* @param column the column
* @param width the width of the column
* @param unit the {@link Unit} of measurement
* @see #setTableLayoutFixed(boolean)
*/
public void setColumnWidth(Column column, double width, Unit unit) {
setColumnWidth(column, width + unit.getType());
}
/**
* Set the widget to display when the table has no rows.
*
* @param widget the empty table widget, or null to disable
*/
public void setEmptyTableWidget(Widget widget) {
emptyTableWidgetContainer.setWidget(widget);
}
/**
* Set the widget to display when the data is loading.
*
* @param widget the loading indicator, or null to disable
*/
public void setLoadingIndicator(Widget widget) {
loadingIndicatorContainer.setWidget(widget);
}
/**
* Sets the object used to determine how a row is styled; the change will take
* effect the next time that the table is rendered.
*
* @param rowStyles a {@link RowStyles} object
*/
public void setRowStyles(RowStyles rowStyles) {
this.rowStyles = rowStyles;
}
/**
*
* Enable or disable fixed table layout.
*
*
*
*
Fixed Table Layout
* When using the fixed table layout, cell contents are truncated as needed,
* which allows you to set the exact width of columns and the table. The
* default column width is 0 (invisible). In order to see all columns, you
* must set the width of the table (recommended 100%), or set the width of
* every column in the table. The following conditions are true for fixed
* layout tables:
*
* -
* If the widths of all columns are set, the width becomes a weight and
* the columns are resized proportionally.
* - If the widths of some columns are set using absolute values
* (PX), those columns are fixed and the remaining width is divided evenly
* over the other columns. If there is no remaining width, the other columns
* will not be visible.
* - If the width of some columns are set in absolute values (PX) and others
* are set in relative values (PCT), the absolute columns will be fixed and
* the remaining width is divided proportionally over the PCT columns. This
* allows users to define how the remaining width is allocated.
*
*
*
* @param isFixed true to use fixed table layout, false not to
* @see W3C HTML
* Specification
*/
public void setTableLayoutFixed(boolean isFixed) {
if (isFixed) {
table.getStyle().setTableLayout(TableLayout.FIXED);
} else {
table.getStyle().clearTableLayout();
}
}
/**
* Set the width of the width and specify whether or not it should use fixed
* table layout. See {@link #setTableLayoutFixed(boolean)} for more
* information about fixed layout tables.
*
* @param width the width of the table
* @param isFixedLayout true to use fixed width layout, false not to
* @see #setTableLayoutFixed(boolean)
* @see W3C HTML
* Specification
*/
public final void setWidth(String width, boolean isFixedLayout) {
super.setWidth(width);
setTableLayoutFixed(isFixedLayout);
}
@Override
protected Element convertToElements(SafeHtml html) {
return TABLE_IMPL.convertToSectionElement(CellTable.this, "tbody", html);
}
@Override
protected boolean dependsOnSelection() {
return dependsOnSelection;
}
/**
* Called when a user action triggers selection.
*
* @param event the event that triggered selection
* @param value the value that was selected
* @param row the row index of the value on the page
* @param column the column index where the event occurred
* @deprecated use
* {@link #addCellPreviewHandler(com.google.gwt.view.client.CellPreviewEvent.Handler)}
* instead
*/
@Deprecated
protected void doSelection(Event event, T value, int row, int column) {
}
@Override
protected Element getChildContainer() {
return tbody;
}
@Override
protected Element getKeyboardSelectedElement() {
// Do not use getRowElement() because that will flush the presenter.
int rowIndex = getKeyboardSelectedRow();
NodeList rows = tbody.getRows();
if (rowIndex >= 0 && rowIndex < rows.getLength() && columns.size() > 0) {
TableRowElement tr = rows.getItem(rowIndex);
TableCellElement td = tr.getCells().getItem(keyboardSelectedColumn);
return getCellParent(td);
}
return null;
}
@Override
protected boolean isKeyboardNavigationSuppressed() {
return cellIsEditing;
}
@Override
protected void onBlur() {
Element elem = getKeyboardSelectedElement();
if (elem != null) {
TableCellElement td = elem.getParentElement().cast();
TableRowElement tr = td.getParentElement().cast();
td.removeClassName(style.cellTableKeyboardSelectedCell());
setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
style.cellTableKeyboardSelectedRowCell(), false);
}
}
@SuppressWarnings("deprecation")
@Override
protected void onBrowserEvent2(Event event) {
// Get the event target.
EventTarget eventTarget = event.getEventTarget();
if (!Element.is(eventTarget)) {
return;
}
final Element target = event.getEventTarget().cast();
// Ignore keydown events unless the cell is in edit mode
String eventType = event.getType();
if ("keydown".equals(eventType) && !isKeyboardNavigationSuppressed()
&& KeyboardSelectionPolicy.DISABLED != getKeyboardSelectionPolicy()) {
if (handleKey(event)) {
return;
}
}
// Find the cell where the event occurred.
TableCellElement tableCell = findNearestParentCell(target);
if (tableCell == null) {
return;
}
// Determine if we are in the header, footer, or body. Its possible that
// the table has been refreshed before the current event fired (ex. change
// event refreshes before mouseup fires), so we need to check each parent
// element.
Element trElem = tableCell.getParentElement();
if (trElem == null) {
return;
}
TableRowElement tr = TableRowElement.as(trElem);
Element sectionElem = tr.getParentElement();
if (sectionElem == null) {
return;
}
TableSectionElement section = TableSectionElement.as(sectionElem);
// Forward the event to the associated header, footer, or column.
boolean isClick = "click".equals(eventType);
int col = tableCell.getCellIndex();
if (section == thead) {
Header> header = headers.get(col);
if (header != null) {
// Fire the event to the header.
if (cellConsumesEventType(header.getCell(), eventType)) {
Context context = new Context(0, col, header.getKey());
header.onBrowserEvent(context, tableCell, event);
}
// Sort the header.
Column column = columns.get(col);
if (isClick && column.isSortable()) {
updatingSortList = true;
sortList.push(column);
updatingSortList = false;
ColumnSortEvent.fire(this, sortList);
}
}
} else if (section == tfoot) {
Header> footer = footers.get(col);
if (footer != null && cellConsumesEventType(footer.getCell(), eventType)) {
Context context = new Context(0, col, footer.getKey());
footer.onBrowserEvent(context, tableCell, event);
}
} else if (section == tbody) {
// Update the hover state.
int row = tr.getSectionRowIndex();
if ("mouseover".equals(eventType)) {
// Unstyle the old row if it is still part of the table.
if (hoveringRow != null && tbody.isOrHasChild(hoveringRow)) {
setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
style.cellTableHoveredRowCell(), false);
}
hoveringRow = tr;
setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
style.cellTableHoveredRowCell(), true);
} else if ("mouseout".equals(eventType) && hoveringRow != null) {
setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
style.cellTableHoveredRowCell(), false);
hoveringRow = null;
} else if (isClick
&& ((getPresenter().getKeyboardSelectedRowInView() != row) || (keyboardSelectedColumn != col))) {
// Move keyboard focus. Since the user clicked, allow focus to go to a
// non-interactive column.
boolean isFocusable = CellBasedWidgetImpl.get().isFocusable(target);
isFocused = isFocused || isFocusable;
keyboardSelectedColumn = col;
getPresenter().setKeyboardSelectedRow(row, !isFocusable, true);
}
// Update selection. Selection occurs before firing the event to the cell
// in case the cell operates on the currently selected item.
if (!isRowWithinBounds(row)) {
// If the event causes us to page, then the physical index will be out
// of bounds of the underlying data.
return;
}
boolean isSelectionHandled = handlesSelection
|| KeyboardSelectionPolicy.BOUND_TO_SELECTION == getKeyboardSelectionPolicy();
T value = getVisibleItem(row);
Context context = new Context(row + getPageStart(), col,
getValueKey(value));
CellPreviewEvent previewEvent = CellPreviewEvent.fire(this, event,
this, context, value, cellIsEditing, isSelectionHandled);
if (isClick && !cellIsEditing && !isSelectionHandled) {
doSelection(event, value, row, col);
}
// Pass the event to the cell.
if (!previewEvent.isCanceled()) {
fireEventToCell(event, eventType, tableCell, value, context,
columns.get(col));
}
}
}
@Override
protected void onFocus() {
Element elem = getKeyboardSelectedElement();
if (elem != null) {
TableCellElement td = elem.getParentElement().cast();
TableRowElement tr = td.getParentElement().cast();
td.addClassName(style.cellTableKeyboardSelectedCell());
setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
style.cellTableKeyboardSelectedRowCell(), true);
}
}
/**
* Called when the loading state changes.
*
* @param state the new loading state
*/
@Override
protected void onLoadingStateChanged(LoadingState state) {
Widget message = null;
if (state == LoadingState.LOADING) {
// Loading indicator.
message = loadingIndicatorContainer;
} else if (state == LoadingState.LOADED && getPresenter().isEmpty()) {
// Empty table.
message = emptyTableWidgetContainer;
}
// Switch out the message to display.
if (message != null) {
messagesPanel.showWidget(messagesPanel.getWidgetIndex(message));
}
// Adjust the colspan of the messages panel container.
tbodyLoadingCell.setColSpan(Math.max(1, columns.size()));
// Show the correct container.
showOrHide(getChildContainer(), message == null);
showOrHide(tbodyLoading, message != null);
// Fire an event.
super.onLoadingStateChanged(state);
}
@Override
protected void renderRowValues(SafeHtmlBuilder sb, List values, int start,
SelectionModel super T> selectionModel) {
createHeadersAndFooters();
int keyboardSelectedRow = getKeyboardSelectedRow() + getPageStart();
String evenRowStyle = style.cellTableEvenRow();
String oddRowStyle = style.cellTableOddRow();
String cellStyle = style.cellTableCell();
String evenCellStyle = " " + style.cellTableEvenRowCell();
String oddCellStyle = " " + style.cellTableOddRowCell();
String firstColumnStyle = " " + style.cellTableFirstColumn();
String lastColumnStyle = " " + style.cellTableLastColumn();
String selectedRowStyle = " " + style.cellTableSelectedRow();
String selectedCellStyle = " " + style.cellTableSelectedRowCell();
String keyboardRowStyle = " " + style.cellTableKeyboardSelectedRow();
String keyboardRowCellStyle = " "
+ style.cellTableKeyboardSelectedRowCell();
String keyboardCellStyle = " " + style.cellTableKeyboardSelectedCell();
int columnCount = columns.size();
int length = values.size();
int end = start + length;
for (int i = start; i < end; i++) {
T value = values.get(i - start);
boolean isSelected = (selectionModel == null || value == null) ? false
: selectionModel.isSelected(value);
boolean isEven = i % 2 == 0;
boolean isKeyboardSelected = i == keyboardSelectedRow && isFocused;
String trClasses = isEven ? evenRowStyle : oddRowStyle;
if (isSelected) {
trClasses += selectedRowStyle;
}
if (isKeyboardSelected) {
trClasses += keyboardRowStyle;
}
if (rowStyles != null) {
String extraRowStyles = rowStyles.getStyleNames(value, i);
if (extraRowStyles != null) {
trClasses += " ";
trClasses += extraRowStyles;
}
}
SafeHtmlBuilder trBuilder = new SafeHtmlBuilder();
int curColumn = 0;
for (Column column : columns) {
String tdClasses = cellStyle;
tdClasses += isEven ? evenCellStyle : oddCellStyle;
if (curColumn == 0) {
tdClasses += firstColumnStyle;
}
if (isSelected) {
tdClasses += selectedCellStyle;
}
if (isKeyboardSelected) {
tdClasses += keyboardRowCellStyle;
}
// The first and last column could be the same column.
if (curColumn == columnCount - 1) {
tdClasses += lastColumnStyle;
}
SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
if (value != null) {
Context context = new Context(i, curColumn, getValueKey(value));
column.render(context, value, cellBuilder);
}
// Build the contents.
SafeHtml contents = SafeHtmlUtils.EMPTY_SAFE_HTML;
if (i == keyboardSelectedRow && curColumn == keyboardSelectedColumn) {
// This is the focused cell.
if (isFocused) {
tdClasses += keyboardCellStyle;
}
char accessKey = getAccessKey();
if (accessKey != 0) {
contents = template.divFocusableWithKey(getTabIndex(), accessKey,
cellBuilder.toSafeHtml());
} else {
contents = template.divFocusable(getTabIndex(),
cellBuilder.toSafeHtml());
}
} else {
contents = template.div(cellBuilder.toSafeHtml());
}
// Build the cell.
HorizontalAlignmentConstant hAlign = column.getHorizontalAlignment();
VerticalAlignmentConstant vAlign = column.getVerticalAlignment();
if (hAlign != null && vAlign != null) {
trBuilder.append(template.tdBothAlign(tdClasses,
hAlign.getTextAlignString(), vAlign.getVerticalAlignString(),
contents));
} else if (hAlign != null) {
trBuilder.append(template.tdHorizontalAlign(tdClasses,
hAlign.getTextAlignString(), contents));
} else if (vAlign != null) {
trBuilder.append(template.tdVerticalAlign(tdClasses,
vAlign.getVerticalAlignString(), contents));
} else {
trBuilder.append(template.td(tdClasses, contents));
}
curColumn++;
}
sb.append(template.tr(trClasses, trBuilder.toSafeHtml()));
}
}
@Override
protected void replaceAllChildren(List values, SafeHtml html) {
TABLE_IMPL.replaceAllRows(CellTable.this, tbody,
CellBasedWidgetImpl.get().processHtml(html));
}
@Override
protected boolean resetFocusOnCell() {
int row = getKeyboardSelectedRow();
if (isRowWithinBounds(row) && columns.size() > 0) {
Column column = columns.get(keyboardSelectedColumn);
return resetFocusOnCellImpl(row, keyboardSelectedColumn, column);
}
return false;
}
@Override
protected void setKeyboardSelected(int index, boolean selected,
boolean stealFocus) {
if (KeyboardSelectionPolicy.DISABLED == getKeyboardSelectionPolicy()
|| !isRowWithinBounds(index) || columns.size() == 0) {
return;
}
TableRowElement tr = getRowElement(index);
String cellStyle = style.cellTableKeyboardSelectedCell();
boolean updatedSelection = !selected || isFocused || stealFocus;
setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
style.cellTableKeyboardSelectedRowCell(), selected);
NodeList cells = tr.getCells();
for (int i = 0; i < cells.getLength(); i++) {
TableCellElement td = cells.getItem(i);
// Update the selected style.
setStyleName(td, cellStyle, updatedSelection && selected
&& i == keyboardSelectedColumn);
// Mark as focusable.
final com.google.gwt.user.client.Element cellParent = getCellParent(td).cast();
setFocusable(cellParent, selected && i == keyboardSelectedColumn);
}
// Move focus to the cell.
if (selected && stealFocus && !cellIsEditing) {
TableCellElement td = tr.getCells().getItem(keyboardSelectedColumn);
final com.google.gwt.user.client.Element cellParent = getCellParent(td).cast();
CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
public void execute() {
cellParent.focus();
}
});
}
}
/**
* @deprecated this method is never called by AbstractHasData, render the
* selected styles in
* {@link #renderRowValues(SafeHtmlBuilder, List, int, SelectionModel)}
*/
@Override
@Deprecated
protected void setSelected(Element elem, boolean selected) {
TableRowElement tr = elem.cast();
setRowStyleName(tr, style.cellTableSelectedRow(),
style.cellTableSelectedRowCell(), selected);
}
/**
* Check that the specified column is within bounds.
*
* @param col the column index
* @throws IndexOutOfBoundsException if the column is out of bounds
*/
private void checkColumnBounds(int col) {
if (col < 0 || col >= getColumnCount()) {
throw new IndexOutOfBoundsException("Column index is out of bounds: "
+ col);
}
}
/**
* Render the header or footer.
*
* @param isFooter true if this is the footer table, false if the header table
*/
private void createHeaders(boolean isFooter) {
List> theHeaders = isFooter ? footers : headers;
TableSectionElement section = isFooter ? tfoot : thead;
String className = isFooter ? style.cellTableFooter()
: style.cellTableHeader();
String firstColumnStyle = " "
+ (isFooter ? style.cellTableFirstColumnFooter()
: style.cellTableFirstColumnHeader());
String lastColumnStyle = " "
+ (isFooter ? style.cellTableLastColumnFooter()
: style.cellTableLastColumnHeader());
String sortableStyle = " " + style.cellTableSortableHeader();
String sortedAscStyle = " " + style.cellTableSortedHeaderAscending();
String sortedDescStyle = " " + style.cellTableSortedHeaderDescending();
boolean hasHeader = false;
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("");
int columnCount = columns.size();
if (columnCount > 0) {
// Get information about the sorted column.
ColumnSortInfo sortedInfo = (sortList.size() == 0) ? null
: sortList.get(0);
Column, ?> sortedColumn = (sortedInfo == null) ? null
: sortedInfo.getColumn();
boolean isSortAscending = (sortedInfo == null) ? false
: sortedInfo.isAscending();
// Setup the first column.
Header> prevHeader = theHeaders.get(0);
Column column = columns.get(0);
int prevColspan = 1;
boolean isSortable = false;
boolean isSorted = false;
StringBuilder classesBuilder = new StringBuilder(className);
classesBuilder.append(firstColumnStyle);
if (!isFooter && column.isSortable()) {
isSortable = true;
isSorted = (column == sortedColumn);
}
// Loop through all column headers.
int curColumn;
for (curColumn = 1; curColumn < columnCount; curColumn++) {
Header> header = theHeaders.get(curColumn);
if (header != prevHeader) {
// The header has changed, so append the previous one.
SafeHtml headerHtml = SafeHtmlUtils.EMPTY_SAFE_HTML;
if (prevHeader != null) {
hasHeader = true;
// Build the header.
SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
Context context = new Context(0, curColumn - prevColspan,
prevHeader.getKey());
prevHeader.render(context, headerBuilder);
// Wrap the header with a sort icon.
if (isSorted) {
SafeHtml unwrappedHeader = headerBuilder.toSafeHtml();
headerBuilder = new SafeHtmlBuilder();
getSortDecorator(isSortAscending).render(null, unwrappedHeader,
headerBuilder);
}
headerHtml = headerBuilder.toSafeHtml();
}
if (isSortable) {
classesBuilder.append(sortableStyle);
}
if (isSorted) {
classesBuilder.append(isSortAscending ? sortedAscStyle
: sortedDescStyle);
}
sb.append(template.th(prevColspan, classesBuilder.toString(),
headerHtml));
// Reset the previous header.
prevHeader = header;
prevColspan = 1;
classesBuilder = new StringBuilder(className);
isSortable = false;
isSorted = false;
} else {
// Increment the colspan if the headers == each other.
prevColspan++;
}
// Update the sorted state.
column = columns.get(curColumn);
if (!isFooter && column.isSortable()) {
isSortable = true;
isSorted = (column == sortedColumn);
}
}
// Append the last header.
SafeHtml headerHtml = SafeHtmlUtils.EMPTY_SAFE_HTML;
if (prevHeader != null) {
hasHeader = true;
// Build the header.
SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
Context context = new Context(0, curColumn - prevColspan,
prevHeader.getKey());
prevHeader.render(context, headerBuilder);
// Wrap the header with a sort icon.
if (isSorted) {
SafeHtml unwrappedHeader = headerBuilder.toSafeHtml();
headerBuilder = new SafeHtmlBuilder();
getSortDecorator(isSortAscending).render(null, unwrappedHeader,
headerBuilder);
}
headerHtml = headerBuilder.toSafeHtml();
}
if (isSortable) {
classesBuilder.append(sortableStyle);
}
if (isSorted) {
classesBuilder.append(isSortAscending ? sortedAscStyle
: sortedDescStyle);
}
// The first and last columns could be the same column.
classesBuilder.append(" ");
classesBuilder.append(lastColumnStyle);
sb.append(template.th(prevColspan, classesBuilder.toString(), headerHtml));
}
sb.appendHtmlConstant(" ");
// Render the section contents.
TABLE_IMPL.replaceAllRows(this, section, sb.toSafeHtml());
// If the section isn't used, hide it.
setVisible(section, hasHeader);
}
private void createHeadersAndFooters() {
createHeaders(false);
createHeaders(true);
}
/**
* Get the {@link TableColElement} at the specified index, creating it if
* necessary.
*
* @param index the column index
* @return the {@link TableColElement}
*/
private TableColElement ensureTableColElement(int index) {
// Ensure that we have enough columns.
for (int i = colgroup.getChildCount(); i <= index; i++) {
colgroup.appendChild(Document.get().createColElement());
}
return colgroup.getChild(index).cast();
}
/**
* Find and return the index of the next interactive column. If no column is
* interactive, 0 is returned. If the start index is the only interactive
* column, it is returned.
*
* @param start the start index, exclusive unless it is the only option
* @param reverse true to do a reverse search
* @return the interactive column index, or 0 if not interactive
*/
private int findInteractiveColumn(int start, boolean reverse) {
if (!isInteractive) {
return 0;
} else if (reverse) {
for (int i = start - 1; i >= 0; i--) {
if (isColumnInteractive(columns.get(i))) {
return i;
}
}
// Wrap to the end.
for (int i = columns.size() - 1; i >= start; i--) {
if (isColumnInteractive(columns.get(i))) {
return i;
}
}
} else {
for (int i = start + 1; i < columns.size(); i++) {
if (isColumnInteractive(columns.get(i))) {
return i;
}
}
// Wrap to the start.
for (int i = 0; i <= start; i++) {
if (isColumnInteractive(columns.get(i))) {
return i;
}
}
}
return 0;
}
/**
* Find the cell that contains the element. Note that the TD element is not
* the parent. The parent is the div inside the TD cell.
*
* @param elem the element
* @return the parent cell
*/
private TableCellElement findNearestParentCell(Element elem) {
while ((elem != null) && (elem != table)) {
// TODO: We need is() implementations in all Element subclasses.
// This would allow us to use TableCellElement.is() -- much cleaner.
String tagName = elem.getTagName();
if ("td".equalsIgnoreCase(tagName) || "th".equalsIgnoreCase(tagName)) {
return elem.cast();
}
elem = elem.getParentElement();
}
return null;
}
/**
* Fire an event to the Cell within the specified {@link TableCellElement}.
*/
private void fireEventToCell(Event event, String eventType,
TableCellElement tableCell, T value, Context context, Column column) {
Cell cell = column.getCell();
if (cellConsumesEventType(cell, eventType)) {
C cellValue = column.getValue(value);
Element parentElem = getCellParent(tableCell);
boolean cellWasEditing = cell.isEditing(context, parentElem, cellValue);
column.onBrowserEvent(context, parentElem, value, event);
cellIsEditing = cell.isEditing(context, parentElem, cellValue);
if (cellWasEditing && !cellIsEditing) {
CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
public void execute() {
setFocus(true);
}
});
}
}
}
/**
* Get the parent element that is passed to the {@link Cell} from the table
* cell element.
*
* @param td the table cell
* @return the parent of the {@link Cell}
*/
private Element getCellParent(TableCellElement td) {
return td.getFirstChildElement();
}
private native int getClientHeight(Element element) /*-{
return element.clientHeight;
}-*/;
/**
* Get the {@link IconCellDecorator} used to decorate sorted column headers.
*
* @param ascending true if ascending, false if descending
* @return the {@link IconCellDecorator}
*/
private IconCellDecorator getSortDecorator(boolean ascending) {
if (ascending) {
if (sortAscDecorator == null) {
sortAscDecorator = new IconCellDecorator(
resources.cellTableSortAscending(), new SafeHtmlCell());
}
return sortAscDecorator;
} else {
if (sortDescDecorator == null) {
sortDescDecorator = new IconCellDecorator(
resources.cellTableSortDescending(), new SafeHtmlCell());
}
return sortDescDecorator;
}
}
private boolean handleKey(Event event) {
HasDataPresenter presenter = getPresenter();
int oldRow = getKeyboardSelectedRow();
boolean isRtl = LocaleInfo.getCurrentLocale().isRTL();
int keyCodeLineEnd = isRtl ? KeyCodes.KEY_LEFT : KeyCodes.KEY_RIGHT;
int keyCodeLineStart = isRtl ? KeyCodes.KEY_RIGHT : KeyCodes.KEY_LEFT;
int keyCode = event.getKeyCode();
if (keyCode == keyCodeLineEnd) {
int nextColumn = findInteractiveColumn(keyboardSelectedColumn, false);
if (nextColumn <= keyboardSelectedColumn) {
// Wrap to the next row.
if (presenter.hasKeyboardNext()) {
keyboardSelectedColumn = nextColumn;
presenter.keyboardNext();
event.preventDefault();
return true;
}
} else {
// Reselect the row to move the selected column.
keyboardSelectedColumn = nextColumn;
getPresenter().setKeyboardSelectedRow(oldRow, true, true);
event.preventDefault();
return true;
}
} else if (keyCode == keyCodeLineStart) {
int prevColumn = findInteractiveColumn(keyboardSelectedColumn, true);
if (prevColumn >= keyboardSelectedColumn) {
// Wrap to the previous row.
if (presenter.hasKeyboardPrev()) {
keyboardSelectedColumn = prevColumn;
presenter.keyboardPrev();
event.preventDefault();
return true;
}
} else {
// Reselect the row to move the selected column.
keyboardSelectedColumn = prevColumn;
getPresenter().setKeyboardSelectedRow(oldRow, true, true);
event.preventDefault();
return true;
}
}
return false;
}
/**
* Check if a column consumes events.
*/
private boolean isColumnInteractive(Column column) {
Set consumedEvents = column.getCell().getConsumedEvents();
return consumedEvents != null && consumedEvents.size() > 0;
}
private void refreshColumnWidths() {
int columnCount = getColumnCount();
for (int i = 0; i < columnCount; i++) {
Column column = columns.get(i);
String width = columnWidths.get(column);
if (width == null) {
ensureTableColElement(i).getStyle().clearWidth();
} else {
ensureTableColElement(i).getStyle().setProperty("width", width);
}
}
/*
* Set the width to zero for all col elements that appear after the last
* column. Clearing the width would cause it to take up the remaining width
* in a fixed layout table.
*/
int colCount = colgroup.getChildCount();
for (int i = columnCount; i < colCount; i++) {
ensureTableColElement(i).getStyle().setWidth(0.0, Unit.PX);
}
}
private boolean resetFocusOnCellImpl(int row, int col, Column column) {
Element parent = getKeyboardSelectedElement();
T value = getVisibleItem(row);
Object key = getValueKey(value);
C cellValue = column.getValue(value);
Cell cell = column.getCell();
Context context = new Context(row + getPageStart(), col, key);
return cell.resetFocus(context, parent, cellValue);
}
/**
* Apply a style to a row and all cells in the row.
*
* @param tr the row element
* @param rowStyle the style to apply to the row
* @param cellStyle the style to apply to the cells
* @param add true to add the style, false to remove
*/
private void setRowStyleName(TableRowElement tr, String rowStyle,
String cellStyle, boolean add) {
setStyleName(tr, rowStyle, add);
NodeList cells = tr.getCells();
for (int i = 0; i < cells.getLength(); i++) {
setStyleName(cells.getItem(i), cellStyle, add);
}
}
/**
* Update the dependsOnSelection and handlesSelection booleans.
*/
private void updateDependsOnSelection() {
dependsOnSelection = false;
handlesSelection = false;
isInteractive = false;
for (Column column : columns) {
Cell> cell = column.getCell();
if (cell.dependsOnSelection()) {
dependsOnSelection = true;
}
if (cell.handlesSelection()) {
handlesSelection = true;
}
if (isColumnInteractive(column)) {
isInteractive = true;
}
}
}
}