/* * 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); } } }