Add a demo of SimpleCellList and fix some bugs found by the demo
Review at http://gwt-code-reviews.appspot.com/272801
Review by: jlabanca@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7794 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
index 0d23a0f..ed7eea3 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
@@ -25,7 +25,6 @@
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
@@ -43,30 +42,40 @@
private final Cell<T, Void> cell;
private final ArrayList<T> data = new ArrayList<T>();
private int increment;
+ private int initialMaxSize;
private int maxSize;
private ListModel<T> model;
- private final Element showMoreElem;
- private final Element tmpElem;
private ListRegistration reg;
+ private int seq; // for debugging - TODO: remove
+ private final Element showFewerElem;
+ private final Element showMoreElem;
+ private int size;
+ private final Element tmpElem;
private ValueUpdater<T, Void> valueUpdater;
-
public SimpleCellList(ListModel<T> model, Cell<T, Void> cell, int maxSize,
int increment) {
- this.maxSize = maxSize;
+ this.initialMaxSize = this.maxSize = maxSize;
this.increment = increment;
this.model = model;
this.cell = cell;
+ this.seq = 0;
tmpElem = Document.get().createDivElement();
showMoreElem = Document.get().createDivElement();
- showMoreElem.setInnerHTML("<i>Show " + increment + " more</i>");
- showMoreElem.getStyle().setDisplay(Display.NONE);
+ showMoreElem.setInnerHTML("<button>Show more</button>");
+
+ showFewerElem = Document.get().createDivElement();
+ showFewerElem.setInnerHTML("<button>Show fewer</button>");
+
+ showOrHide(showMoreElem, false);
+ showOrHide(showFewerElem, false);
// TODO: find some way for cells to communicate what they're interested in.
DivElement outerDiv = Document.get().createDivElement();
DivElement innerDiv = Document.get().createDivElement();
outerDiv.appendChild(innerDiv);
+ outerDiv.appendChild(showFewerElem);
outerDiv.appendChild(showMoreElem);
setElement(outerDiv);
sinkEvents(Event.ONCLICK);
@@ -76,14 +85,22 @@
@Override
public void onBrowserEvent(Event event) {
Element target = event.getEventTarget().cast();
- String idxString = "";
- while ((target != null)
- && ((idxString = target.getAttribute("__idx")).length() == 0)) {
- target = target.getParentElement();
- }
- if (idxString.length() > 0) {
- int idx = Integer.parseInt(idxString);
- cell.onBrowserEvent(target, data.get(idx), null, event, valueUpdater);
+ if (target.getParentElement() == showMoreElem) {
+ this.maxSize += increment;
+ reg.setRangeOfInterest(0, maxSize);
+ } else if (target.getParentElement() == showFewerElem) {
+ this.maxSize = Math.max(initialMaxSize, maxSize - increment);
+ reg.setRangeOfInterest(0, maxSize);
+ } else {
+ String idxString = "";
+ while ((target != null)
+ && ((idxString = target.getAttribute("__idx")).length() == 0)) {
+ target = target.getParentElement();
+ }
+ if (idxString.length() > 0) {
+ int idx = Integer.parseInt(idxString);
+ cell.onBrowserEvent(target, data.get(idx), null, event, valueUpdater);
+ }
}
}
@@ -98,38 +115,64 @@
// Register for model events.
this.reg = model.addListHandler(new ListHandler<T>() {
public void onDataChanged(ListEvent<T> event) {
- int start = event.getStart(), len = event.getLength();
+ int start = event.getStart();
+ int len = event.getLength();
List<T> values = event.getValues();
- for (int i = 0; i < len; ++i) {
- data.set(start + i, values.get(i));
+
+ // Construct a run of element from start (inclusive) to start + len (exclusive)
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ sb.append("<div __idx='" + (start + i) + "' __seq='" + seq++ + "'>");
+ cell.render(values.get(i), null, sb);
+ sb.append("</div>");
}
- render(start, len, values);
+
+ Element parent = getElement().getFirstChildElement();
+ if (start == 0 && len == maxSize) {
+ parent.setInnerHTML(sb.toString());
+ } else {
+ makeElements();
+ tmpElem.setInnerHTML(sb.toString());
+ for (int i = 0; i < len; i++) {
+ Element child = parent.getChild(start + i).cast();
+ parent.replaceChild(tmpElem.getChild(0), child);
+ }
+ }
}
public void onSizeChanged(SizeChangeEvent event) {
- int size = event.getSize();
- if (size > maxSize) {
- showMoreElem.getStyle().clearDisplay();
- } else {
- showMoreElem.getStyle().setDisplay(Display.NONE);
- }
+ size = event.getSize();
+ showOrHide(showMoreElem, size > maxSize);
+ showOrHide(showFewerElem, maxSize > initialMaxSize);
+ }
+
+ private void makeElements() {
+ Element parent = getElement().getFirstChildElement();
+ int childCount = parent.getChildCount();
- int dataSize = data.size();
- if (size < dataSize) {
- while (size < dataSize) {
- data.remove(dataSize - 1);
- dataSize--;
+ int actualSize = Math.min(size, maxSize);
+ if (actualSize > childCount) {
+ // Create new elements with a "loading..." message
+ StringBuilder sb = new StringBuilder();
+ int newElements = actualSize - childCount;
+ for (int i = 0; i < newElements; i++) {
+ sb.append("<div __idx='" + (childCount + i) + "'><i>loading...</i></div>");
}
- } else {
- data.ensureCapacity(size);
- while (dataSize < size) {
- data.add(null);
- dataSize++;
+
+ if (childCount == 0) {
+ parent.setInnerHTML(sb.toString());
+ } else {
+ tmpElem.setInnerHTML(sb.toString());
+ for (int i = 0; i < newElements; i++) {
+ parent.appendChild(tmpElem.getChild(0));
+ }
+ }
+ } else if (actualSize < childCount) {
+ // Remove excess elements
+ while (actualSize < childCount) {
+ parent.getChild(--childCount).removeFromParent();
}
}
-
- // TODO: This only grows. It needs to shrink as well.
- gc(size);
}
});
@@ -143,69 +186,11 @@
this.reg = null;
}
- private void gc(int size) {
- // Remove unused children if the size shrinks.
- int childCount = getElement().getChildCount();
- while (size < childCount) {
- getElement().getChild(--childCount).removeFromParent();
- }
- }
-
- private void render(int start, int len, List<T> values) {
- Element parent = getElement().getFirstChildElement();
- int childCount = parent.getChildCount();
-
- // Create innerHTML for the new items.
- int end = start + len;
- StringBuilder html = new StringBuilder();
-
- // Empty items to fill any gaps.
- int totalToAdd = 0;
- for (int i = childCount; i < start; ++i) {
- html.append("<div __idx='" + i + "'>");
- cell.render(null, null, html);
- html.append("</div>");
- ++totalToAdd;
- }
-
- // Items rendered from data.
- for (int i = start; i < end; ++i) {
- html.append("<div __idx='" + i + "'>");
- cell.render(values.get(i - start), null, html);
- html.append("</div>");
- ++totalToAdd;
- }
-
- if (childCount == 0) {
- // Fast path: No cells existed, so we can just user innerHTML.
- parent.setInnerHTML(html.toString());
+ private void showOrHide(Element element, boolean show) {
+ if (show) {
+ element.getStyle().clearDisplay();
} else {
- // Slower path: We can't clobber the existing cells, so we use innerHTML
- // in a temporary element, then move the cells back to the main element.
- tmpElem.setInnerHTML(html.toString());
-
- // Clear out old cells that overlap the new cells.
- if (start < childCount) {
- int toRemove = Math.min(end, childCount) - start;
- for (int i = 0; i < toRemove; ++i) {
- parent.removeChild(parent.getChild(start));
- }
- childCount = parent.getChildCount();
- }
-
- // Move the new cells over from the temp element.
- if (start >= childCount) {
- // Just append to the end.
- for (int i = 0; i < totalToAdd; ++i) {
- parent.appendChild(tmpElem.getChild(0));
- }
- } else {
- // Insert them in the middle somewhere.
- Node before = parent.getChild(start);
- for (int i = 0; i < totalToAdd; ++i) {
- parent.insertBefore(tmpElem.getChild(0), before);
- }
- }
+ element.getStyle().setDisplay(Display.NONE);
}
}
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
index 574230f..b9c1a11 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
@@ -53,8 +53,14 @@
curSize = newSize;
updateDataSize(curSize, true);
}
- updateViewData(0, list.size(), list);
+ if (modified) {
+ int length = maxModified - minModified;
+ updateViewData(minModified, length, list.subList(minModified, maxModified));
+ modified = false;
+ }
+ minModified = Integer.MAX_VALUE;
+ maxModified = Integer.MIN_VALUE;
flushPending = false;
}
};
@@ -69,29 +75,72 @@
*/
private List<T> list;
+ /**
+ * If modified is true, the smallest modified index.
+ */
+ private int maxModified;
+
+ /**
+ * If modified is true, one past the largest modified index.
+ */
+ private int minModified;
+
+ /**
+ * True if the list data has been modified.
+ */
+ private boolean modified;
+
public ListWrapper(List<T> list) {
this.list = list;
+ minModified = 0;
+ maxModified = list.size();
+ modified = true;
}
public void add(int index, T element) {
- list.add(index, element);
- flush();
+ try {
+ list.add(index, element);
+ minModified = Math.min(minModified, index);
+ maxModified = size();
+ modified = true;
+ flush();
+ } catch (IndexOutOfBoundsException e) {
+ throw new IndexOutOfBoundsException(e.getMessage());
+ }
}
public boolean add(T e) {
- return flush(list.add(e));
+ boolean toRet = list.add(e);
+ minModified = Math.min(minModified, size() - 1);
+ maxModified = size();
+ modified = true;
+ return flush(toRet);
}
public boolean addAll(Collection<? extends T> c) {
- return flush(list.addAll(c));
+ minModified = Math.min(minModified, size());
+ boolean toRet = list.addAll(c);
+ maxModified = size();
+ modified = true;
+ return flush(toRet);
}
public boolean addAll(int index, Collection<? extends T> c) {
- return flush(list.addAll(index, c));
+ try {
+ boolean toRet = list.addAll(index, c);
+ minModified = Math.min(minModified, index);
+ maxModified = size();
+ modified = true;
+ return flush(toRet);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IndexOutOfBoundsException(e.getMessage());
+ }
}
public void clear() {
list.clear();
+ minModified = maxModified = 0;
+ modified = true;
flush();
}
@@ -145,25 +194,48 @@
}
public T remove(int index) {
- T toRet = list.remove(index);
- flush();
- return toRet;
+ try {
+ T toRet = list.remove(index);
+ minModified = Math.min(minModified, index);
+ maxModified = size();
+ modified = true;
+ flush();
+ return toRet;
+ } catch (IndexOutOfBoundsException e) {
+ throw new IndexOutOfBoundsException(e.getMessage());
+ }
}
public boolean remove(Object o) {
- return flush(list.remove(o));
+ int index = indexOf(o);
+ if (index == -1) {
+ return false;
+ }
+ remove(index);
+ return true;
}
public boolean removeAll(Collection<?> c) {
- return flush(list.removeAll(c));
+ boolean toRet = list.removeAll(c);
+ minModified = 0;
+ maxModified = size();
+ modified = true;
+ return flush(toRet);
}
public boolean retainAll(Collection<?> c) {
- return flush(list.retainAll(c));
+ boolean toRet = list.retainAll(c);
+ minModified = 0;
+ maxModified = size();
+ modified = true;
+ return flush(toRet);
}
public T set(int index, T element) {
T toRet = list.set(index, element);
+ minModified = Math.min(minModified, index);
+ maxModified = Math.max(maxModified, index + 1);
+ modified = true;
flush();
return toRet;
}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/SimpleCellList.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/SimpleCellList.gwt.xml
new file mode 100644
index 0000000..9ac9c97
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/SimpleCellList.gwt.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Could not determine the version of your GWT SDK; using the module DTD from GWT 1.6.4. You may want to change this. -->
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
+<module rename-to='simplecelllist'>
+ <inherits name="com.google.gwt.bikeshed.list.List" />
+ <source path="client" />
+ <entry-point
+ class="com.google.gwt.sample.bikeshed.simplecelllist.client.SimpleCellListSample">
+ </entry-point>
+</module>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/client/SimpleCellListSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/client/SimpleCellListSample.java
new file mode 100644
index 0000000..4fb0590
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/simplecelllist/client/SimpleCellListSample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.bikeshed.simplecelllist.client;
+
+import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.bikeshed.list.client.SimpleCellList;
+import com.google.gwt.bikeshed.list.shared.ListListModel;
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.RootPanel;
+
+import java.util.List;
+
+/**
+ * SimpleCellList demo.
+ */
+public class SimpleCellListSample implements EntryPoint {
+
+ public void onModuleLoad() {
+ ListListModel<String> listModel = new ListListModel<String>();
+ final List<String> list = listModel.getList();
+ for (int i = 0; i < 50; i++) {
+ list.add("" + (i * 1000));
+ }
+
+ SimpleCellList<String> simpleCellList = new SimpleCellList<String>(listModel, new TextCell(), 10, 5);
+
+ RootPanel.get().add(simpleCellList);
+
+ new Timer() {
+ int index = 0;
+
+ @Override
+ public void run() {
+ list.set(index, "" + (Integer.parseInt(list.get(index)) + 1));
+ list.set(index + 15, "" + (Integer.parseInt(list.get(index + 15)) + 1));
+ index = (index + 1) % 10;
+ schedule(100);
+ }
+ }.schedule(100);
+ }
+}
diff --git a/bikeshed/war/SimpleCellList.html b/bikeshed/war/SimpleCellList.html
new file mode 100644
index 0000000..3119975
--- /dev/null
+++ b/bikeshed/war/SimpleCellList.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Simple Cell List Demo</title>
+ <script type="text/javascript" language="javascript" src="simplecelllist/simplecelllist.nocache.js"></script>
+ </head>
+
+ <body>
+ <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
+
+ </body>
+</html>