Replacing PagingListView.setPageStart/Size with PagingListView.setRange. Replacing CellListImpl with PagingListViewPresenter, which makes the implementation easier to test and reuse. Adding lots of tests.
Review at http://gwt-code-reviews.appspot.com/614803
Review by: jgw@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8357 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
index 9397f2d..9a5349b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
@@ -21,6 +21,7 @@
import com.google.gwt.cell.client.ClickableTextCell;
import com.google.gwt.cell.client.DatePickerCell;
import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.ListBoxCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -30,6 +31,7 @@
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.Header;
+import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -42,6 +44,7 @@
import com.google.gwt.view.client.ListViewAdapter;
import com.google.gwt.view.client.ProvidesKey;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@@ -308,12 +311,19 @@
@Override
protected Widget createWidget() {
- ListViewAdapter<Message> adapter = new ListViewAdapter<Message>();
+ final ListViewAdapter<Message> adapter = new ListViewAdapter<Message>();
messages = adapter.getList();
addMessages(10);
table = new CellTable<Message>(10);
+ new Timer() {
+ @Override
+ public void run() {
+ table.redraw();
+ schedule(4000);
+ }
+ }.schedule(4000);
table.setSelectionModel(selectionModel);
adapter.addView(table);
@@ -342,12 +352,11 @@
});
table.addColumn(selectedColumn, selectedHeader);
- addColumn(table, "ID", new TextCell(),
- new GetValue<Message, String>() {
- public String getValue(Message object) {
- return "" + object.id;
- }
- }, idComparator);
+ addColumn(table, "ID", new TextCell(), new GetValue<Message, String>() {
+ public String getValue(Message object) {
+ return "" + object.id;
+ }
+ }, idComparator);
addColumn(table, "Read", new GetValue<Message, String>() {
public String getValue(Message object) {
@@ -365,7 +374,7 @@
public void update(int index, Message object, Date value) {
Window.alert("Changed date from " + object.date + " to " + value);
object.date = value;
- table.refresh();
+ table.redraw();
}
});
@@ -396,6 +405,40 @@
});
table.addColumn(toggleColumn, "Toggle Read/Unread");
+ final ListViewAdapter<String> monthAdapter = new ListViewAdapter<String>();
+ final List<String> monthList = monthAdapter.getList();
+ monthList.add("January");
+ monthList.add("February");
+ monthList.add("March");
+ monthList.add("April");
+ monthList.add("May");
+ monthList.add("June");
+ monthList.add("July");
+ monthList.add("August");
+ monthList.add("September");
+ monthList.add("October");
+ monthList.add("November");
+ monthList.add("December");
+ ListBoxCell<String> listBoxCell = new ListBoxCell<String>(new TextCell());
+ monthAdapter.addView(listBoxCell);
+ Column<Message, List<String>> monthColumn = new Column<Message, List<String>>(
+ listBoxCell) {
+ @Override
+ public List<String> getValue(Message object) {
+ List<String> l = new ArrayList<String>();
+ l.add(monthList.get(object.getDate().getMonth()));
+ return l;
+ }
+ };
+ monthColumn.setFieldUpdater(new FieldUpdater<Message, List<String>>() {
+ public void update(int index, Message object, List<String> values) {
+ int month = monthList.indexOf(values.get(0));
+ object.getDate().setMonth(month);
+ adapter.refresh();
+ }
+ });
+ table.addColumn(monthColumn, "Month");
+
ScrollbarPager<Message> pager = new ScrollbarPager<Message>(table);
Label searchLabel = new Label("Search Sender or Subject:");
@@ -434,9 +477,8 @@
}
private <C extends Comparable<C>> Column<Message, C> addColumn(
- CellTable<Message> table, final String text,
- final Cell<C> cell, final GetValue<Message, C> getter,
- final Comparator<Message> comparator) {
+ CellTable<Message> table, final String text, final Cell<C> cell,
+ final GetValue<Message, C> getter, final Comparator<Message> comparator) {
Column<Message, C> column = new Column<Message, C>(cell) {
@Override
public C getValue(Message object) {
@@ -469,9 +511,8 @@
return column;
}
- private Column<Message, String> addColumn(
- CellTable<Message> table, final String text,
- final GetValue<Message, String> getter) {
+ private Column<Message, String> addColumn(CellTable<Message> table,
+ final String text, final GetValue<Message, String> getter) {
return addColumn(table, text, new TextCell(), getter, null);
}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
index 927afb4..d9091c0 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
@@ -73,7 +73,7 @@
}
public void onRangeOrSizeChanged(PagingListView<T> listView) {
- this.pageSize = listView.getPageSize();
+ this.pageSize = listView.getRange().getLength();
this.dataSize = listView.getDataSize();
this.height = view.getBodyHeight();
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
index 388b3a5..ef11024 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
@@ -22,6 +22,7 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
/**
* A pager for controlling a PagingListView that uses a series of buttons for
@@ -58,7 +59,7 @@
* additional rows to be displayed.
*/
public boolean canAddRows(int rows) {
- return view.getDataSize() - view.getPageSize() >= rows;
+ return view.getDataSize() - getPageSize() >= rows;
}
/**
@@ -66,7 +67,7 @@
* to be removed.
*/
public boolean canRemoveRows(int rows) {
- return view.getPageSize() > rows;
+ return getPageSize() > rows;
}
public void onClick(ClickEvent event) {
@@ -91,7 +92,7 @@
}
private void addRows(int rows) {
- view.setPageSize(view.getPageSize() + rows);
+ setPageSize(getPageSize() + rows);
}
private Button makeButton(String label, String id) {
@@ -102,7 +103,7 @@
}
private void removeRows(int rows) {
- view.setPageSize(view.getPageSize() - rows);
+ setPageSize(getPageSize() - rows);
}
private void updateButtons() {
@@ -111,11 +112,13 @@
prevPageButton.setEnabled(hasPreviousPage());
nextPageButton.setEnabled(hasNextPage());
- int page = (view.getPageStart() / view.getPageSize()) + 1;
- int numPages = (view.getDataSize() + view.getPageSize() - 1)
- / view.getPageSize();
+ Range range = view.getRange();
+ int pageStart = range.getStart();
+ int pageSize = range.getLength();
+ int page = (pageStart / pageSize) + 1;
+ int numPages = (view.getDataSize() + pageSize - 1) / pageSize;
infoLabel.setText("Page " + page + " of " + numPages + ": Page Start = "
- + view.getPageStart() + ", Page Size = " + view.getPageSize()
- + ", Data Size = " + view.getDataSize());
+ + pageStart + ", Page Size = " + pageSize + ", Data Size = "
+ + view.getDataSize());
}
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java
index 0ffda82..d23547f 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java
@@ -548,7 +548,7 @@
}
allHeaders.get(0).setSorted(true);
allHeaders.get(0).setReverseSort(false);
- table.refreshHeaders();
+ table.redrawHeaders();
// Request the expenses.
requestExpenses();
@@ -731,7 +731,7 @@
sortExpenses(items.getList(), header.getReverseSort() ? descComparator
: ascComparator);
- table.refreshHeaders();
+ table.redrawHeaders();
}
});
table.addColumn(column, header);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java
index d2423c0..1f3f8ec 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java
@@ -372,7 +372,7 @@
// Refresh the table.
pager.setPageStart(0);
- table.refresh();
+ requestReports(false);
}
public void setListener(Listener listener) {
@@ -432,7 +432,7 @@
otherHeader.setReverseSort(true);
}
}
- table.refreshHeaders();
+ table.redrawHeaders();
// Request sorted rows.
orderBy = property.getName();
@@ -441,7 +441,7 @@
}
searchBox.resetDefaultText();
searchRegExp = null;
-
+
// Go to the first page of the newly-sorted results
pager.firstPage();
requestReports(false);
@@ -560,7 +560,7 @@
header.setSorted(false);
header.setReverseSort(false);
}
- table.refreshHeaders();
+ table.redrawHeaders();
}
// Request the total data size.
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
index e330bc5..13083ea 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
@@ -180,7 +180,7 @@
if (clear) {
expenseAdapter.updateDataSize(0, true);
}
- expenseList.refresh();
+ requestExpenses();
}
public void show(ReportRecord report) {
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
index d05bfa3..75d3927 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
@@ -125,7 +125,7 @@
if (clear) {
reportAdapter.updateDataSize(0, true);
}
- reportList.refresh();
+ requestReports();
}
private Collection<Property<?>> getReportColumns() {
diff --git a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
index 63f78f4..c1c613d 100644
--- a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
+++ b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
@@ -180,12 +180,12 @@
PagingListView<R> table = getView().asPagingListView();
int rows = response.intValue();
table.setDataSize(rows, true);
- int pageSize = table.getPageSize();
+ int pageSize = table.getRange().getLength();
int remnant = rows % pageSize;
if (remnant == 0) {
- table.setPageStart(rows - pageSize);
+ table.setRange(rows - pageSize, pageSize);
} else {
- table.setPageStart(rows - remnant);
+ table.setRange(rows - remnant, pageSize);
}
onRangeChanged(table);
}
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractPager.java b/user/src/com/google/gwt/user/cellview/client/AbstractPager.java
index ade5370..db2b8a4 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractPager.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractPager.java
@@ -17,6 +17,7 @@
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.PagingListView.Pager;
/**
@@ -32,15 +33,21 @@
private boolean isRangeLimited = true;
/**
+ * The last data size.
+ */
+ private int lastDataSize;
+
+ /**
* The {@link PagingListView} being paged.
*/
private final PagingListView<T> view;
public AbstractPager(PagingListView<T> view) {
this.view = view;
+ this.lastDataSize = view.getDataSize();
view.setPager(this);
}
-
+
/**
* Go to the first page.
*/
@@ -58,8 +65,9 @@
* @return the page index
*/
public int getPage() {
- int pageSize = view.getPageSize();
- return (view.getPageStart() + pageSize - 1) / pageSize;
+ Range range = view.getRange();
+ int pageSize = range.getLength();
+ return (range.getStart() + pageSize - 1) / pageSize;
}
/**
@@ -68,11 +76,29 @@
* @return the page count
*/
public int getPageCount() {
- int pageSize = view.getPageSize();
+ int pageSize = getPageSize();
return (view.getDataSize() + pageSize - 1) / pageSize;
}
/**
+ * Get the page size.
+ *
+ * @return the page size
+ */
+ public int getPageSize() {
+ return view.getRange().getLength();
+ }
+
+ /**
+ * Get the page start index.
+ *
+ * @return the page start index
+ */
+ public int getPageStart() {
+ return view.getRange().getStart();
+ }
+
+ /**
* Get the {@link PagingListView} being paged.
*
* @return the {@link PagingListView}
@@ -80,7 +106,7 @@
public PagingListView<T> getPagingListView() {
return view;
}
-
+
/**
* Returns true if there is enough data such that a call to
* {@link #nextPage()} will succeed in moving the starting point of the table
@@ -90,7 +116,8 @@
if (!view.isDataSizeExact()) {
return true;
}
- return view.getPageStart() + view.getPageSize() < view.getDataSize();
+ Range range = view.getRange();
+ return range.getStart() + range.getLength() < view.getDataSize();
}
/**
@@ -98,7 +125,8 @@
* additional pages.
*/
public boolean hasNextPages(int pages) {
- return view.getPageStart() + pages * view.getPageSize() < view.getDataSize();
+ Range range = view.getRange();
+ return range.getStart() + pages * range.getLength() < view.getDataSize();
}
/**
@@ -106,7 +134,7 @@
* range.
*/
public boolean hasPage(int index) {
- return view.getPageSize() * index < view.getDataSize();
+ return getPageSize() * index < view.getDataSize();
}
/**
@@ -115,7 +143,16 @@
* table backward.
*/
public boolean hasPreviousPage() {
- return view.getPageStart() > 0 && view.getDataSize() > 0;
+ return getPageStart() > 0 && view.getDataSize() > 0;
+ }
+
+ /**
+ * Returns true if there is enough data to display a given number of previous
+ * pages.
+ */
+ public boolean hasPreviousPages(int pages) {
+ Range range = view.getRange();
+ return (pages - 1) * range.getLength() < range.getStart();
}
/**
@@ -139,19 +176,25 @@
* Set the page start to the last index that will still show a full page.
*/
public void lastPageStart() {
- setPageStart(view.getDataSize() - view.getPageSize());
+ setPageStart(view.getDataSize() - getPageSize());
}
/**
* Advance the starting row by 'pageSize' rows.
*/
public void nextPage() {
- setPageStart(view.getPageStart() + view.getPageSize());
+ Range range = view.getRange();
+ setPageStart(range.getStart() + range.getLength());
}
public void onRangeOrSizeChanged(PagingListView<T> listView) {
- if (isRangeLimited) {
- setPageStart(view.getPageStart());
+ int oldDataSize = lastDataSize;
+ lastDataSize = listView.getDataSize();
+
+ // If the data size has changed, limit the range. If the page start or size
+ // was changed through the pager, it will already be limited.
+ if (isRangeLimited && oldDataSize != lastDataSize) {
+ setPageStart(getPageStart());
}
}
@@ -159,7 +202,8 @@
* Move the starting row back by 'pageSize' rows.
*/
public void previousPage() {
- setPageStart(view.getPageStart() - view.getPageSize());
+ Range range = view.getRange();
+ setPageStart(range.getStart() - range.getLength());
}
/**
@@ -169,24 +213,43 @@
*/
public void setPage(int index) {
if (!isRangeLimited || !view.isDataSizeExact() || hasPage(index)) {
- // We don't use the local version of setPageStart because the user
- // probably wants to use absolute page indexes.
- view.setPageStart(view.getPageSize() * index);
+ // We don't use the local version of setPageStart because it would
+ // constrain the index, but the user probably wants to use absolute page
+ // indexes.
+ int pageSize = getPageSize();
+ view.setRange(pageSize * index, pageSize);
}
}
/**
+ * Set the page size of the view.
+ *
+ * @param pageSize the new page size
+ */
+ public void setPageSize(int pageSize) {
+ Range range = view.getRange();
+ int pageStart = range.getStart();
+ if (isRangeLimited && view.isDataSizeExact()) {
+ pageStart = Math.min(pageStart, view.getDataSize() - pageSize);
+ }
+ pageStart = Math.max(0, pageStart);
+ view.setRange(pageStart, pageSize);
+ }
+
+ /**
* Set the page start index.
*
* @param index the index
*/
public void setPageStart(int index) {
+ Range range = view.getRange();
+ int pageSize = range.getLength();
if (isRangeLimited && view.isDataSizeExact()) {
- index = Math.min(index, view.getDataSize() - view.getPageSize());
+ index = Math.min(index, view.getDataSize() - pageSize);
}
index = Math.max(0, index);
- if (index != view.getPageStart()) {
- view.setPageStart(index);
+ if (index != range.getStart()) {
+ view.setRange(index, pageSize);
}
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
index 37cd213..2fb3685 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
@@ -617,7 +617,7 @@
* @return the {@link Pager}
*/
protected <C> Pager<C> createPager(PagingListView<C> listView) {
- return new PageSizePager<C>(listView, listView.getPageSize());
+ return new PageSizePager<C>(listView, listView.getRange().getLength());
}
/**
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.java b/user/src/com/google/gwt/user/cellview/client/CellList.java
index 0c8c7f2..b41fcdb 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellList.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellList.java
@@ -27,6 +27,7 @@
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.LoadingState;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.PagingListView;
@@ -83,6 +84,51 @@
}
/**
+ * The view used by the presenter.
+ */
+ private class View extends PagingListViewPresenter.DefaultView<T> {
+
+ public View(Element childContainer) {
+ super(childContainer);
+ }
+
+ public boolean dependsOnSelection() {
+ return cell.dependsOnSelection();
+ }
+
+ public void render(StringBuilder sb, List<T> values, int start,
+ SelectionModel<? super T> selectionModel) {
+ int length = values.size();
+ int end = start + length;
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ boolean isSelected = selectionModel == null ? false
+ : selectionModel.isSelected(value);
+ // TODO(jlabanca): Factor out __idx because rows can move.
+ sb.append("<div onclick='' __idx='").append(i).append("'");
+ sb.append(" class='");
+ sb.append(i % 2 == 0 ? style.evenItem() : style.oddItem());
+ if (isSelected) {
+ sb.append(" ").append(style.selectedItem());
+ }
+ sb.append("'>");
+ cell.render(value, null, sb);
+ sb.append("</div>");
+ }
+ }
+
+ public void setLoadingState(LoadingState state) {
+ showOrHide(emptyMessageElem, state == LoadingState.EMPTY);
+ // TODO(jlabanca): Add a loading icon.
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(elem, style.selectedItem(), selected);
+ }
+ }
+
+ /**
* The default page size.
*/
private static final int DEFAULT_PAGE_SIZE = 25;
@@ -100,9 +146,10 @@
private final Element childContainer;
private String emptyListMessage = "";
private final Element emptyMessageElem;
- private final CellListImpl<T> impl;
+ private final PagingListViewPresenter<T> presenter;
private final Style style;
private ValueUpdater<T> valueUpdater;
+ private final View view;
/**
* Construct a new {@link CellList}.
@@ -139,49 +186,12 @@
sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
// Create the implementation.
- impl = new CellListImpl<T>(this, DEFAULT_PAGE_SIZE, childContainer) {
-
- @Override
- protected boolean dependsOnSelection() {
- return cell.dependsOnSelection();
- }
-
- @Override
- protected void emitHtml(StringBuilder sb, List<T> values, int start,
- SelectionModel<? super T> selectionModel) {
- int length = values.size();
- int end = start + length;
- for (int i = start; i < end; i++) {
- T value = values.get(i - start);
- boolean isSelected = selectionModel == null ? false
- : selectionModel.isSelected(value);
- sb.append("<div onclick='' __idx='").append(i).append("'");
- sb.append(" class='");
- sb.append(i % 2 == 0 ? style.evenItem() : style.oddItem());
- if (isSelected) {
- sb.append(" ").append(style.selectedItem());
- }
- sb.append("'>");
- cell.render(value, null, sb);
- sb.append("</div>");
- }
- }
-
- @Override
- protected void onSizeChanged() {
- super.onSizeChanged();
- showOrHide(emptyMessageElem, impl.getDataSize() == 0);
- }
-
- @Override
- protected void setSelected(Element elem, boolean selected) {
- setStyleName(elem, style.selectedItem(), selected);
- }
- };
+ view = new View(childContainer);
+ presenter = new PagingListViewPresenter<T>(this, view, DEFAULT_PAGE_SIZE);
}
public int getDataSize() {
- return impl.getDataSize();
+ return presenter.getDataSize();
}
/**
@@ -192,11 +202,11 @@
*/
public T getDisplayedItem(int indexOnPage) {
checkRowBounds(indexOnPage);
- return impl.getData().get(indexOnPage);
+ return presenter.getData().get(indexOnPage);
}
public List<T> getDisplayedItems() {
- return new ArrayList<T>(impl.getData());
+ return new ArrayList<T>(presenter.getData());
}
/**
@@ -208,16 +218,16 @@
return emptyListMessage;
}
- public int getPageSize() {
- return impl.getPageSize();
+ public final int getPageSize() {
+ return getRange().getLength();
}
- public int getPageStart() {
- return impl.getPageStart();
+ public final int getPageStart() {
+ return getRange().getStart();
}
public Range getRange() {
- return impl.getRange();
+ return presenter.getRange();
}
/**
@@ -238,7 +248,7 @@
}
public boolean isDataSizeExact() {
- return impl.dataSizeIsExact();
+ return presenter.isDataSizeExact();
}
@Override
@@ -254,10 +264,10 @@
}
if (idxString.length() > 0) {
int idx = Integer.parseInt(idxString);
- T value = impl.getData().get(idx - impl.getPageStart());
+ T value = presenter.getData().get(idx - getPageStart());
cell.onBrowserEvent(target, value, null, event, valueUpdater);
if (event.getTypeInt() == Event.ONCLICK && !cell.consumesEvents()) {
- SelectionModel<? super T> selectionModel = impl.getSelectionModel();
+ SelectionModel<? super T> selectionModel = presenter.getSelectionModel();
if (selectionModel != null) {
selectionModel.setSelected(value, true);
}
@@ -269,26 +279,19 @@
* Redraw the list using the existing data.
*/
public void redraw() {
- impl.redraw();
- }
-
- /**
- * Redraw the list, requesting data from the delegate.
- */
- public void refresh() {
- impl.refresh();
+ presenter.redraw();
}
public void setData(int start, int length, List<T> values) {
- impl.setData(values, start);
+ presenter.setData(start, length, values);
}
public void setDataSize(int size, boolean isExact) {
- impl.setDataSize(size, isExact);
+ presenter.setDataSize(size, isExact);
}
public void setDelegate(Delegate<T> delegate) {
- impl.setDelegate(delegate);
+ presenter.setDelegate(delegate);
}
/**
@@ -302,19 +305,33 @@
}
public void setPager(Pager<T> pager) {
- impl.setPager(pager);
+ presenter.setPager(pager);
}
- public void setPageSize(int pageSize) {
- impl.setPageSize(pageSize);
+ /**
+ * Set the page size.
+ *
+ * @param pageSize the new page size
+ */
+ public final void setPageSize(int pageSize) {
+ setRange(getPageStart(), pageSize);
}
- public void setPageStart(int pageStart) {
- impl.setPageStart(pageStart);
+ /**
+ * Set the page start index.
+ *
+ * @param pageStart the new page start
+ */
+ public final void setPageStart(int pageStart) {
+ setRange(pageStart, getPageSize());
+ }
+
+ public void setRange(int start, int length) {
+ presenter.setRange(start, length);
}
public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
- impl.setSelectionModel(selectionModel, true);
+ presenter.setSelectionModel(selectionModel);
}
/**
@@ -333,10 +350,10 @@
* @throws IndexOutOfBoundsException
*/
protected void checkRowBounds(int row) {
- int rowSize = impl.getDisplayedItemCount();
- if ((row >= rowSize) || (row < 0)) {
+ int rowCount = view.getChildCount();
+ if ((row >= rowCount) || (row < 0)) {
throw new IndexOutOfBoundsException("Row index: " + row + ", Row size: "
- + rowSize);
+ + rowCount);
}
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellListImpl.java b/user/src/com/google/gwt/user/cellview/client/CellListImpl.java
deleted file mode 100644
index 6ab24d4..0000000
--- a/user/src/com/google/gwt/user/cellview/client/CellListImpl.java
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * 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.user.cellview.client;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.view.client.PagingListView;
-import com.google.gwt.view.client.Range;
-import com.google.gwt.view.client.SelectionModel;
-import com.google.gwt.view.client.ListView.Delegate;
-import com.google.gwt.view.client.PagingListView.Pager;
-import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.view.client.SelectionModel.SelectionChangeHandler;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Implementation of {@link com.google.gwt.user.cellview.client.CellList}. This
- * class is subject to change or deletion. Do not rely on this class.
- *
- * @param <T> the data type of items in the list
- */
-public abstract class CellListImpl<T> {
-
- /**
- * The Element that holds the rendered child items.
- */
- private Element childContainer;
-
- /**
- * The local cache of data in the view. The 0th index in the list corresponds
- * to the data at pageStart.
- */
- private final List<T> data = new ArrayList<T>();
-
- private int dataSize;
-
- /**
- * A boolean indicating whether or not the data size has ever been set. If the
- * data size has never been set, then we will always pass it along to the
- * view.
- */
- private boolean dataSizeInitialized;
-
- private boolean dataSizeIsExact;
-
- private Delegate<T> delegate;
-
- /**
- * As an optimization, keep track of the last HTML string that we rendered. If
- * the contents do not change the next time we render, then we don't have to
- * set inner html.
- */
- private String lastContents = null;
- private final PagingListView<T> listView;
-
- private Pager<T> pager;
-
- /**
- * The number of elements to show on the page.
- */
- private int pageSize;
-
- /**
- * The start index of the current page.
- */
- private int pageStart = 0;
-
- /**
- * Set to true when the page start changes, and we need to do a full refresh.
- */
- private boolean pageStartChanged;
-
- /**
- * Indicates whether or not a redraw is scheduled.
- */
- private boolean redrawScheduled;
-
- /**
- * The command used to refresh or redraw the page. If both are scheduled, the
- * refresh will take priority.
- */
- private final Scheduler.ScheduledCommand refreshCommand = new Scheduler.ScheduledCommand() {
- public void execute() {
- // We clear the variables before making the refresh/redraw call so another
- // refresh/redraw can be scheduled synchronously.
- boolean wasRefreshScheduled = refreshScheduled;
- boolean wasRedrawScheduled = redrawScheduled;
- refreshScheduled = false;
- redrawScheduled = false;
- if (wasRefreshScheduled && delegate != null) {
- // Refresh takes priority over redraw.
- delegate.onRangeChanged(listView);
- } else if (wasRedrawScheduled) {
- setData(data, pageStart);
- }
- }
- };
-
- /**
- * Indicates whether or not a refresh is scheduled.
- */
- private boolean refreshScheduled;
-
- /**
- * A local cache of the currently selected rows. We cannot track selected keys
- * instead because we might end up in an inconsistent state where we render a
- * subset of a list with duplicate values, styling a value in the subset but
- * not styling the duplicate value outside of the subset.
- */
- private final Set<Integer> selectedRows = new HashSet<Integer>();
-
- private HandlerRegistration selectionHandler;
-
- private SelectionModel<? super T> selectionModel;
-
- /**
- * The temporary element use to convert HTML to DOM.
- */
- private final Element tmpElem;
-
- public CellListImpl(PagingListView<T> listView, int pageSize,
- Element childContainer) {
- this.childContainer = childContainer;
- this.listView = listView;
- this.pageSize = pageSize;
- tmpElem = Document.get().createDivElement();
- }
-
- public boolean dataSizeIsExact() {
- return dataSizeIsExact;
- }
-
- /**
- * Get the list of data within the current range. The data may not be
- * complete.
- *
- * @return the list of data
- */
- public List<T> getData() {
- return data;
- }
-
- /**
- * Get the overall data size.
- *
- * @return the data size
- */
- public int getDataSize() {
- return dataSize;
- }
-
- /**
- * Get the number of items that are within the current page and data range.
- *
- * @return the number of displayed items
- */
- public int getDisplayedItemCount() {
- return Math.min(pageSize, dataSize - pageStart);
- }
-
- /**
- * @return the page size
- */
- public int getPageSize() {
- return pageSize;
- }
-
- /**
- * @return the start index of the current page (inclusive)
- */
- public int getPageStart() {
- return pageStart;
- }
-
- /**
- * @return the range of data being displayed
- */
- public Range getRange() {
- return new Range(pageStart, pageSize);
- }
-
- public SelectionModel<? super T> getSelectionModel() {
- return selectionModel;
- }
-
- /**
- * Redraw the list with the current data.
- */
- public void redraw() {
- lastContents = null;
- scheduleRefresh(true);
- }
-
- /**
- * Request data from the delegate.
- */
- public void refresh() {
- scheduleRefresh(false);
- }
-
- /**
- * Set the data in the list.
- *
- * @param values the new data
- * @param valuesStart the start index of the values
- */
- public void setData(List<T> values, int valuesStart) {
- int valuesLength = values.size();
- int valuesEnd = valuesStart + valuesLength;
-
- // Calculate the bounded start (inclusive) and end index (exclusive).
- int pageEnd = pageStart + pageSize;
- int boundedStart = Math.max(valuesStart, pageStart);
- int boundedEnd = Math.min(valuesEnd, pageEnd);
- if (boundedStart >= boundedEnd) {
- // The data is out of range for the current page.
- return;
- }
-
- // The data size must be at least as large as the data.
- if (valuesEnd > dataSize) {
- dataSize = valuesEnd;
- onSizeChanged();
- }
-
- // Create placeholders up to the specified index.
- int lastCacheIndex = pageStart + data.size();
- while (lastCacheIndex < boundedStart) {
- data.add(null);
- lastCacheIndex++;
- }
-
- // Insert the new values into the data array.
- for (int i = boundedStart; i < boundedEnd; i++) {
- T value = values.get(i - valuesStart);
- int dataIndex = i - pageStart;
- if (dataIndex < data.size()) {
- data.set(dataIndex, value);
- } else {
- data.add(value);
- }
-
- // Update our local cache of selected rows.
- if (selectionModel != null) {
- if (value != null && selectionModel.isSelected(value)) {
- selectedRows.add(i);
- } else {
- selectedRows.remove(i);
- }
- }
- }
-
- // Construct a run of elements within the range of the data and the page.
- boundedStart = pageStartChanged ? pageStart : boundedStart;
- List<T> boundedValues = data.subList(boundedStart - pageStart, boundedEnd
- - pageStart);
- int boundedSize = boundedValues.size();
- StringBuilder sb = new StringBuilder();
- emitHtml(sb, boundedValues, boundedStart, selectionModel);
-
- // Replace the DOM elements with the new rendered cells.
- int childCount = childContainer.getChildCount();
- if (boundedStart == pageStart
- && (boundedSize >= childCount || boundedSize >= getDisplayedItemCount())) {
- // If the contents have changed, we're done.
- String newContents = sb.toString();
- if (!newContents.equals(lastContents)) {
- lastContents = newContents;
- childContainer = renderChildContents(newContents);
- }
- } else {
- lastContents = null;
- Element container = convertToElements(sb.toString());
- Element toReplace = null;
- int realStart = boundedStart - pageStart;
- if (realStart < childCount) {
- toReplace = childContainer.getChild(realStart).cast();
- }
- for (int i = boundedStart; i < boundedEnd; i++) {
- if (toReplace == null) {
- // The child will be removed from tmpElem, so always use index 0.
- childContainer.appendChild(container.getChild(0));
- } else {
- Element nextSibling = toReplace.getNextSiblingElement();
- childContainer.replaceChild(container.getChild(0), toReplace);
- toReplace = nextSibling;
- }
- }
- }
-
- // Reset the pageStartChanged boolean.
- pageStartChanged = false;
- }
-
- /**
- * Set the overall size of the list.
- *
- * @param size the overall size
- */
- public void setDataSize(int size, boolean isExact) {
- if (dataSizeInitialized && size == this.dataSize) {
- return;
- }
- dataSizeInitialized = true;
- this.dataSize = size;
- this.dataSizeIsExact = isExact;
- this.lastContents = null;
- updateDataAndView();
- onSizeChanged();
- }
-
- public void setDelegate(Delegate<T> delegate) {
- this.delegate = delegate;
- }
-
- public void setPager(PagingListView.Pager<T> pager) {
- this.pager = pager;
- }
-
- /**
- * Set the number of items to show on each page.
- *
- * @param pageSize the page size
- */
- public void setPageSize(int pageSize) {
- if (pageSize == this.pageSize) {
- return;
- }
- this.pageSize = pageSize;
- updateDataAndView();
- onSizeChanged();
- refresh();
- }
-
- /**
- * Set the start index of the range.
- *
- * @param pageStart the start index
- */
- public void setPageStart(int pageStart) {
- if (pageStart == this.pageStart) {
- return;
- } else if (pageStart > this.pageStart) {
- if (data.size() > pageStart - this.pageStart) {
- // Remove the data we no longer need.
- for (int i = this.pageStart; i < pageStart; i++) {
- data.remove(0);
- }
- } else {
- // We have no overlapping data, so just clear it.
- data.clear();
- }
- } else {
- if ((data.size() > 0) && (this.pageStart - pageStart < pageSize)) {
- // Insert null data at the beginning.
- for (int i = pageStart; i < this.pageStart; i++) {
- data.add(0, null);
- }
- } else {
- // We have no overlapping data, so just clear it.
- data.clear();
- }
- }
-
- // Update the start index.
- this.pageStart = pageStart;
- this.pageStartChanged = true;
- updateDataAndView();
- onSizeChanged();
-
- // Refresh the view with the data that is currently available.
- setData(data, pageStart);
-
- // Send a request for new data in the range.
- refresh();
- }
-
- /**
- * Set the {@link SelectionModel}, optionally triggering an update.
- *
- * @param selectionModel the new {@link SelectionModel}
- * @param updateSelection true to update selection
- */
- public void setSelectionModel(final SelectionModel<? super T> selectionModel,
- boolean updateSelection) {
- // Remove the old selection model.
- if (selectionHandler != null) {
- selectionHandler.removeHandler();
- selectionHandler = null;
- }
-
- // Set the new selection model.
- this.selectionModel = selectionModel;
- if (selectionModel != null) {
- selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
- public void onSelectionChange(SelectionChangeEvent event) {
- updateSelection();
- }
- });
- }
-
- // Update the current selection state based on the new model.
- if (updateSelection) {
- updateSelection();
- }
- }
-
- /**
- * Convert the specified HTML into DOM elements and return the parent of the
- * DOM elements.
- *
- * @param html the HTML to convert
- * @return the parent element
- */
- protected Element convertToElements(String html) {
- tmpElem.setInnerHTML(html);
- return tmpElem;
- }
-
- /**
- * Check whether or not the cells in the list depend on the selection state.
- *
- * @return true if cells depend on selection, false if not
- */
- protected abstract boolean dependsOnSelection();
-
- /**
- * Construct the HTML that represents the list of items.
- *
- * @param sb the {@link StringBuilder} to build into
- * @param values the values to render
- * @param start the start index
- * @param selectionModel the {@link SelectionModel}
- */
- protected abstract void emitHtml(StringBuilder sb, List<T> values, int start,
- SelectionModel<? super T> selectionModel);
-
- /**
- * Called when pageStart, pageSize, or data size changes.
- */
- protected void onSizeChanged() {
- // Inform the pager about a change in page start, page size, or data size
- if (pager != null) {
- pager.onRangeOrSizeChanged(listView);
- }
- }
-
- /**
- * Remove the last element from the list.
- */
- protected void removeLastItem() {
- childContainer.getLastChild().removeFromParent();
- }
-
- /**
- * Set the contents of the child container.
- *
- * @param html the html to render in the child
- * @return the new child container
- */
- protected Element renderChildContents(String html) {
- childContainer.setInnerHTML(html);
- return childContainer;
- }
-
- /**
- * Mark an element as selected or unselected. This is called when a cells
- * selection state changes, but the cell does not depend on selection.
- *
- * @param elem the element to modify
- * @param selected true if selected, false if not
- */
- protected abstract void setSelected(Element elem, boolean selected);
-
- /**
- * Update the table based on the current selection.
- */
- protected void updateSelection() {
- // Determine if our selection states are stale.
- boolean dependsOnSelection = dependsOnSelection();
- boolean refreshRequired = false;
- Element cellElem = childContainer.getFirstChildElement();
- int row = pageStart;
- for (T value : data) {
- boolean selected = selectionModel == null ? false
- : selectionModel.isSelected(value);
- if (selected != selectedRows.contains(row)) {
- refreshRequired = true;
- if (selected) {
- selectedRows.add(row);
- } else {
- selectedRows.remove(row);
- }
- if (!dependsOnSelection) {
- if (cellElem != null) {
- // TODO: do a better check?
- // The cell doesn't depend on selection, so we only need to update
- // the style.
- setSelected(cellElem, selected);
- }
- }
- }
- if (cellElem == null) {
- // TODO: do a better check?
- break;
- }
- cellElem = cellElem.getNextSiblingElement();
- row++;
- }
-
- // Refresh the entire list if needed.
- if (refreshRequired && dependsOnSelection) {
- setData(data, pageStart);
- }
- }
-
- /**
- * Schedule a redraw or refresh.
- *
- * @param redrawOnly if true, only schedule a redraw
- */
- private void scheduleRefresh(boolean redrawOnly) {
- if (!refreshScheduled && !redrawScheduled) {
- Scheduler.get().scheduleDeferred(refreshCommand);
- }
- if (redrawOnly) {
- redrawScheduled = true;
- } else {
- refreshScheduled = true;
- }
- }
-
- /**
- * Ensure that the data and the view are in a consistent state.
- */
- private void updateDataAndView() {
- // Update the data size.
- int expectedLastIndex = Math.max(0,
- Math.min(pageSize, dataSize - pageStart));
- int lastIndex = data.size() - 1;
- while (lastIndex >= expectedLastIndex) {
- data.remove(lastIndex);
- selectedRows.remove(lastIndex + pageStart);
- lastIndex--;
- }
-
- // Update the DOM.
- int expectedChildCount = data.size();
- int childCount = childContainer.getChildCount();
- while (childCount > expectedChildCount) {
- removeLastItem();
- childCount--;
- }
- }
-}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.java b/user/src/com/google/gwt/user/cellview/client/CellTable.java
index 59153d6..2abc00e 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.cellview.client;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
@@ -30,6 +31,7 @@
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.LoadingState;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.PagingListView;
@@ -236,7 +238,7 @@
/**
* Implementation of {@link CellTable} used by IE. Table sections do not
- * support setInnerHtml in IE, so we need to replace the entire elements.
+ * support setInnerHtml in IE, so we need to replace the entire element.
*/
@SuppressWarnings("unused")
private static class ImplTrident extends Impl {
@@ -252,6 +254,112 @@
}
/**
+ * The view used by the presenter.
+ */
+ private class View extends PagingListViewPresenter.DefaultView<T> {
+
+ public View(Element childContainer) {
+ super(childContainer);
+ }
+
+ public boolean dependsOnSelection() {
+ for (Column<T, ?> column : columns) {
+ if (column.dependsOnSelection()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onUpdateSelection() {
+ // Refresh headers.
+ for (Header<?> header : headers) {
+ if (header != null && header.dependsOnSelection()) {
+ createHeaders(false);
+ break;
+ }
+ }
+
+ // Refresh footers.
+ for (Header<?> footer : footers) {
+ if (footer != null && footer.dependsOnSelection()) {
+ createHeaders(true);
+ break;
+ }
+ }
+ }
+
+ public void render(StringBuilder sb, List<T> values, int start,
+ SelectionModel<? super T> selectionModel) {
+ createHeadersAndFooters();
+
+ String firstColumnStyle = style.firstColumn();
+ String lastColumnStyle = style.lastColumn();
+ int columnCount = columns.size();
+ int length = values.size();
+ int end = start + length;
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ boolean isSelected = (selectionModel == null || value == null) ? false
+ : selectionModel.isSelected(value);
+ sb.append("<tr onclick=''");
+ sb.append(" class='");
+ sb.append(i % 2 == 0 ? style.evenRow() : style.oddRow());
+ if (isSelected) {
+ sb.append(" ").append(style.selectedRow());
+ }
+ sb.append("'>");
+ int curColumn = 0;
+ for (Column<T, ?> column : columns) {
+ // TODO(jlabanca): How do we sink ONFOCUS and ONBLUR?
+ sb.append("<td class='").append(style.cell());
+ if (curColumn == 0) {
+ sb.append(" ").append(firstColumnStyle);
+ }
+ // The first and last column could be the same column.
+ if (curColumn == columnCount - 1) {
+ sb.append(" ").append(lastColumnStyle);
+ }
+ sb.append("'>");
+ int bufferLength = sb.length();
+ if (value != null) {
+ column.render(value, providesKey, sb);
+ }
+
+ // Add blank space to ensure empty rows aren't squished.
+ if (bufferLength == sb.length()) {
+ sb.append(" ");
+ }
+ sb.append("</td>");
+ curColumn++;
+ }
+ sb.append("</tr>");
+ }
+ }
+
+ @Override
+ public void replaceAllChildren(List<T> values, String html) {
+ Element section = TABLE_IMPL.renderSectionContents(tbody, html);
+ setChildContainer(section);
+ }
+
+ public void setLoadingState(LoadingState state) {
+ setLoadingIconVisible(state == LoadingState.LOADING);
+ }
+
+ @Override
+ protected Element convertToElements(String html) {
+ return TABLE_IMPL.convertToSectionElement("tbody", html);
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(elem, style.selectedRow(), selected);
+ }
+ }
+
+ /**
* The default page size.
*/
private static final int DEFAULT_PAGESIZE = 15;
@@ -281,7 +389,6 @@
private boolean headersStale;
private TableRowElement hoveringRow;
- private final CellListImpl<T> impl;
/**
* If true, enable selection via the mouse.
@@ -289,16 +396,46 @@
private boolean isSelectionEnabled;
/**
+ * The presenter.
+ */
+ private final PagingListViewPresenter<T> presenter;
+
+ /**
* If null, each T will be used as its own key.
*/
private ProvidesKey<T> providesKey;
+ /**
+ * Indicates whether or not the scheduled redraw has been cancelled.
+ */
+ private boolean redrawCancelled;
+
+ /**
+ * The command used to redraw the table after adding columns.
+ */
+ private final Scheduler.ScheduledCommand redrawCommand = new Scheduler.ScheduledCommand() {
+ public void execute() {
+ redrawScheduled = false;
+ if (redrawCancelled) {
+ redrawCancelled = false;
+ return;
+ }
+ redraw();
+ }
+ };
+
+ /**
+ * Indicates whether or not a redraw is scheduled.
+ */
+ private boolean redrawScheduled;
+
private final Style style;
private final TableElement table;
private TableSectionElement tbody;
private final TableSectionElement tbodyLoading;
private TableSectionElement tfoot;
private TableSectionElement thead;
+ private final View view;
/**
* Constructs a table with a default page size of 15.
@@ -351,110 +488,8 @@
}
// Create the implementation.
- this.impl = new CellListImpl<T>(this, pageSize, tbody) {
-
- @Override
- public void setData(List<T> values, int start) {
- createHeadersAndFooters();
- super.setData(values, start);
- }
-
- @Override
- protected Element convertToElements(String html) {
- return TABLE_IMPL.convertToSectionElement("tbody", html);
- }
-
- @Override
- protected boolean dependsOnSelection() {
- for (Column<T, ?> column : columns) {
- if (column.dependsOnSelection()) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- protected void emitHtml(StringBuilder sb, List<T> values, int start,
- SelectionModel<? super T> selectionModel) {
- setLoadingIconVisible(false);
-
- String firstColumnStyle = style.firstColumn();
- String lastColumnStyle = style.lastColumn();
- int columnCount = columns.size();
- int length = values.size();
- int end = start + length;
- for (int i = start; i < end; i++) {
- T value = values.get(i - start);
- boolean isSelected = (selectionModel == null || value == null)
- ? false : selectionModel.isSelected(value);
- sb.append("<tr onclick='' __idx='").append(i).append("'");
- sb.append(" class='");
- sb.append(i % 2 == 0 ? style.evenRow() : style.oddRow());
- if (isSelected) {
- sb.append(" ").append(style.selectedRow());
- }
- sb.append("'>");
- int curColumn = 0;
- for (Column<T, ?> column : columns) {
- // TODO(jlabanca): How do we sink ONFOCUS and ONBLUR?
- sb.append("<td class='").append(style.cell());
- if (curColumn == 0) {
- sb.append(" ").append(firstColumnStyle);
- }
- // The first and last column could be the same column.
- if (curColumn == columnCount - 1) {
- sb.append(" ").append(lastColumnStyle);
- }
- sb.append("'>");
- int bufferLength = sb.length();
- if (value != null) {
- column.render(value, providesKey, sb);
- }
-
- // Add blank space to ensure empty rows aren't squished.
- if (bufferLength == sb.length()) {
- sb.append(" ");
- }
- sb.append("</td>");
- curColumn++;
- }
- sb.append("</tr>");
- }
- }
-
- @Override
- protected Element renderChildContents(String html) {
- return (tbody = TABLE_IMPL.renderSectionContents(tbody, html));
- }
-
- @Override
- protected void setSelected(Element elem, boolean selected) {
- setStyleName(elem, style.selectedRow(), selected);
- }
-
- @Override
- protected void updateSelection() {
- // Refresh headers.
- for (Header<?> header : headers) {
- if (header != null && header.dependsOnSelection()) {
- createHeaders(false);
- break;
- }
- }
-
- // Refresh footers.
- for (Header<?> footer : footers) {
- if (footer != null && footer.dependsOnSelection()) {
- createHeaders(true);
- break;
- }
- }
-
- // Update data.
- super.updateSelection();
- }
- };
+ view = new View(tbody);
+ this.presenter = new PagingListViewPresenter<T>(this, view, pageSize);
setPageSize(pageSize);
@@ -462,9 +497,6 @@
// those events actually needed by cells.
sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS
| Event.ONCHANGE | Event.FOCUSEVENTS);
-
- // Show the loading indicator by default.
- setLoadingIconVisible(true);
}
/**
@@ -489,7 +521,7 @@
footers.add(footer);
columns.add(col);
headersStale = true;
- redraw();
+ scheduleRedraw();
}
/**
@@ -507,8 +539,6 @@
addColumn(col, new TextHeader(headerString), new TextHeader(footerString));
}
- // TODO: remove(Column)
-
/**
* Add a style name to the {@link TableColElement} at the specified index,
* creating it if necessary.
@@ -526,16 +556,16 @@
}
public int getDataSize() {
- return impl.getDataSize();
+ return presenter.getDataSize();
}
public T getDisplayedItem(int indexOnPage) {
checkRowBounds(indexOnPage);
- return impl.getData().get(indexOnPage);
+ return presenter.getData().get(indexOnPage);
}
public List<T> getDisplayedItems() {
- return new ArrayList<T>(impl.getData());
+ return new ArrayList<T>(presenter.getData());
}
public int getHeaderHeight() {
@@ -547,20 +577,16 @@
return providesKey;
}
- public int getNumDisplayedItems() {
- return impl.getDisplayedItemCount();
+ public final int getPageSize() {
+ return getRange().getLength();
}
- public int getPageSize() {
- return impl.getPageSize();
- }
-
- public int getPageStart() {
- return impl.getPageStart();
+ public final int getPageStart() {
+ return getRange().getStart();
}
public Range getRange() {
- return impl.getRange();
+ return presenter.getRange();
}
/**
@@ -578,12 +604,8 @@
return rows.getLength() > row ? rows.getItem(row) : null;
}
- public int getSize() {
- return impl.getDataSize();
- }
-
public boolean isDataSizeExact() {
- return impl.dataSizeIsExact();
+ return presenter.isDataSizeExact();
}
/**
@@ -637,14 +659,14 @@
tr.removeClassName(style.hoveredRow());
}
- T value = impl.getData().get(row);
+ T value = presenter.getData().get(row);
Column<T, ?> column = columns.get(col);
- column.onBrowserEvent(cell, impl.getPageStart() + row, value, event,
+ column.onBrowserEvent(cell, getPageStart() + row, value, event,
providesKey);
// Update selection.
if (isSelectionEnabled && event.getTypeInt() == Event.ONCLICK) {
- SelectionModel<? super T> selectionModel = impl.getSelectionModel();
+ SelectionModel<? super T> selectionModel = presenter.getSelectionModel();
if (selectionModel != null) {
selectionModel.setSelected(value, true);
}
@@ -656,27 +678,52 @@
* Redraw the table using the existing data.
*/
public void redraw() {
- setLoadingIconVisible(false);
- impl.redraw();
+ if (redrawScheduled) {
+ redrawCancelled = true;
+ }
+ presenter.redraw();
}
- /**
- * Redraw the table, requesting data from the delegate.
- */
- public void refresh() {
- setLoadingIconVisible(true);
- impl.refresh();
- }
-
- public void refreshFooters() {
+ public void redrawFooters() {
createHeaders(true);
}
- public void refreshHeaders() {
+ public void redrawHeaders() {
createHeaders(false);
}
/**
+ * Remove a column.
+ *
+ * @param index the column index
+ */
+ public void removeColumn(int index) {
+ if (index < 0 || index >= columns.size()) {
+ throw new IndexOutOfBoundsException(
+ "The specified column index is out of bounds.");
+ }
+ columns.remove(index);
+ headers.remove(index);
+ footers.remove(index);
+ headersStale = true;
+ scheduleRedraw();
+ }
+
+ /**
+ * Remove a column.
+ *
+ * @param col the column to remove
+ */
+ public void removeColumn(Column<T, ?> col) {
+ int index = columns.indexOf(col);
+ if (index < 0) {
+ throw new IllegalArgumentException(
+ "The specified column is not part of this table.");
+ }
+ removeColumn(index);
+ }
+
+ /**
* Remove a style from the {@link TableColElement} at the specified index.
*
* @param index the column index
@@ -690,20 +737,15 @@
}
public void setData(int start, int length, List<T> values) {
- impl.setData(values, start);
+ presenter.setData(start, length, values);
}
public void setDataSize(int size, boolean isExact) {
- impl.setDataSize(size, isExact);
-
- // If there is no data, then we are done loading.
- if (size <= 0) {
- setLoadingIconVisible(false);
- }
+ presenter.setDataSize(size, isExact);
}
public void setDelegate(Delegate<T> delegate) {
- impl.setDelegate(delegate);
+ presenter.setDelegate(delegate);
}
/**
@@ -719,7 +761,7 @@
}
public void setPager(PagingListView.Pager<T> pager) {
- impl.setPager(pager);
+ presenter.setPager(pager);
}
/**
@@ -729,8 +771,8 @@
*
* @throws IllegalArgumentException if pageSize is negative or 0
*/
- public void setPageSize(int pageSize) {
- impl.setPageSize(pageSize);
+ public final void setPageSize(int pageSize) {
+ setRange(getPageStart(), pageSize);
}
/**
@@ -740,9 +782,12 @@
* @param pageStart the index of the row that should appear at the start of
* the page
*/
- public void setPageStart(int pageStart) {
- setLoadingIconVisible(true);
- impl.setPageStart(pageStart);
+ public final void setPageStart(int pageStart) {
+ setRange(pageStart, getPageSize());
+ }
+
+ public void setRange(int start, int length) {
+ presenter.setRange(start, length);
}
/**
@@ -755,7 +800,7 @@
}
public void setSelectionModel(SelectionModel<? super T> selectionModel) {
- impl.setSelectionModel(selectionModel, true);
+ presenter.setSelectionModel(selectionModel);
}
/**
@@ -765,10 +810,10 @@
* @throws IndexOutOfBoundsException
*/
protected void checkRowBounds(int row) {
- int rowSize = impl.getDisplayedItemCount();
- if ((row >= rowSize) || (row < 0)) {
+ int rowCount = view.getChildCount();
+ if ((row >= rowCount) || (row < 0)) {
throw new IndexOutOfBoundsException("Row index: " + row + ", Row size: "
- + rowSize);
+ + rowCount);
}
}
@@ -863,6 +908,17 @@
}-*/;
/**
+ * Schedule a redraw for the end of the event loop.
+ */
+ private void scheduleRedraw() {
+ redrawCancelled = false;
+ if (!redrawScheduled) {
+ redrawScheduled = true;
+ Scheduler.get().scheduleFinally(redrawCommand);
+ }
+ }
+
+ /**
* Show or hide the loading icon.
*
* @param visible true to show, false to hide.
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
index 3a72edd..0905387 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -23,6 +23,7 @@
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.LoadingState;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.view.client.TreeViewModel;
import com.google.gwt.view.client.PagingListView;
@@ -33,8 +34,10 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* A view of a tree node.
@@ -98,214 +101,258 @@
*
* @param <C> the child item type
*/
- private static class NodeListView<C> implements PagingListView<C> {
+ private static class NodeCellList<C> implements PagingListView<C> {
+ /**
+ * The view used by the NodeCellList.
+ */
+ private class View extends PagingListViewPresenter.DefaultView<C> {
+
+ public View(Element childContainer) {
+ super(childContainer);
+ }
+
+ public boolean dependsOnSelection() {
+ return cell.dependsOnSelection();
+ }
+
+ public void render(StringBuilder sb, List<C> values, int start,
+ SelectionModel<? super C> selectionModel) {
+ // Cache the style names that will be used for each child.
+ CellTree.Style style = nodeView.tree.getStyle();
+ String selectedStyle = style.selectedItem();
+ String itemStyle = style.item();
+ String itemImageValueStyle = style.itemImageValue();
+ String itemValueStyle = style.itemValue();
+ String openStyle = style.openItem();
+ String topStyle = style.topItem();
+ String topImageValueStyle = style.topItemImageValue();
+ boolean isRootNode = nodeView.isRootNode();
+ String openImage = nodeView.tree.getOpenImageHtml(isRootNode);
+ String closedImage = nodeView.tree.getClosedImageHtml(isRootNode);
+ int imageWidth = nodeView.tree.getImageWidth();
+ int paddingLeft = imageWidth * nodeView.depth;
+
+ // Create a set of currently open nodes.
+ Set<Object> openNodes = new HashSet<Object>();
+ int childCount = nodeView.getChildCount();
+ int end = start + values.size();
+ for (int i = start; i < end && i < childCount; i++) {
+ CellTreeNodeView<?> child = nodeView.getChildNode(i);
+ // Ignore child nodes that are closed.
+ if (child.isOpen()) {
+ openNodes.add(child.getValueKey());
+ }
+ }
+
+ // Render the child nodes.
+ ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
+ TreeViewModel model = nodeView.tree.getTreeViewModel();
+ for (C value : values) {
+ Object key = providesKey.getKey(value);
+ boolean isOpen = openNodes.contains(key);
+
+ // Outer div contains image, value, and children (when open).
+ sb.append("<div>");
+
+ // The selection pads the content based on the depth.
+ sb.append("<div style='padding-left:");
+ sb.append(paddingLeft);
+ sb.append("px;' class='").append(itemStyle);
+ if (isOpen) {
+ sb.append(" ").append(openStyle);
+ }
+ if (isRootNode) {
+ sb.append(" ").append(topStyle);
+ }
+ if (selectionModel != null && selectionModel.isSelected(value)) {
+ sb.append(" ").append(selectedStyle);
+ }
+ sb.append("'>");
+
+ // Inner div contains image and value.
+ sb.append("<div onclick='' style='position:relative;padding-left:");
+ sb.append(imageWidth);
+ sb.append("px;' class='").append(itemImageValueStyle);
+ if (isRootNode) {
+ sb.append(" ").append(topImageValueStyle);
+ }
+ sb.append("'>");
+
+ // Add the open/close icon.
+ if (isOpen) {
+ sb.append(openImage);
+ } else if (model.isLeaf(value)) {
+ sb.append(LEAF_IMAGE);
+ } else {
+ sb.append(closedImage);
+ }
+
+ // Content div contains value.
+ sb.append("<div class='").append(itemValueStyle).append("'>");
+ cell.render(value, null, sb);
+ sb.append("</div></div></div></div>");
+ }
+ }
+
+ @Override
+ public void replaceAllChildren(List<C> values, String html) {
+ // Hide the child container so we can animate it.
+ if (nodeView.tree.isAnimationEnabled()) {
+ nodeView.ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
+ }
+
+ // Replace the child nodes.
+ Map<Object, CellTreeNodeView<?>> savedViews = saveChildState(values, 0);
+ super.replaceAllChildren(values, html);
+ loadChildState(values, 0, savedViews);
+
+ // Animate the child container open.
+ if (nodeView.tree.isAnimationEnabled()) {
+ nodeView.tree.maybeAnimateTreeNode(nodeView);
+ }
+ }
+
+ @Override
+ public void replaceChildren(List<C> values, int start, String html) {
+ Map<Object, CellTreeNodeView<?>> savedViews = saveChildState(values, 0);
+ super.replaceChildren(values, start, html);
+ loadChildState(values, 0, savedViews);
+ }
+
+ public void setLoadingState(LoadingState state) {
+ nodeView.updateImage(state == LoadingState.LOADING);
+ showOrHide(nodeView.emptyMessageElem, state == LoadingState.EMPTY);
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(getSelectionElement(elem),
+ nodeView.tree.getStyle().selectedItem(), selected);
+ }
+
+ /**
+ * Reload the open children after rendering new items in this node.
+ *
+ * @param values the values being replaced
+ * @param start the start index
+ * @param savedViews the open nodes
+ */
+ private void loadChildState(List<C> values, int start,
+ Map<Object, CellTreeNodeView<?>> savedViews) {
+ int len = values.size();
+ int end = start + len;
+ int childCount = nodeView.getChildCount();
+ ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
+ Element childElem = nodeView.ensureChildContainer().getFirstChildElement();
+ for (int i = start; i < end; i++) {
+ C childValue = values.get(i - start);
+ CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
+ childElem, childValue, null);
+ CellTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
+ // Copy the saved child's state into the new child
+ if (savedChild != null) {
+ child.animationFrame = savedChild.animationFrame;
+ child.contentContainer = savedChild.contentContainer;
+ child.childContainer = savedChild.childContainer;
+ child.children = savedChild.children;
+ child.emptyMessageElem = savedChild.emptyMessageElem;
+ child.nodeInfo = savedChild.nodeInfo;
+ child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
+ child.open = savedChild.open;
+ child.showMoreElem = savedChild.showMoreElem;
+
+ // Swap the node view in the child. We reuse the same NodeListView
+ // so that we don't have to unset and register a new view with the
+ // NodeInfo.
+ savedChild.listView.setNodeView(child);
+
+ // Copy the child container element to the new child
+ child.getElement().appendChild(savedChild.ensureAnimationFrame());
+ }
+
+ if (childCount > i) {
+ if (savedChild == null) {
+ // Cleanup the child node if we aren't going to reuse it.
+ nodeView.children.get(i).cleanup();
+ }
+ nodeView.children.set(i, child);
+ } else {
+ nodeView.children.add(child);
+ }
+ childElem = childElem.getNextSiblingElement();
+ }
+ }
+
+ /**
+ * Save the state of the open child nodes within the range of the
+ * specified values. Use {@link #loadChildState(List, int, Map)} to
+ * re-attach the open nodes after they have been replaced.
+ *
+ * @param values the values being replaced
+ * @param start the start index
+ * @return the map of open nodes
+ */
+ private Map<Object, CellTreeNodeView<?>> saveChildState(List<C> values,
+ int start) {
+ // Ensure that we have a children array.
+ if (nodeView.children == null) {
+ nodeView.children = new ArrayList<CellTreeNodeView<?>>();
+ }
+
+ // Construct a map of former child views based on their value keys.
+ int len = values.size();
+ int end = start + len;
+ int childCount = nodeView.getChildCount();
+ Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<Object, CellTreeNodeView<?>>();
+ for (int i = start; i < end && i < childCount; i++) {
+ CellTreeNodeView<?> child = nodeView.getChildNode(i);
+ // Ignore child nodes that are closed.
+ if (child.isOpen()) {
+ openNodes.put(child.getValueKey(), child);
+ }
+ }
+
+ // Trim the saved views down to the children that still exists.
+ ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
+ Map<Object, CellTreeNodeView<?>> savedViews = new HashMap<Object, CellTreeNodeView<?>>();
+ for (C childValue : values) {
+ // Remove any child elements that correspond to prior children
+ // so the call to setInnerHtml will not destroy them
+ Object key = providesKey.getKey(childValue);
+ CellTreeNodeView<?> savedView = openNodes.remove(key);
+ if (savedView != null) {
+ savedView.ensureAnimationFrame().removeFromParent();
+ savedViews.put(key, savedView);
+ }
+ }
+ return savedViews;
+ }
+ }
+
+ private final Cell<C> cell;
private final int defaultPageSize;
- private final CellListImpl<C> impl;
+ private final NodeInfo<C> nodeInfo;
private CellTreeNodeView<?> nodeView;
- private Map<Object, CellTreeNodeView<?>> savedViews;
+ private final PagingListViewPresenter<C> presenter;
- public NodeListView(final NodeInfo<C> nodeInfo,
+ public NodeCellList(final NodeInfo<C> nodeInfo,
final CellTreeNodeView<?> nodeView, int pageSize) {
this.defaultPageSize = pageSize;
+ this.nodeInfo = nodeInfo;
this.nodeView = nodeView;
+ cell = nodeInfo.getCell();
- final Cell<C> cell = nodeInfo.getCell();
- impl = new CellListImpl<C>(this, pageSize,
- nodeView.ensureChildContainer()) {
-
- @Override
- public void setData(List<C> values, int start) {
- nodeView.updateImage(false);
-
- // Ensure that we have a children array.
- if (nodeView.children == null) {
- nodeView.children = new ArrayList<CellTreeNodeView<?>>();
- }
-
- // Construct a map of former child views based on their value keys.
- int len = values.size();
- int end = start + len;
- int childCount = nodeView.getChildCount();
- Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<Object, CellTreeNodeView<?>>();
- for (int i = start; i < end && i < childCount; i++) {
- CellTreeNodeView<?> child = nodeView.getChildNode(i);
- // Ignore child nodes that are closed.
- if (child.isOpen()) {
- openNodes.put(child.getValueKey(), child);
- }
- }
-
- // Hide the child container so we can animate it.
- if (nodeView.tree.isAnimationEnabled()) {
- nodeView.ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
- }
-
- // Trim the saved views down to the children that still exists.
- ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
- savedViews = new HashMap<Object, CellTreeNodeView<?>>();
- for (C childValue : values) {
- // Remove any child elements that correspond to prior children
- // so the call to setInnerHtml will not destroy them
- Object key = providesKey.getKey(childValue);
- CellTreeNodeView<?> savedView = openNodes.remove(key);
- if (savedView != null) {
- savedView.ensureAnimationFrame().removeFromParent();
- savedViews.put(key, savedView);
- }
- }
-
- // Create the new cells.
- super.setData(values, start);
-
- // Create the child TreeNodeViews from the new elements.
- Element childElem = nodeView.ensureChildContainer().getFirstChildElement();
- for (int i = start; i < end; i++) {
- C childValue = values.get(i - start);
- CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
- childElem, childValue, null);
- CellTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
- // Copy the saved child's state into the new child
- if (savedChild != null) {
- child.animationFrame = savedChild.animationFrame;
- child.contentContainer = savedChild.contentContainer;
- child.childContainer = savedChild.childContainer;
- child.children = savedChild.children;
- child.emptyMessageElem = savedChild.emptyMessageElem;
- child.nodeInfo = savedChild.nodeInfo;
- child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
- child.open = savedChild.open;
- child.showMoreElem = savedChild.showMoreElem;
-
- // Swap the node view in the child. We reuse the same NodeListView
- // so that we don't have to unset and register a new view with the
- // NodeInfo.
- savedChild.listView.setNodeView(child);
-
- // Copy the child container element to the new child
- child.getElement().appendChild(savedChild.ensureAnimationFrame());
- }
-
- if (childCount > i) {
- if (savedChild == null) {
- // Cleanup the child node if we aren't going to reuse it.
- nodeView.children.get(i).cleanup();
- }
- nodeView.children.set(i, child);
- } else {
- nodeView.children.add(child);
- }
- childElem = childElem.getNextSiblingElement();
- }
-
- // Clear temporary state.
- savedViews = null;
-
- // Animate the child container open.
- if (nodeView.tree.isAnimationEnabled()) {
- nodeView.tree.maybeAnimateTreeNode(nodeView);
- }
- }
-
- @Override
- protected boolean dependsOnSelection() {
- return cell.dependsOnSelection();
- }
-
- @Override
- protected void emitHtml(StringBuilder sb, List<C> values, int start,
- SelectionModel<? super C> selectionModel) {
- // Cache the style names that will be used for each child.
- CellTree.Style style = nodeView.tree.getStyle();
- String selectedStyle = style.selectedItem();
- String itemStyle = style.item();
- String itemImageValueStyle = style.itemImageValue();
- String itemValueStyle = style.itemValue();
- String openStyle = style.openItem();
- String topStyle = style.topItem();
- String topImageValueStyle = style.topItemImageValue();
- boolean isRootNode = nodeView.isRootNode();
- String openImage = nodeView.tree.getOpenImageHtml(isRootNode);
- String closedImage = nodeView.tree.getClosedImageHtml(isRootNode);
- int imageWidth = nodeView.tree.getImageWidth();
- int paddingLeft = imageWidth * nodeView.depth;
-
- // Render the child nodes.
- ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
- TreeViewModel model = nodeView.tree.getTreeViewModel();
- for (C value : values) {
- Object key = providesKey.getKey(value);
- boolean isOpen = savedViews.containsKey(key);
-
- // Outer div contains image, value, and children (when open).
- sb.append("<div>");
-
- // The selection pads the content based on the depth.
- sb.append("<div style='padding-left:");
- sb.append(paddingLeft);
- sb.append("px;' class='").append(itemStyle);
- if (isOpen) {
- sb.append(" ").append(openStyle);
- }
- if (isRootNode) {
- sb.append(" ").append(topStyle);
- }
- if (selectionModel != null && selectionModel.isSelected(value)) {
- sb.append(" ").append(selectedStyle);
- }
- sb.append("'>");
-
- // Inner div contains image and value.
- sb.append("<div onclick='' style='position:relative;padding-left:");
- sb.append(imageWidth);
- sb.append("px;' class='").append(itemImageValueStyle);
- if (isRootNode) {
- sb.append(" ").append(topImageValueStyle);
- }
- sb.append("'>");
-
- // Add the open/close icon.
- if (isOpen) {
- sb.append(openImage);
- } else if (model.isLeaf(value)) {
- sb.append(LEAF_IMAGE);
- } else {
- sb.append(closedImage);
- }
-
- // Content div contains value.
- sb.append("<div class='").append(itemValueStyle).append("'>");
- cell.render(value, null, sb);
- sb.append("</div></div></div></div>");
- }
- }
-
- @Override
- protected void removeLastItem() {
- CellTreeNodeView<?> child = nodeView.children.remove(nodeView.children.size() - 1);
- child.cleanup();
- super.removeLastItem();
- }
-
- @Override
- protected void setSelected(Element elem, boolean selected) {
- setStyleName(getSelectionElement(elem),
- nodeView.tree.getStyle().selectedItem(), selected);
- }
- };
+ presenter = new PagingListViewPresenter<C>(this, new View(
+ nodeView.ensureChildContainer()), pageSize);
// Use a pager to update buttons.
- impl.setPager(new Pager<C>() {
+ presenter.setPager(new Pager<C>() {
public void onRangeOrSizeChanged(PagingListView<C> listView) {
// Assumes a page start of 0.
- int dataSize = impl.getDataSize();
- showOrHide(nodeView.showMoreElem, dataSize > impl.getPageSize());
- if (dataSize == 0) {
- showOrHide(nodeView.emptyMessageElem, true);
- nodeView.updateImage(false);
- } else {
- showOrHide(nodeView.emptyMessageElem, false);
- }
+ int dataSize = presenter.getDataSize();
+ int pageSize = getRange().getLength();
+ showOrHide(nodeView.showMoreElem, dataSize > pageSize);
}
});
}
@@ -314,59 +361,47 @@
* Cleanup this node view.
*/
public void cleanup() {
- impl.setSelectionModel(null, false);
+ presenter.clearSelectionModel();
}
-
+
public int getDataSize() {
- return impl.getDataSize();
+ return presenter.getDataSize();
}
public int getDefaultPageSize() {
return defaultPageSize;
}
- public int getPageSize() {
- return impl.getPageSize();
- }
-
- public int getPageStart() {
- return impl.getPageStart();
- }
-
public Range getRange() {
- return impl.getRange();
+ return presenter.getRange();
}
public boolean isDataSizeExact() {
- return impl.dataSizeIsExact();
+ return presenter.isDataSizeExact();
}
public void setData(int start, int length, List<C> values) {
- impl.setData(values, start);
+ presenter.setData(start, length, values);
}
public void setDataSize(int size, boolean isExact) {
- impl.setDataSize(size, isExact);
+ presenter.setDataSize(size, isExact);
}
public void setDelegate(Delegate<C> delegate) {
- impl.setDelegate(delegate);
+ presenter.setDelegate(delegate);
}
public void setPager(Pager<C> pager) {
- impl.setPager(pager);
+ presenter.setPager(pager);
}
- public void setPageSize(int pageSize) {
- impl.setPageSize(pageSize);
- }
-
- public void setPageStart(int pageStart) {
- impl.setPageStart(pageStart);
+ public void setRange(int start, int length) {
+ presenter.setRange(start, length);
}
public void setSelectionModel(final SelectionModel<? super C> selectionModel) {
- impl.setSelectionModel(selectionModel, true);
+ presenter.setSelectionModel(selectionModel);
}
/**
@@ -422,7 +457,7 @@
/**
* The list view used to display the nodes.
*/
- private NodeListView<?> listView;
+ private NodeCellList<?> listView;
/**
* The info about children of this node.
@@ -539,7 +574,6 @@
setStyleName(getCellParent(), tree.getStyle().openItem(), true);
}
ensureAnimationFrame().getStyle().setProperty("display", "");
- updateImage(true);
onOpen(nodeInfo);
}
} else {
@@ -643,7 +677,7 @@
* @param <C> the child data type of the node
*/
protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
- NodeListView<C> view = new NodeListView<C>(nodeInfo, this,
+ NodeCellList<C> view = new NodeCellList<C>(nodeInfo, this,
tree.getDefaultNodeSize());
listView = view;
view.setSelectionModel(nodeInfo.getSelectionModel());
@@ -720,15 +754,17 @@
}
void showFewer() {
+ Range range = listView.getRange();
int defaultPageSize = listView.getDefaultPageSize();
- int maxSize = Math.max(defaultPageSize, listView.impl.getPageSize()
- - defaultPageSize);
- listView.impl.setPageSize(maxSize);
+ int maxSize = Math.max(defaultPageSize, range.getLength() - defaultPageSize);
+ listView.setRange(range.getStart(), maxSize);
}
void showMore() {
- listView.impl.setPageSize(listView.impl.getPageSize()
- + listView.getDefaultPageSize());
+ Range range = listView.getRange();
+ int pageSize = listView.getRange().getLength()
+ + listView.getDefaultPageSize();
+ listView.setRange(range.getStart(), pageSize);
}
/**
diff --git a/user/src/com/google/gwt/user/cellview/client/PageSizePager.java b/user/src/com/google/gwt/user/cellview/client/PageSizePager.java
index 103fb43..3259577 100644
--- a/user/src/com/google/gwt/user/cellview/client/PageSizePager.java
+++ b/user/src/com/google/gwt/user/cellview/client/PageSizePager.java
@@ -22,6 +22,7 @@
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.PagingListView.Pager;
/**
@@ -55,15 +56,17 @@
// Show more button.
showMoreButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
- int pageSize = Math.min(listView.getPageSize() + increment,
+ Range range = listView.getRange();
+ int pageSize = Math.min(range.getLength() + increment,
listView.getDataSize());
- listView.setPageSize(pageSize);
+ listView.setRange(range.getStart(), pageSize);
}
});
showLessButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
- int pageSize = Math.max(listView.getPageSize() - increment, increment);
- listView.setPageSize(pageSize);
+ Range range = listView.getRange();
+ int pageSize = Math.max(range.getLength() - increment, increment);
+ listView.setRange(range.getStart(), pageSize);
}
});
@@ -79,8 +82,9 @@
public void onRangeOrSizeChanged(PagingListView<T> listView) {
// Assumes a page start index of 0.
- boolean hasLess = listView.getPageSize() > increment;
- boolean hasMore = listView.getPageSize() < listView.getDataSize();
+ int pageSize = listView.getRange().getLength();
+ boolean hasLess = pageSize > increment;
+ boolean hasMore = pageSize < listView.getDataSize();
showLessButton.setVisible(hasLess);
showMoreButton.setVisible(hasMore);
layout.setText(0, 1, (hasLess && hasMore) ? " | " : "");
diff --git a/user/src/com/google/gwt/user/cellview/client/PagingListViewPresenter.java b/user/src/com/google/gwt/user/cellview/client/PagingListViewPresenter.java
new file mode 100644
index 0000000..59ddc35
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/PagingListViewPresenter.java
@@ -0,0 +1,680 @@
+/*
+ * 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.user.cellview.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeHandler;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * <p>
+ * Presenter implementation of {@link PagingListView} that presents data for
+ * various cell based widgets. This class contains most of the shared logic used
+ * by these widgets, making it easier to test the common code.
+ * <p>
+ * <p>
+ * In proper MVP design, user code would interact with the presenter. However,
+ * that would complicate the widget code. Instead, each widget owns its own
+ * presenter and contains its own View. The widget forwards commands through to
+ * the presenter, which then updates the widget via the view. This keeps the
+ * user facing API simpler.
+ * <p>
+ *
+ * @param <T> the data type of items in the list
+ */
+class PagingListViewPresenter<T> implements PagingListView<T> {
+
+ /**
+ * Default iterator over DOM elements.
+ */
+ static class DefaultElementIterator implements ElementIterator {
+ private Element current;
+ private Element next;
+ private final DefaultView<?> view;
+
+ public DefaultElementIterator(DefaultView<?> view, Element first) {
+ this.view = view;
+ next = first;
+ }
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ public Element next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ current = next;
+ next = next.getNextSiblingElement();
+ return current;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Set the selection state of the current element.
+ *
+ * @param selected the selection state
+ * @throws IllegalStateException if {@link #next()} has not been called
+ */
+ public void setSelected(boolean selected) throws IllegalStateException {
+ if (current == null) {
+ throw new IllegalStateException();
+ }
+ view.setSelected(current, selected);
+ }
+ }
+
+ /**
+ * The default implementation of View.
+ *
+ * @param <T> the data type
+ */
+ abstract static class DefaultView<T> implements View<T> {
+
+ /**
+ * The Element that holds the rendered child items.
+ */
+ private Element childContainer;
+
+ /**
+ * The temporary element use to convert HTML to DOM.
+ */
+ private final Element tmpElem;
+
+ /**
+ * Construct a new View.
+ *
+ * @param childContainer the element that contains the children
+ */
+ public DefaultView(Element childContainer) {
+ this.childContainer = childContainer;
+ tmpElem = Document.get().createDivElement();
+ }
+
+ public int getChildCount() {
+ return childContainer.getChildCount();
+ }
+
+ public ElementIterator getChildIterator() {
+ return new DefaultElementIterator(this,
+ childContainer.getFirstChildElement());
+ }
+
+ public void onUpdateSelection() {
+ }
+
+ public void replaceAllChildren(List<T> values, String html) {
+ childContainer.setInnerHTML(html);
+ }
+
+ public void replaceChildren(List<T> values, int start, String html) {
+ // Convert the html to DOM elements.
+ Element container = convertToElements(html);
+ int count = container.getChildCount();
+
+ // Get the first element to be replaced.
+ Element toReplace = null;
+ if (start < getChildCount()) {
+ toReplace = childContainer.getChild(start).cast();
+ }
+
+ // Replace the elements.
+ for (int i = 0; i < count; i++) {
+ if (toReplace == null) {
+ // The child will be removed from tmpElem, so always use index 0.
+ childContainer.appendChild(container.getChild(0));
+ } else {
+ Element nextSibling = toReplace.getNextSiblingElement();
+ childContainer.replaceChild(container.getChild(0), toReplace);
+ toReplace = nextSibling;
+ }
+ }
+ }
+
+ /**
+ * Convert the specified HTML into DOM elements and return the parent of the
+ * DOM elements.
+ *
+ * @param html the HTML to convert
+ * @return the parent element
+ */
+ protected Element convertToElements(String html) {
+ tmpElem.setInnerHTML(html);
+ return tmpElem;
+ }
+
+ /**
+ * Update an element to reflect its selected state.
+ *
+ * @param elem the element to update
+ * @param selected true if selected, false if not
+ */
+ protected abstract void setSelected(Element elem, boolean selected);
+
+ /**
+ * Replace the child container.
+ *
+ * @param childContainer the new container
+ */
+ void setChildContainer(Element childContainer) {
+ this.childContainer = childContainer;
+ }
+ }
+
+ /**
+ * An iterator over DOM elements.
+ */
+ static interface ElementIterator extends Iterator<Element> {
+ /**
+ * Set the selection state of the current element.
+ *
+ * @param selected the selection state
+ * @throws IllegalStateException if {@link #next()} has not been called
+ */
+ void setSelected(boolean selected) throws IllegalStateException;
+ }
+
+ /**
+ * The loading state of the data.
+ */
+ static enum LoadingState {
+ LOADING, // Waiting for data to load.
+ PARTIALLY_LOADED, // Partial page data loaded.
+ LOADED, // All page data loaded.
+ EMPTY; // The data size is 0.
+ }
+
+ /**
+ * The view that this presenter presents.
+ *
+ * @param <T> the data type
+ */
+ static interface View<T> {
+
+ /**
+ * Check whether or not the cells in the view depend on the selection state.
+ *
+ * @return true if cells depend on selection, false if not
+ */
+ boolean dependsOnSelection();
+
+ /**
+ * Get the physical child count.
+ *
+ * @return the child count
+ */
+ int getChildCount();
+
+ /**
+ * Get an iterator over the children of the view.
+ *
+ * @return the iterator
+ */
+ ElementIterator getChildIterator();
+
+ /**
+ * Called when selection changes.
+ */
+ void onUpdateSelection();
+
+ /**
+ * Construct the HTML that represents the list of values, taking the
+ * selection state into account.
+ *
+ * @param sb the {@link StringBuilder} to build into
+ * @param values the values to render
+ * @param start the start index that is being rendered
+ * @param selectionModel the {@link SelectionModel}
+ */
+ void render(StringBuilder sb, List<T> values, int start,
+ SelectionModel<? super T> selectionModel);
+
+ /**
+ * Replace all children with the specified html.
+ *
+ * @param values the values of the new children
+ * @param html the html to render in the child
+ */
+ void replaceAllChildren(List<T> values, String html);
+
+ /**
+ * Convert the specified HTML into DOM elements and replace the existing
+ * elements starting at the specified index. If the number of children
+ * specified exceeds the existing number of children, the remaining children
+ * should be appended.
+ *
+ * @param values the values of the new children
+ * @param start the start index to be replaced
+ * @param html the HTML to convert
+ */
+ void replaceChildren(List<T> values, int start, String html);
+
+ /**
+ * Set the current loading state of the data.
+ *
+ * @param state the loading state
+ */
+ void setLoadingState(LoadingState state);
+ }
+
+ /**
+ * The local cache of data in the view. The 0th index in the list corresponds
+ * to the value at pageStart.
+ */
+ private final List<T> data = new ArrayList<T>();
+
+ private int dataSize = Integer.MIN_VALUE;
+ private boolean dataSizeIsExact;
+ private Delegate<T> delegate;
+
+ /**
+ * As an optimization, keep track of the last HTML string that we rendered. If
+ * the contents do not change the next time we render, then we don't have to
+ * set inner html.
+ */
+ private String lastContents = null;
+
+ private final PagingListView<T> listView;
+ private Pager<T> pager;
+ private int pageSize;
+ private int pageStart = 0;
+
+ /**
+ * Set to true when the page start changes, and we need to do a full refresh.
+ */
+ private boolean pageStartChangedSinceRender;
+
+ /**
+ * A local cache of the currently selected rows. We cannot track selected keys
+ * instead because we might end up in an inconsistent state where we render a
+ * subset of a list with duplicate values, styling a value in the subset but
+ * not styling the duplicate value outside of the subset.
+ */
+ private final Set<Integer> selectedRows = new HashSet<Integer>();
+
+ private HandlerRegistration selectionHandler;
+ private SelectionModel<? super T> selectionModel;
+ private final View<T> view;
+
+ /**
+ * Construct a new {@link PagingListViewPresenter}.
+ *
+ * @param listView the listView that is being presented
+ * @param view the view implementation
+ * @param pageSize the default page size
+ */
+ public PagingListViewPresenter(PagingListView<T> listView, View<T> view,
+ int pageSize) {
+ this.listView = listView;
+ this.view = view;
+ this.pageSize = pageSize;
+ updateLoadingState();
+ }
+
+ /**
+ * Clear the {@link SelectionModel} without updating the view.
+ */
+ public void clearSelectionModel() {
+ if (selectionHandler != null) {
+ selectionHandler.removeHandler();
+ selectionHandler = null;
+ }
+ selectionModel = null;
+ }
+
+ /**
+ * Get the current page size. This is usually the page size, but can be less
+ * if the data size cannot fill the current page.
+ *
+ * @return the size of the current page
+ */
+ public int getCurrentPageSize() {
+ return Math.min(pageSize, dataSize - pageStart);
+ }
+
+ /**
+ * Get the list of data within the current range. The 0th index corresponds to
+ * the first value on the page. The data may not be complete or may contain
+ * null values.
+ *
+ * @return the list of data for the current page
+ */
+ public List<T> getData() {
+ return data;
+ }
+
+ /**
+ * Get the overall data size.
+ *
+ * @return the data size
+ */
+ public int getDataSize() {
+ return dataSize;
+ }
+
+ /**
+ * @return the range of data being displayed
+ */
+ public Range getRange() {
+ return new Range(pageStart, pageSize);
+ }
+
+ public SelectionModel<? super T> getSelectionModel() {
+ return selectionModel;
+ }
+
+ public boolean isDataSizeExact() {
+ return dataSizeIsExact;
+ }
+
+ /**
+ * Redraw the list with the current data.
+ */
+ public void redraw() {
+ lastContents = null;
+ setData(pageStart, data.size(), data);
+ }
+
+ public void setData(int start, int length, List<T> values) {
+ int valuesLength = values.size();
+ int valuesEnd = start + valuesLength;
+
+ // Calculate the bounded start (inclusive) and end index (exclusive).
+ int pageEnd = pageStart + pageSize;
+ int boundedStart = Math.max(start, pageStart);
+ int boundedEnd = Math.min(valuesEnd, pageEnd);
+ if (boundedStart >= boundedEnd) {
+ // The data is out of range for the current page.
+ return;
+ }
+
+ // The data size must be at least as large as the data.
+ if (valuesEnd > dataSize) {
+ dataSize = valuesEnd;
+ onSizeChanged();
+ }
+
+ // Create placeholders up to the specified index.
+ int cacheOffset = Math.max(0, boundedStart - pageStart - data.size());
+ for (int i = 0; i < cacheOffset; i++) {
+ data.add(null);
+ }
+
+ // Insert the new values into the data array.
+ for (int i = boundedStart; i < boundedEnd; i++) {
+ T value = values.get(i - start);
+ int dataIndex = i - pageStart;
+ if (dataIndex < data.size()) {
+ data.set(dataIndex, value);
+ } else {
+ data.add(value);
+ }
+
+ // Update our local cache of selected rows.
+ if (selectionModel != null) {
+ if (value != null && selectionModel.isSelected(value)) {
+ selectedRows.add(i);
+ } else {
+ selectedRows.remove(i);
+ }
+ }
+ }
+
+ // Construct a run of elements within the range of the data and the page.
+ boundedStart = pageStartChangedSinceRender ? pageStart : boundedStart;
+ boundedStart -= cacheOffset;
+ List<T> boundedValues = data.subList(boundedStart - pageStart, boundedEnd
+ - pageStart);
+ int boundedSize = boundedValues.size();
+ StringBuilder sb = new StringBuilder();
+ view.render(sb, boundedValues, boundedStart, selectionModel);
+
+ // Update the loading state.
+ updateLoadingState();
+
+ // Replace the DOM elements with the new rendered cells.
+ int childCount = view.getChildCount();
+ if (boundedStart == pageStart
+ && (boundedSize >= childCount || boundedSize >= getCurrentPageSize())) {
+ // If the contents have not changed, we're done.
+ String newContents = sb.toString();
+ if (!newContents.equals(lastContents)) {
+ lastContents = newContents;
+ view.replaceAllChildren(boundedValues, newContents);
+ }
+ } else {
+ lastContents = null;
+ view.replaceChildren(boundedValues, boundedStart - pageStart,
+ sb.toString());
+ }
+
+ // Reset the pageStartChanged boolean.
+ pageStartChangedSinceRender = false;
+ }
+
+ /**
+ * Set the overall size of the list.
+ *
+ * @param size the overall size
+ */
+ public void setDataSize(int size, boolean isExact) {
+ if (size == this.dataSize && isExact == this.dataSizeIsExact) {
+ return;
+ }
+ this.dataSize = size;
+ this.dataSizeIsExact = isExact;
+ updateLoadingState();
+
+ // Redraw the current page if it is affected by the new data size.
+ if (updateCachedData()) {
+ redraw();
+ }
+
+ // Update the pager.
+ onSizeChanged();
+ }
+
+ public void setDelegate(Delegate<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ public void setPager(PagingListView.Pager<T> pager) {
+ this.pager = pager;
+ }
+
+ public void setRange(int start, int length) {
+ // Update the page start.
+ boolean pageStartChanged = false;
+ if (pageStart != start) {
+ if (start > pageStart) {
+ int increase = start - pageStart;
+ if (data.size() > increase) {
+ // Remove the data we no longer need.
+ for (int i = 0; i < increase; i++) {
+ data.remove(0);
+ }
+ } else {
+ // We have no overlapping data, so just clear it.
+ data.clear();
+ }
+ } else {
+ int decrease = pageStart - start;
+ if ((data.size() > 0) && (decrease < pageSize)) {
+ // Insert null data at the beginning.
+ for (int i = 0; i < decrease; i++) {
+ data.add(0, null);
+ }
+ } else {
+ // We have no overlapping data, so just clear it.
+ data.clear();
+ }
+ }
+ pageStart = start;
+ pageStartChanged = true;
+ pageStartChangedSinceRender = true;
+ }
+
+ // Update the page size.
+ boolean pageSizeChanged = false;
+ if (pageSize != length) {
+ pageSize = length;
+ pageSizeChanged = true;
+ }
+
+ // Early exit if the range hasn't changed.
+ if (!pageStartChanged && !pageSizeChanged) {
+ return;
+ }
+
+ // Update the loading state.
+ updateLoadingState();
+
+ // Redraw with the existing data.
+ boolean dataStale = updateCachedData();
+ if (pageStartChanged || dataStale) {
+ redraw();
+ }
+
+ // Update the pager.
+ onSizeChanged();
+
+ // Update the delegate with the new range.
+ if (delegate != null) {
+ delegate.onRangeChanged(listView);
+ }
+ }
+
+ public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
+ clearSelectionModel();
+
+ // Set the new selection model.
+ this.selectionModel = selectionModel;
+ if (selectionModel != null) {
+ selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
+ public void onSelectionChange(SelectionChangeEvent event) {
+ updateSelection();
+ }
+ });
+ }
+
+ // Update the current selection state based on the new model.
+ updateSelection();
+ }
+
+ /**
+ * Called when pageStart, pageSize, or data size changes.
+ */
+ private void onSizeChanged() {
+ if (pager != null) {
+ pager.onRangeOrSizeChanged(listView);
+ }
+ }
+
+ /**
+ * Ensure that the cached data is consistent with the data size.
+ *
+ * @return true if the data was updated, false if not
+ */
+ private boolean updateCachedData() {
+ boolean updated = false;
+ int expectedLastIndex = Math.max(0,
+ Math.min(pageSize, dataSize - pageStart));
+ int lastIndex = data.size() - 1;
+ while (lastIndex >= expectedLastIndex) {
+ data.remove(lastIndex);
+ selectedRows.remove(lastIndex + pageStart);
+ lastIndex--;
+ updated = true;
+ }
+ return updated;
+ }
+
+ /**
+ * Update the loading state of the view based on the data size and page size.
+ */
+ private void updateLoadingState() {
+ int cacheSize = data.size();
+ int curPageSize = isDataSizeExact() ? getCurrentPageSize() : pageSize;
+ if (dataSize == 0) {
+ view.setLoadingState(LoadingState.EMPTY);
+ } else if (cacheSize >= curPageSize) {
+ view.setLoadingState(LoadingState.LOADED);
+ } else if (cacheSize == 0) {
+ view.setLoadingState(LoadingState.LOADING);
+ } else {
+ view.setLoadingState(LoadingState.PARTIALLY_LOADED);
+ }
+ }
+
+ /**
+ * Update the table based on the current selection.
+ */
+ private void updateSelection() {
+ view.onUpdateSelection();
+
+ // Determine if our selection states are stale.
+ boolean dependsOnSelection = view.dependsOnSelection();
+ boolean refreshRequired = false;
+ ElementIterator children = view.getChildIterator();
+ int row = pageStart;
+ for (T value : data) {
+ // Increment the child.
+ if (!children.hasNext()) {
+ break;
+ }
+ children.next();
+
+ // Update the selection state.
+ boolean selected = selectionModel == null ? false
+ : selectionModel.isSelected(value);
+ if (selected != selectedRows.contains(row)) {
+ refreshRequired = true;
+ if (selected) {
+ selectedRows.add(row);
+ } else {
+ selectedRows.remove(row);
+ }
+ if (!dependsOnSelection) {
+ // The cell doesn't depend on selection, so we only need to update
+ // the style.
+ children.setSelected(selected);
+ }
+ }
+ row++;
+ }
+
+ // Redraw the entire list if needed.
+ if (refreshRequired && dependsOnSelection) {
+ redraw();
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/SimplePager.java b/user/src/com/google/gwt/user/cellview/client/SimplePager.java
index 1e00ea2..e4d1faa 100644
--- a/user/src/com/google/gwt/user/cellview/client/SimplePager.java
+++ b/user/src/com/google/gwt/user/cellview/client/SimplePager.java
@@ -28,6 +28,7 @@
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
/**
* A pager for controlling a {@link PagingListView} that only supports simple
@@ -51,7 +52,7 @@
* The disabled "fast forward" image.
*/
ImageResource simplePagerFastForwardDisabled();
-
+
/**
* The image used to go to the first page.
*/
@@ -135,7 +136,7 @@
}
return DEFAULT_RESOURCES;
}
-
+
private final Image fastForward;
private final int fastForwardPages;
@@ -196,7 +197,7 @@
// Hack for Google I/O demo
public SimplePager(PagingListView<T> view, TextLocation location) {
this(view, location, getDefaultResources(), true,
- 1000 / view.getPageSize(), false);
+ 1000 / view.getRange().getLength(), false);
}
/**
@@ -212,8 +213,7 @@
*/
public SimplePager(final PagingListView<T> view, TextLocation location,
Resources resources, boolean showFastForwardButton,
- final int fastForwardPages,
- boolean showLastPageButton) {
+ final int fastForwardPages, boolean showLastPageButton) {
super(view);
this.resources = resources;
this.showFastForwardButton = showFastForwardButton;
@@ -232,7 +232,8 @@
// Add handlers.
fastForward.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
- setPageStart(view.getPageStart() + view.getPageSize() * fastForwardPages);
+ Range range = view.getRange();
+ setPageStart(range.getStart() + range.getLength() * fastForwardPages);
}
});
firstPage.addClickHandler(new ClickHandler() {
@@ -337,14 +338,14 @@
style.disabledButton());
}
}
-
+
if (showFastForwardButton) {
if (hasNextPages(fastForwardPages)) {
- fastForward.setResource(resources.simplePagerFastForward());
+ fastForward.setResource(resources.simplePagerFastForward());
fastForward.getElement().getParentElement().removeClassName(
- style.disabledButton());
+ style.disabledButton());
} else {
- fastForward.setResource(resources.simplePagerFastForwardDisabled());
+ fastForward.setResource(resources.simplePagerFastForwardDisabled());
fastForward.getElement().getParentElement().addClassName(
style.disabledButton());
}
@@ -371,8 +372,9 @@
// Default text is 1 based.
NumberFormat formatter = NumberFormat.getFormat("#,###");
PagingListView<T> view = getPagingListView();
- int pageStart = view.getPageStart() + 1;
- int pageSize = view.getPageSize();
+ Range range = view.getRange();
+ int pageStart = range.getStart() + 1;
+ int pageSize = range.getLength();
int dataSize = view.getDataSize();
int endIndex = Math.min(dataSize, pageStart + pageSize - 1);
endIndex = Math.max(pageStart, endIndex);
diff --git a/user/src/com/google/gwt/view/client/ListView.java b/user/src/com/google/gwt/view/client/ListView.java
index ef7f16e..56b2cc1 100644
--- a/user/src/com/google/gwt/view/client/ListView.java
+++ b/user/src/com/google/gwt/view/client/ListView.java
@@ -36,15 +36,17 @@
public interface Delegate<T> {
void onRangeChanged(ListView<T> listView);
}
-
+
/**
- * Returns the value of the 'isExact' parameter of the most recent call
- * to {@link #setDataSize(int, boolean)}.
+ * Returns the value of the 'isExact' parameter of the most recent call to
+ * {@link #setDataSize(int, boolean)}.
*/
boolean isDataSizeExact();
/**
- * TODO: doc.
+ * Get the range that this view is displaying.
+ *
+ * @return the range
*/
Range getRange();
@@ -58,17 +60,17 @@
void setData(int start, int length, List<T> values);
/**
- * TODO: doc.
+ * Set the total data size of the underlying data.
*
- * @param size
- * @param isExact
+ * @param size the total data size
+ * @param isExact true if the size is exact, false if it is an estimate
*/
void setDataSize(int size, boolean isExact);
/**
- * TODO: doc.
+ * Set the {@link Delegate} that responds to changes in the range.
*
- * @param delegate
+ * @param delegate the {@link Delegate}
*/
void setDelegate(Delegate<T> delegate);
diff --git a/user/src/com/google/gwt/view/client/PagingListView.java b/user/src/com/google/gwt/view/client/PagingListView.java
index 0d3e646..9392112 100644
--- a/user/src/com/google/gwt/view/client/PagingListView.java
+++ b/user/src/com/google/gwt/view/client/PagingListView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -17,11 +17,11 @@
/**
* A list view that displays data in 'pages'.
- *
+ *
* <p>
* Note: This class is new and its interface subject to change.
* </p>
- *
+ *
* @param <T> the data type of each row
*/
public interface PagingListView<T> extends ListView<T> {
@@ -29,7 +29,7 @@
/**
* A pager delegate, implemented by classes that depend on the start index,
* number of visible rows, or data size of a view.
- *
+ *
* @param <T> the data type of each row
*/
public interface Pager<T> {
@@ -37,38 +37,22 @@
}
/**
- * TODO: doc.
+ * Get the total data size.
*/
int getDataSize();
/**
- * TODO: doc.
- */
- int getPageSize();
-
- /**
- * TODO: doc.
- */
- int getPageStart();
-
- /**
- * TODO: doc.
+ * Set the {@link Pager} that allows the user to change the range.
*
- * @param pager
+ * @param pager the {@link Pager}
*/
void setPager(Pager<T> pager);
/**
- * TODO: doc.
+ * Set a new range.
*
- * @param pageSize
+ * @param start the new start index
+ * @param length the new page size
*/
- void setPageSize(int pageSize);
-
- /**
- * TODO: doc.
- *
- * @param pageStart
- */
- void setPageStart(int pageStart);
+ void setRange(int start, int length);
}
diff --git a/user/test/com/google/gwt/user/cellview/CellViewSuite.java b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
new file mode 100644
index 0000000..ec2a040
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
@@ -0,0 +1,37 @@
+/*
+ * 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.user.cellview;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.user.cellview.client.AbstractPagerTest;
+import com.google.gwt.user.cellview.client.PagingListViewPresenterTest;
+import com.google.gwt.user.cellview.client.SimplePagerTest;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the cellview package.
+ */
+public class CellViewSuite {
+ public static Test suite() {
+ GWTTestSuite suite = new GWTTestSuite("Test suite for all cellview classes");
+
+ suite.addTestSuite(AbstractPagerTest.class);
+ suite.addTestSuite(PagingListViewPresenterTest.class);
+ suite.addTestSuite(SimplePagerTest.class);
+ return suite;
+ }
+}
diff --git a/user/test/com/google/gwt/user/cellview/client/AbstractPagerTest.java b/user/test/com/google/gwt/user/cellview/client/AbstractPagerTest.java
new file mode 100644
index 0000000..a59fdef
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/AbstractPagerTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.user.cellview.client;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.view.client.MockPagingListView;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
+
+/**
+ * Tests for {@link AbstractPager}.
+ */
+public class AbstractPagerTest extends GWTTestCase {
+
+ /**
+ * Mock {@link PagingListView.Pager} used for testing.
+ */
+ private class MockPager<T> extends AbstractPager<T> {
+ public MockPager(PagingListView<T> view) {
+ super(view);
+ }
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.cellview.CellView";
+ }
+
+ public void testFirstPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(14, 20);
+
+ pager.firstPage();
+ assertEquals(new Range(0, 20), view.getRange());
+ }
+
+ public void testGetPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+
+ // Exact page.
+ view.setRange(0, 20);
+ assertEquals(0, pager.getPage());
+ view.setRange(200, 20);
+ assertEquals(10, pager.getPage());
+
+ // Inexact page.
+ view.setRange(1, 20);
+ assertEquals(1, pager.getPage());
+ view.setRange(205, 20);
+ assertEquals(11, pager.getPage());
+ }
+
+ public void testGetPageCount() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(0, 20);
+
+ // Perfect count.
+ view.setDataSize(100, true);
+ assertEquals(5, pager.getPageCount());
+
+ // Imperfect page.
+ view.setDataSize(105, true);
+ assertEquals(6, pager.getPageCount());
+ }
+
+ public void testHasNextPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(0, 20);
+
+ view.setDataSize(20, true);
+ assertFalse(pager.hasNextPage());
+ assertFalse(pager.hasNextPages(1));
+
+ view.setDataSize(105, true);
+ assertTrue(pager.hasNextPage());
+ assertTrue(pager.hasNextPages(5));
+ assertFalse(pager.hasNextPages(6));
+ }
+
+ public void testHasPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(0, 20);
+ view.setDataSize(105, true);
+
+ assertTrue(pager.hasPage(0));
+ assertTrue(pager.hasPage(5));
+ assertFalse(pager.hasPage(6));
+ }
+
+ public void testHasPreviousPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setDataSize(105, true);
+
+ view.setRange(0, 20);
+ assertFalse(pager.hasPreviousPage());
+ assertFalse(pager.hasPreviousPages(1));
+
+ view.setRange(40, 20);
+ assertTrue(pager.hasPreviousPage());
+ assertTrue(pager.hasPreviousPages(2));
+ assertFalse(pager.hasPreviousPages(3));
+
+ view.setRange(41, 20);
+ assertTrue(pager.hasPreviousPage());
+ assertTrue(pager.hasPreviousPages(3));
+ assertFalse(pager.hasPreviousPages(4));
+ }
+
+ public void testLastPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(14, 20);
+ view.setDataSize(105, true);
+
+ pager.lastPage();
+ assertEquals(new Range(100, 20), view.getRange());
+ }
+
+ public void testLastPageStart() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ pager.setRangeLimited(false);
+ view.setRange(14, 20);
+ view.setDataSize(105, true);
+
+ pager.lastPageStart();
+ assertEquals(new Range(85, 20), view.getRange());
+ }
+
+ public void testNextPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(10, 20);
+ view.setDataSize(105, true);
+
+ pager.nextPage();
+ assertEquals(new Range(30, 20), view.getRange());
+ }
+
+ public void testPreviousPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(45, 20);
+ view.setDataSize(105, true);
+
+ pager.previousPage();
+ assertEquals(new Range(25, 20), view.getRange());
+ }
+
+ public void testSetPage() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(10, 20);
+ view.setDataSize(105, true);
+
+ pager.setPage(0);
+ assertEquals(new Range(0, 20), view.getRange());
+
+ pager.setPage(3);
+ assertEquals(new Range(60, 20), view.getRange());
+
+ pager.setPage(5);
+ assertEquals(new Range(100, 20), view.getRange());
+ }
+
+ public void testSetPageStart() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setRange(10, 20);
+ view.setDataSize(105, true);
+
+ pager.setPageStart(0);
+ assertEquals(new Range(0, 20), view.getRange());
+
+ pager.setPageStart(45);
+ assertEquals(new Range(45, 20), view.getRange());
+
+ pager.setPageStart(100);
+ assertEquals(new Range(85, 20), view.getRange());
+ }
+
+ public void testSetRangeLimited() {
+ AbstractPager<Void> pager = createPager();
+ PagingListView<Void> view = pager.getPagingListView();
+ view.setDataSize(110, true);
+ view.setRange(70, 20);
+
+ // Invalid ranges should be constrained by default.
+ assertTrue(pager.isRangeLimited());
+ view.setDataSize(84, true);
+ assertEquals(new Range(64, 20), view.getRange());
+
+ // Allow invalid ranges.
+ pager.setRangeLimited(false);
+ assertFalse(pager.isRangeLimited());
+ view.setRange(50, 20);
+ view.setDataSize(10, true);
+ assertEquals(new Range(50, 20), view.getRange());
+ }
+
+ protected <R> AbstractPager<R> createPager() {
+ return new MockPager<R>(new MockPagingListView<R>());
+ }
+}
diff --git a/user/test/com/google/gwt/user/cellview/client/PagingListViewPresenterTest.java b/user/test/com/google/gwt/user/cellview/client/PagingListViewPresenterTest.java
new file mode 100644
index 0000000..7d4d182
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/PagingListViewPresenterTest.java
@@ -0,0 +1,803 @@
+/*
+ * 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.user.cellview.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.ElementIterator;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.LoadingState;
+import com.google.gwt.user.cellview.client.PagingListViewPresenter.View;
+import com.google.gwt.view.client.MockPagingListView;
+import com.google.gwt.view.client.MockSelectionModel;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.MockPagingListView.MockDelegate;
+import com.google.gwt.view.client.MockPagingListView.MockPager;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * Tests for {@link PagingListViewPresenter}.
+ */
+public class PagingListViewPresenterTest extends TestCase {
+
+ /**
+ * Mock iterator over DOM elements.
+ */
+ private static class MockElementIterator implements ElementIterator {
+
+ private final int count;
+ private int next = 0;
+ private final MockView<?> view;
+
+ public MockElementIterator(MockView<?> view, int count) {
+ this.view = view;
+ this.count = count;
+ }
+
+ public boolean hasNext() {
+ return next < count;
+ }
+
+ public Element next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ next++;
+ return null;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Set the selection state of the current element.
+ *
+ * @param selected the selection state
+ * @throws IllegalStateException if {@link #next()} has not been called
+ */
+ public void setSelected(boolean selected) throws IllegalStateException {
+ if (next == 0) {
+ throw new IllegalStateException();
+ }
+ view.setSelected(next - 1, selected);
+ }
+ }
+
+ /**
+ * A mock view used for testing.
+ *
+ * @param <T> the data type
+ */
+ private static class MockView<T> implements View<T> {
+
+ private int childCount;
+ private boolean dependsOnSelection;
+ private String lastHtml;
+ private LoadingState loadingState;
+ private boolean onUpdateSelectionFired;
+ private boolean replaceAllChildrenCalled;
+ private boolean replaceChildrenCalled;
+ private Set<Integer> selectedRows = new HashSet<Integer>();
+
+ public void assertLastHtml(String html) {
+ assertEquals(html, lastHtml);
+ lastHtml = null;
+ }
+
+ public void assertLoadingState(LoadingState expected) {
+ assertEquals(expected, loadingState);
+ }
+
+ public void assertOnUpdateSelectionFired(boolean expected) {
+ assertEquals(expected, onUpdateSelectionFired);
+ onUpdateSelectionFired = false;
+ }
+
+ public void assertReplaceAllChildrenCalled(boolean expected) {
+ assertEquals(expected, replaceAllChildrenCalled);
+ replaceAllChildrenCalled = false;
+ }
+
+ public void assertReplaceChildrenCalled(boolean expected) {
+ assertEquals(expected, replaceChildrenCalled);
+ replaceChildrenCalled = false;
+ }
+
+ /**
+ * Assert that {@link #setSelected(int, boolean)} was called for the
+ * specified rows.
+ *
+ * @param rows the rows
+ */
+ public void assertSelectedRows(Integer... rows) {
+ assertEquals(rows.length, selectedRows.size());
+ for (Integer row : rows) {
+ assertTrue("Row " + row + "is not selected", selectedRows.contains(row));
+ }
+ }
+
+ public boolean dependsOnSelection() {
+ return dependsOnSelection;
+ }
+
+ public int getChildCount() {
+ return childCount;
+ }
+
+ public MockElementIterator getChildIterator() {
+ return new MockElementIterator(this, 10);
+ }
+
+ public void onUpdateSelection() {
+ onUpdateSelectionFired = true;
+ }
+
+ public void render(StringBuilder sb, List<T> values, int start,
+ SelectionModel<? super T> selectionModel) {
+ sb.append("start=").append(start);
+ sb.append(",size=").append(values.size());
+ }
+
+ public void replaceAllChildren(List<T> values, String html) {
+ childCount = values.size();
+ replaceAllChildrenCalled = true;
+ lastHtml = html;
+ }
+
+ public void replaceChildren(List<T> values, int start, String html) {
+ childCount = Math.max(childCount, start + values.size());
+ replaceChildrenCalled = true;
+ lastHtml = html;
+ }
+
+ public void setDependsOnSelection(boolean dependsOnSelection) {
+ this.dependsOnSelection = dependsOnSelection;
+ }
+
+ public void setLoadingState(LoadingState state) {
+ this.loadingState = state;
+ }
+
+ protected void setSelected(int index, boolean selected) {
+ if (selected) {
+ selectedRows.add(index);
+ } else {
+ selectedRows.remove(index);
+ }
+ }
+ }
+
+ public void testClearSelectionModel() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ view.setDependsOnSelection(true);
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ assertNull(presenter.getSelectionModel());
+
+ // Initialize some data.
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+
+ // Set the selection model.
+ SelectionModel<String> model = new MockSelectionModel<String>();
+ model.setSelected("test 0", true);
+ presenter.setSelectionModel(model);
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows();
+
+ // Clear the selection model without updating the view.
+ presenter.clearSelectionModel();
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertOnUpdateSelectionFired(false);
+ view.assertSelectedRows();
+ }
+
+ public void testGetCurrentPageSize() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ presenter.setDataSize(35, true);
+
+ // First page.
+ assertEquals(10, presenter.getCurrentPageSize());
+
+ // Last page.
+ presenter.setRange(30, 10);
+ assertEquals(5, presenter.getCurrentPageSize());
+ }
+
+ public void testRedraw() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Initialize some data.
+ presenter.setDataSize(10, true);
+ presenter.setData(0, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Redraw.
+ presenter.redraw();
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ public void testSetData() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ presenter.setRange(5, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Page range same as data range.
+ List<String> expectedData = createData(5, 10);
+ presenter.setData(5, 10, createData(5, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=5,size=10");
+ assertEquals(10, view.getChildCount());
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Page range contains data range.
+ expectedData.set(2, "test 100");
+ expectedData.set(3, "test 101");
+ presenter.setData(7, 2, createData(100, 2));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(true);
+ view.assertLastHtml("start=7,size=2");
+ assertEquals(10, view.getChildCount());
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Data range overlaps page start.
+ expectedData.set(0, "test 202");
+ expectedData.set(1, "test 203");
+ presenter.setData(3, 4, createData(200, 4));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(true);
+ view.assertLastHtml("start=5,size=2");
+ assertEquals(10, view.getChildCount());
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Data range overlaps page end.
+ expectedData.set(8, "test 300");
+ expectedData.set(9, "test 301");
+ presenter.setData(13, 4, createData(300, 4));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(true);
+ view.assertLastHtml("start=13,size=2");
+ assertEquals(10, view.getChildCount());
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Data range contains page range.
+ expectedData = createData(400, 20).subList(2, 12);
+ presenter.setData(3, 20, createData(400, 20));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=5,size=10");
+ assertEquals(10, view.getChildCount());
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ /**
+ * Setting data outside of the data size should update the data size.
+ */
+ public void testSetDataChangesDataSize() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Set the initial data size.
+ presenter.setDataSize(10, true);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Set the data within the range.
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Set the data past the range.
+ presenter.setData(5, 10, createData(5, 10));
+ assertEquals(15, presenter.getDataSize());
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ public void testSetDataOutsideRange() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ presenter.setRange(5, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Page range same as data range.
+ List<String> expectedData = createData(5, 10);
+ presenter.setData(5, 10, createData(5, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=5,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Data range past page end.
+ presenter.setData(15, 5, createData(15, 5));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Data range before page start.
+ presenter.setData(0, 5, createData(0, 5));
+ assertEquals(10, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ /**
+ * As an optimization, the presenter does not replace the rendered string if
+ * the rendered string is identical to the previously rendered string. This is
+ * useful for tables that refresh on an interval.
+ */
+ public void testSetDataSameContents() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Initialize some data.
+ presenter.setRange(0, 10);
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Set the same data over the entire range.
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ /**
+ * Set data at the end of the page only.
+ */
+ public void testSetDataSparse() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ List<String> expectedData = createData(5, 3);
+ expectedData.add(0, null);
+ expectedData.add(0, null);
+ expectedData.add(0, null);
+ expectedData.add(0, null);
+ expectedData.add(0, null);
+ presenter.setRange(0, 10);
+ presenter.setData(5, 3, createData(5, 3));
+ assertEquals(8, presenter.getData().size());
+ assertEquals(expectedData, presenter.getData());
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=8");
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+ }
+
+ public void testSetDataSize() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Set size to 100.
+ presenter.setDataSize(100, true);
+ assertEquals(100, presenter.getDataSize());
+ assertTrue(presenter.isDataSizeExact());
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Set size to 0.
+ presenter.setDataSize(0, false);
+ assertEquals(0, presenter.getDataSize());
+ assertFalse(presenter.isDataSizeExact());
+ view.assertLoadingState(LoadingState.EMPTY);
+ }
+
+ public void testSetDataSizeTrimsCurrentPage() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Initialize some data.
+ presenter.setDataSize(10, true);
+ presenter.setRange(0, 10);
+ assertEquals(new Range(0, 10), presenter.getRange());
+ presenter.setData(0, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Trim the size.
+ presenter.setDataSize(8, true);
+ assertEquals(8, presenter.getDataSize());
+ assertTrue(presenter.isDataSizeExact());
+ assertEquals(new Range(0, 10), presenter.getRange());
+ assertEquals(8, presenter.getData().size());
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=8");
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ public void testSetDelegate() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ MockDelegate<String> delegate = new MockDelegate<String>();
+ presenter.setDelegate(delegate);
+
+ // Change the pageStart.
+ presenter.setRange(10, 10);
+ assertEquals(listView, delegate.getLastListView());
+ delegate.clearListView();
+
+ // Change the pageSize.
+ presenter.setRange(10, 20);
+ assertEquals(listView, delegate.getLastListView());
+ delegate.clearListView();
+
+ // Reuse the same range.
+ presenter.setRange(10, 20);
+ assertNull(delegate.getLastListView());
+
+ // Change the data size, which does not affect the delegate.
+ presenter.setDataSize(100, true);
+ assertNull(delegate.getLastListView());
+
+ // Unset the delegate.
+ presenter.setDelegate(null);
+ presenter.setRange(20, 100);
+ assertNull(delegate.getLastListView());
+ }
+
+ public void testSetPager() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ MockPager<String> pager = new MockPager<String>();
+ presenter.setPager(pager);
+
+ // Change the pageStart.
+ presenter.setRange(10, 10);
+ assertEquals(listView, pager.getLastListView());
+ pager.clearListView();
+
+ // Change the pageSize.
+ presenter.setRange(10, 20);
+ assertEquals(listView, pager.getLastListView());
+ pager.clearListView();
+
+ // Reuse the same range.
+ presenter.setRange(10, 20);
+ assertNull(pager.getLastListView());
+
+ // Change the data size.
+ presenter.setDataSize(100, true);
+ assertEquals(listView, pager.getLastListView());
+ pager.clearListView();
+
+ // Unset the delegate.
+ presenter.setPager(null);
+ presenter.setRange(20, 100);
+ assertNull(pager.getLastListView());
+ }
+
+ public void testSetRange() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Set the range the first time.
+ presenter.setRange(0, 100);
+ assertEquals(new Range(0, 100), presenter.getRange());
+ assertEquals(0, presenter.getData().size());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.LOADING);
+
+ // Set the range to the same value.
+ presenter.setRange(0, 100);
+ assertEquals(new Range(0, 100), presenter.getRange());
+ assertEquals(0, presenter.getData().size());
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.LOADING);
+ }
+
+ public void testSetRangeDecreasePageSize() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Initialize some data.
+ presenter.setRange(0, 10);
+ assertEquals(new Range(0, 10), presenter.getRange());
+ presenter.setData(0, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Decrease the page size.
+ presenter.setRange(0, 8);
+ assertEquals(new Range(0, 8), presenter.getRange());
+ assertEquals(8, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=8");
+ view.assertLoadingState(LoadingState.LOADED);
+ }
+
+ public void testSetRangeDecreasePageStart() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Initialize some data.
+ presenter.setRange(10, 30);
+ assertEquals(new Range(10, 30), presenter.getRange());
+ presenter.setData(10, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=10,size=10");
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+
+ // Decrease the start index.
+ presenter.setRange(8, 30);
+ assertEquals(new Range(8, 30), presenter.getRange());
+ assertEquals(12, presenter.getData().size());
+ assertEquals(null, presenter.getData().get(0));
+ assertEquals(null, presenter.getData().get(1));
+ assertEquals("test 0", presenter.getData().get(2));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=8,size=12");
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+ }
+
+ public void testSetRangeIncreasePageSize() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Initialize some data.
+ presenter.setRange(0, 10);
+ assertEquals(new Range(0, 10), presenter.getRange());
+ presenter.setData(0, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.LOADED);
+
+ // Increase the page size.
+ presenter.setRange(0, 20);
+ assertEquals(new Range(0, 20), presenter.getRange());
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+ }
+
+ public void testSetRangeIncreasePageStart() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+
+ // Initialize some data.
+ presenter.setRange(0, 20);
+ assertEquals(new Range(0, 20), presenter.getRange());
+ presenter.setData(0, 10, createData(0, 10));
+ assertEquals(10, presenter.getData().size());
+ assertEquals("test 0", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+
+ // Increase the start index.
+ presenter.setRange(2, 20);
+ assertEquals(new Range(2, 20), presenter.getRange());
+ assertEquals(8, presenter.getData().size());
+ assertEquals("test 2", presenter.getData().get(0));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=2,size=8");
+ view.assertLoadingState(LoadingState.PARTIALLY_LOADED);
+ }
+
+ /**
+ * If the cells depend on selection, the cells should be replaced.
+ */
+ public void testSetSelectionModelDependOnSelection() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ view.setDependsOnSelection(true);
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ assertNull(presenter.getSelectionModel());
+
+ // Initialize some data.
+ presenter.setRange(0, 10);
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+
+ // Set the selection model.
+ SelectionModel<String> model = new MockSelectionModel<String>();
+ model.setSelected("test 0", true);
+ presenter.setSelectionModel(model);
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows();
+
+ // Select something.
+ model.setSelected("test 2", true);
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows();
+
+ // Set selection model to null.
+ presenter.setSelectionModel(null);
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows();
+ }
+
+ /**
+ * If the cells do not depend on selection, the view should be told to update
+ * the cell container element.
+ */
+ public void testSetSelectionModelDoesNotDependOnSelection() {
+ PagingListView<String> listView = new MockPagingListView<String>();
+ MockView<String> view = new MockView<String>();
+ PagingListViewPresenter<String> presenter = new PagingListViewPresenter<String>(
+ listView, view, 10);
+ assertNull(presenter.getSelectionModel());
+
+ // Initialize some data.
+ presenter.setRange(0, 10);
+ presenter.setData(0, 10, createData(0, 10));
+ view.assertReplaceAllChildrenCalled(true);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml("start=0,size=10");
+
+ // Set the selection model.
+ SelectionModel<String> model = new MockSelectionModel<String>();
+ model.setSelected("test 0", true);
+ presenter.setSelectionModel(model);
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows(0);
+
+ // Select something.
+ model.setSelected("test 2", true);
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows(0, 2);
+
+ // Set selection model to null.
+ presenter.setSelectionModel(null);
+ view.assertReplaceAllChildrenCalled(false);
+ view.assertReplaceChildrenCalled(false);
+ view.assertLastHtml(null);
+ view.assertOnUpdateSelectionFired(true);
+ view.assertSelectedRows();
+ }
+
+ /**
+ * Create a list of data for testing.
+ *
+ * @param start the start index
+ * @param length the length
+ * @return a list of data
+ */
+ private List<String> createData(int start, int length) {
+ List<String> toRet = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ toRet.add("test " + (i + start));
+ }
+ return toRet;
+ }
+}
diff --git a/user/test/com/google/gwt/user/cellview/client/SimplePagerTest.java b/user/test/com/google/gwt/user/cellview/client/SimplePagerTest.java
new file mode 100644
index 0000000..f866bd0
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/SimplePagerTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.user.cellview.client;
+
+import com.google.gwt.view.client.MockPagingListView;
+
+/**
+ * Tests for {@link SimplePager}.
+ */
+public class SimplePagerTest extends AbstractPagerTest {
+
+ @Override
+ protected <R> AbstractPager<R> createPager() {
+ return new SimplePager<R>(new MockPagingListView<R>());
+ }
+}
diff --git a/user/test/com/google/gwt/view/ViewSuite.java b/user/test/com/google/gwt/view/ViewSuite.java
index 07fa087..688b460 100644
--- a/user/test/com/google/gwt/view/ViewSuite.java
+++ b/user/test/com/google/gwt/view/ViewSuite.java
@@ -21,6 +21,7 @@
import com.google.gwt.view.client.AsyncListViewAdapterTest;
import com.google.gwt.view.client.DefaultNodeInfoTest;
import com.google.gwt.view.client.DefaultSelectionModelTest;
+import com.google.gwt.view.client.ListViewAdapterTest;
import com.google.gwt.view.client.MultiSelectionModelTest;
import com.google.gwt.view.client.RangeTest;
import com.google.gwt.view.client.SingleSelectionModelTest;
@@ -39,6 +40,7 @@
suite.addTestSuite(AsyncListViewAdapterTest.class);
suite.addTestSuite(DefaultNodeInfoTest.class);
suite.addTestSuite(DefaultSelectionModelTest.class);
+ suite.addTestSuite(ListViewAdapterTest.class);
suite.addTestSuite(MultiSelectionModelTest.class);
suite.addTestSuite(RangeTest.class);
suite.addTestSuite(SingleSelectionModelTest.class);
diff --git a/user/test/com/google/gwt/view/client/ListViewAdapterTest.java b/user/test/com/google/gwt/view/client/ListViewAdapterTest.java
new file mode 100644
index 0000000..a15d187
--- /dev/null
+++ b/user/test/com/google/gwt/view/client/ListViewAdapterTest.java
@@ -0,0 +1,458 @@
+/*
+ * 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.view.client;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Test cases for {@link ListViewAdapter}.
+ */
+public class ListViewAdapterTest extends AbstractListViewAdapterTest {
+
+ public void testConstructorList() {
+ List<String> list = new ArrayList<String>();
+ list.add("helloworld");
+ ListViewAdapter<String> adapter = new ListViewAdapter<String>(list);
+ assertEquals("helloworld", adapter.getList().get(0));
+ }
+
+ public void testFlush() {
+ ListViewAdapter<String> adapter = createListViewAdapter();
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ view.clearLastDataAndRange();
+ view.setDataSize(0, true);
+
+ // Add data to the list.
+ for (int i = 0; i < 10; i++) {
+ list.add("test " + i);
+ }
+ assertEquals(0, view.getDataSize());
+ assertNull(view.getLastData());
+ assertNull(view.getLastDataRange());
+
+ // Flush the data immediately.
+ adapter.flush();
+ assertEquals(10, view.getDataSize());
+ assertTrue(view.isDataSizeExact());
+ assertEquals(list, view.getLastData());
+ assertEquals(new Range(0, 10), view.getLastDataRange());
+ }
+
+ public void testListAdd() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ // add(String).
+ list.add("added");
+ assertEquals("added", list.get(10));
+ adapter.flush();
+ assertEquals(new Range(10, 1), view.getLastDataRange());
+
+ // add(int, String).
+ list.add(2, "inserted");
+ assertEquals("inserted", list.get(2));
+ adapter.flush();
+ assertEquals(new Range(2, 10), view.getLastDataRange());
+ }
+
+ public void testListAddAll() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 25);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ // addAll(Collection).
+ List<String> toAdd = createData(10, 3);
+ list.addAll(toAdd);
+ assertEquals("test 10", list.get(10));
+ assertEquals("test 11", list.get(11));
+ assertEquals("test 12", list.get(12));
+ adapter.flush();
+ assertEquals(toAdd, view.getLastData());
+ assertEquals(new Range(10, 3), view.getLastDataRange());
+
+ // addAll(int, Collection).
+ List<String> toInsert = createData(20, 3);
+ list.addAll(2, toInsert);
+ assertEquals("test 20", list.get(2));
+ assertEquals("test 21", list.get(3));
+ assertEquals("test 22", list.get(4));
+ adapter.flush();
+ assertEquals(new Range(2, 14), view.getLastDataRange());
+ }
+
+ public void testListClear() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ assertEquals(10, list.size());
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ list.clear();
+ assertEquals(0, list.size());
+ adapter.flush();
+ assertEquals(0, view.getDataSize());
+ }
+
+ public void testListContains() {
+ List<String> list = createListViewAdapter(5).getList();
+
+ // contains(Object).
+ assertTrue(list.contains("test 0"));
+ assertFalse(list.contains("platypus"));
+
+ // containsAll(Collection).
+ assertTrue(list.containsAll(createData(1, 2)));
+ assertFalse(list.containsAll(createData(10, 2)));
+ }
+
+ public void testListEquals() {
+ List<String> list = createListViewAdapter(5).getList();
+ assertTrue(list.equals(createData(0, 5)));
+ assertFalse(list.equals(createData(0, 4)));
+ }
+
+ public void testListIndexOf() {
+ List<String> list = createListViewAdapter(5).getList();
+
+ // indexOf(Object).
+ assertEquals(3, list.indexOf("test 3"));
+ assertEquals(-1, list.indexOf("duck"));
+
+ // lastIndexOf(Object).
+ assertEquals(3, list.lastIndexOf("test 3"));
+ assertEquals(-1, list.lastIndexOf("duck"));
+ list.add("test 3");
+ assertEquals(5, list.lastIndexOf("test 3"));
+ }
+
+ public void testListIsEmpty() {
+ List<String> list = createListViewAdapter(0).getList();
+ assertTrue(list.isEmpty());
+
+ list.add("test");
+ assertFalse(list.isEmpty());
+ }
+
+ public void testListIterator() {
+ List<String> list = createListViewAdapter(3).getList();
+ Iterator<String> iterator = list.iterator();
+
+ // Modify before next.
+ try {
+ iterator.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // next and hasNext.
+ assertTrue(iterator.hasNext());
+ assertEquals("test 0", iterator.next());
+ assertEquals("test 1", iterator.next());
+ assertEquals("test 2", iterator.next());
+ assertFalse(iterator.hasNext());
+
+ // remove.
+ iterator = list.iterator();
+ iterator.next();
+ iterator.remove();
+ assertEquals("test 1", list.get(0));
+ assertEquals(2, list.size());
+ try {
+ iterator.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ assertEquals("test 1", iterator.next());
+ }
+
+ public void testListListIterator() {
+ List<String> list = createListViewAdapter(3).getList();
+ ListIterator<String> iterator = list.listIterator();
+
+ // Modify before next.
+ try {
+ iterator.set("test");
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ try {
+ iterator.add("test");
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ try {
+ iterator.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+
+ // next, hasNext, and nextIndex.
+ assertTrue(iterator.hasNext());
+ assertEquals(0, iterator.nextIndex());
+ assertEquals("test 0", iterator.next());
+ assertEquals("test 1", iterator.next());
+ assertEquals("test 2", iterator.next());
+ assertFalse(iterator.hasNext());
+ assertEquals(3, iterator.nextIndex());
+
+ // previo0us, hasPrevious, and previousIndex.
+ assertTrue(iterator.hasPrevious());
+ assertEquals(2, iterator.previousIndex());
+ assertEquals("test 2", iterator.previous());
+ assertEquals("test 1", iterator.previous());
+ assertEquals("test 0", iterator.previous());
+ assertFalse(iterator.hasPrevious());
+ assertEquals(-1, iterator.previousIndex());
+
+ // set.
+ iterator.set("set0");
+ assertEquals("set0", list.get(0));
+ iterator.set("set1");
+ assertEquals("set1", list.get(0));
+
+ // add.
+ iterator.add("added");
+ assertEquals("added", list.get(0));
+ assertEquals("set1", list.get(1));
+ assertEquals(4, list.size());
+ try {
+ iterator.add("double add");
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ assertEquals("set1", iterator.next());
+
+ // remove.
+ iterator.remove();
+ assertEquals("test 1", list.get(1));
+ assertEquals(3, list.size());
+ try {
+ iterator.remove();
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ assertEquals("added", iterator.previous());
+ }
+
+ public void testListListIteratorAtIndex() {
+ List<String> list = createListViewAdapter(3).getList();
+ ListIterator<String> iterator = list.listIterator(2);
+ assertEquals("test 2", iterator.next());
+ }
+
+ public void testListRemove() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ // remove(int).
+ assertEquals("test 4", list.remove(4));
+ assertEquals("test 5", list.get(4));
+ adapter.flush();
+ assertEquals(new Range(4, 5), view.getLastDataRange());
+
+ // remove(String).
+ assertTrue(list.remove("test 2"));
+ assertEquals("test 3", list.get(2));
+ adapter.flush();
+ assertEquals(new Range(2, 6), view.getLastDataRange());
+
+ // remove(String)
+ assertFalse(list.remove("not in list"));
+ }
+
+ public void testListRemoveAll() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ List<String> toRemove = createData(2, 3);
+ assertTrue(list.removeAll(toRemove));
+ assertEquals(7, list.size());
+ assertEquals("test 5", list.get(2));
+ adapter.flush();
+ assertEquals(new Range(0, 7), view.getLastDataRange());
+
+ assertFalse(list.removeAll(toRemove));
+ }
+
+ public void testListRetainAll() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ List<String> toRetain = createData(2, 3);
+ assertTrue(list.retainAll(toRetain));
+ assertEquals(3, list.size());
+ assertEquals("test 2", list.get(0));
+ adapter.flush();
+ assertEquals(new Range(0, 3), view.getLastDataRange());
+ }
+
+ public void testListSet() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ list.set(3, "newvalue");
+ assertEquals("newvalue", list.get(3));
+ adapter.flush();
+ assertEquals(new Range(3, 1), view.getLastDataRange());
+ }
+
+ public void testSubList() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ List<String> subList = list.subList(2, 5);
+ assertEquals(3, subList.size());
+
+ subList.set(0, "test");
+ assertEquals("test", subList.get(0));
+ assertEquals("test", list.get(2));
+ adapter.flush();
+ assertEquals(new Range(2, 1), view.getLastDataRange());
+ }
+
+ public void testToArray() {
+ List<String> list = createListViewAdapter(3).getList();
+ String[] expected = new String[] {"test 0", "test 1", "test 2"};
+
+ Object[] objects = list.toArray();
+ String[] strings = list.toArray(new String[3]);
+ assertEquals(3, strings.length);
+ assertEquals(3, objects.length);
+ for (int i = 0; i < 3; i++) {
+ String s = expected[i];
+ assertEquals(s, objects[i]);
+ assertEquals(s, strings[i]);
+ }
+ }
+
+ public void testListSize() {
+ List<String> list = createListViewAdapter(10).getList();
+ assertEquals(10, list.size());
+ }
+
+ public void testOnRangeChanged() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view0 = new MockPagingListView<String>();
+ MockPagingListView<String> view1 = new MockPagingListView<String>();
+ view0.setRange(0, 15);
+ view1.setRange(0, 15);
+ adapter.addView(view0);
+ adapter.addView(view1);
+ adapter.flush();
+ view0.clearLastDataAndRange();
+ view1.clearLastDataAndRange();
+
+ // Change the range of view0.
+ view0.setRange(0, 12);
+ assertEquals(list, view0.getLastData());
+ assertEquals(new Range(0, 10), view0.getLastDataRange());
+ assertNull(view1.getLastData());
+ assertNull(view1.getLastDataRange());
+ }
+
+ public void testRefresh() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ List<String> list = adapter.getList();
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+
+ // Refresh the view.
+ adapter.refresh();
+ assertEquals(list, view.getLastData());
+ assertEquals(new Range(0, 10), view.getLastDataRange());
+ }
+
+ public void testSetList() {
+ ListViewAdapter<String> adapter = createListViewAdapter(10);
+ MockPagingListView<String> view = new MockPagingListView<String>();
+ view.setRange(0, 15);
+ adapter.addView(view);
+ adapter.flush();
+ view.clearLastDataAndRange();
+ assertEquals("test 0", adapter.getList().get(0));
+
+ List<String> replace = new ArrayList<String>();
+ replace.add("helloworld");
+ adapter.setList(replace);
+ assertEquals("helloworld", adapter.getList().get(0));
+ assertEquals(1, view.getDataSize());
+ assertEquals(replace, view.getLastData());
+ assertEquals(new Range(0, 1), view.getLastDataRange());
+ }
+
+ @Override
+ protected ListViewAdapter<String> createListViewAdapter() {
+ return createListViewAdapter(0);
+ }
+
+ private ListViewAdapter<String> createListViewAdapter(int size) {
+ return new ListViewAdapter<String>(createData(0, size));
+ }
+}
diff --git a/user/test/com/google/gwt/view/client/MockPagingListView.java b/user/test/com/google/gwt/view/client/MockPagingListView.java
index b916ae5..cc0234a 100644
--- a/user/test/com/google/gwt/view/client/MockPagingListView.java
+++ b/user/test/com/google/gwt/view/client/MockPagingListView.java
@@ -30,6 +30,66 @@
private static final int DEFAULT_PAGE_SIZE = 10;
+ /**
+ * A mock delegate used for testing.
+ *
+ * @param <T> the data type of each row
+ */
+ public static class MockDelegate<T> implements Delegate<T> {
+
+ private ListView<T> listView;
+
+ /**
+ * Clear the last list view.
+ */
+ public void clearListView() {
+ this.listView = null;
+ }
+
+ /**
+ * Get the last list view to use the delegate.
+ *
+ * @return the last {@link ListView}
+ */
+ public ListView<T> getLastListView() {
+ return listView;
+ }
+
+ public void onRangeChanged(ListView<T> listView) {
+ this.listView = listView;
+ }
+ }
+
+ /**
+ * A mock pager used for testing.
+ *
+ * @param <T> the data type of each row
+ */
+ public static class MockPager<T> implements Pager<T> {
+
+ private ListView<T> listView;
+
+ /**
+ * Clear the last list view.
+ */
+ public void clearListView() {
+ this.listView = null;
+ }
+
+ /**
+ * Get the last list view to use the pager.
+ *
+ * @return the last {@link ListView}
+ */
+ public ListView<T> getLastListView() {
+ return listView;
+ }
+
+ public void onRangeOrSizeChanged(PagingListView<T> listView) {
+ this.listView = listView;
+ }
+ }
+
private int dataSize;
private boolean dataSizeExact;
private Delegate<T> delegate;
@@ -71,14 +131,6 @@
return lastRange;
}
- public int getPageSize() {
- return pageSize;
- }
-
- public int getPageStart() {
- return pageStart;
- }
-
public Range getRange() {
return new Range(pageStart, pageSize);
}
@@ -113,14 +165,6 @@
this.pager = pager;
}
- public void setPageSize(int pageSize) {
- setRange(pageStart, pageSize);
- }
-
- public void setPageStart(int pageStart) {
- setRange(pageStart, pageSize);
- }
-
public void setRange(int start, int length) {
if (this.pageStart == start && this.pageSize == length) {
return;
diff --git a/user/test/com/google/gwt/view/client/MockSelectionModel.java b/user/test/com/google/gwt/view/client/MockSelectionModel.java
new file mode 100644
index 0000000..e612db0
--- /dev/null
+++ b/user/test/com/google/gwt/view/client/MockSelectionModel.java
@@ -0,0 +1,30 @@
+/*
+ * 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.view.client;
+
+/**
+ * A mock {@link SelectionModel} used for testing without used any GWT client
+ * code.
+ *
+ * @param <T> the selection type
+ */
+public class MockSelectionModel<T> extends MultiSelectionModel<T> {
+
+ @Override
+ protected void scheduleSelectionChangeEvent() {
+ fireSelectionChangeEvent();
+ }
+}