Browse Source

Implemented simple paging table and forked scrolling table into "composition" and "DOM" implementations

svn changeset:1776/svn branch:trunk
tags/6.7.0.beta1
Matti Tahvonen 17 years ago
parent
commit
26b5f9c76c

+ 3
- 2
src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetFactory.java View File

@@ -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();


+ 230
- 0
src/com/itmill/toolkit/terminal/gwt/client/ui/ITablePaging.java View File

@@ -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);
}
}

}

+ 425
- 0
src/com/itmill/toolkit/terminal/gwt/client/ui/ITableScrollingByComposition.java View File

@@ -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;
}
}
}

+ 473
- 0
src/com/itmill/toolkit/terminal/gwt/client/ui/ITableScrollingByRecyclingOldDomImplementation.java View File

@@ -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;
}
}
}

Loading…
Cancel
Save