/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.table;
import java.util.Collections;
import java.util.Comparator;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.SortDir;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XDOM;
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.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.google.gwt.dom.client.NodeList;
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;
/**
* This class encapsulates the user interface of a {@link Table}.
*
* <p />
* When using custom cell renderers or nested widgets within cells it may be
* necessary 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)}.
*
* @deprecated see {@link Grid}
*/
public class TableView {
private static String bodyHTML;
static {
StringBuffer sb = new StringBuffer();
sb.append("<div style='overflow: hidden;'>");
sb.append("<div style='overflow: scroll;'>");
sb.append("<div class='my-tbl-data'></div>");
sb.append("</div></div>");
bodyHTML = sb.toString();
}
protected static native void markRendered(TableItem item) /*-{
item.@com.extjs.gxt.ui.client.widget.Component::rendered = true;
}-*/;
// styles
protected String baseStyle = "my-tbl-item";
protected String overStyle = baseStyle + "-over";
protected String selStyle = baseStyle + "-sel";
protected String cellStyle = baseStyle + "-" + "cell";
protected String cellOverflowStyle = cellStyle + "-" + "overflow";
protected String textStyle = cellStyle + "-text";
protected String widgetStyle = cellStyle + "-widget";
protected String rowSelector = ".my-tbl-item";
protected String cellSelector = ".my-tbl-item-cell";
protected TableColumnModel cm;
protected El dataEl, scrollEl;
protected Table table;
protected int scrollBarWidth;
private int cellSelectorDepth = 10;
private int rowSelectorDepth = 10;
/**
* Returns the cell.
*
* @param elem the cell element or any child element
* @return the cell or null if not match
*/
public Element findCell(Element elem) {
if (elem == null) {
return null;
}
return fly(elem).findParentElement(cellSelector, cellSelectorDepth);
}
/**
* Returns the elements cell index.
*
* @param elem the row element
* @return the cell index or -1 if not match
*/
public int findCellIndex(Element elem) {
Element cell = findCell(elem);
if (cell != null) {
return getCellIndex(cell);
}
return -1;
}
/**
* Returns the row element.
*
* @param el the row element or any child element
* @return the matching row or null if no match
*/
public Element findRow(Element el) {
if (el == null) {
return null;
}
return fly(el).findParentElement(rowSelector, rowSelectorDepth);
}
/**
* Reutrns the row index of the element.
*
* @param elem the row element or any child element
* @return the row index or -1 if not found
*/
public int findRowIndex(Element elem) {
Element r = findRow(elem);
return r != null ? r.getPropertyInt("rowIndex") : -1;
}
/**
* Returns the cell selector depth.
*
* @return the cell selector depth
*/
public int getCellSelectorDepth() {
return cellSelectorDepth;
}
/**
* Returns the data element.
*
* @return the data element
*/
public El getDataEl() {
return dataEl;
}
/**
* Returns the row selector depth.
*
* @return the row selector depth
*/
public int getRowSelectorDepth() {
return rowSelectorDepth;
}
/**
* Returns the scroll element.
*
* @return the scroll element
*/
public El getScrollEl() {
return scrollEl;
}
/**
* Adjusts the table to its current size.
*/
public void resize() {
if (table != null && table.isRendered()) {
int width = table.getOffsetWidth();
int headerHeight = table.getTableHeader().getOffsetHeight();
int bodyHeight = table.getOffsetHeight() - headerHeight;
int bodyWidth = width;
if (table.isAutoHeight()) {
scrollEl.setHeight("auto");
dataEl.setHeight("auto");
bodyHeight = dataEl.getHeight();
bodyHeight += table.el().getBorderWidth("tb");
}
int columnModelWidth = cm.getTotalWidth();
dataEl.setWidth(Math.max(width, columnModelWidth));
table.getTableHeader().setWidth(columnModelWidth);
bodyHeight -= table.el().getBorderWidth("tb");
bodyWidth -= table.el().getBorderWidth("lr");
if (dataEl.getHeight() < bodyHeight) {
scrollEl.setStyleAttribute("overflowY", "hidden");
} else {
scrollEl.setStyleAttribute("overflowY", "auto");
}
if (table.getHorizontalScroll()) {
scrollEl.setStyleAttribute("overflowX", "auto");
if (columnModelWidth < width) {
scrollEl.setStyleAttribute("overflowX", "hidden");
table.getTableHeader().el().setLeft(0);
scrollEl.setScrollLeft(0);
}
}
if (table.isAutoHeight()) {
bodyHeight = -1;
}
scrollEl.setSize(bodyWidth, bodyHeight);
}
}
/**
* The number of levels to search for cells in event delegation (defaults to
* 10).
*
* @param cellSelectorDepth the cell selector depth
*/
public void setCellSelectorDepth(int cellSelectorDepth) {
this.cellSelectorDepth = cellSelectorDepth;
}
/**
* The number of levels to search for rows in event delegation (defaults to
* 10).
*
* @param rowSelectorDepth the row selector depth
*/
public void setRowSelectorDepth(int rowSelectorDepth) {
this.rowSelectorDepth = rowSelectorDepth;
}
/**
* Sorts the table.
*
* @param index the column to sort
* @param direction the sort direction
*/
public void sort(int index, SortDir direction) {
doSort(index, direction);
}
protected void applyCellStyles(TableItem item) {
if (item.cellStyles != null) {
for (int i = 0; i < item.cellStyles.length; i++) {
setCellStyle(item, i, item.cellStyles[i]);
}
}
}
protected void bulkRender() {
int count = table.getItemCount();
int cols = cm.getColumnCount();
TableColumn[] columns = new TableColumn[cols];
int[] widths = new int[cols];
String[] align = new String[cols];
for (int i = 0; i < columns.length; i++) {
columns[i] = cm.getColumn(i);
widths[i] = cm.getWidthInPixels(i)
- (table.getVerticalLines() && !GXT.isBorderBox ? 1 : 0);
columns[i].lastWidth = widths[i];
HorizontalAlignment ha = columns[i].getAlignment();
switch (ha) {
case LEFT:
align[i] = "left";
break;
case CENTER:
align[i] = "center";
break;
case RIGHT:
align[i] = "right";
break;
}
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
TableItem item = table.getItem(i);
item.init(table);
markRendered(item);
Object[] values = item.getValues();
Object[] styles = item.getCellStyles();
String[] tips = item.getCellToolTips();
sb.append("<div class=my-tbl-item><table cellpadding=0 cellspacing=0 tabIndex=1><tr>");
for (int j = 0; j < cols; j++) {
sb.append("<td class=\"");
sb.append(cellStyle);
sb.append(" my-tbl-td-");
sb.append(j);
sb.append("\" style=\"display: ");
sb.append(columns[j].isHidden() ? "none" : "static");
sb.append("; width: ");
sb.append(widths[j]);
sb.append("px;\" index=\"");
sb.append(j);
sb.append("\"><div class=\"");
sb.append(cellOverflowStyle);
sb.append(" my-tbl-td-inner-");
sb.append(j);
sb.append("\" style=\"width: ");
sb.append(widths[j]);
sb.append("px;\"><div");
if (tips != null) {
sb.append(" qtip=\"");
sb.append(tips[j]);
sb.append("\"");
}
sb.append(" class=\"my-tbl-td-cell-");
sb.append(j);
sb.append(" ");
sb.append(textStyle);
if (styles != null) {
sb.append(" ");
sb.append(styles[j]);
}
sb.append("\" style=\"text-align: ");
sb.append(align[j]);
sb.append(";\">");
sb.append(table.getRenderedValue(item, j, values[j]));
sb.append("</div></div></td>");
}
sb.append("</tr></table></div>");
}
dataEl.dom.setInnerHTML(sb.toString());
NodeList<Element> elems = dataEl.select(".my-tbl-item");
int ct = table.getItemCount();
for (int i = 0; i < ct; i++) {
TableItem item = table.getItem(i);
item.setElement(elems.getItem(i));
applyCellStyles(item);
}
processRows(0);
}
protected void clearHoverStyles() {
int count = table.getItemCount();
for (int i = 0; i < count; i++) {
TableItem item = table.getItem(i);
onHighlightRow(item, false);
}
}
@SuppressWarnings("unchecked")
protected void doSort(int index, final SortDir direction) {
TableColumn column = table.getColumn(index);
final Comparator comparator = direction.comparator(column.getComparator());
final int col = index;
Collections.sort(table.getItems(), new Comparator() {
public int compare(Object arg0, Object arg1) {
TableItem item1 = (TableItem) arg0;
TableItem item2 = (TableItem) arg1;
Object o1 = item1.getValue(col);
Object o2 = item2.getValue(col);
return comparator.compare(o1, o2);
}
});
reorderItems();
updateIndexes(0);
processRows(0);
}
protected El fly(Element elem) {
return El.fly(elem);
}
protected Element getCell(TableItem item, int cell) {
return item.el().select("td.my-tbl-item-cell").getItem(cell);
}
protected int getCellIndex(Element elem) {
if (elem != null) {
String index = elem.getAttribute("index");
if (index != null && index.length() != 0) {
return Integer.parseInt(index);
}
}
return -1;
}
protected Element getTextCellElement(TableItem item, int cell) {
return getTextCellInternal(item.getElement(), cell);
}
protected native Element getTextCellInternal(Element elem, int column) /*-{
return elem.firstChild.firstChild.firstChild.childNodes[column].firstChild.firstChild;
}-*/;
protected void init(final Table table) {
this.table = table;
this.cm = table.getColumnModel();
Listener<TableEvent> l = new Listener<TableEvent>() {
public void handleEvent(TableEvent e) {
EventType type = e.getType();
if (type == Events.HeaderChange) {
TableColumn c = cm.getColumn(e.getColumnIndex());
table.getTableHeader().getColumnUI(e.getColumnIndex()).onTextChange(c.getText());
} else if (type == Events.WidthChange) {
table.getTableHeader().resizeColumn(e.getColumnIndex(), true);
} else if (type == Events.HiddenChange) {
TableColumn c = cm.getColumn(e.getColumnIndex());
table.getTableHeader().showColumn(e.getColumnIndex(), !c.isHidden());
}
}
};
table.addListener(Events.Remove, new Listener<TableEvent>() {
public void handleEvent(TableEvent be) {
onRemove(be.getRowIndex());
}
});
cm.addListener(Events.HeaderChange, l);
cm.addListener(Events.WidthChange, l);
cm.addListener(Events.HiddenChange, l);
}
protected void onHighlightRow(TableItem item, boolean highlight) {
if (highlight) {
item.addStyleName(overStyle);
} else {
item.removeStyleName(overStyle);
}
}
protected void onRemove(int index) {
updateIndexes(index);
processRows(0);
}
protected void onSelectItem(TableItem item, boolean select) {
if (select) {
item.addStyleName(selStyle);
} else {
item.removeStyleName(selStyle);
item.removeStyleName(overStyle);
}
}
protected void render() {
scrollBarWidth = XDOM.getScrollBarWidth();
Element div = DOM.createDiv();
div.setInnerHTML(bodyHTML.toString());
scrollEl = new El(El.fly(div).getSubChild(2));
dataEl = scrollEl.firstChild();
DOM.appendChild(table.getElement(), DOM.getFirstChild(div));
if (table.getVerticalLines()) {
table.addStyleName("my-tbl-vlines");
}
if (!GXT.isIE) {
DOM.setElementPropertyInt(table.getElement(), "tabIndex", 0);
}
DOM.sinkEvents(scrollEl.dom, Event.ONSCROLL);
if (!GXT.isGecko) {
table.disableTextSelection(true);
}
table.el().addEventsSunk(
Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS);
}
protected void renderItem(TableItem item, int index) {
item.setStyleName(baseStyle);
item.init(table);
int cols = cm.getColumnCount();
Object[] values = item.getValues();
Object[] styles = item.getCellStyles();
String[] tips = item.getCellToolTips();
Object[] svalues = new Object[cols];
for (int i = 0; i < cols; i++) {
if (!item.hasWidgets && values[i] instanceof Widget) {
item.hasWidgets = true;
if (table.bulkRender) {
throw new RuntimeException(
"Bulk rendering must be disabled when adding widgets to table items");
}
}
svalues[i] = table.getRenderedValue(item, i, values[i]);
}
StringBuffer sb = new StringBuffer();
sb.append("<table cellpadding=0 cellspacing=0 tabIndex=1><tr>");
for (int i = 0; i < cols; i++) {
TableColumn c = cm.getColumn(i);
String display = c.isHidden() ? "none" : "static";
int w = table.getColumnModel().getWidthInPixels(c.index);
if (!GXT.isBorderBox) {
w -= table.getVerticalLines() ? 1 : 0;
}
HorizontalAlignment align = c.getAlignment();
String salign = "left";
if (align == HorizontalAlignment.CENTER) {
salign = "center";
} else if (align == HorizontalAlignment.RIGHT) {
salign = "right";
}
String tip = tips == null ? "" : "qtip='" + tips[i] + "'";
sb.append("<td class=" + cellStyle + " style='display: " + display + ";width: " + w
+ "px' index=" + i + "><div class=" + cellOverflowStyle + " style='width:" + w
+ "'><div class='" + textStyle + (styles == null ? "" : " " + styles[i])
+ "' style='text-align:" + salign + "' " + tip + ">" + svalues[i]
+ "</div></div></td>");
}
sb.append("</tr></table>");
item.render(dataEl.dom, index);
item.getElement().setInnerHTML(sb.toString());
if (item.hasWidgets) {
for (int i = 0; i < cols; i++) {
if (values[i] instanceof Widget) {
Widget w = (Widget) values[i];
Element text = getTextCellElement(item, i);
El textEl = El.fly(text);
textEl.dom.setInnerHTML("");
textEl.dom.setClassName(widgetStyle);
textEl.dom.appendChild(w.getElement());
if (table.isAttached()) {
ComponentHelper.doAttach(w);
}
}
}
}
applyCellStyles(item);
item.cellsRendered = true;
updateIndexes(index);
processRows(index);
}
protected void renderItems() {
if (table.getBulkRender()) {
bulkRender();
} else {
int count = table.getItemCount();
for (int i = 0; i < count; i++) {
TableItem item = table.getItem(i);
renderItem(item, i);
}
}
updateIndexes(0);
}
protected void renderItemValue(TableItem item, int index, Object value) {
Element textElem = getTextCellElement(item, index);
if (textElem != null) {
Element child = DOM.getChild(textElem, 0);
if (child != null) {
DOM.removeChild(textElem, DOM.getChild(textElem, 0));
}
DOM.setInnerHTML(textElem, "");
if (value instanceof Widget) {
Widget widget = (Widget) value;
textElem.setClassName(widgetStyle);
DOM.appendChild(textElem, widget.getElement());
if (table.isAttached()) {
ComponentHelper.doAttach(widget);
}
} else {
String s = table.getRenderedValue(item, index, value);
textElem.setInnerHTML(s);
}
}
applyCellStyles(item);
}
/**
* Sorts the table items based on the current order.
*/
protected void reorderItems() {
dataEl.removeChildren();
int numRows = table.getItemCount();
for (int i = 0; i < numRows; i++) {
TableItem item = table.getItem(i);
dataEl.dom.appendChild(item.getElement());
}
table.getSelectionModel().refresh();
}
protected void resizeCells(int columnIndex) {
TableColumn c = cm.getColumn(columnIndex);
int w = cm.getWidthInPixels(c.index);
if (!GXT.isBorderBox) {
w -= table.getVerticalLines() ? 1 : 0;
}
if (c.lastWidth != 0 && c.lastWidth == w) {
return;
}
c.lastWidth = w;
int rows = table.getItemCount();
for (int j = 0; j < rows; j++) {
TableItem item = table.getItem(j);
sizeCell(item.getElement(), columnIndex, w);
if (j == 0) {
showColumn(item.getElement(), !c.isHidden(), columnIndex);
}
}
}
protected void setCellStyle(TableItem item, int index, String style) {
if (item.cellsRendered) {
Element cell = getTextCellElement(item, index);
El.fly(cell).addStyleName(textStyle, style);
}
}
protected native void showColumn(Element elem, boolean show, int index) /*-{
var tbl = elem.firstChild;
var cell = tbl.firstChild.firstChild.childNodes[index]
cell.style.display = show ? '' : 'none';
}-*/;
protected void showColumn(int index, boolean show) {
int count = table.getItemCount();
for (int i = 0; i < count; i++) {
showColumn(table.getItem(i).getElement(), show, index);
}
}
protected native void sizeCell(Element elem, int index, int width) /*-{
var tbl = elem.firstChild;
var cell = tbl.firstChild.firstChild.childNodes[index];
cell.style.width = width;
cell.firstChild.style.width = width;
}-*/;
protected void processRows(int startIndex) {
if (table.getItemCount() == 0) {
return;
}
for (int i = 0, len = table.getItemCount(); i < len; i++) {
Element row = table.getItem(i).getElement();
String cls = " x-grid3-row-alt ";
if (table.isStripeRows()) {
boolean isAlt = ((i + 1) % 2 == 0);
boolean hasAlt = (" " + row.getClassName() + " ").indexOf(cls) != -1;
if (isAlt == hasAlt) {
continue;
}
if (isAlt) {
row.setClassName(row.getClassName() + " " + cls);
} else {
row.setClassName(row.getClassName().replaceFirst(cls, ""));
}
}
}
}
private void updateIndexes(int start) {
int count = table.getItemCount();
for (int i = start; i < count; i++) {
TableItem item = table.getItem(i);
item.getElement().setPropertyInt("rowIndex", i);
}
}
}