/* @ITMillApache2LicenseForJavaFiles@ */ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.util.ContainerOrderedWrapper; import com.vaadin.data.util.IndexedContainer; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.DataBoundTransferable; import com.vaadin.event.ItemClickEvent; import com.vaadin.event.ItemClickEvent.ItemClickListener; import com.vaadin.event.ItemClickEvent.ItemClickNotifier; import com.vaadin.event.ItemClickEvent.ItemClickSource; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DragSource; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.acceptcriteria.ClientCriterion; import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; import com.vaadin.terminal.KeyMapper; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.Resource; import com.vaadin.terminal.gwt.client.MouseEventDetails; import com.vaadin.terminal.gwt.client.ui.VScrollTable; import com.vaadin.terminal.gwt.client.ui.dd.VLazyInitItemIdentifiers; /** *
* Table
is used for representing data or components in a pageable
* and selectable table.
*
* Scalability of the Table is largely dictated by the container. A table does * not have a limit for the number of items and is just as fast with hundreds of * thousands of items as with just a few. The current GWT implementation with * scrolling however limits the number of rows to around 500000, depending on * the browser and the pixel height of rows. *
* ** Components in a Table will not have their caption nor icon rendered. *
* * @author IT Mill Ltd. * @version * @VERSION@ * @since 3.0 */ @SuppressWarnings({ "serial", "deprecation" }) @ClientWidget(VScrollTable.class) public class Table extends AbstractSelect implements Action.Container, Container.Ordered, Container.Sortable, ItemClickSource, ItemClickNotifier, DragSource, DropTarget { private static final Logger logger = Logger .getLogger(Table.class.getName()); /** * Modes that Table support as drag sourse. */ public enum TableDragMode { /** * Table does not start drag and drop events. HTM5 style events started * by browser may still happen. */ NONE, /** * Table starts drag with a one row only. */ ROW, /** * Table drags selected rows, if drag starts on a selected rows. Else it * starts like in ROW mode. Note, that in Transferable there will still * be only the row on which the drag started, other dragged rows need to * be checked from the source Table. */ MULTIROW } protected static final int CELL_KEY = 0; protected static final int CELL_HEADER = 1; protected static final int CELL_ICON = 2; protected static final int CELL_ITEMID = 3; protected static final int CELL_GENERATED_ROW = 4; protected static final int CELL_FIRSTCOL = 5; /** * Left column alignment. This is the default behaviour. */ public static final String ALIGN_LEFT = "b"; /** * Center column alignment. */ public static final String ALIGN_CENTER = "c"; /** * Right column alignment. */ public static final String ALIGN_RIGHT = "e"; /** * Column header mode: Column headers are hidden. */ public static final int COLUMN_HEADER_MODE_HIDDEN = -1; /** * Column header mode: Property ID:s are used as column headers. */ public static final int COLUMN_HEADER_MODE_ID = 0; /** * Column header mode: Column headers are explicitly specified with * {@link #setColumnHeaders(String[])}. */ public static final int COLUMN_HEADER_MODE_EXPLICIT = 1; /** * Column header mode: Column headers are explicitly specified with * {@link #setColumnHeaders(String[])}. If a header is not specified for a * given property, its property id is used instead. *
* This is the default behavior.
*/
public static final int COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2;
/**
* Row caption mode: The row headers are hidden. This is the default
* mode.
*/
public static final int ROW_HEADER_MODE_HIDDEN = -1;
/**
* Row caption mode: Items Id-objects toString is used as row caption.
*/
public static final int ROW_HEADER_MODE_ID = AbstractSelect.ITEM_CAPTION_MODE_ID;
/**
* Row caption mode: Item-objects toString is used as row caption.
*/
public static final int ROW_HEADER_MODE_ITEM = AbstractSelect.ITEM_CAPTION_MODE_ITEM;
/**
* Row caption mode: Index of the item is used as item caption. The index
* mode can only be used with the containers implementing Container.Indexed
* interface.
*/
public static final int ROW_HEADER_MODE_INDEX = AbstractSelect.ITEM_CAPTION_MODE_INDEX;
/**
* Row caption mode: Item captions are explicitly specified.
*/
public static final int ROW_HEADER_MODE_EXPLICIT = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT;
/**
* Row caption mode: Item captions are read from property specified with
* {@link #setItemCaptionPropertyId(Object)}.
*/
public static final int ROW_HEADER_MODE_PROPERTY = AbstractSelect.ITEM_CAPTION_MODE_PROPERTY;
/**
* Row caption mode: Only icons are shown, the captions are hidden.
*/
public static final int ROW_HEADER_MODE_ICON_ONLY = AbstractSelect.ITEM_CAPTION_MODE_ICON_ONLY;
/**
* Row caption mode: Item captions are explicitly specified, but if the
* caption is missing, the item id objects toString()
is used
* instead.
*/
public static final int ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID;
/**
* The default rate that table caches rows for smooth scrolling.
*/
private static final double CACHE_RATE_DEFAULT = 2;
private static final String ROW_HEADER_COLUMN_KEY = "0";
private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new Object();
/* Private table extensions to Select */
/**
* True if column collapsing is allowed.
*/
private boolean columnCollapsingAllowed = false;
/**
* True if reordering of columns is allowed on the client side.
*/
private boolean columnReorderingAllowed = false;
/**
* Keymapper for column ids.
*/
private final KeyMapper columnIdMap = new KeyMapper();
/**
* Holds visible column propertyIds - in order.
*/
private LinkedList
* The items in the array must match the properties identified by * {@link #getVisibleColumns()}. The possible values for the alignments * include: *
* Column can either have a fixed width or expand ratio. The latter one set * is used. See @link {@link #setColumnExpandRatio(Object, float)}. * * @param propertyId * colunmns property id * @param width * width to be reserved for colunmns content * @since 4.0.3 */ public void setColumnWidth(Object propertyId, int width) { if (propertyId == null) { // Since propertyId is null, this is the row header. Use the magic // id to store the width of the row header. propertyId = ROW_HEADER_FAKE_PROPERTY_ID; } if (width < 0) { columnWidths.remove(propertyId); } else { columnWidths.put(propertyId, Integer.valueOf(width)); } requestRepaint(); } /** * Sets the column expand ratio for given column. *
* Expand ratios can be defined to customize the way how excess space is * divided among columns. Table can have excess space if it has its width * defined and there is horizontally more space than columns consume * naturally. Excess space is the space that is not used by columns with * explicit width (see {@link #setColumnWidth(Object, int)}) or with natural * width (no width nor expand ratio). * *
* By default (without expand ratios) the excess space is divided * proportionally to columns natural widths. * *
* Only expand ratios of visible columns are used in final calculations. * *
* Column can either have a fixed width or expand ratio. The latter one set * is used. * *
* A column with expand ratio is considered to be minimum width by default * (if no excess space exists). The minimum width is defined by terminal * implementation. * *
* If terminal implementation supports re-sizable columns the column becomes * fixed width column if users resizes the column. * * @param propertyId * columns property id * @param expandRatio * the expandRatio used to divide excess space for this column */ public void setColumnExpandRatio(Object propertyId, float expandRatio) { if (expandRatio < 0) { columnWidths.remove(propertyId); } else { columnWidths.put(propertyId, new Float(expandRatio)); } } public float getColumnExpandRatio(Object propertyId) { final Object width = columnWidths.get(propertyId); if (width == null || !(width instanceof Float)) { return -1; } final Float value = (Float) width; return value.floatValue(); } /** * Gets the pixel width of column * * @param propertyId * @return width of column or -1 when value not set */ public int getColumnWidth(Object propertyId) { if (propertyId == null) { // Since propertyId is null, this is the row header. Use the magic // id to retrieve the width of the row header. propertyId = ROW_HEADER_FAKE_PROPERTY_ID; } final Object width = columnWidths.get(propertyId); if (width == null || !(width instanceof Integer)) { return -1; } final Integer value = (Integer) width; return value.intValue(); } /** * Gets the page length. * *
* Setting page length 0 disables paging. *
* * @return the Length of one page. */ public int getPageLength() { return pageLength; } /** * Sets the page length. * ** Setting page length 0 disables paging. The page length defaults to 15. *
* ** If Table has width set ({@link #setWidth(float, int)} ) the client side * may update the page length automatically the correct value. *
* * @param pageLength * the length of one page. */ public void setPageLength(int pageLength) { if (pageLength >= 0 && this.pageLength != pageLength) { this.pageLength = pageLength; // Assures the visual refresh resetPageBuffer(); refreshRenderedCells(); } } /** * This method adjusts a possible caching mechanism of table implementation. * ** Table component may fetch and render some rows outside visible area. With * complex tables (for example containing layouts and components), the * client side may become unresponsive. Setting the value lower, UI will * become more responsive. With higher values scrolling in client will hit * server less frequently. * *
* The amount of cached rows will be cacheRate multiplied with pageLength ( * {@link #setPageLength(int)} both below and above visible area.. * * @param cacheRate * a value over 0 (fastest rendering time). Higher value will * cache more rows on server (smoother scrolling). Default value * is 2. */ public void setCacheRate(double cacheRate) { if (cacheRate < 0) { throw new IllegalArgumentException( "cacheRate cannot be less than zero"); } if (this.cacheRate != cacheRate) { this.cacheRate = cacheRate; requestRepaint(); } } /** * @see #setCacheRate(double) * * @return the current cache rate value */ public double getCacheRate() { return cacheRate; } /** * Getter for property currentPageFirstItem. * * @return the Value of property currentPageFirstItem. */ public Object getCurrentPageFirstItemId() { // Priorise index over id if indexes are supported if (items instanceof Container.Indexed) { final int index = getCurrentPageFirstItemIndex(); Object id = null; if (index >= 0 && index < size()) { id = getIdByIndex(index); } if (id != null && !id.equals(currentPageFirstItemId)) { currentPageFirstItemId = id; } } // If there is no item id at all, use the first one if (currentPageFirstItemId == null) { currentPageFirstItemId = firstItemId(); } return currentPageFirstItemId; } protected Object getIdByIndex(int index) { return ((Container.Indexed) items).getIdByIndex(index); } /** * Setter for property currentPageFirstItemId. * * @param currentPageFirstItemId * the New value of property currentPageFirstItemId. */ public void setCurrentPageFirstItemId(Object currentPageFirstItemId) { // Gets the corresponding index int index = -1; if (items instanceof Container.Indexed) { index = indexOfId(currentPageFirstItemId); } else { // If the table item container does not have index, we have to // calculates the index by hand Object id = firstItemId(); while (id != null && !id.equals(currentPageFirstItemId)) { index++; id = nextItemId(id); } if (id == null) { index = -1; } } // If the search for item index was successful if (index >= 0) { /* * The table is not capable of displaying an item in the container * as the first if there are not enough items following the selected * item so the whole table (pagelength) is filled. */ int maxIndex = size() - pageLength; if (maxIndex < 0) { maxIndex = 0; } if (index > maxIndex) { setCurrentPageFirstItemIndex(maxIndex); return; } this.currentPageFirstItemId = currentPageFirstItemId; currentPageFirstItemIndex = index; } // Assures the visual refresh resetPageBuffer(); refreshRenderedCells(); } protected int indexOfId(Object itemId) { return ((Container.Indexed) items).indexOfId(itemId); } /** * Gets the icon Resource for the specified column. * * @param propertyId * the propertyId indentifying the column. * @return the icon for the specified column; null if the column has no icon * set, or if the column is not visible. */ public Resource getColumnIcon(Object propertyId) { return columnIcons.get(propertyId); } /** * Sets the icon Resource for the specified column. *
* Throws IllegalArgumentException if the specified column is not visible. *
* * @param propertyId * the propertyId identifying the column. * @param icon * the icon Resource to set. */ public void setColumnIcon(Object propertyId, Resource icon) { if (icon == null) { columnIcons.remove(propertyId); } else { columnIcons.put(propertyId, icon); } requestRepaint(); } /** * Gets the header for the specified column. * * @param propertyId * the propertyId identifying the column. * @return the header for the specified column if it has one. */ public String getColumnHeader(Object propertyId) { if (getColumnHeaderMode() == COLUMN_HEADER_MODE_HIDDEN) { return null; } String header = columnHeaders.get(propertyId); if ((header == null && getColumnHeaderMode() == COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) || getColumnHeaderMode() == COLUMN_HEADER_MODE_ID) { header = propertyId.toString(); } return header; } /** * Sets the column header for the specified column; * * @param propertyId * the propertyId identifying the column. * @param header * the header to set. */ public void setColumnHeader(Object propertyId, String header) { if (header == null) { columnHeaders.remove(propertyId); } else { columnHeaders.put(propertyId, header); } requestRepaint(); } /** * Gets the specified column's alignment. * * @param propertyId * the propertyID identifying the column. * @return the specified column's alignment if it as one; null otherwise. */ public String getColumnAlignment(Object propertyId) { final String a = columnAlignments.get(propertyId); return a == null ? ALIGN_LEFT : a; } /** * Sets the specified column's alignment. * ** Throws IllegalArgumentException if the alignment is not one of the * following: {@link #ALIGN_LEFT}, {@link #ALIGN_CENTER} or * {@link #ALIGN_RIGHT} *
* * @param propertyId * the propertyID identifying the column. * @param alignment * the desired alignment. */ public void setColumnAlignment(Object propertyId, String alignment) { // Checks for valid alignments if (alignment != null && !alignment.equals(ALIGN_LEFT) && !alignment.equals(ALIGN_CENTER) && !alignment.equals(ALIGN_RIGHT)) { throw new IllegalArgumentException("Column alignment '" + alignment + "' is not supported."); } if (alignment == null || alignment.equals(ALIGN_LEFT)) { columnAlignments.remove(propertyId); } else { columnAlignments.put(propertyId, alignment); } // Assures the visual refresh refreshRenderedCells(); } /** * Checks if the specified column is collapsed. * * @param propertyId * the propertyID identifying the column. * @return true if the column is collapsed; false otherwise; */ public boolean isColumnCollapsed(Object propertyId) { return collapsedColumns != null && collapsedColumns.contains(propertyId); } /** * Sets whether the specified column is collapsed or not. * * * @param propertyId * the propertyID identifying the column. * @param collapsed * the desired collapsedness. * @throws IllegalStateException * if column collapsing is not allowed */ public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException { if (!isColumnCollapsingAllowed()) { throw new IllegalStateException("Column collapsing not allowed!"); } if (collapsed) { collapsedColumns.add(propertyId); } else { collapsedColumns.remove(propertyId); } // Assures the visual refresh resetPageBuffer(); refreshRenderedCells(); } /** * Checks if column collapsing is allowed. * * @return true if columns can be collapsed; false otherwise. */ public boolean isColumnCollapsingAllowed() { return columnCollapsingAllowed; } /** * Sets whether column collapsing is allowed or not. * * @param collapsingAllowed * specifies whether column collapsing is allowed. */ public void setColumnCollapsingAllowed(boolean collapsingAllowed) { columnCollapsingAllowed = collapsingAllowed; if (!collapsingAllowed) { collapsedColumns.clear(); } // Assures the visual refresh refreshRenderedCells(); } /** * Checks if column reordering is allowed. * * @return true if columns can be reordered; false otherwise. */ public boolean isColumnReorderingAllowed() { return columnReorderingAllowed; } /** * Sets whether column reordering is allowed or not. * * @param columnReorderingAllowed * specifies whether column reordering is allowed. */ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { if (columnReorderingAllowed != this.columnReorderingAllowed) { this.columnReorderingAllowed = columnReorderingAllowed; requestRepaint(); } } /* * Arranges visible columns according to given columnOrder. Silently ignores * colimnId:s that are not visible columns, and keeps the internal order of * visible columns left out of the ordering (trailing). Silently does * nothing if columnReordering is not allowed. */ private void setColumnOrder(Object[] columnOrder) { if (columnOrder == null || !isColumnReorderingAllowed()) { return; } final LinkedList* The table is not selectable by default. *
* * @return the Value of property selectable. */ public boolean isSelectable() { return selectable; } /** * Setter for property selectable. * ** The table is not selectable by default. *
* * @param selectable * the New value of property selectable. */ public void setSelectable(boolean selectable) { if (this.selectable != selectable) { this.selectable = selectable; requestRepaint(); } } /** * Getter for property columnHeaderMode. * * @return the Value of property columnHeaderMode. */ public int getColumnHeaderMode() { return columnHeaderMode; } /** * Setter for property columnHeaderMode. * * @param columnHeaderMode * the New value of property columnHeaderMode. */ public void setColumnHeaderMode(int columnHeaderMode) { if (columnHeaderMode != this.columnHeaderMode && columnHeaderMode >= COLUMN_HEADER_MODE_HIDDEN && columnHeaderMode <= COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) { this.columnHeaderMode = columnHeaderMode; requestRepaint(); } } /** * Refreshes rendered rows */ protected void refreshRenderedCells() { if (getParent() == null) { return; } if (!isContentRefreshesEnabled) { return; } // Collects the basic facts about the table page final int pagelen = getPageLength(); int firstIndex = getCurrentPageFirstItemIndex(); int rows, totalRows; rows = totalRows = size(); if (rows > 0 && firstIndex >= 0) { rows -= firstIndex; } if (pagelen > 0 && pagelen < rows) { rows = pagelen; } // If "to be painted next" variables are set, use them if (lastToBeRenderedInClient - firstToBeRenderedInClient > 0) { rows = lastToBeRenderedInClient - firstToBeRenderedInClient + 1; } if (firstToBeRenderedInClient >= 0) { if (firstToBeRenderedInClient < totalRows) { firstIndex = firstToBeRenderedInClient; } else { firstIndex = totalRows - 1; } } else { // initial load firstToBeRenderedInClient = firstIndex; } if (totalRows > 0) { if (rows + firstIndex > totalRows) { rows = totalRows - firstIndex; } } else { rows = 0; } // Saves the results to internal buffer pageBuffer = getVisibleCellsNoCache(firstIndex, rows, true); if (rows > 0) { pageBufferFirstIndex = firstIndex; } setRowCacheInvalidated(true); requestRepaint(); } private void removeRowsFromCacheAndFillBottom(int firstIndex, int rows) { int totalCachedRows = pageBuffer[CELL_ITEMID].length; int totalRows = size(); int cacheIx = firstIndex - pageBufferFirstIndex; // Make sure that no components leak. unregisterComponentsAndPropertiesInRows(firstIndex, rows); int newCachedRowCount = totalRows < totalCachedRows ? totalRows : totalCachedRows; int firstAppendedRow = newCachedRowCount > rows ? newCachedRowCount - rows : firstIndex; int rowsToAdd = Math.min(rows, totalCachedRows - firstAppendedRow); rowsToAdd = Math.min(rowsToAdd, totalRows - (firstAppendedRow + pageBufferFirstIndex)); if (rowsToAdd == 0) { return; } Object[][] cells = getVisibleCellsNoCache(firstAppendedRow, rowsToAdd, false); // Create the new cache buffer by copying data from the old one and // appending more rows if applicable. Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount]; for (int ix = 0; ix < newCachedRowCount; ix++) { for (int i = 0; i < pageBuffer.length; i++) { if (ix >= firstAppendedRow) { newPageBuffer[i][ix] = cells[i][ix - firstAppendedRow]; } else if (ix >= cacheIx && ix + rows < totalCachedRows) { newPageBuffer[i][ix] = pageBuffer[i][ix + rows]; } else { newPageBuffer[i][ix] = pageBuffer[i][ix]; } } } pageBuffer = newPageBuffer; } private Object[][] getVisibleCellsUpdateCacheRows(int firstIndex, int rows) { Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false); int cacheIx = firstIndex - pageBufferFirstIndex; // update the new rows in the cache. int totalCachedRows = pageBuffer[CELL_ITEMID].length; int end = Math.min(cacheIx + rows, totalCachedRows); for (int ix = cacheIx; ix < end; ix++) { for (int i = 0; i < pageBuffer.length; i++) { pageBuffer[i][ix] = cells[i][ix - cacheIx]; } } return cells; } private Object[][] getVisibleCellsInsertIntoCache(int firstIndex, int rows) { Object[][] cells = getVisibleCellsNoCache(firstIndex, rows, false); int currentlyCachedRowCount = pageBuffer[CELL_ITEMID].length; int lastCachedRow = currentlyCachedRowCount - rows; int cacheIx = firstIndex - pageBufferFirstIndex; // Unregister all components that fall beyond the cache limits after // inserting the new rows. unregisterComponentsAndPropertiesInRows(lastCachedRow + 1, currentlyCachedRowCount - lastCachedRow); // Calculate the new cache size int newCachedRowCount = currentlyCachedRowCount; if (currentlyCachedRowCount < pageLength) { newCachedRowCount = currentlyCachedRowCount + rows; if (newCachedRowCount > pageLength) { newCachedRowCount = pageLength; } } // Create the new cache buffer and fill it with the data from the old // buffer as well as the inserted rows. Object[][] newPageBuffer = new Object[pageBuffer.length][newCachedRowCount]; for (int ix = 0; ix < newCachedRowCount; ix++) { for (int i = 0; i < pageBuffer.length; i++) { if (ix >= cacheIx && ix < cacheIx + rows) { newPageBuffer[i][ix] = cells[i][ix - cacheIx]; } else if (ix >= cacheIx + rows) { newPageBuffer[i][ix] = pageBuffer[i][ix - rows]; } else { newPageBuffer[i][ix] = pageBuffer[i][ix]; } } } pageBuffer = newPageBuffer; return cells; } private Object[][] getVisibleCellsNoCache(int firstIndex, int rows, boolean replaceListeners) { final Object[] colids = getVisibleColumns(); final int cols = colids.length; HashSet* The mode can be one of the following ones: *
toString()
* is used as row caption.
* toString()
* is used as row caption.
* toString()
is used as row header. If caption is explicitly
* specified, it overrides the id-caption.
* Container.Indexed
interface.ItemId
from the Container.
*
* @see com.vaadin.data.Container#removeItem(Object)
*/
@Override
public boolean removeItem(Object itemId) {
final Object nextItemId = nextItemId(itemId);
final boolean ret = super.removeItem(itemId);
if (ret && (itemId != null) && (itemId.equals(currentPageFirstItemId))) {
currentPageFirstItemId = nextItemId;
}
if (!(items instanceof Container.ItemSetChangeNotifier)) {
resetPageBuffer();
refreshRenderedCells();
}
return ret;
}
/**
* Removes a Property specified by the given Property ID from the Container.
*
* @see com.vaadin.data.Container#removeContainerProperty(Object)
*/
@Override
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
// If a visible property is removed, remove the corresponding column
visibleColumns.remove(propertyId);
columnAlignments.remove(propertyId);
columnIcons.remove(propertyId);
columnHeaders.remove(propertyId);
columnFooters.remove(propertyId);
return super.removeContainerProperty(propertyId);
}
/**
* Adds a new property to the table and show it as a visible column.
*
* @param propertyId
* the Id of the proprty.
* @param type
* the class of the property.
* @param defaultValue
* the default value given for all existing items.
* @see com.vaadin.data.Container#addContainerProperty(Object, Class,
* Object)
*/
@Override
public boolean addContainerProperty(Object propertyId, Class> type,
Object defaultValue) throws UnsupportedOperationException {
boolean visibleColAdded = false;
if (!visibleColumns.contains(propertyId)) {
visibleColumns.add(propertyId);
visibleColAdded = true;
}
if (!super.addContainerProperty(propertyId, type, defaultValue)) {
if (visibleColAdded) {
visibleColumns.remove(propertyId);
}
return false;
}
if (!(items instanceof Container.PropertySetChangeNotifier)) {
resetPageBuffer();
refreshRenderedCells();
}
return true;
}
/**
* Adds a new property to the table and show it as a visible column.
*
* @param propertyId
* the Id of the proprty
* @param type
* the class of the property
* @param defaultValue
* the default value given for all existing items
* @param columnHeader
* the Explicit header of the column. If explicit header is not
* needed, this should be set null.
* @param columnIcon
* the Icon of the column. If icon is not needed, this should be
* set null.
* @param columnAlignment
* the Alignment of the column. Null implies align left.
* @throws UnsupportedOperationException
* if the operation is not supported.
* @see com.vaadin.data.Container#addContainerProperty(Object, Class,
* Object)
*/
public boolean addContainerProperty(Object propertyId, Class> type,
Object defaultValue, String columnHeader, Resource columnIcon,
String columnAlignment) throws UnsupportedOperationException {
if (!this.addContainerProperty(propertyId, type, defaultValue)) {
return false;
}
setColumnAlignment(propertyId, columnAlignment);
setColumnHeader(propertyId, columnHeader);
setColumnIcon(propertyId, columnIcon);
return true;
}
/**
* Adds a generated column to the Table.
* * A generated column is a column that exists only in the Table, not as a * property in the underlying Container. It shows up just as a regular * column. *
** A generated column will override a property with the same id, so that the * generated column is shown instead of the column representing the * property. Note that getContainerProperty() will still get the real * property. *
** Table will not listen to value change events from properties overridden * by generated columns. If the content of your generated column depends on * properties that are not directly visible in the table, attach value * change listener to update the content on all depended properties. * Otherwise your UI might not get updated as expected. *
** Also note that getVisibleColumns() will return the generated columns, * while getContainerPropertyIds() will not. *
* * @param id * the id of the column to be added * @param generatedColumn * the {@link ColumnGenerator} to use for this column */ public void addGeneratedColumn(Object id, ColumnGenerator generatedColumn) { if (generatedColumn == null) { throw new IllegalArgumentException( "Can not add null as a GeneratedColumn"); } if (columnGenerators.containsKey(id)) { throw new IllegalArgumentException( "Can not add the same GeneratedColumn twice, id:" + id); } else { columnGenerators.put(id, generatedColumn); /* * add to visible column list unless already there (overriding * column from DS) */ if (!visibleColumns.contains(id)) { visibleColumns.add(id); } resetPageBuffer(); refreshRenderedCells(); } } /** * Returns the ColumnGenerator used to generate the given column. * * @param columnId * The id of the generated column * @return The ColumnGenerator used for the given columnId or null. */ public ColumnGenerator getColumnGenerator(Object columnId) throws IllegalArgumentException { return columnGenerators.get(columnId); } /** * Removes a generated column previously added with addGeneratedColumn. * * @param columnId * id of the generated column to remove * @return true if the column could be removed (existed in the Table) */ public boolean removeGeneratedColumn(Object columnId) { if (columnGenerators.containsKey(columnId)) { columnGenerators.remove(columnId); // remove column from visibleColumns list unless it exists in // container (generator previously overrode this column) if (!items.getContainerPropertyIds().contains(columnId)) { visibleColumns.remove(columnId); } resetPageBuffer(); refreshRenderedCells(); return true; } else { return false; } } /** * Returns item identifiers of the items which are currently rendered on the * client. *
* Note, that some due to historical reasons the name of the method is bit
* misleading. Some items may be partly or totally out of the viewport of
* the table's scrollable area. Actully detecting rows which can be actually
* seen by the end user may be problematic due to the client server
* architecture. Using {@link #getCurrentPageFirstItemId()} combined with
* {@link #getPageLength()} may produce good enough estimates in some
* situations.
*
* @see com.vaadin.ui.Select#getVisibleItemIds()
*/
@Override
public Collection> getVisibleItemIds() {
final LinkedList
* Note, that on some clients the mode may not be respected. E.g. on touch
* based devices CTRL/SHIFT base selection method is invalid, so touch based
* browsers always use the {@link MultiSelectMode#SIMPLE}.
*
* @param mode
* The select mode of the table
*/
public void setMultiSelectMode(MultiSelectMode mode) {
multiSelectMode = mode;
requestRepaint();
}
/**
* Returns the select mode in which multi-select is used.
*
* @return The multi select mode
*/
public MultiSelectMode getMultiSelectMode() {
return multiSelectMode;
}
/**
* Lazy loading accept criterion for Table. Accepted target rows are loaded
* from server once per drag and drop operation. Developer must override one
* method that decides on which rows the currently dragged data can be
* dropped.
*
*
* Initially pretty much no data is sent to client. On first required
* criterion check (per drag request) the client side data structure is
* initialized from server and no subsequent requests requests are needed
* during that drag and drop operation.
*/
@ClientCriterion(VLazyInitItemIdentifiers.class)
public static abstract class TableDropCriterion extends ServerSideCriterion {
private Table table;
private Set
* The listener will receive events which contain information about which
* column was clicked and some details about the mouse event.
*
* The listener will receive events which contain information about which
* column was clicked and some details about the mouse event.
*
* The footer can be used to add column related data like sums to the bottom
* of the Table using setColumnFooter(Object propertyId, String footer).
*
* The GeneratedRow data object contains the text that should be
* rendered in the row. The itemId in the container thus works only as a
* placeholder.
*
* If GeneratedRow.setSpanColumns(true) is used, there will be one
* String spanning all columns (use setText("Spanning text")). Otherwise
* you can define one String per visible column.
*
* If GeneratedRow.setRenderAsHtml(true) is used, the strings can
* contain HTML markup, otherwise all strings will be rendered as text
* (the default).
*
* A "v-table-generated-row" CSS class is added to all generated rows.
* For custom styling of a generated row you can combine a RowGenerator
* with a CellStyleGenerator.
*
*
* @param table
* The Table that is being painted
* @param itemId
* The itemId for the row
* @return A GeneratedRow describing how the row should be painted or
* null to paint the row with the contents from the container
*/
public GeneratedRow generateRow(Table table, Object itemId);
}
public static class GeneratedRow implements Serializable {
private boolean htmlContentAllowed = false;
private boolean spanColumns = false;
private String[] text = null;
/**
* Creates a new generated row. If only one string is passed in, columns
* are automatically spanned.
*
* @param text
*/
public GeneratedRow(String... text) {
setHtmlContentAllowed(false);
setSpanColumns(text == null || text.length == 1);
setText(text);
}
/**
* Pass one String if spanColumns is used, one String for each visible
* column otherwise
*/
public void setText(String... text) {
if (text == null || (text.length == 1 && text[0] == null)) {
text = new String[] { "" };
}
this.text = text;
}
protected String[] getText() {
return text;
}
protected Object getValue() {
return getText();
}
protected boolean isHtmlContentAllowed() {
return htmlContentAllowed;
}
/**
* If set to true, all strings passed to {@link #setText(String...)}
* will be rendered as HTML.
*
* @param htmlContentAllowed
*/
public void setHtmlContentAllowed(boolean htmlContentAllowed) {
this.htmlContentAllowed = htmlContentAllowed;
}
protected boolean isSpanColumns() {
return spanColumns;
}
/**
* If set to true, only one string will be rendered, spanning the entire
* row.
*
* @param spanColumns
*/
public void setSpanColumns(boolean spanColumns) {
this.spanColumns = spanColumns;
}
}
/**
* Assigns a row generator to the table. The row generator will be able to
* replace rows in the table when it is rendered.
*
* @param generator
* the new row generator
*/
public void setRowGenerator(RowGenerator generator) {
rowGenerator = generator;
refreshRenderedCells();
}
/**
* @return the current row generator
*/
public RowGenerator getRowGenerator() {
return rowGenerator;
}
}
true
if ascending, false
if descending.
*/
public boolean isSortAscending() {
return sortAscending;
}
/**
* Sets the table in ascending order.
*
* @param ascending
* true
if ascending, false
if
* descending.
*/
public void setSortAscending(boolean ascending) {
setSortAscending(ascending, true);
}
/**
* Internal method to set sort ascending. With doSort flag actual sort can
* be bypassed.
*
* @param ascending
* @param doSort
*/
private void setSortAscending(boolean ascending, boolean doSort) {
if (sortAscending != ascending) {
sortAscending = ascending;
if (doSort) {
sort();
}
}
// Assures the visual refresh
refreshRenderedCells();
}
/**
* Is sorting disabled altogether.
*
* True iff no sortable columns are given even in the case where data source
* would support this.
*
* @return True iff sorting is disabled.
*/
public boolean isSortDisabled() {
return sortDisabled;
}
/**
* Disables the sorting altogether.
*
* To disable sorting altogether, set to true. In this case no sortable
* columns are given even in the case where datasource would support this.
*
* @param sortDisabled
* True iff sorting is disabled.
*/
public void setSortDisabled(boolean sortDisabled) {
if (this.sortDisabled != sortDisabled) {
this.sortDisabled = sortDisabled;
requestRepaint();
}
}
/**
* Table does not support lazy options loading mode. Setting this true will
* throw UnsupportedOperationException.
*
* @see com.vaadin.ui.Select#setLazyLoading(boolean)
*/
public void setLazyLoading(boolean useLazyLoading) {
if (useLazyLoading) {
throw new UnsupportedOperationException(
"Lazy options loading is not supported by Table.");
}
}
/**
* Used to create "generated columns"; columns that exist only in the Table,
* not in the underlying Container. Implement this interface and pass it to
* Table.addGeneratedColumn along with an id for the column to be generated.
*
*/
public interface ColumnGenerator extends Serializable {
/**
* Called by Table when a cell in a generated column needs to be
* generated.
*
* @param source
* the source Table
* @param itemId
* the itemId (aka rowId) for the of the cell to be generated
* @param columnId
* the id for the generated column (as specified in
* addGeneratedColumn)
* @return A {@link Component} that should be rendered in the cell or a
* {@link String} that should be displayed in the cell. Other
* return values are not supported.
*/
public abstract Object generateCell(Table source, Object itemId,
Object columnId);
}
/**
* Set cell style generator for Table.
*
* @param cellStyleGenerator
* New cell style generator or null to remove generator.
*/
public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
this.cellStyleGenerator = cellStyleGenerator;
refreshRenderedCells();
}
/**
* Get the current cell style generator.
*
*/
public CellStyleGenerator getCellStyleGenerator() {
return cellStyleGenerator;
}
/**
* Allow to define specific style on cells (and rows) contents. Implements
* this interface and pass it to Table.setCellStyleGenerator. Row styles are
* generated when porpertyId is null. The CSS class name that will be added
* to the cell content is v-table-cell-content-[style name], and
* the row style will be v-table-row-[style name].
*/
public interface CellStyleGenerator extends Serializable {
/**
* Called by Table when a cell (and row) is painted.
*
* @param itemId
* The itemId of the painted cell
* @param propertyId
* The propertyId of the cell, null when getting row style
* @return The style name to add to this cell or row. (the CSS class
* name will be v-table-cell-content-[style name], or
* v-table-row-[style name] for rows)
*/
public abstract String getStyle(Object itemId, Object propertyId);
}
public void addListener(ItemClickListener listener) {
addListener(VScrollTable.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
listener, ItemClickEvent.ITEM_CLICK_METHOD);
}
public void removeListener(ItemClickListener listener) {
removeListener(VScrollTable.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
listener);
}
// Identical to AbstractCompoenentContainer.setEnabled();
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (getParent() != null && !getParent().isEnabled()) {
// some ancestor still disabled, don't update children
return;
} else {
requestRepaintAll();
}
}
// Virtually identical to AbstractCompoenentContainer.setEnabled();
public void requestRepaintAll() {
requestRepaint();
if (visibleComponents != null) {
for (Iterator