svn changeset:1776/svn branch:trunktags/6.7.0.beta1
@@ -15,7 +15,8 @@ import com.itmill.toolkit.terminal.gwt.client.ui.IOptionGroup; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.IPanel; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.IPasswordField; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ISelect; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITable; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITablePaging; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITableScrollingByComposition; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITabsheet; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITextArea; | |||
import com.itmill.toolkit.terminal.gwt.client.ui.ITextField; | |||
@@ -75,7 +76,7 @@ public class DefaultWidgetFactory implements WidgetFactory { | |||
return new ITextField(); | |||
} | |||
if ("table".equals(tag)) | |||
return new ITable(); | |||
return new ITablePaging(); | |||
if("datefield".equals(tag)) | |||
return new IDateField(); | |||
@@ -0,0 +1,230 @@ | |||
package com.itmill.toolkit.terminal.gwt.client.ui; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import com.google.gwt.user.client.Command; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.DeferredCommand; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.Timer; | |||
import com.google.gwt.user.client.ui.Button; | |||
import com.google.gwt.user.client.ui.ClickListener; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.google.gwt.user.client.ui.FlexTable; | |||
import com.google.gwt.user.client.ui.Grid; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.google.gwt.user.client.ui.HorizontalPanel; | |||
import com.google.gwt.user.client.ui.ScrollListener; | |||
import com.google.gwt.user.client.ui.ScrollPanel; | |||
import com.google.gwt.user.client.ui.VerticalPanel; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; | |||
import com.itmill.toolkit.terminal.gwt.client.Client; | |||
import com.itmill.toolkit.terminal.gwt.client.Paintable; | |||
import com.itmill.toolkit.terminal.gwt.client.UIDL; | |||
public class ITablePaging extends Composite implements Paintable, ClickListener { | |||
private Grid tBody = new Grid(); | |||
private Button nextPage = new Button(">"); | |||
private Button prevPage = new Button("<"); | |||
private Button firstPage = new Button("<<"); | |||
private Button lastPage = new Button(">>"); | |||
private int pageLength = 15; | |||
private boolean rowHeaders = false; | |||
private Map columnOrder = new HashMap(); | |||
private Client client; | |||
private String id; | |||
private boolean immediate; | |||
private int totalRows; | |||
private HashMap columnWidths = new HashMap(); | |||
private HashMap visibleColumns = new HashMap(); | |||
private int rowHeight = 0; | |||
private int rows; | |||
private int firstRow; | |||
public ITablePaging() { | |||
tBody.setStyleName("itable-tbody"); | |||
VerticalPanel panel = new VerticalPanel(); | |||
HorizontalPanel pager = new HorizontalPanel(); | |||
pager.add(firstPage); | |||
firstPage.addClickListener(this); | |||
pager.add(prevPage); | |||
prevPage.addClickListener(this); | |||
pager.add(nextPage); | |||
nextPage.addClickListener(this); | |||
pager.add(lastPage); | |||
lastPage.addClickListener(this); | |||
panel.add(pager); | |||
panel.add(tBody); | |||
initWidget(panel); | |||
} | |||
public void updateFromUIDL(UIDL uidl, Client client) { | |||
if (client.updateComponent(this, uidl, true)) | |||
return; | |||
this.client = client; | |||
this.id = uidl.getStringAttribute("id"); | |||
this.immediate = uidl.getBooleanAttribute("immediate"); | |||
this.totalRows = uidl.getIntAttribute("totalrows"); | |||
this.pageLength = uidl.getIntAttribute("pagelength"); | |||
this.firstRow = uidl.getIntAttribute("firstrow"); | |||
this.rows = uidl.getIntAttribute("rows"); | |||
if(uidl.hasAttribute("rowheaders")) | |||
rowHeaders = true; | |||
UIDL columnInfo = null; | |||
UIDL rowData = null; | |||
for(Iterator it = uidl.getChildIterator(); it.hasNext();) { | |||
UIDL c = (UIDL) it.next(); | |||
if(c.getTag().equals("cols")) | |||
columnInfo = c; | |||
else if(c.getTag().equals("rows")) | |||
rowData = c; | |||
else if(c.getTag().equals("actions")) | |||
updateActionMap(c); | |||
else if(c.getTag().equals("visiblecolumns")) | |||
updateVisibleColumns(c); | |||
} | |||
tBody.resize(rows+1, visibleColumns.size() + (rowHeaders ? 1 : 0 )); | |||
updateHeader(columnInfo); | |||
updateBody(rowData); | |||
updatePager(); | |||
} | |||
private void updateVisibleColumns(UIDL c) { | |||
Iterator it = c.getChildIterator(); | |||
int count = 0; | |||
visibleColumns.clear(); | |||
while(it.hasNext()) { | |||
count++; | |||
UIDL col = (UIDL) it.next(); | |||
visibleColumns.put(col.getStringAttribute("cid"), col.getStringAttribute("caption")); | |||
} | |||
} | |||
private void updateActionMap(UIDL c) { | |||
// TODO Auto-generated method stub | |||
} | |||
private void updateHeader(UIDL uidl) { | |||
if(uidl == null) | |||
return; | |||
int colIndex = (rowHeaders ? 1 : 0); | |||
for(Iterator it = uidl.getChildIterator();it.hasNext();) { | |||
UIDL col = (UIDL) it.next(); | |||
String cid = col.getStringAttribute("cid"); | |||
tBody.setText(0, colIndex, col.getStringAttribute("caption")); | |||
DOM.setAttribute(tBody.getCellFormatter().getElement(0, colIndex), "cid", cid); | |||
colIndex++; | |||
} | |||
} | |||
/** | |||
* Updates row data from uidl. UpdateFromUIDL delegates updating | |||
* tBody to this method. | |||
* | |||
* Updates may be to different part of tBody, depending on update type. | |||
* It can be initial row data, scroll up, scroll down... | |||
* | |||
* @param uidl which contains row data | |||
*/ | |||
private void updateBody(UIDL uidl) { | |||
Iterator it = uidl.getChildIterator(); | |||
int curRowIndex = 1; | |||
while(it.hasNext()){ | |||
UIDL row = (UIDL) it.next(); | |||
int colIndex = 0; | |||
if(rowHeaders) { | |||
tBody.setText(curRowIndex, colIndex, row.getStringAttribute("caption")); | |||
colIndex++; | |||
} | |||
Iterator cells = row.getChildIterator(); | |||
while(cells.hasNext()) { | |||
Object cell = cells.next(); | |||
if (cell instanceof String) { | |||
tBody.setText(curRowIndex, colIndex, (String) cell); | |||
} else { | |||
Widget cellContent = client.getWidget((UIDL) cell); | |||
tBody.setWidget(curRowIndex, colIndex, cellContent); | |||
} | |||
colIndex++; | |||
} | |||
Element rowElement = tBody.getRowFormatter().getElement(curRowIndex); | |||
DOM.setIntAttribute(rowElement, "key", uidl.getIntAttribute("key")); | |||
curRowIndex++; | |||
} | |||
} | |||
private void updatePager() { | |||
if(isFirstPage()) { | |||
firstPage.setEnabled(false); | |||
prevPage.setEnabled(false); | |||
} else { | |||
firstPage.setEnabled(true); | |||
prevPage.setEnabled(true); | |||
} | |||
if(hasNextPage()) { | |||
nextPage.setEnabled(true); | |||
lastPage.setEnabled(true); | |||
} else { | |||
nextPage.setEnabled(false); | |||
lastPage.setEnabled(false); | |||
} | |||
} | |||
private boolean hasNextPage() { | |||
if(firstRow + pageLength + 1 > totalRows) | |||
return false; | |||
return true; | |||
} | |||
private boolean isFirstPage() { | |||
if(firstRow == 0) | |||
return true; | |||
return false; | |||
} | |||
public void onClick(Widget sender) { | |||
if(sender == firstPage) | |||
client.updateVariable(this.id, "firstvisible", 0, true); | |||
else if(sender == nextPage) | |||
client.updateVariable(this.id, "firstvisible", firstRow + pageLength, true); | |||
else if(sender == prevPage) { | |||
int newFirst = firstRow - pageLength; | |||
if(newFirst < 0) | |||
newFirst = 0; | |||
client.updateVariable(this.id, "firstvisible", newFirst, true); | |||
} else if (sender == lastPage) { | |||
client.updateVariable(this.id, "firstvisible", totalRows - pageLength, true); | |||
} | |||
} | |||
} |
@@ -0,0 +1,425 @@ | |||
package com.itmill.toolkit.terminal.gwt.client.ui; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import com.google.gwt.user.client.Command; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.DeferredCommand; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.Timer; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.google.gwt.user.client.ui.FlexTable; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.google.gwt.user.client.ui.ScrollListener; | |||
import com.google.gwt.user.client.ui.ScrollPanel; | |||
import com.google.gwt.user.client.ui.VerticalPanel; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; | |||
import com.itmill.toolkit.terminal.gwt.client.Client; | |||
import com.itmill.toolkit.terminal.gwt.client.Paintable; | |||
import com.itmill.toolkit.terminal.gwt.client.UIDL; | |||
public class ITableScrollingByComposition extends Composite implements Paintable, ScrollListener { | |||
/** | |||
* multiple of pagelenght which component will | |||
* cache when requesting more rows | |||
*/ | |||
private static final double CACHE_RATE = 3; | |||
/** | |||
* fraction of pageLenght which can be scrolled without | |||
* making new request | |||
*/ | |||
private static final double CACHE_REACT_RATE = 1; | |||
private int firstRendered = -1; | |||
private int lastRendered = -1; | |||
private int firstRowInViewPort = 0; | |||
private int pageLength = 15; | |||
private boolean rowHeaders = false; | |||
private Map columnOrder = new HashMap(); | |||
private Client client; | |||
private String id; | |||
private boolean immediate; | |||
private FlexTable tHead = new FlexTable(); | |||
private FlexTable tBody = new FlexTable(); | |||
private ScrollPanel bodyContainer = new ScrollPanel(); | |||
private VerticalPanel bodyContent = new VerticalPanel(); | |||
private ScrollPanel headerContainer = new ScrollPanel(); | |||
private HTML preSpacer = new HTML(); | |||
private HTML postSpacer = new HTML(); | |||
private boolean colWidthsInitialized = false; | |||
private int totalRows; | |||
private HashMap columnWidths = new HashMap(); | |||
private int rowHeight = 0; | |||
private RowRequestHandler rowRequestHandler; | |||
public ITableScrollingByComposition() { | |||
headerContainer.add(tHead); | |||
DOM.setStyleAttribute(headerContainer.getElement(), "overflow", "hidden"); | |||
tBody.setStyleName("itable-tbody"); | |||
bodyContent.add(preSpacer); | |||
bodyContent.add(tBody); | |||
bodyContent.add(postSpacer); | |||
//TODO remove debug color | |||
DOM.setStyleAttribute(postSpacer.getElement(), "background", "gray"); | |||
bodyContainer.add(bodyContent); | |||
bodyContainer.addScrollListener(this); | |||
VerticalPanel panel = new VerticalPanel(); | |||
panel.add(headerContainer); | |||
panel.add(bodyContainer); | |||
rowRequestHandler = new RowRequestHandler(); | |||
initWidget(panel); | |||
} | |||
public void updateFromUIDL(UIDL uidl, Client client) { | |||
if (client.updateComponent(this, uidl, true)) | |||
return; | |||
this.client = client; | |||
this.id = uidl.getStringAttribute("id"); | |||
this.immediate = uidl.getBooleanAttribute("immediate"); | |||
this.totalRows = uidl.getIntAttribute("totalrows"); | |||
this.pageLength = uidl.getIntAttribute("pagelength"); | |||
if(uidl.hasAttribute("rowheaders")) | |||
rowHeaders = true; | |||
UIDL columnInfo = null; | |||
UIDL rowData = null; | |||
for(Iterator it = uidl.getChildIterator(); it.hasNext();) { | |||
UIDL c = (UIDL) it.next(); | |||
if(c.getTag().equals("cols")) | |||
columnInfo = c; | |||
else if(c.getTag().equals("rows")) | |||
rowData = c; | |||
else if(c.getTag().equals("actions")) | |||
updateActionMap(c); | |||
else if(c.getTag().equals("visiblecolumns")) | |||
; | |||
} | |||
updateHeader(columnInfo); | |||
updateBody(rowData, uidl.getIntAttribute("firstrow"),uidl.getIntAttribute("rows")); | |||
if(!colWidthsInitialized) { | |||
DeferredCommand.add(new Command() { | |||
public void execute() { | |||
initSize(); | |||
updateSpacers(); | |||
bodyContainer.setScrollPosition(getRowHeight()*(firstRowInViewPort -1)); | |||
colWidthsInitialized = true; | |||
if(totalRows - 1 > lastRendered) { | |||
// fetch cache rows | |||
rowRequestHandler.setReqFirstRow(lastRendered+1); | |||
rowRequestHandler.setReqRows((int) (pageLength*CACHE_RATE)); | |||
rowRequestHandler.deferRowFetch(); | |||
} | |||
} | |||
}); | |||
} | |||
} | |||
private void updateActionMap(UIDL c) { | |||
// TODO Auto-generated method stub | |||
} | |||
private void updateHeader(UIDL uidl) { | |||
if(uidl == null) | |||
return; | |||
for(Iterator it = uidl.getChildIterator();it.hasNext();) { | |||
UIDL col = (UIDL) it.next(); | |||
String cid = col.getStringAttribute("cid"); | |||
int colIndex = getColIndexByKey(cid); | |||
if(colIndex > -1) | |||
setHeaderText(colIndex, col.getStringAttribute("caption")); | |||
DOM.setAttribute(tHead.getFlexCellFormatter().getElement(0, colIndex), "cid", cid); | |||
} | |||
} | |||
/** | |||
* Updates row data from uidl. UpdateFromUIDL delegates updating | |||
* tBody to this method. | |||
* | |||
* Updates may be to different part of tBody, depending on update type. | |||
* It can be initial row data, scroll up, scroll down... | |||
* | |||
* @param uidl which contains row data | |||
* @param firstRow first row in data set | |||
* @param reqRows amount of rows in data set | |||
*/ | |||
private void updateBody(UIDL uidl, int firstRow, int reqRows) { | |||
if(uidl == null || reqRows < 1) | |||
return; | |||
Iterator it = uidl.getChildIterator(); | |||
if(firstRendered == -1 || firstRow == lastRendered + 1) { | |||
//initial data to body or appending rows to table | |||
while(it.hasNext()) { | |||
appendRow( (UIDL) it.next() ); | |||
if(colWidthsInitialized) | |||
updateSpacers(); | |||
} | |||
// lastRendered = firstRow + reqRows - 1; | |||
if(firstRendered == -1) { | |||
firstRendered = firstRow; | |||
} | |||
} else if(firstRendered == firstRow + reqRows) { | |||
// add received rows before old ones | |||
int rowsAdded = 0; | |||
while(it.hasNext()){ | |||
tBody.insertRow(rowsAdded); | |||
updateSpacers(); | |||
updateRow( (UIDL) it.next(), rowsAdded); | |||
} | |||
firstRendered = firstRow; | |||
} else { | |||
// complitely new set received, truncate body and recurse | |||
tBody.clear(); | |||
firstRendered = -1; | |||
lastRendered = -1; | |||
updateBody(uidl, firstRow, reqRows); | |||
} | |||
trimBody(); | |||
} | |||
/** | |||
* Returns calculated height of row. | |||
* @return height in pixels | |||
*/ | |||
private int getRowHeight() { | |||
if(rowHeight == 0) | |||
rowHeight = tBody.getOffsetHeight()/getRenderedRowCount(); | |||
return rowHeight; | |||
} | |||
/** | |||
* This method removes rows from body which are "out of | |||
* cache area" to keep amount of rendered rows sane. | |||
*/ | |||
private void trimBody() { | |||
int toBeRemovedFromTheBeginning = (int) (firstRowInViewPort - CACHE_RATE*pageLength) - firstRendered; | |||
int toBeRemovedFromTheEnd = lastRendered - (int) (firstRowInViewPort + CACHE_RATE*pageLength + pageLength); | |||
if(toBeRemovedFromTheBeginning > 0) { | |||
// remove extra rows from the beginning of the table | |||
while(toBeRemovedFromTheBeginning > 0) { | |||
tBody.removeRow(0); | |||
firstRendered++; | |||
toBeRemovedFromTheBeginning--; | |||
updateSpacers(); | |||
} | |||
} | |||
if(toBeRemovedFromTheEnd > 0) { | |||
// remove extra rows from the end of the table | |||
while(toBeRemovedFromTheEnd > 0) { | |||
tBody.removeRow(tBody.getRowCount() - 1); | |||
toBeRemovedFromTheEnd--; | |||
lastRendered--; | |||
updateSpacers(); | |||
} | |||
} | |||
// bodyContainer.setScrollPosition(getRowHeight()*firstRowInViewPort); | |||
} | |||
private void appendRow(UIDL uidl) { | |||
lastRendered++; | |||
updateRow(uidl, lastRendered); | |||
} | |||
private void updateRow(UIDL uidl, int rowIndex) { | |||
int colIndex = 0; | |||
if(rowHeaders) { | |||
setCellContent(rowIndex, colIndex, uidl.getStringAttribute("caption")); | |||
colIndex++; | |||
} | |||
for(Iterator it = uidl.getChildIterator(); it.hasNext();) { | |||
Object cell = it.next(); | |||
if (cell instanceof String) { | |||
setCellContent(rowIndex, colIndex, (String) cell); | |||
} else { | |||
setCellContent(rowIndex, colIndex, (UIDL) cell); | |||
} | |||
colIndex++; | |||
} | |||
Element row = tBody.getRowFormatter().getElement(rowIndex); | |||
DOM.setIntAttribute(row, "key", uidl.getIntAttribute("key")); | |||
} | |||
private int getColIndexByKey(String colKey) { | |||
return Integer.parseInt(colKey) - 1 + (rowHeaders ? 1 : 0); | |||
} | |||
private String getColKeyByIndex(int index) { | |||
return DOM.getAttribute(tHead.getCellFormatter().getElement(0, index), "cid"); | |||
} | |||
public void setHeaderText(int colIndex, String text) { | |||
tHead.setText(0, colIndex, text); | |||
} | |||
public void setCellContent(int rowId, int colId, UIDL cell) { | |||
if(cell == null) | |||
return; | |||
Widget cellContent = client.getWidget(cell); | |||
tBody.setWidget(rowId, colId, cellContent); | |||
((Paintable)cell).updateFromUIDL(cell, client); | |||
tBody.getCellFormatter().setWordWrap(rowId, colId, false); | |||
} | |||
public void setCellContent(int rowId, int colId, String text) { | |||
HTML cellContent = new HTML(); | |||
cellContent.setText(text); | |||
tBody.setWidget(rowId, colId, cellContent); | |||
} | |||
/** | |||
* Run when receices its initial content. Syncs headers and bodys | |||
* "natural widths and saves the values. | |||
*/ | |||
private void initSize() { | |||
int cols = tHead.getCellCount(0); | |||
FlexCellFormatter hf = tHead.getFlexCellFormatter(); | |||
FlexCellFormatter bf = tBody.getFlexCellFormatter(); | |||
for (int i = 0; i < cols; i++) { | |||
Element hCell = hf.getElement(0, i); | |||
Element bCell = bf.getElement(1, i); | |||
int hw = DOM.getIntAttribute(hCell, "offsetWidth"); | |||
int cw = DOM.getIntAttribute(bCell, "offsetWidth"); | |||
setColWidth(i , hw > cw ? hw : cw); | |||
} | |||
bodyContainer.setHeight(tBody.getOffsetHeight() + "px"); | |||
bodyContainer.setWidth((tBody.getOffsetWidth() + 20) + "px"); | |||
} | |||
private void setColWidth(int colIndex, int w) { | |||
String cid = getColKeyByIndex(colIndex); | |||
tHead.getCellFormatter().setWidth(0, colIndex, w + "px"); | |||
tBody.getCellFormatter().setWidth(0, colIndex, w + "px"); | |||
columnWidths.put(cid,new Integer(w)); | |||
} | |||
private int getColWidth(String colKey) { | |||
return ( (Integer) this.columnWidths.get(colKey)).intValue(); | |||
} | |||
private void updateSpacers() { | |||
int preSpacerHeight = (firstRendered)*getRowHeight(); | |||
int postSpacerHeight = (totalRows - 1 - lastRendered)*getRowHeight(); | |||
preSpacer.setHeight(preSpacerHeight+"px"); | |||
postSpacer.setHeight(postSpacerHeight + "px"); | |||
} | |||
private int getRenderedRowCount() { | |||
return lastRendered-firstRendered; | |||
} | |||
/** | |||
* This method has logick which rows needs to be requested from | |||
* server when user scrolls | |||
* | |||
*/ | |||
public void onScroll(Widget widget, int scrollLeft, int scrollTop) { | |||
rowRequestHandler.cancel(); | |||
firstRowInViewPort = (int) Math.ceil( scrollTop / rowHeight ); | |||
client.console.log("At scrolltop: " + scrollTop + " At row " + firstRowInViewPort); | |||
int postLimit = (int) (firstRowInViewPort + pageLength + pageLength*CACHE_REACT_RATE); | |||
if(postLimit > totalRows) | |||
postLimit = totalRows; | |||
int preLimit = (int) (firstRowInViewPort - pageLength*CACHE_REACT_RATE); | |||
if(preLimit < 0) | |||
preLimit = 0; | |||
if( | |||
(postLimit <= lastRendered && preLimit >= firstRendered ) | |||
) { | |||
client.updateVariable(this.id, "firstvisible", firstRowInViewPort, false); | |||
return; // scrolled withing "non-react area" | |||
} | |||
if(firstRowInViewPort - pageLength*CACHE_RATE > lastRendered || | |||
firstRowInViewPort + pageLength + pageLength*CACHE_RATE < firstRendered ) { | |||
// need a totally new set | |||
client.console.log("Table: need a totally new set"); | |||
rowRequestHandler.setReqFirstRow((int) (firstRowInViewPort - pageLength*CACHE_RATE)); | |||
rowRequestHandler.setReqRows((int) (2*CACHE_RATE*pageLength + pageLength)); | |||
rowRequestHandler.deferRowFetch(); | |||
return; | |||
} | |||
if(preLimit < firstRendered ) { | |||
// need some rows to the beginning of the rendered area | |||
client.console.log("Table: need some rows to the beginning of the rendered area"); | |||
rowRequestHandler.setReqFirstRow((int) (firstRowInViewPort - pageLength*CACHE_RATE)); | |||
rowRequestHandler.setReqRows(firstRendered - rowRequestHandler.getReqFirstRow()); | |||
rowRequestHandler.deferRowFetch(); | |||
return; | |||
} | |||
if(postLimit > lastRendered) { | |||
// need some rows to the end of the rendered area | |||
client.console.log("need some rows to the end of the rendered area"); | |||
rowRequestHandler.setReqFirstRow(lastRendered + 1); | |||
rowRequestHandler.setReqRows((int) ((firstRowInViewPort + pageLength + pageLength*CACHE_RATE) - lastRendered)); | |||
rowRequestHandler.deferRowFetch(); | |||
} | |||
} | |||
private class RowRequestHandler extends Timer { | |||
private int reqFirstRow = 0; | |||
private int reqRows = 0; | |||
public void deferRowFetch() { | |||
if(reqRows > 0 && reqFirstRow < totalRows) | |||
schedule(250); | |||
} | |||
public void setReqFirstRow(int reqFirstRow) { | |||
if(reqFirstRow < 0) | |||
reqFirstRow = 0; | |||
else if(reqFirstRow >= totalRows) | |||
reqFirstRow = totalRows - 1; | |||
this.reqFirstRow = reqFirstRow; | |||
} | |||
public void setReqRows(int reqRows) { | |||
this.reqRows = reqRows; | |||
} | |||
public void run() { | |||
client.console.log("Getting " + reqRows + " rows from " + reqFirstRow); | |||
client.updateVariable(id, "firstvisible", firstRowInViewPort, false); | |||
client.updateVariable(id, "reqfirstrow", reqFirstRow, false); | |||
client.updateVariable(id, "reqrows", reqRows, true); | |||
} | |||
public int getReqFirstRow() { | |||
return reqFirstRow; | |||
} | |||
public int getReqRows() { | |||
return reqRows; | |||
} | |||
} | |||
} |
@@ -0,0 +1,473 @@ | |||
package com.itmill.toolkit.terminal.gwt.client.ui; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import com.google.gwt.user.client.Command; | |||
import com.google.gwt.user.client.DOM; | |||
import com.google.gwt.user.client.DeferredCommand; | |||
import com.google.gwt.user.client.Element; | |||
import com.google.gwt.user.client.Timer; | |||
import com.google.gwt.user.client.ui.Composite; | |||
import com.google.gwt.user.client.ui.FlexTable; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.google.gwt.user.client.ui.ScrollListener; | |||
import com.google.gwt.user.client.ui.ScrollPanel; | |||
import com.google.gwt.user.client.ui.VerticalPanel; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; | |||
import com.itmill.toolkit.terminal.gwt.client.Client; | |||
import com.itmill.toolkit.terminal.gwt.client.Paintable; | |||
import com.itmill.toolkit.terminal.gwt.client.UIDL; | |||
// TODO Blow this out and build it directly with DOM | |||
// Below is the structure of the 4.0 implementation | |||
/* | |||
<div class="border"> | |||
<div id="PID7status" class="tablestatus" style="display: none; margin-top: 35px; margin-left: 122px;"/> | |||
<div class="hcontainer"> | |||
<div class="colsel-container"></div> | |||
<div id="PID7hout" class="bg hout" style="overflow: hidden; width: 375px;"> | |||
<div style="width: 6000px;"> | |||
<table id="PID7hin" class="hin" cellspacing="0" cellpadding="0" border="0"> | |||
<tbody> | |||
<tr> | |||
<td id="PID7heh" class="heh clickable" cid="heh" style="width: 20px;"> | |||
</td> | |||
<td id="PID7he0" class="clickable" cid="1" style="width: 91px;"> | |||
</td> | |||
<td id="PID7he1" class="clickable" cid="2" style="width: 88px;"> | |||
</td> | |||
<td id="PID7he2" class="clickable" cid="3" style="width: 108px;"> | |||
<img id="PID7ha3" class="colresizer" src="/demo/TableDemo/RES/corporate/img/table/handle.gif"/> | |||
<div class="headerContent" style="width: 91px; left: 0px; top: 0px;">TITLE</div> | |||
</td> | |||
<td id="PID7he3" class="clickable" cid="4" style="width: 68px;"> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div/> | |||
</div> | |||
</div> | |||
</div> | |||
<div id="PID7cout" class="cout" style="overflow: auto; height: 140px; width: 391px;"> | |||
<div class="spacer" style="height: 0px;"/> | |||
<table id="PID7cin" class="cin"> | |||
<tbody> | |||
<tr class="odd clickable"> | |||
<td class="tablecell rowheader" width="20"> | |||
<div class="cellContent" style="width: 16px;">0</div> | |||
</td> | |||
... | |||
</tr> | |||
... | |||
</tbody> | |||
</table> | |||
<div class="spacer" style="height: 13440px;"/> | |||
</div> | |||
</div> | |||
*/ | |||
public class ITableScrollingByRecyclingOldDomImplementation extends Composite implements Paintable, ScrollListener { | |||
/** | |||
* multiple of pagelenght which component will | |||
* cache when requesting more rows | |||
*/ | |||
private static final double CACHE_RATE = 3; | |||
/** | |||
* fraction of pageLenght which can be scrolled without | |||
* making new request | |||
*/ | |||
private static final double CACHE_REACT_RATE = 1; | |||
private int firstRendered = -1; | |||
private int lastRendered = -1; | |||
private int firstRowInViewPort = 0; | |||
private int pageLength = 15; | |||
private boolean rowHeaders = false; | |||
private Map columnOrder = new HashMap(); | |||
private Client client; | |||
private String id; | |||
private boolean immediate; | |||
private FlexTable tHead = new FlexTable(); | |||
private FlexTable tBody = new FlexTable(); | |||
private ScrollPanel bodyContainer = new ScrollPanel(); | |||
private VerticalPanel bodyContent = new VerticalPanel(); | |||
private ScrollPanel headerContainer = new ScrollPanel(); | |||
private HTML preSpacer = new HTML(); | |||
private HTML postSpacer = new HTML(); | |||
private boolean colWidthsInitialized = false; | |||
private int totalRows; | |||
private HashMap columnWidths = new HashMap(); | |||
private int rowHeight = 0; | |||
private RowRequestHandler rowRequestHandler; | |||
public ITableScrollingByRecyclingOldDomImplementation() { | |||
headerContainer.add(tHead); | |||
DOM.setStyleAttribute(headerContainer.getElement(), "overflow", "hidden"); | |||
tBody.setStyleName("itable-tbody"); | |||
bodyContent.add(preSpacer); | |||
bodyContent.add(tBody); | |||
bodyContent.add(postSpacer); | |||
//TODO remove debug color | |||
DOM.setStyleAttribute(postSpacer.getElement(), "background", "gray"); | |||
bodyContainer.add(bodyContent); | |||
bodyContainer.addScrollListener(this); | |||
VerticalPanel panel = new VerticalPanel(); | |||
panel.add(headerContainer); | |||
panel.add(bodyContainer); | |||
rowRequestHandler = new RowRequestHandler(); | |||
initWidget(panel); | |||
} | |||
public void updateFromUIDL(UIDL uidl, Client client) { | |||
if (client.updateComponent(this, uidl, true)) | |||
return; | |||
this.client = client; | |||
this.id = uidl.getStringAttribute("id"); | |||
this.immediate = uidl.getBooleanAttribute("immediate"); | |||
this.totalRows = uidl.getIntAttribute("totalrows"); | |||
this.pageLength = uidl.getIntAttribute("pagelength"); | |||
if(uidl.hasAttribute("rowheaders")) | |||
rowHeaders = true; | |||
UIDL columnInfo = null; | |||
UIDL rowData = null; | |||
for(Iterator it = uidl.getChildIterator(); it.hasNext();) { | |||
UIDL c = (UIDL) it.next(); | |||
if(c.getTag().equals("cols")) | |||
columnInfo = c; | |||
else if(c.getTag().equals("rows")) | |||
rowData = c; | |||
else if(c.getTag().equals("actions")) | |||
updateActionMap(c); | |||
else if(c.getTag().equals("visiblecolumns")) | |||
; | |||
} | |||
updateHeader(columnInfo); | |||
updateBody(rowData, uidl.getIntAttribute("firstrow"),uidl.getIntAttribute("rows")); | |||
if(!colWidthsInitialized) { | |||
DeferredCommand.add(new Command() { | |||
public void execute() { | |||
initSize(); | |||
updateSpacers(); | |||
bodyContainer.setScrollPosition(getRowHeight()*(firstRowInViewPort -1)); | |||
colWidthsInitialized = true; | |||
if(totalRows - 1 > lastRendered) { | |||
// fetch cache rows | |||
rowRequestHandler.setReqFirstRow(lastRendered+1); | |||
rowRequestHandler.setReqRows((int) (pageLength*CACHE_RATE)); | |||
rowRequestHandler.deferRowFetch(); | |||
} | |||
} | |||
}); | |||
} | |||
} | |||
private void updateActionMap(UIDL c) { | |||
// TODO Auto-generated method stub | |||
} | |||
private void updateHeader(UIDL uidl) { | |||
if(uidl == null) | |||
return; | |||
for(Iterator it = uidl.getChildIterator();it.hasNext();) { | |||
UIDL col = (UIDL) it.next(); | |||
String cid = col.getStringAttribute("cid"); | |||
int colIndex = getColIndexByKey(cid); | |||
if(colIndex > -1) | |||
setHeaderText(colIndex, col.getStringAttribute("caption")); | |||
DOM.setAttribute(tHead.getFlexCellFormatter().getElement(0, colIndex), "cid", cid); | |||
} | |||
} | |||
/** | |||
* Updates row data from uidl. UpdateFromUIDL delegates updating | |||
* tBody to this method. | |||
* | |||
* Updates may be to different part of tBody, depending on update type. | |||
* It can be initial row data, scroll up, scroll down... | |||
* | |||
* @param uidl which contains row data | |||
* @param firstRow first row in data set | |||
* @param reqRows amount of rows in data set | |||
*/ | |||
private void updateBody(UIDL uidl, int firstRow, int reqRows) { | |||
if(uidl == null || reqRows < 1) | |||
return; | |||
Iterator it = uidl.getChildIterator(); | |||
if(firstRendered == -1 || firstRow == lastRendered + 1) { | |||
//initial data to body or appending rows to table | |||
while(it.hasNext()) { | |||
appendRow( (UIDL) it.next() ); | |||
if(colWidthsInitialized) | |||
updateSpacers(); | |||
} | |||
// lastRendered = firstRow + reqRows - 1; | |||
if(firstRendered == -1) { | |||
firstRendered = firstRow; | |||
} | |||
} else if(firstRendered == firstRow + reqRows) { | |||
// add received rows before old ones | |||
int rowsAdded = 0; | |||
while(it.hasNext()){ | |||
tBody.insertRow(rowsAdded); | |||
updateSpacers(); | |||
updateRow( (UIDL) it.next(), rowsAdded); | |||
} | |||
firstRendered = firstRow; | |||
} else { | |||
// complitely new set received, truncate body and recurse | |||
tBody.clear(); | |||
firstRendered = -1; | |||
lastRendered = -1; | |||
updateBody(uidl, firstRow, reqRows); | |||
} | |||
trimBody(); | |||
} | |||
/** | |||
* Returns calculated height of row. | |||
* @return height in pixels | |||
*/ | |||
private int getRowHeight() { | |||
if(rowHeight == 0) | |||
rowHeight = tBody.getOffsetHeight()/getRenderedRowCount(); | |||
return rowHeight; | |||
} | |||
/** | |||
* This method removes rows from body which are "out of | |||
* cache area" to keep amount of rendered rows sane. | |||
*/ | |||
private void trimBody() { | |||
int toBeRemovedFromTheBeginning = (int) (firstRowInViewPort - CACHE_RATE*pageLength) - firstRendered; | |||
int toBeRemovedFromTheEnd = lastRendered - (int) (firstRowInViewPort + CACHE_RATE*pageLength + pageLength); | |||
if(toBeRemovedFromTheBeginning > 0) { | |||
// remove extra rows from the beginning of the table | |||
while(toBeRemovedFromTheBeginning > 0) { | |||
tBody.removeRow(0); | |||
firstRendered++; | |||
toBeRemovedFromTheBeginning--; | |||
updateSpacers(); | |||
} | |||
} | |||
if(toBeRemovedFromTheEnd > 0) { | |||
// remove extra rows from the end of the table | |||
while(toBeRemovedFromTheEnd > 0) { | |||
tBody.removeRow(tBody.getRowCount() - 1); | |||
toBeRemovedFromTheEnd--; | |||
lastRendered--; | |||
updateSpacers(); | |||
} | |||
} | |||
// bodyContainer.setScrollPosition(getRowHeight()*firstRowInViewPort); | |||
} | |||
private void appendRow(UIDL uidl) { | |||
lastRendered++; | |||
updateRow(uidl, lastRendered); | |||
} | |||
private void updateRow(UIDL uidl, int rowIndex) { | |||
int colIndex = 0; | |||
if(rowHeaders) { | |||
setCellContent(rowIndex, colIndex, uidl.getStringAttribute("caption")); | |||
colIndex++; | |||
} | |||
for(Iterator it = uidl.getChildIterator(); it.hasNext();) { | |||
Object cell = it.next(); | |||
if (cell instanceof String) { | |||
setCellContent(rowIndex, colIndex, (String) cell); | |||
} else { | |||
setCellContent(rowIndex, colIndex, (UIDL) cell); | |||
} | |||
colIndex++; | |||
} | |||
Element row = tBody.getRowFormatter().getElement(rowIndex); | |||
DOM.setIntAttribute(row, "key", uidl.getIntAttribute("key")); | |||
} | |||
private int getColIndexByKey(String colKey) { | |||
return Integer.parseInt(colKey) - 1 + (rowHeaders ? 1 : 0); | |||
} | |||
private String getColKeyByIndex(int index) { | |||
return DOM.getAttribute(tHead.getCellFormatter().getElement(0, index), "cid"); | |||
} | |||
public void setHeaderText(int colIndex, String text) { | |||
tHead.setText(0, colIndex, text); | |||
} | |||
public void setCellContent(int rowId, int colId, UIDL cell) { | |||
if(cell == null) | |||
return; | |||
Widget cellContent = client.getWidget(cell); | |||
tBody.setWidget(rowId, colId, cellContent); | |||
((Paintable)cell).updateFromUIDL(cell, client); | |||
tBody.getCellFormatter().setWordWrap(rowId, colId, false); | |||
} | |||
public void setCellContent(int rowId, int colId, String text) { | |||
HTML cellContent = new HTML(); | |||
cellContent.setText(text); | |||
tBody.setWidget(rowId, colId, cellContent); | |||
} | |||
/** | |||
* Run when receices its initial content. Syncs headers and bodys | |||
* "natural widths and saves the values. | |||
*/ | |||
private void initSize() { | |||
int cols = tHead.getCellCount(0); | |||
FlexCellFormatter hf = tHead.getFlexCellFormatter(); | |||
FlexCellFormatter bf = tBody.getFlexCellFormatter(); | |||
for (int i = 0; i < cols; i++) { | |||
Element hCell = hf.getElement(0, i); | |||
Element bCell = bf.getElement(1, i); | |||
int hw = DOM.getIntAttribute(hCell, "offsetWidth"); | |||
int cw = DOM.getIntAttribute(bCell, "offsetWidth"); | |||
setColWidth(i , hw > cw ? hw : cw); | |||
} | |||
bodyContainer.setHeight(tBody.getOffsetHeight() + "px"); | |||
bodyContainer.setWidth((tBody.getOffsetWidth() + 20) + "px"); | |||
} | |||
private void setColWidth(int colIndex, int w) { | |||
String cid = getColKeyByIndex(colIndex); | |||
tHead.getCellFormatter().setWidth(0, colIndex, w + "px"); | |||
tBody.getCellFormatter().setWidth(0, colIndex, w + "px"); | |||
columnWidths.put(cid,new Integer(w)); | |||
} | |||
private int getColWidth(String colKey) { | |||
return ( (Integer) this.columnWidths.get(colKey)).intValue(); | |||
} | |||
private void updateSpacers() { | |||
int preSpacerHeight = (firstRendered)*getRowHeight(); | |||
int postSpacerHeight = (totalRows - 1 - lastRendered)*getRowHeight(); | |||
preSpacer.setHeight(preSpacerHeight+"px"); | |||
postSpacer.setHeight(postSpacerHeight + "px"); | |||
} | |||
private int getRenderedRowCount() { | |||
return lastRendered-firstRendered; | |||
} | |||
/** | |||
* This method has logick which rows needs to be requested from | |||
* server when user scrolls | |||
* | |||
*/ | |||
public void onScroll(Widget widget, int scrollLeft, int scrollTop) { | |||
rowRequestHandler.cancel(); | |||
firstRowInViewPort = (int) Math.ceil( scrollTop / rowHeight ); | |||
client.console.log("At scrolltop: " + scrollTop + " At row " + firstRowInViewPort); | |||
int postLimit = (int) (firstRowInViewPort + pageLength + pageLength*CACHE_REACT_RATE); | |||
if(postLimit > totalRows) | |||
postLimit = totalRows; | |||
int preLimit = (int) (firstRowInViewPort - pageLength*CACHE_REACT_RATE); | |||
if(preLimit < 0) | |||
preLimit = 0; | |||
if( | |||
(postLimit <= lastRendered && preLimit >= firstRendered ) | |||
) { | |||
client.updateVariable(this.id, "firstvisible", firstRowInViewPort, false); | |||
return; // scrolled withing "non-react area" | |||
} | |||
if(firstRowInViewPort - pageLength*CACHE_RATE > lastRendered || | |||
firstRowInViewPort + pageLength + pageLength*CACHE_RATE < firstRendered ) { | |||
// need a totally new set | |||
client.console.log("Table: need a totally new set"); | |||
rowRequestHandler.setReqFirstRow((int) (firstRowInViewPort - pageLength*CACHE_RATE)); | |||
rowRequestHandler.setReqRows((int) (2*CACHE_RATE*pageLength + pageLength)); | |||
rowRequestHandler.deferRowFetch(); | |||
return; | |||
} | |||
if(preLimit < firstRendered ) { | |||
// need some rows to the beginning of the rendered area | |||
client.console.log("Table: need some rows to the beginning of the rendered area"); | |||
rowRequestHandler.setReqFirstRow((int) (firstRowInViewPort - pageLength*CACHE_RATE)); | |||
rowRequestHandler.setReqRows(firstRendered - rowRequestHandler.getReqFirstRow()); | |||
rowRequestHandler.deferRowFetch(); | |||
return; | |||
} | |||
if(postLimit > lastRendered) { | |||
// need some rows to the end of the rendered area | |||
client.console.log("need some rows to the end of the rendered area"); | |||
rowRequestHandler.setReqFirstRow(lastRendered + 1); | |||
rowRequestHandler.setReqRows((int) ((firstRowInViewPort + pageLength + pageLength*CACHE_RATE) - lastRendered)); | |||
rowRequestHandler.deferRowFetch(); | |||
} | |||
} | |||
private class RowRequestHandler extends Timer { | |||
private int reqFirstRow = 0; | |||
private int reqRows = 0; | |||
public void deferRowFetch() { | |||
if(reqRows > 0 && reqFirstRow < totalRows) | |||
schedule(250); | |||
} | |||
public void setReqFirstRow(int reqFirstRow) { | |||
if(reqFirstRow < 0) | |||
reqFirstRow = 0; | |||
else if(reqFirstRow >= totalRows) | |||
reqFirstRow = totalRows - 1; | |||
this.reqFirstRow = reqFirstRow; | |||
} | |||
public void setReqRows(int reqRows) { | |||
this.reqRows = reqRows; | |||
} | |||
public void run() { | |||
client.console.log("Getting " + reqRows + " rows from " + reqFirstRow); | |||
client.updateVariable(id, "firstvisible", firstRowInViewPort, false); | |||
client.updateVariable(id, "reqfirstrow", reqFirstRow, false); | |||
client.updateVariable(id, "reqrows", reqRows, true); | |||
} | |||
public int getReqFirstRow() { | |||
return reqFirstRow; | |||
} | |||
public int getReqRows() { | |||
return reqRows; | |||
} | |||
} | |||
} |