All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.user.cellview.client.AbstractCellTable Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.Unit;
import com.google.gwt.dom.client.TableCellElement;
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.ImageResource;
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.client.DOM;
import com.google.gwt.user.client.Event;
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.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;

/**
 * Abstract base class for tabular views 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 AbstractCellTable}. 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. *

* * @param the data type of each row */ public abstract class AbstractCellTable extends AbstractHasData { /** * A ClientBundle that provides images for this widget. */ public interface Resources { /** * Icon used when a column is sorted in ascending order. */ ImageResource sortAscending(); /** * Icon used when a column is sorted in descending order. */ ImageResource sortDescending(); /** * The styles used in this widget. */ Style style(); } /** * Styles used by this widget. */ public interface Style { /** * Applied to every cell. */ String cell(); /** * Applied to even rows. */ String evenRow(); /** * Applied to cells in even rows. */ String evenRowCell(); /** * Applied to the first column. */ String firstColumn(); /** * Applied to the first column footers. */ String firstColumnFooter(); /** * Applied to the first column headers. */ String firstColumnHeader(); /** * Applied to footers cells. */ String footer(); /** * Applied to headers cells. */ String header(); /** * Applied to the hovered row. */ String hoveredRow(); /** * Applied to the cells in the hovered row. */ String hoveredRowCell(); /** * Applied to the keyboard selected cell. */ String keyboardSelectedCell(); /** * Applied to the keyboard selected row. */ String keyboardSelectedRow(); /** * Applied to the cells in the keyboard selected row. */ String keyboardSelectedRowCell(); /** * Applied to the last column. */ String lastColumn(); /** * Applied to the last column footers. */ String lastColumnFooter(); /** * Applied to the last column headers. */ String lastColumnHeader(); /** * Applied to odd rows. */ String oddRow(); /** * Applied to cells in odd rows. */ String oddRowCell(); /** * Applied to selected rows. */ String selectedRow(); /** * Applied to cells in selected rows. */ String selectedRowCell(); /** * Applied to header cells that are sortable. */ String sortableHeader(); /** * Applied to header cells that are sorted in ascending order. */ String sortedHeaderAscending(); /** * Applied to header cells that are sorted in descending order. */ String sortedHeaderDescending(); /** * Applied to the table. */ String widget(); } interface Template extends SafeHtmlTemplates { @SafeHtmlTemplates.Template("
{0}
") SafeHtml div(SafeHtml contents); @SafeHtmlTemplates.Template("
{1}
") SafeHtml divFocusable(int tabIndex, SafeHtml contents); @SafeHtmlTemplates.Template("
{2}
") SafeHtml divFocusableWithKey(int tabIndex, char accessKey, SafeHtml contents); @SafeHtmlTemplates.Template("
") SafeHtml loading(String loading); @SafeHtmlTemplates.Template("{0}
") SafeHtml tbody(SafeHtml rowHtml); @SafeHtmlTemplates.Template("{1}") SafeHtml td(String classes, SafeHtml contents); @SafeHtmlTemplates.Template("{3}") SafeHtml tdBothAlign(String classes, String hAlign, String vAlign, SafeHtml contents); @SafeHtmlTemplates.Template("{2}") SafeHtml tdHorizontalAlign(String classes, String hAlign, SafeHtml contents); @SafeHtmlTemplates.Template("{2}") SafeHtml tdVerticalAlign(String classes, String vAlign, SafeHtml contents); @SafeHtmlTemplates.Template("{0}
") SafeHtml tfoot(SafeHtml rowHtml); @SafeHtmlTemplates.Template("{2}") SafeHtml th(int colspan, String classes, SafeHtml contents); @SafeHtmlTemplates.Template("{0}
") SafeHtml thead(SafeHtml rowHtml); @SafeHtmlTemplates.Template("{1}") SafeHtml tr(String classes, SafeHtml contents); } /** * Implementation of {@link AbstractCellTable}. */ 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 AbstractCellTable} * @param sectionTag the table section tag * @param rowHtml the Html for the rows * @return the section element */ protected TableSectionElement convertToSectionElement(AbstractCellTable 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. We do the same for all browsers to avoid any * future bugs, since setting innerHTML on a table section seems brittle. */ 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); } } /** * Detach a table section element from its parent. * * @param section the element to detach */ protected void detachSectionElement(TableSectionElement section) { section.removeFromParent(); } /** * Reattach a table section element from its parent. * * @param parent the parent element * @param section the element to reattach * @param nextSection the next section */ protected void reattachSectionElement(Element parent, TableSectionElement section, Element nextSection) { parent.insertBefore(section, nextSection); } /** * Render a table section in the table. * * @param table the {@link AbstractCellTable} * @param section the {@link TableSectionElement} to replace * @param html the html to render */ protected void replaceAllRows(AbstractCellTable 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); } // Remove the section from the tbody. Element parent = section.getParentElement(); Element nextSection = section.getNextSiblingElement(); detachSectionElement(section); // Render the html. section.setInnerHTML(html.asString()); /* * Reattach the section. If next section is null, the section will be * appended instead. */ reattachSectionElement(parent, section, nextSection); // Detach the event listener. if (!table.isAttached()) { DOM.setEventListener(table.getElement(), null); } } } /** * Implementation of {@link CellTable} used by Firefox. */ @SuppressWarnings("unused") private static class ImplMozilla extends Impl { /** * Firefox 3.6 and earlier convert td elements to divs if the tbody is * removed from the table element. */ @Override protected void detachSectionElement(TableSectionElement section) { if (isGecko192OrBefore()) { return; } super.detachSectionElement(section); } @Override protected void reattachSectionElement(Element parent, TableSectionElement section, Element nextSection) { if (isGecko192OrBefore()) { return; } super.reattachSectionElement(parent, section, nextSection); } /** * Return true if using Gecko 1.9.2 (Firefox 3.6) or earlier. */ private native boolean isGecko192OrBefore() /*-{ return @com.google.gwt.dom.client.DOMImplMozilla::isGecko192OrBefore()(); }-*/; } /** * Implementation of {@link AbstractCellTable} 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(AbstractCellTable 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 table specific {@link Impl}. */ private static Impl TABLE_IMPL; static Template template; 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 Widget emptyTableWidget; 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 Widget loadingIndicator; private final Resources resources; private RowStyles rowStyles; private IconCellDecorator sortAscDecorator; private IconCellDecorator sortDescDecorator; private final ColumnSortList sortList = new ColumnSortList(new ColumnSortList.Delegate() { @Override public void onModification() { if (!updatingSortList) { createHeaders(false); } } }); private final Style style; private boolean updatingSortList; /** * Constructs a table with the given page size, the specified {@link Style}, * and the given key provider. * * @param elem the parent {@link Element} * @param pageSize the page size * @param resources the resources to apply to the widget * @param keyProvider an instance of ProvidesKey, or null if the record * object should act as its own key */ public AbstractCellTable(Element elem, final int pageSize, Resources resources, ProvidesKey keyProvider) { super(elem, pageSize, keyProvider); this.resources = resources; this.style = resources.style(); init(); } /** * Constructs a table with the given page size, the specified {@link Style}, * and the given key provider. * * @param widget the parent widget * @param pageSize the page size * @param resources the resources to apply to the widget * @param keyProvider an instance of ProvidesKey, or null if the record * object should act as its own key */ public AbstractCellTable(Widget widget, final int pageSize, Resources resources, ProvidesKey keyProvider) { super(widget, pageSize, keyProvider); this.resources = resources; this.style = resources.style(); init(); } /** * 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 col element at the specified index, * creating it if necessary. * * @param index the column index * @param styleName the style name to add */ public abstract void addColumnStyleName(int index, String styleName); /** * Clear the width of the specified {@link Column}. * * @param column the column */ public void clearColumnWidth(Column column) { columnWidths.remove(column); refreshColumnWidths(); } /** * Flush all pending changes to the table and render immediately. * *

* Modifications to the table, such as adding columns or setting data, are not * rendered immediately. Instead, changes are coalesced at the end of the * current event loop to avoid rendering the table multiple times. Use this * method to force the table to render all pending modifications immediately. *

*/ public void flush() { getPresenter().flush(); } /** * 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 width of a {@link Column}. * * @param column the column * @return the width of the column, or null if not set * @see #setColumnWidth(Column, double, Unit) */ public String getColumnWidth(Column column) { return columnWidths.get(column); } /** * Get the widget displayed when the table has no rows. * * @return the empty table widget */ public Widget getEmptyTableWidget() { return emptyTableWidget; } /** * Get the widget displayed when the data is loading. * * @return the loading indicator */ public Widget getLoadingIndicator() { return loadingIndicator; } /** * 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) { flush(); checkRowBounds(row); NodeList rows = getTableBodyElement().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; coalesceCellProperties(); // 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); coalesceCellProperties(); // 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 col element at the specified index. * * @param index the column index * @param styleName the style name to remove */ public abstract void removeColumnStyleName(int index, String styleName); /** * Set the width of a {@link Column}. * * @param column the column * @param width the width of the column */ public void setColumnWidth(Column column, String width) { columnWidths.put(column, width); refreshColumnWidths(); } /** * Set the width of a {@link Column}. * * @param column the column * @param width the width of the column * @param unit the {@link Unit} of measurement */ 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) { this.emptyTableWidget = 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) { loadingIndicator = 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; } @Override protected Element convertToElements(SafeHtml html) { return TABLE_IMPL.convertToSectionElement(AbstractCellTable.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) { } /** * Set the width of a column. * * @param column the column index * @param width the width, or null to clear the width */ protected abstract void doSetColumnWidth(int column, String width); /** * Show or hide a header section. * * @param isFooter true for the footer, false for the header * @param isVisible true to show, false to hide */ protected abstract void doSetHeaderVisible(boolean isFooter, boolean isVisible); @Override protected Element getChildContainer() { return getTableBodyElement(); } @Override protected Element getKeyboardSelectedElement() { // Do not use getRowElement() because that will flush the presenter. int rowIndex = getKeyboardSelectedRow(); NodeList rows = getTableBodyElement().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; } /** * Get the tbody element that contains the render row values. */ protected abstract TableSectionElement getTableBodyElement(); /** * Get the tfoot element that contains the footers. */ protected abstract TableSectionElement getTableFootElement(); /** * Get the thead element that contains theh eaders. */ protected abstract TableSectionElement getTableHeadElement(); @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.keyboardSelectedCell()); setRowStyleName(tr, style.keyboardSelectedRow(), style.keyboardSelectedRowCell(), 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 == getTableHeadElement()) { 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 == getTableFootElement()) { 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 == getTableBodyElement()) { // 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 && getTableBodyElement().isOrHasChild(hoveringRow)) { setRowStyleName(hoveringRow, style.hoveredRow(), style.hoveredRowCell(), false); } hoveringRow = tr; setRowStyleName(hoveringRow, style.hoveredRow(), style.hoveredRowCell(), true); } else if ("mouseout".equals(eventType) && hoveringRow != null) { setRowStyleName(hoveringRow, style.hoveredRow(), style.hoveredRowCell(), 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.keyboardSelectedCell()); setRowStyleName(tr, style.keyboardSelectedRow(), style.keyboardSelectedRowCell(), true); } } protected void refreshColumnWidths() { int columnCount = getColumnCount(); for (int i = 0; i < columnCount; i++) { Column column = columns.get(i); String width = columnWidths.get(column); doSetColumnWidth(i, width); } } @Override protected void renderRowValues(SafeHtmlBuilder sb, List values, int start, SelectionModel selectionModel) { createHeadersAndFooters(); int keyboardSelectedRow = getKeyboardSelectedRow() + getPageStart(); String evenRowStyle = style.evenRow(); String oddRowStyle = style.oddRow(); String cellStyle = style.cell(); String evenCellStyle = " " + style.evenRowCell(); String oddCellStyle = " " + style.oddRowCell(); String firstColumnStyle = " " + style.firstColumn(); String lastColumnStyle = " " + style.lastColumn(); String selectedRowStyle = " " + style.selectedRow(); String selectedCellStyle = " " + style.selectedRowCell(); String keyboardRowStyle = " " + style.keyboardSelectedRow(); String keyboardRowCellStyle = " " + style.keyboardSelectedRowCell(); String keyboardCellStyle = " " + style.keyboardSelectedCell(); 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; } // Add class names specific to the cell. Context context = new Context(i, curColumn, getValueKey(value)); String cellStyles = column.getCellStyleNames(context, value); if (cellStyles != null) { tdClasses += " " + cellStyles; } SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder(); if (value != null) { 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(AbstractCellTable.this, getTableBodyElement(), 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.keyboardSelectedCell(); boolean updatedSelection = !selected || isFocused || stealFocus; setRowStyleName(tr, style.keyboardSelectedRow(), style.keyboardSelectedRowCell(), 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() { @Override 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.selectedRow(), style.selectedRowCell(), 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); } } /** * Coalesce the various cell properties (dependsOnSelection, handlesSelection, * isInteractive) into a table policy. */ private void coalesceCellProperties() { 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; } } } /** * 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 ? getTableFootElement() : getTableHeadElement(); String className = isFooter ? style.footer() : style.header(); String firstColumnStyle = " " + (isFooter ? style.firstColumnFooter() : style.firstColumnHeader()); String lastColumnStyle = " " + (isFooter ? style.lastColumnFooter() : style.lastColumnHeader()); String sortableStyle = " " + style.sortableHeader(); String sortedAscStyle = " " + style.sortedHeaderAscending(); String sortedDescStyle = " " + style.sortedHeaderDescending(); 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. doSetHeaderVisible(isFooter, hasHeader); } private void createHeadersAndFooters() { createHeaders(false); createHeaders(true); } /** * 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 != getElement())) { // 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() { @Override 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(); } /** * 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.sortAscending(), new SafeHtmlCell()); } return sortAscDecorator; } else { if (sortDescDecorator == null) { sortDescDecorator = new IconCellDecorator(resources.sortDescending(), 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; } /** * Initialize the widget. */ private void init() { if (TABLE_IMPL == null) { TABLE_IMPL = GWT.create(Impl.class); } if (template == null) { template = GWT.create(Template.class); } // Sink events. Set eventTypes = new HashSet(); eventTypes.add("mouseover"); eventTypes.add("mouseout"); CellBasedWidgetImpl.get().sinkEvents(this, eventTypes); } /** * Check if a column consumes events. */ private boolean isColumnInteractive(Column column) { Set consumedEvents = column.getCell().getConsumedEvents(); return consumedEvents != null && consumedEvents.size() > 0; } 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy