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>