/*
* Ext GWT 2.2.4 - Ext for GWT
* Copyright(c) 2007-2010, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.table;
import java.util.List;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.Style.SortDir;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.ContainerEvent;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.TableEvent;
import com.extjs.gxt.ui.client.event.TableListener;
import com.extjs.gxt.ui.client.util.DelayedTask;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Size;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.Container;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.selection.Selectable;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
/**
* The table is used to display two-dimensional table of cells.
*
* <p /> When using custom cell renderers or nested widgets within cells it may
* be nesseccary to increase the cell selector depth to account for the new
* nested elements within each cell. For example, if nesting a table structure
* in a cell, the selector depth should be increased to at least a value of 10 (@see
* {@link TableView#setCellSelectorDepth(int)}.
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>BeforeAdd</b> : TableEvent(table, item, rowIndex)<br>
* <div>Fires before a item is added or inserted. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* <li>item : the item being added</li>
* <li>rowIndex : the index at which the item will be added</li>
* </ul>
* </dd>
*
* <dd><b>BeforeRemove</b> : TableEvent(table, item, rowIndex)<br>
* <div>Fires before a item is removed. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* <li>item : the item being removed</li>
* <li>rowIndex : the index of the removed item</li>
* </ul>
* </dd>
*
* <dd><b>Add</b> : TableEvent(table, item, rowIndex)<br>
* <div>Fires after a item has been added or inserted.</div>
* <ul>
* <li>table : this</li>
* <li>item : the item that was added</li>
* <li>rowIndex : the index at which the item will be added</li>
* </ul>
* </dd>
*
* <dd><b>Remove</b> : TableEvent(table, item, rowIndex)<br>
* <div>Fires after a item has been removed.</div>
* <ul>
* <li>table : this</li>
* <li>item : the item that was removed</li>
* <li>rowIndex : the index of the item that was removed</li>
* </ul>
* </dd>
*
* <dd><b>SelectionChange</b> : TableEvent(table this)<br>
* <div>Fired after the selection has changed</div>
* <ul>
* <li>table : this</li>
* </ul>
* </dd>
*
* <dd><b>ContextMenu</b> : TableEvent(table)<br>
* <div>Fires before the tables context menu is shown. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* </ul>
* </dd>
*
* <dd><b>CellClick</b> : TableEvent(table, rowIndex, cellIndex, event)<br>
* <div>Fires after a cell is clicked.</div>
* <ul>
* <li>table : this</li>
* <li>rowIndex : row index</li>
* <li>cellIndex : cell index</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>CellDoubleClick</b> : TableEvent(table, rowIndex, cellIndex, event)<br>
* <div>Fires after a cell is double clicked.</div>
* <ul>
* <li>table : this</li>
* <li>rowIndex : row index</li>
* <li>cellIndex : cell index</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>ColumnClick</b> : TableEvent(table, columnIndex)<br>
* <div>Fires after a column is clicked.</div>
* <ul>
* <li>table : this</li>
* <li>columnIndex : the column index</li>
* </ul>
* </dd>
*
* <dd><b>RowClick</b> : TableEvent(table, rowIndex, cell index, event)<br>
* <div>Fires after a row is clicked.</div>
* <ul>
* <li>table : this</li>
* <li>rowIndex : the row index</li>
* <li>index : the cell index</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>RowDoubleClick</b> : TableEvent(table, rowIndex, cellIndex, event)<br>
* <div>Fires after a row is double clicked.</div>
* <ul>
* <li>table : this</li>
* <li>rowIndex : the row index</li>
* <li>index : the cell index</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>SortChange</b> : TableEvent(table, colIndex, sortDir)<br>
* <div>Fires before the table is sorted. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* <li>colIndex : the column index</li>
* <li>sortDir : the sort direction</li>
* </ul>
* </dd>
*
* <dd><b>BeforeSelect</b> : TableEvent(table, item)<br>
* <div>Fires before a item is selected. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* <li>item : the selected item</li>
* </ul>
* </dd>
*
* <dd><b>SelectionChange</b> : TableEvent(table, selected)<br>
* <div>Fires after a item has been removed.</div>
* <ul>
* <li>table : this</li>
* <li>selected : the selected items</li>
* </ul>
* </dd>
*
* <dd><b>KeyPress</b> : TableEvent(table)<br>
* <div>Fires before the table is sorted. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>table : this</li>
* </ul>
* </dd>
*
* <dd><b>HeaderContextMenu</b> : TableEvent(table, colIndex, menu)<br>
* <div>Fires right before the header's context menu is displayed.</div>
* <ul>
* <li>table : this</li>
* <li>colIndex : the column index</li>
* <li>menu : the context menu</li>
* </ul>
* </dd>
* </dl>
*
* <dl>
* <dt>Inherited Events:</dt>
* <dd>BoxComponent Move</dd>
* <dd>BoxComponent Resize</dd>
* <dd>Component Enable</dd>
* <dd>Component Disable</dd>
* <dd>Component BeforeHide</dd>
* <dd>Component Hide</dd>
* <dd>Component BeforeShow</dd>
* <dd>Component Show</dd>
* <dd>Component Attach</dd>
* <dd>Component Detach</dd>
* <dd>Component BeforeRender</dd>
* <dd>Component Render</dd>
* <dd>Component BrowserEvent</dd>
* <dd>Component BeforeStateRestore</dd>
* <dd>Component StateRestore</dd>
* <dd>Component BeforeStateSave</dd>
* <dd>Component SaveState</dd>
* </dl>
*
* <dl>
* <dt><b>CSS:</b></dt>
* <dd>.my-tbl-col-[column id] { each column }</dd>
* <dd>.my-tbl-td-[column index] { cell td }</dd>
* </dd>
* <dd>.my-tbl-td-inner-[column index] { cell inner element }</dd>
* <dd>.my-tbl-td-cell-[column index] { cell text element }</dd>
* </dl>
*
* @see TableColumn
* @see TableColumnModel
*
* @deprecated see {@link Grid}
*/
public class Table extends Container<TableItem> implements BaseTable, Selectable<TableItem> {
/**
* The table's column model.
*/
protected TableColumnModel cm;
/**
* The selection model.
*/
protected TableSelectionModel sm;
/**
* The table header.
*/
protected TableHeader header;
protected boolean bulkRender = true;
private boolean highlight = true;
private boolean columnContextMenu = true;
private boolean verticalLines;
private boolean horizontalScroll = true;
private TableView view;
private Size lastSize;
private int lastLeft;
private boolean stripeRows;
private DelayedTask scrollTask = new DelayedTask(new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
header.updateSplitBars();
}
});
private DelayedTask recaluateTask = new DelayedTask(new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
recalculate();
}
});
/**
* Creates a new single select table. A column model must be set before the
* table is rendered.
*/
public Table() {
focusable = true;
baseStyle = "my-tbl";
attachChildren = false;
setSelectionModel(new TableSelectionModel());
}
/**
* Creates a new table.
*
* @param cm the column model
*/
public Table(TableColumnModel cm) {
this();
this.cm = cm;
cm.table = this;
}
@Override
public boolean add(TableItem item) {
return super.add(item);
}
/**
* Adds a table listener.
*
* @param listener thet able listener
*/
public void addTableListener(TableListener listener) {
addListener(Events.ColumnClick, listener);
addListener(Events.SortChange, listener);
addListener(Events.CellClick, listener);
addListener(Events.CellDoubleClick, listener);
addListener(Events.RowClick, listener);
addListener(Events.RowDoubleClick, listener);
}
@Override
public TableItem findItem(Element elem) {
if (rendered && view != null) {
int idx = view.findRowIndex(elem);
TableItem item = idx != -1 ? getItem(idx) : super.findItem(elem);
return item;
}
return super.findItem(elem);
}
/**
* Returns true if bulk rendering is enabled.
*
* @return the bulk rendering state
*/
public boolean getBulkRender() {
return bulkRender;
}
/**
* Returns the column at the specified index.
*
* @param index the column index
* @return the column
*/
public TableColumn getColumn(int index) {
return cm.getColumn(index);
}
/**
* Returns the column with the given id.
*
* @param id the column id
* @return the column
*/
public TableColumn getColumn(String id) {
return cm.getColumn(id);
}
/**
* Returns the column context menu enabed state.
*
* @return <code>true</code> if enabled, <code>false</code> otherwise.
*/
public boolean getColumnContextMenu() {
return columnContextMenu;
}
/**
* Returns the number of columns contained in the table.
*
* @return the number of columns
*/
public int getColumnCount() {
return cm.getColumnCount();
}
/**
* Returns the table's column model.
*
* @return the column model
*/
public TableColumnModel getColumnModel() {
return cm;
}
public Menu getContextMenu() {
return super.getContextMenu();
}
/**
* Returns true if highlights are enabled.
*
* @return true if mouse overs are enable
*/
public boolean getHighlight() {
return highlight;
}
/**
* Returns true if horizontal scrolling is enabled
*
* @return the horizontal scroll state
*/
public boolean getHorizontalScroll() {
return horizontalScroll;
}
public TableItem getSelectedItem() {
return (TableItem) sm.getSelectedItem();
}
public List<TableItem> getSelectedItems() {
return sm.getSelectedItems();
}
public SelectionMode getSelectionMode() {
return sm.getSelectionMode();
}
/**
* Returns the table's selection model.
*
* @return the selection model
*/
public TableSelectionModel getSelectionModel() {
return sm;
}
/**
* Returns the table's header.
*
* @return the table header
*/
public TableHeader getTableHeader() {
if (header == null) {
header = new TableHeader(this);
}
return header;
}
/**
* Returns true if vertical lines are enabled.
*
* @return the vertical lines state
*/
public boolean getVerticalLines() {
return verticalLines;
}
/**
* Returns the table's view.
*
* @return the view
*/
public TableView getView() {
if (view == null) {
view = new TableView();
}
return view;
}
@Override
public boolean insert(TableItem item, int index) {
boolean added = super.insert(item, index);
if (added) {
if (rendered) {
view.renderItem(item, index);
}
if (rendered) {
recaluateTask.delay(100);
}
}
return added;
}
/**
* Returns true if row striping is enabled.
*
* @return the strip row state
*/
public boolean isStripeRows() {
return stripeRows;
}
@Override
public void onAttach() {
super.onAttach();
header.resizeColumns(false, true);
}
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
int type = DOM.eventGetType(event);
if (type == Event.ONSCROLL) {
int left = view.getScrollEl().getScrollLeft();
if (left == lastLeft) {
return;
}
lastLeft = left;
header.el().setLeft(-left);
scrollTask.delay(400);
}
}
public void onComponentEvent(ComponentEvent ce) {
super.onComponentEvent(ce);
TableItem item = findItem(ce.getTarget());
if (item != null) {
item.onComponentEvent(ce);
}
}
public void onSelectChange(TableItem item, boolean select) {
getView().onSelectItem(item, select);
}
@Override
public void recalculate() {
if (rendered) {
header.resizeColumns(false, true);
}
}
@Override
public boolean remove(TableItem item) {
return super.remove(item);
}
/**
* Remvoves a table listener.
*
* @param listener the table listener
*/
public void removeTableLisetener(TableListener listener) {
removeListener(Events.ColumnClick, listener);
removeListener(Events.SortChange, listener);
removeListener(Events.CellClick, listener);
removeListener(Events.CellDoubleClick, listener);
removeListener(Events.RowClick, listener);
removeListener(Events.RowDoubleClick, listener);
}
@Override
public void scrollIntoView(TableItem item) {
item.el().scrollIntoView(view.getScrollEl().dom, false);
}
/**
* True to bulk render the table when first rendered (defaults to true). When
* true, widget are not supported in table cells.
*
* @param bulkRender true for bulk rendering
*/
public void setBulkRender(boolean bulkRender) {
this.bulkRender = bulkRender;
}
/**
* Sets whether the table header context menu is displayed (defaults to true).
*
* @param columnContextMenu the column context menu sate
*/
public void setColumnContextMenu(boolean columnContextMenu) {
this.columnContextMenu = columnContextMenu;
}
/**
* Sets the table's column model.
*
* @param cm the column model
*/
public void setColumnModel(TableColumnModel cm) {
this.cm = cm;
cm.table = this;
}
@Override
public void setContextMenu(Menu menu) {
super.setContextMenu(menu);
}
/**
* True to highlight the current row (defaults to true).
*
* @param highlight the highlight state
*/
public void setHighlight(boolean highlight) {
this.highlight = highlight;
}
/**
* True to display a horizonatal scroll bar when needed (defaults to true).
*
* @param horizontalScroll the horizontal scroll state
*/
public void setHorizontalScroll(boolean horizontalScroll) {
this.horizontalScroll = horizontalScroll;
}
public void setSelectedItem(TableItem item) {
sm.select(item, false);
}
public void setSelectedItems(List<TableItem> items) {
sm.select(items, false);
}
/**
* Sets the table's selection mode.
*
* @param mode the selection mode
*/
public void setSelectionMode(SelectionMode mode) {
setSelectionModel(new TableSelectionModel(mode));
}
/**
* Sets the table's selection model.
*
* @param sm the selection model
*/
public void setSelectionModel(TableSelectionModel sm) {
assert sm != null;
if (this.sm != null) {
this.sm.bind(null);
}
this.sm = sm;
sm.bind(this);
}
/**
* True to stripe the rows (defaults to false).
*
* @param stripeRows true to strip rows
*/
public void setStripeRows(boolean stripeRows) {
this.stripeRows = stripeRows;
}
/**
* Sets the table's header. Should only be called when providing a custom
* table header. Has no effect if called after the table has been rendered.
*
* @param header the table header
*/
public void setTableHeader(TableHeader header) {
if (!isRendered()) {
header.table = this;
this.header = header;
}
}
/**
* True to display vertical borders on the table data (defaults to false).
*
* @param verticalLines true for vertical lines
*/
public void setVerticalLines(boolean verticalLines) {
this.verticalLines = verticalLines;
}
/**
* Sets the table's view. Provides a way to provide specialized views. table
* views.
*
* @param view the view
*/
public void setView(TableView view) {
this.view = view;
}
/**
* Sorts the table using the specified column index.
*
* @param index the column index
* @param sortDir the direction to sort (NONE, ASC, DESC)
*/
public void sort(int index, SortDir sortDir) {
if (rendered) {
TableEvent te = new TableEvent(this);
te.setColumnIndex(index);
te.setSortDir(sortDir);
if (fireEvent(Events.SortChange, te)) {
getTableHeader().sort(index, sortDir);
getView().sort(index, sortDir);
}
} else {
TableColumn col = getColumn(index);
col.sortDir = sortDir;
}
}
@Override
protected ComponentEvent createComponentEvent(Event event) {
TableEvent te = new TableEvent(this);
te.setEvent(event);
if (view != null && event != null) {
Element target = event.getEventTarget().cast();
int idx = view.findRowIndex(target);
if (idx != -1) {
te.setItem(idx != -1 ? getItem(idx) : null);
te.setCellIndex(view.findCellIndex(target));
}
}
return te;
}
@SuppressWarnings("rawtypes")
@Override
protected ContainerEvent createContainerEvent(TableItem item) {
return new TableEvent(this, item);
}
protected void doAttachChildren() {
if (header != null) ComponentHelper.doAttach(header);
if (!bulkRender) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
TableItem item = getItem(i);
if (item.hasWidgets) {
int ct = item.getValues().length;
for (int j = 0; j < ct; j++) {
Object obj = item.getValue(j);
if (obj != null && obj instanceof Widget) {
ComponentHelper.doAttach((Widget) item.getValue(j));
}
}
}
}
}
}
protected void doDetachChildren() {
if (header != null) ComponentHelper.doDetach(header);
if (!getBulkRender()) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
TableItem item = getItem(i);
if (item.hasWidgets) {
int ct = item.getValues().length;
for (int j = 0; j < ct; j++) {
Object obj = item.getValue(j);
if (obj != null && obj instanceof Widget) {
ComponentHelper.doDetach((Widget) item.getValue(j));
}
}
}
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected String getRenderedValue(TableItem item, int column, Object value) {
TableColumn col = cm.getColumn(column);
CellRenderer r = col.getRenderer();
if (r != null) {
return r.render(item, col.getId(), value);
} else {
if (value != null) {
return value.toString();
}
return " ";
}
}
protected void onKeyPress(TableEvent te) {
TableEvent e = new TableEvent(this);
e.setEvent(te.getEvent());
e.setItem(te.getItem());
fireEvent(Events.KeyPress, e);
}
@Override
protected void onRender(Element target, int index) {
setElement(DOM.createDiv(), target, index);
new KeyNav<ComponentEvent>(this) {
@Override
public void onKeyPress(ComponentEvent ce) {
Table.this.onKeyPress((TableEvent) ce);
}
};
header = getTableHeader();
header.render(el().dom);
header.init(this);
for (TableColumn col : getColumnModel().getColumns()) {
if (col.sortDir != SortDir.NONE) {
header.onSortChange(col, col.sortDir);
}
}
view = getView();
view.init(this);
view.render();
view.renderItems();
}
protected void onResize(int width, int height) {
int h = width;
int w = height;
if (lastSize != null) {
if (lastSize.width == w && lastSize.height == h) {
return;
}
}
lastSize = new Size(w, h);
header.resizeColumns(false, true);
}
@Override
protected void onShowContextMenu(int x, int y) {
super.onShowContextMenu(x, y);
getView().clearHoverStyles();
}
@Override
protected ComponentEvent previewEvent(EventType type, ComponentEvent ce) {
if (type == Events.Remove || type == Events.Add) {
TableEvent te = (TableEvent) ce;
te.setRowIndex(te.getIndex());
}
return super.previewEvent(type, ce);
}
}