]> source.dussan.org Git - vaadin-framework.git/commitdiff
somewhat scrolling scrolltable
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Tue, 26 Jun 2007 11:48:18 +0000 (11:48 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Tue, 26 Jun 2007 11:48:18 +0000 (11:48 +0000)
svn changeset:1788/svn branch:trunk

src/com/itmill/toolkit/terminal/gwt/client/DefaultWidgetFactory.java
src/com/itmill/toolkit/terminal/gwt/client/ui/ITableScrollingByComposition.java [deleted file]
src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTable.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableBody.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableRow.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/public/component-themes/common/css/common.css

index 8fb04329bb560f33da33841a091fa302b9dc54c9..0a22858281fd73e6d0de6c8894cce309cec79737 100644 (file)
@@ -16,7 +16,6 @@ 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.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;
@@ -25,6 +24,7 @@ import com.itmill.toolkit.terminal.gwt.client.ui.ITwinColSelect;
 import com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent;
 import com.itmill.toolkit.terminal.gwt.client.ui.IVerticalLayout;
 import com.itmill.toolkit.terminal.gwt.client.ui.IWindow;
+import com.itmill.toolkit.terminal.gwt.client.ui.scrolltable.IScrollTable;
 
 public class DefaultWidgetFactory implements WidgetFactory {
 
@@ -75,8 +75,13 @@ public class DefaultWidgetFactory implements WidgetFactory {
                                return new IPasswordField();
                        return new ITextField();
                }
-               if ("table".equals(tag))
+               if ("table".equals(tag)) {
+                       if(uidl.hasAttribute("style")) {
+                               if("scrolling".equals(uidl.getStringAttribute("style")))
+                                               return new IScrollTable();
+                       }
                        return new ITablePaging();
+               }
                if("datefield".equals(tag))
                        return new IDateField();
 
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITableScrollingByComposition.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITableScrollingByComposition.java
deleted file mode 100644 (file)
index ecddab1..0000000
+++ /dev/null
@@ -1,425 +0,0 @@
-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;
-               }
-               
-       }
-}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTable.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTable.java
new file mode 100644 (file)
index 0000000..34a5ade
--- /dev/null
@@ -0,0 +1,358 @@
+package com.itmill.toolkit.terminal.gwt.client.ui.scrolltable;
+
+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.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;
+import com.itmill.toolkit.terminal.gwt.client.ui.ITable;
+
+public class IScrollTable extends Composite implements Paintable, ITable, 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 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 boolean initializedAndAttached = false;
+       
+       private FlexTable tHead = new FlexTable();
+
+       private ScrollPanel bodyContainer = new ScrollPanel();
+       
+       private ScrollPanel headerContainer = new ScrollPanel();
+       
+       private boolean colWidthsInitialized = false;
+       private int totalRows;
+       private HashMap columnWidths = new HashMap();
+       
+       private RowRequestHandler rowRequestHandler;
+       private IScrollTableBody tBody;
+       private int width = -1;
+       private int height = -1;
+       private int firstvisible;
+       
+       public IScrollTable() {
+               headerContainer.setStyleName("iscrolltable-header");
+               headerContainer.add(tHead);
+               DOM.setStyleAttribute(headerContainer.getElement(), "overflow", "hidden");
+               
+               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");
+               this.firstvisible = uidl.getIntVariable("firstvisible");
+               if(uidl.hasAttribute("rowheaders"))
+                       rowHeaders = true;
+               if(uidl.hasAttribute("width"))
+                       width = uidl.getIntAttribute("width");
+               if(uidl.hasAttribute("height"))
+                       width = uidl.getIntAttribute("height");
+               
+               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);
+               
+               if(initializedAndAttached) {
+                       updateBody(rowData, uidl.getIntAttribute("firstrow"),uidl.getIntAttribute("rows"));
+               } else {
+                       getTBody().renderInitialRows(rowData, 
+                                       uidl.getIntAttribute("firstrow"), 
+                                       uidl.getIntAttribute("rows"), 
+                                       totalRows);
+                       bodyContainer.add(tBody);
+                       initializedAndAttached = true;
+               }
+       }
+       
+       private IScrollTableBody getTBody() {
+               if(tBody == null || totalRows != tBody.getTotalRows()) {
+                       if(tBody != null)
+                               tBody.removeFromParent();
+                       tBody = new IScrollTableBody(client);
+               }
+               return tBody;
+       }
+
+       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);
+               }
+       }
+       
+       /**
+        * @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;
+               
+               tBody.renderRows(uidl, firstRow, reqRows);
+               
+               int optimalFirstRow = (int) (firstRowInViewPort - pageLength*CACHE_RATE);
+               while(tBody.getFirstRendered() < optimalFirstRow) {
+//                     client.console.log("removing row from start");
+                       tBody.unlinkRow(true);
+               }
+               int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength*CACHE_RATE);
+               while(tBody.getLastRendered() > optimalLastRow) {
+//                     client.console.log("removing row from the end");
+                       tBody.unlinkRow(false);
+               }
+               
+       }
+       
+       
+       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);
+       }
+       
+       private void setColWidth(int colIndex, int w) {
+               String cid = getColKeyByIndex(colIndex);
+               tHead.getCellFormatter().setWidth(0, colIndex, w + "px");
+               tBody.setColWidth(colIndex, w);
+               columnWidths.put(cid,new Integer(w));
+       }
+       
+       private int getColWidth(String colKey) {
+               return ( (Integer) this.columnWidths.get(colKey)).intValue();
+       }
+       
+       private int getRenderedRowCount() {
+               return tBody.getLastRendered()-tBody.getFirstRendered();
+       }
+
+       /**
+        * This method has logick which rows needs to be requested from
+        * server when user scrolls
+        *
+        */
+       public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
+               if(!initializedAndAttached)
+                       return;
+               
+               rowRequestHandler.cancel();
+               
+               firstRowInViewPort = (int) Math.ceil( scrollTop / tBody.getRowHeight() );
+               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;
+               int lastRendered = tBody.getLastRendered();
+               int firstRendered = tBody.getFirstRendered();
+               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();
+               }
+       }
+       
+       
+       
+       protected void onAttach() {
+               
+               super.onAttach();
+               
+               // sync column widths
+               initColumnWidths();
+
+               if(height  < 0) {
+                       bodyContainer.setHeight((tBody.getRowHeight()*pageLength) + "px");
+               } else {
+                       bodyContainer.setHeight(height + "px");
+               }
+
+               if(width  < 0) {
+                       bodyContainer.setWidth((tBody.getOffsetWidth() + getScrollBarWidth() ) + "px");
+               } else {
+                       bodyContainer.setWidth(width + "px");
+               }
+               
+               if(firstvisible > 0)
+                       bodyContainer.setScrollPosition(firstvisible*tBody.getRowHeight());
+               
+               DeferredCommand.add(new Command() {
+                       public void execute() {
+                               if(totalRows - 1 > tBody.getLastRendered()) {
+                                       // fetch cache rows
+                                       rowRequestHandler.setReqFirstRow(tBody.getLastRendered()+1);
+                                       rowRequestHandler.setReqRows((int) (pageLength*CACHE_RATE));
+                                       rowRequestHandler.deferRowFetch();
+                               }
+                       }
+               });
+
+               
+       }
+
+       /**
+        * Run when receices its initial content. Syncs headers and bodys
+        * "natural widths and saves the values.
+       */
+       private void initColumnWidths() {
+               int cols = tHead.getCellCount(0);
+               FlexCellFormatter hf = tHead.getFlexCellFormatter();
+                       for (int i = 0; i < cols; i++) {
+                               Element hCell = hf.getElement(0, i);
+                               int hw = DOM.getIntAttribute(hCell, "offsetWidth");
+                               int cw = tBody.getColWidth(i);
+                               int w = (hw > cw ? hw : cw) + IScrollTableBody.CELL_EXTRA_WIDTH;
+                               setColWidth(i , w);
+                       }
+       }
+
+       private int getScrollBarWidth() {
+               // TODO Auto-generated method stub
+               return 30;
+       }
+
+       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;
+               }
+               
+       }
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableBody.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableBody.java
new file mode 100644 (file)
index 0000000..7b2b54b
--- /dev/null
@@ -0,0 +1,235 @@
+package com.itmill.toolkit.terminal.gwt.client.ui.scrolltable;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.Client;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+
+/**
+ * This Panel can only contain IScrollTAbleRow type of 
+ * widgets. This "simulates" very large table, keeping 
+ * spacers which take room of unrendered rows.
+ * 
+ * @author mattitahvonen
+ *
+ */
+public class IScrollTableBody extends Panel {
+
+       public static final int CELL_EXTRA_WIDTH = 20;
+
+       protected static int DEFAULT_ROW_HEIGHT = 25;
+       
+       private int rowHeight = -1;
+       
+       private List renderedRows = new Vector();
+       
+       private boolean initDone = false;
+       
+       private int totalRows;
+
+       Element preSpacer = DOM.createDiv();
+       Element postSpacer = DOM.createDiv();
+       
+       Element container = DOM.createDiv();
+       
+       Element tBody  = DOM.createTBody();
+       Element table = DOM.createTable();
+
+       private int firstRendered;
+
+       private int lastRendered;
+
+       private Client client;
+
+       
+       IScrollTableBody(Client client) {
+               this.client = client;
+               
+               constructDOM();
+               
+               setElement(container);
+               
+       }
+       
+       private void constructDOM() {
+               DOM.setAttribute(table, "className", "iscrolltable-table");
+               DOM.setAttribute(preSpacer, "className", "iscrolltable-rowspacer");
+               DOM.setAttribute(postSpacer, "className", "iscrolltable-rowspacer");
+
+               DOM.appendChild(table, tBody);
+               DOM.appendChild(container, preSpacer);
+               DOM.appendChild(container, table);
+               DOM.appendChild(container, postSpacer);
+               
+       }
+       
+       
+       public void renderInitialRows(UIDL rowData, int firstIndex, int rows, int totalRows) {
+               this.totalRows = totalRows;
+               this.firstRendered = firstIndex;
+               this.lastRendered = firstIndex + rows - 1 ;
+               Iterator it = rowData.getChildIterator();
+               while(it.hasNext()) {
+                       IScrollTableRow row = new IScrollTableRow((UIDL) it.next(), client);
+                       addRow(row);
+               }
+               if(isAttached())
+                       fixSpacers();
+       }
+       
+       public void renderRows(UIDL rowData, int firstIndex, int rows) {
+               Iterator it = rowData.getChildIterator();
+               if(firstIndex == lastRendered + 1) {
+                       while(it.hasNext()) {
+                               IScrollTableRow row = createRow((UIDL) it.next());
+                               addRow(row);
+                               lastRendered++;
+                       }
+                       fixSpacers();
+               } else if(firstIndex + rows == firstRendered) {
+                       IScrollTableRow[] rowArray = new IScrollTableRow[rows];
+                       int i = rows;
+                       while(it.hasNext()) {
+                               i--;
+                               rowArray[i] = createRow((UIDL) it.next());
+                       }
+                       for(i = 0 ; i < rows; i++) {
+                               addRowBeforeFirstRendered(rowArray[i]);
+                               firstRendered--;
+                       }
+               } else if (firstIndex > lastRendered || firstIndex + rows < firstRendered) {
+                       // complitely new set of rows
+                       // create one row before truncating row
+                       IScrollTableRow row = createRow((UIDL) it.next());
+                       while(lastRendered + 1 > firstRendered)
+                               unlinkRow(false);
+
+                       addRow(row);
+                       firstRendered = firstIndex;
+                       this.lastRendered = firstIndex + rows - 1 ;
+                       while(it.hasNext())
+                               addRow(createRow((UIDL) it.next()));
+                       fixSpacers();
+               } else {
+                       client.console.log("Bad update" + firstIndex + "/"+ rows);
+               }
+       }
+       
+       /**
+        * This mehtod is used to instantiate new rows for this table.
+        * It automatically sets correct widths to rows cells and assigns 
+        * correct client reference for child widgets.
+        * 
+        * This method can be called only after table has been initialized
+        * 
+        * @param uidl
+        * @param client2
+        */
+       private IScrollTableRow createRow(UIDL uidl) {
+               IScrollTableRow row = new IScrollTableRow(uidl, client);
+               int cells = DOM.getChildCount(row.getElement());
+               for(int i = 0; i < cells; i++) {
+                       Element cell = DOM.getChild(row.getElement(), i);
+                       int w = getColWidth(i);
+                       DOM.setStyleAttribute(cell, "width", w + "px");
+                       DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", w + "px");
+               }
+               return row;
+       }
+
+       private void addRowBeforeFirstRendered(IScrollTableRow row) {
+               DOM.insertChild(tBody, row.getElement(), 0);
+               adopt(row, null);
+               renderedRows.add(0, row);
+       }
+       
+       private void addRow(IScrollTableRow row) {
+               DOM.appendChild(tBody, row.getElement());
+               adopt(row, null);
+               renderedRows.add(row);
+       }
+       
+       public Iterator iterator() {
+               return renderedRows.iterator();
+       }
+       
+       public void unlinkRow(boolean fromBeginning) {
+               if(lastRendered - firstRendered < 0)
+                       return;
+               int index;
+               if(fromBeginning) {
+                       index = 0;
+                       firstRendered++;
+               } else {
+                       index = renderedRows.size() - 1;
+                       lastRendered--;
+               }
+               IScrollTableRow toBeRemoved = (IScrollTableRow) renderedRows.get(index);
+               this.disown(toBeRemoved);
+               renderedRows.remove(index);
+               fixSpacers();
+       }
+
+       public boolean remove(Widget w) {
+               throw new UnsupportedOperationException();
+       }
+       
+       protected void onAttach() {
+               super.onAttach();
+               fixSpacers();
+       }
+       
+       private void fixSpacers() {
+               int tBodyHeight = DOM.getIntAttribute(table, "offsetHeight");
+               DOM.setStyleAttribute(preSpacer, "height", getRowHeight()*firstRendered + "px");
+               DOM.setStyleAttribute(postSpacer, "height", getRowHeight()*(totalRows - 1  - lastRendered) + "px");
+               
+       }
+
+       public int getTotalRows() {
+               return totalRows;
+       }
+       
+       public int getRowHeight() {
+               if(initDone)
+                       return rowHeight;
+               else {
+                       if(DOM.getChildCount(tBody) > 0) {
+                               rowHeight = DOM.getIntAttribute(tBody, "offsetHeight")/DOM.getChildCount(tBody);
+                       } else {
+                               return DEFAULT_ROW_HEIGHT;
+                       }
+                       initDone = true;
+                       return rowHeight;
+               }
+       }
+
+       public int getColWidth(int i) {
+               Element e = DOM.getChild(DOM.getChild(tBody, 0), i);
+               return DOM.getIntAttribute(e, "offsetWidth");
+       }
+
+       public void setColWidth(int colIndex, int w) {
+               int rows = DOM.getChildCount(tBody);
+               for(int i = 0; i < rows; i++) {
+                       Element cell = DOM.getChild(DOM.getChild(tBody, i), colIndex);
+                       DOM.setStyleAttribute(cell, "width", w + "px");
+                       DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", w + "px");
+               }
+       }
+       
+       public int getLastRendered() {
+               return lastRendered;
+       }
+
+       public int getFirstRendered() {
+               return firstRendered;
+       }
+
+}
diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableRow.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/scrolltable/IScrollTableRow.java
new file mode 100644 (file)
index 0000000..9608dcd
--- /dev/null
@@ -0,0 +1,62 @@
+package com.itmill.toolkit.terminal.gwt.client.ui.scrolltable;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+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 IScrollTableRow extends Panel {
+       
+       Vector childWidgets = new Vector();
+       
+       private IScrollTableRow() {
+               setElement(DOM.createElement("tr"));
+       }
+       
+       public IScrollTableRow(UIDL uidl, Client client) {
+               this();
+               if(uidl.hasAttribute("caption"))
+                       addCell(uidl.getStringAttribute("caption"));
+               Iterator cells = uidl.getChildIterator();
+               while(cells.hasNext()) {
+                       Object cell = cells.next();
+                       if (cell instanceof String) {
+                               addCell(cell.toString());
+                       } else {
+                               Widget cellContent = client.getWidget((UIDL) cell);
+                               (( Paintable) cellContent).updateFromUIDL((UIDL) cell, client);
+                       }
+               }
+       }
+       
+       public void addCell(String text) {
+               addCell(new Label(text));
+       }
+       
+       public void addCell(Widget w) {
+               Element td = DOM.createTD();
+               Element container = DOM.createDiv();
+               DOM.setAttribute(container, "className", "iscrolltable-cellContent");
+               DOM.appendChild(td, container);
+               DOM.appendChild(getElement(), td);
+               adopt(w, container);
+               childWidgets.add(w);
+       }
+
+       public Iterator iterator() {
+               return childWidgets.iterator();
+       }
+
+       public boolean remove(Widget w) {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+}
index 7cd3202b4d35002854d99250695e83662afde2f7..0e70e15a0b21cb40cfc049204008933f57688a11 100644 (file)
@@ -14,4 +14,55 @@ input, select, textarea, button {
 select {\r
        padding: 0;\r
        margin: 0;\r
+}
+
+/* TODO move table styles to separate file */
+
+.iscrolltable-header table {
+       border-collapse:collapse;
+       margin:0;
+       padding:0;
+       border:0;
+       background: yellow;
+       table-layout: fixed;
+}
+
+.iscrolltable-header table td {
+       margin:0;
+       padding:0;
+       border:0;
+}
+
+
+
+.iscrolltable-table {
+       border-collapse:collapse;
+       margin:0;
+       padding:0;
+       border:0;
+       background: yellow;
+       table-layout: fixed;
+}
+
+.iscrolltable-table td {
+       border:0;
+       margin:0;
+       padding:0;
+       background: green;
+}
+iscrolltable-table tr {
+       background: blue;       
+       border:0;
+       margin:0;
+       padding:0;
+}
+
+.iscrolltable-rowspacer {
+       height: 10px;
+       background: brown;      
+}
+
+.iscrolltable-table .iscrolltable-cellContent {
+       white-space: nowrap;
+       overflow: hidden;
 }
\ No newline at end of file