/* * 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.grid; import java.util.ArrayList; import java.util.List; import com.extjs.gxt.ui.client.GXT; import com.extjs.gxt.ui.client.core.XDOM; import com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.store.ListStore; import com.extjs.gxt.ui.client.store.Record; import com.extjs.gxt.ui.client.util.DelayedTask; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NodeList; import com.google.gwt.user.client.ui.Widget; /** * Renders the rows as they scroll into view. This GridView is fast for * displaying many rows at once, but it does not support all features the normal * {link @GridView} supports, such has expanding rows. * * <p /> * Only works with constant row heights that can be specified using * {@link #setRowHeight(int)}. */ public class BufferView extends GridView { private boolean bufferEnabled = true; private int cacheSize = 20; private int cleanDelay = 500; private DelayedTask cleanTask; private DelayedTask renderTask; private int rowHeight = 21; private int scrollDelay = 0; /** * Returns the amount of rows that should be cached. * * @return the cache size */ public int getCacheSize() { return cacheSize; } /** * Returns the amount of time before cleaning is done. * * @return the clean delay */ public int getCleanDelay() { return cleanDelay; } /** * Returns the height of one row. * * @return the height of one row */ public int getRowHeight() { return rowHeight; } /** * Returns the amount of time before new rows are displayed after scrolling * * @return the scroll delay */ public int getScrollDelay() { return scrollDelay; } /** * Returns true if buffering is enabled. * * @return true for buffering */ public boolean isBufferEnabled() { return bufferEnabled; } /** * True to enabled buffered functionality (defaults to true). * * @param bufferEnabled true to buffer, otherwise false */ public void setBufferEnabled(boolean bufferEnabled) { this.bufferEnabled = bufferEnabled; } /** * Sets the amount of rows that should be cached (default to 20). * * @param cacheSize the new cache size */ public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; } /** * Sets the amount of time before cleaning is done (defaults to 500). * * @param cleanDelay the new clean delay */ public void setCleanDelay(int cleanDelay) { this.cleanDelay = cleanDelay; } /** * Sets the height of one row (defaults to 19). * * @param rowHeight the new row height. */ public void setRowHeight(int rowHeight) { this.rowHeight = rowHeight; } /** * Sets the amount of time before new rows are displayed after scrolling * (defaults to 0). * * @param scrollDelay the new scroll delay. */ public void setScrollDelay(int scrollDelay) { this.scrollDelay = scrollDelay; } // a buffered method to clean rows protected void clean() { if (grid == null || !grid.isViewReady() || !bufferEnabled) { return; } if (cleanTask == null) { cleanTask = new DelayedTask(new Listener<BaseEvent>() { public void handleEvent(BaseEvent be) { doClean(); } }); } cleanTask.delay(cleanDelay); } protected void cleanModel(ModelData at) { } @Override protected void doAttach() { super.doAttach(); update(); } protected void doClean() { if (grid == null || !grid.isViewReady() || !bufferEnabled) { return; } int count = getVisibleRowCount(); if (count > 0) { int[] vr = getVisibleRows(count); vr[0] -= cacheSize; vr[1] += cacheSize; int i = 0; NodeList<Element> rows = getRows(); // if first is less than 0, all rows have been rendered // so lets clean the end... if (vr[0] <= 0) { i = vr[1] + 1; } for (int len = grid.getStore().getCount(); i < len; i++) { // if current row is outside of first and last and // has content, update the innerHTML to nothing if ((i < vr[0] || i > vr[1])) { detachWidget(i, false); widgetList.set(i, null); cleanModel(ds.getAt(i)); rows.getItem(i).setInnerHTML(""); } } } } @Override protected String doRender(List<ColumnData> cs, List<ModelData> rows, int startRow, int colCount, boolean stripe) { if (!bufferEnabled) { return super.doRender(cs, rows, startRow, colCount, stripe); } return doRender(cs, rows, startRow, colCount, stripe, false); } protected String doRender(List<ColumnData> cs, List<ModelData> rows, int startRow, int colCount, boolean stripe, boolean onlyBody) { int last = colCount - 1; int rowBodyColSpanCount = colCount; if (enableRowBody) { if (grid.getSelectionModel() instanceof CheckBoxSelectionModel<?>) { CheckBoxSelectionModel<?> sm = (CheckBoxSelectionModel<?>) grid.getSelectionModel(); if (cm.getColumnById(sm.getColumn().getId()) != null) { rowBodyColSpanCount--; } } for (ColumnConfig c : cm.getColumns()) { if (c instanceof RowExpander || c instanceof RowNumberer) { rowBodyColSpanCount--; } } } int rh = getStyleRowHeight(); int[] vr = getVisibleRows(getVisibleRowCount()); String tstyle = "width:" + getTotalWidth() + "px;height:" + rh + "px;"; // buffers StringBuilder buf = new StringBuilder(); StringBuilder cb = new StringBuilder(); for (int j = 0, len = rows.size(); j < len; j++) { ModelData model = (ModelData) rows.get(j); model = prepareData(model); Record r = ds.hasRecord(model) ? ds.getRecord(model) : null; int rowIndex = (j + startRow); boolean visible = rowIndex >= vr[0] && rowIndex <= vr[1]; if (!onlyBody) { widgetList.add(rowIndex, new ArrayList<Widget>()); } if (visible) { for (int i = 0; i < colCount; i++) { ColumnData c = cs.get(i); c.css = c.css == null ? "" : c.css; String rv = getRenderedValue(c, rowIndex, i, model, c.name); String css = (i == 0 ? "x-grid-cell-first " : (i == last ? "x-grid3-cell-last " : " ")) + " " + (c.css == null ? "" : c.css); String attr = c.cellAttr != null ? c.cellAttr : ""; String cellAttr = c.cellAttr != null ? c.cellAttr : ""; if (isShowInvalidCells() && r != null && !r.isValid(c.id)) { buf.append(" x-grid3-invalid-cell"); } if (isShowDirtyCells() && r != null && r.getChanges().containsKey(c.id)) { css += " x-grid3-dirty-cell"; } cb.append("<td id=\"" + XDOM.getUniqueId() + "\" role=\"gridcell\" class=\"x-grid3-col x-grid3-cell x-grid3-td-"); cb.append(c.id); cb.append(" "); cb.append(css); cb.append("\" style=\""); cb.append(c.style); cb.append("\" "); cb.append(cellAttr); cb.append("><div unselectable=\"on\" class=\"x-grid3-cell-inner x-grid3-col-"); cb.append(c.id); cb.append("\" "); cb.append(attr); cb.append(">"); cb.append(rv); cb.append("</div></td>"); } } String alt = ""; if (stripe && ((rowIndex + 1) % 2 == 0)) { alt += " x-grid3-row-alt"; } if (isShowDirtyCells() && r != null && r.isDirty()) { alt += " x-grid3-dirty-row"; } if (!selectable) { alt += " x-unselectable-single"; } if (viewConfig != null) { alt += " " + viewConfig.getRowStyle(model, rowIndex, ds); } if (!onlyBody || !visible) { buf.append("<div role=\"row\" class=\"x-grid3-row "); buf.append(alt); buf.append("\" style=\""); buf.append(tstyle); buf.append("\" id=\""); buf.append(grid.getId()); buf.append("_"); buf.append(ds.getKeyProvider() != null ? ds.getKeyProvider().getKey(model) : XDOM.getUniqueId()); buf.append("\">"); } if (visible) { buf.append("<table role=presentation class=x-grid3-row-table border=0 cellspacing=0 cellpadding=0 style=\""); buf.append(tstyle); buf.append("\"><tbody role=presentation><tr role=presentation>"); buf.append(cb.toString()); buf.append("</tr>"); if (enableRowBody) { buf.append("<tr class=x-grid3-row-body-tr style=\"\"><td colspan="); buf.append(rowBodyColSpanCount); buf.append(" class=x-grid3-body-cell><div class=x-grid3-row-body>${body}</div></td></tr>"); } buf.append("</tbody></table>"); } if (!onlyBody || !visible) { buf.append("</div>"); } cb = new StringBuilder(); } return buf.toString(); } protected void doUpdate() { if (grid == null || !grid.isViewReady() || !bufferEnabled) { return; } int count = getVisibleRowCount(); if (count > 0) { ColumnModel cm = grid.getColumnModel(); ListStore<ModelData> store = grid.getStore(); List<ColumnData> cs = getColumnData(); boolean stripe = grid.isStripeRows(); int[] vr = getVisibleRows(count); int cc = cm.getColumnCount(); for (int i = vr[0]; i <= vr[1]; i++) { // if row is NOT rendered and is visible, render it if (!isRowRendered(i)) { List<ModelData> list = new ArrayList<ModelData>(); list.add(store.getAt(i)); widgetList.add(i, new ArrayList<Widget>()); String html = doRender(cs, list, i, cc, stripe, true); getRow(i).setInnerHTML(html); renderWidgets(i, i); } } clean(); } } protected int getCalculatedRowHeight() { return rowHeight + borderWidth; } protected int getStyleRowHeight() { return rowHeight + (GXT.isBorderBox ? borderWidth : 0); } protected int getVisibleRowCount() { int rh = getCalculatedRowHeight(); int visibleHeight = scroller.getHeight(true); return (int) ((visibleHeight < 1) ? 0 : Math.ceil(((double) visibleHeight / rh))); } protected int[] getVisibleRows(int count) { int sc = scroller.getScrollTop(); int start = sc / getCalculatedRowHeight(); int first = Math.max(start, 0); int last = Math.min(start + count, grid.getStore().getCount() - 1); int[] i = new int[] {first, last}; return i; } protected boolean isRowRendered(int index) { Element row = getRow(index); return row != null && row.hasChildNodes(); } @Override protected void layout(boolean skipResize) { super.layout(skipResize); update(); } @Override protected void notifyShow() { super.notifyShow(); update(); } @Override protected void onAdd(ListStore<ModelData> store, List<ModelData> models, int index) { super.onAdd(store, models, index); update(); } @Override protected void onRemove(ListStore<ModelData> ds, ModelData m, int index, boolean isUpdate) { super.onRemove(ds, m, index, isUpdate); update(); } @Override protected void syncScroll() { super.syncScroll(); update(); } protected void update() { if (grid == null || !grid.isViewReady() || !bufferEnabled) { return; } if (renderTask == null) { renderTask = new DelayedTask(new Listener<BaseEvent>() { public void handleEvent(BaseEvent be) { doUpdate(); } }); } renderTask.delay(scrollDelay); } }