Adding column sorting support to CellTable. Clicking on a header of a sortable Column in CellTable adds a sort icon to the Header and fires a ColumnSortEvent, which user can catch to handle sorting. By default and for backward compatibility, Columns are not sortable. We provide ColumnSortEvent.ListHandler as a default implementation to sort java.util.Lists by mapping Columns to Comparators.
Review at http://gwt-code-reviews.appspot.com/1237801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9493 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactDatabase.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactDatabase.java
index 4d26709..0c06c4c 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactDatabase.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ContactDatabase.java
@@ -55,8 +55,7 @@
/**
* The key provider that provides the unique ID of a contact.
*/
- public static final ProvidesKey<ContactInfo> KEY_PROVIDER = new ProvidesKey<
- ContactInfo>() {
+ public static final ProvidesKey<ContactInfo> KEY_PROVIDER = new ProvidesKey<ContactInfo>() {
public Object getKey(ContactInfo item) {
return item == null ? null : item.getId();
}
@@ -78,8 +77,8 @@
}
public int compareTo(ContactInfo o) {
- return (o == null || o.firstName == null) ? -1 : -o.firstName.compareTo(
- firstName);
+ return (o == null || o.firstName == null) ? -1
+ : -o.firstName.compareTo(firstName);
}
@Override
@@ -315,8 +314,7 @@
/**
* The provider that holds the list of contacts in the database.
*/
- private ListDataProvider<ContactInfo> dataProvider = new ListDataProvider<
- ContactInfo>();
+ private ListDataProvider<ContactInfo> dataProvider = new ListDataProvider<ContactInfo>();
private final Category[] categories;
@@ -371,6 +369,10 @@
}
}
+ public ListDataProvider<ContactInfo> getDataProvider() {
+ return dataProvider;
+ }
+
/**
* Get the categories in the database.
*
@@ -442,8 +444,8 @@
// Create a birthday between 20-80 years ago.
int year = (new Date()).getYear() - 21 - Random.nextInt(61);
- contact.setBirthday(
- new Date(year, Random.nextInt(12), 1 + Random.nextInt(31)));
+ contact.setBirthday(new Date(year, Random.nextInt(12),
+ 1 + Random.nextInt(31)));
// Create an address.
int addrNum = 1 + Random.nextInt(999);
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
index 5232d00..c86b630 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellTable.java
@@ -34,6 +34,7 @@
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -43,6 +44,7 @@
import com.google.gwt.view.client.SelectionModel;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
/**
@@ -121,6 +123,11 @@
cellTable = new CellTable<ContactInfo>(
ContactDatabase.ContactInfo.KEY_PROVIDER);
+ // Attach a column sort handler to the ListDataProvider to sort the list.
+ ListHandler<ContactInfo> sortHandler = new ListHandler<ContactInfo>(
+ ContactDatabase.get().getDataProvider().getList());
+ cellTable.addColumnSortHandler(sortHandler);
+
// Create a Pager to control the table.
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
@@ -130,10 +137,10 @@
final SelectionModel<ContactInfo> selectionModel = new MultiSelectionModel<ContactInfo>(
ContactDatabase.ContactInfo.KEY_PROVIDER);
cellTable.setSelectionModel(selectionModel,
- DefaultSelectionEventManager.<ContactInfo>createCheckboxManager());
+ DefaultSelectionEventManager.<ContactInfo> createCheckboxManager());
// Initialize the columns.
- initTableColumns(selectionModel);
+ initTableColumns(selectionModel, sortHandler);
// Add the CellList to the adapter in the database.
ContactDatabase.get().addDataDisplay(cellTable);
@@ -163,7 +170,9 @@
* Add the columns to the table.
*/
@ShowcaseSource
- private void initTableColumns(final SelectionModel<ContactInfo> selectionModel) {
+ private void initTableColumns(
+ final SelectionModel<ContactInfo> selectionModel,
+ ListHandler<ContactInfo> sortHandler) {
// Checkbox column. This table will uses a checkbox column for selection.
// Alternatively, you can call cellTable.setSelectionEnabled(true) to enable
// mouse selection.
@@ -185,6 +194,12 @@
return object.getFirstName();
}
};
+ firstNameColumn.setSortable(true);
+ sortHandler.setComparator(firstNameColumn, new Comparator<ContactInfo>() {
+ public int compare(ContactInfo o1, ContactInfo o2) {
+ return o1.getFirstName().compareTo(o2.getFirstName());
+ }
+ });
cellTable.addColumn(firstNameColumn, constants.cwCellTableColumnFirstName());
firstNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
public void update(int index, ContactInfo object, String value) {
@@ -202,6 +217,12 @@
return object.getLastName();
}
};
+ lastNameColumn.setSortable(true);
+ sortHandler.setComparator(lastNameColumn, new Comparator<ContactInfo>() {
+ public int compare(ContactInfo o1, ContactInfo o2) {
+ return o1.getLastName().compareTo(o2.getLastName());
+ }
+ });
cellTable.addColumn(lastNameColumn, constants.cwCellTableColumnLastName());
lastNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
public void update(int index, ContactInfo object, String value) {
@@ -238,11 +259,19 @@
});
// Address.
- cellTable.addColumn(new Column<ContactInfo, String>(new TextCell()) {
+ Column<ContactInfo, String> addressColumn = new Column<ContactInfo, String>(
+ new TextCell()) {
@Override
public String getValue(ContactInfo object) {
return object.getAddress();
}
- }, constants.cwCellTableColumnAddress());
+ };
+ addressColumn.setSortable(true);
+ sortHandler.setComparator(addressColumn, new Comparator<ContactInfo>() {
+ public int compare(ContactInfo o1, ContactInfo o2) {
+ return o1.getAddress().compareTo(o2.getAddress());
+ }
+ });
+ cellTable.addColumn(addressColumn, constants.cwCellTableColumnAddress());
}
}
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
index 8f67fb1..dc00d9b 100644
--- a/user/src/com/google/gwt/cell/client/IconCellDecorator.java
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -45,20 +45,20 @@
/**
* The wrapper around the image vertically aligned to the bottom.
*/
- @Template("<div style=\"position:absolute;{0}:0px;bottom:0px;\">{1}</div>")
+ @Template("<div style=\"position:absolute;{0}:0px;bottom:0px;line-height:0px;\">{1}</div>")
SafeHtml imageWrapperBottom(String direction, SafeHtml image);
/**
* The wrapper around the image vertically aligned to the middle.
*/
- @Template("<div style=\"position:absolute;{0}:0px;top:50%;"
+ @Template("<div style=\"position:absolute;{0}:0px;top:50%;line-height:0px;"
+ "margin-top:-{1}px;\">{2}</div>")
SafeHtml imageWrapperMiddle(String direction, int halfHeight, SafeHtml image);
/**
* The wrapper around the image vertically aligned to the top.
*/
- @Template("<div style=\"position:absolute;{0}:0px;top:0px;\">{1}</div>")
+ @Template("<div style=\"position:absolute;{0}:0px;top:0px;line-height:0px;\">{1}</div>")
SafeHtml imageWrapperTop(String direction, SafeHtml image);
}
@@ -196,8 +196,7 @@
} else if (HasVerticalAlignment.ALIGN_BOTTOM == valign) {
return template.imageWrapperBottom(direction, image);
} else {
- // Add one to the margin-top because it looks better in all browsers.
- int halfHeight = 1 + (int) Math.round(res.getHeight() / 2.0);
+ int halfHeight = (int) Math.round(res.getHeight() / 2.0);
return template.imageWrapperMiddle(direction, halfHeight, image);
}
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.css b/user/src/com/google/gwt/user/cellview/client/CellTable.css
index 36cab91..71b07aa 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.css
@@ -62,6 +62,23 @@
}
+.cellTableSortableHeader {
+ cursor: pointer;
+ cursor: hand;
+}
+
+.cellTableSortableHeader:hover {
+ color: #6c6b6b;
+}
+
+.cellTableSortedHeaderAscending {
+
+}
+
+.cellTableSortedHeaderDescending {
+
+}
+
.cellTableEvenRow {
background: #ffffff;
}
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 65ec3c2..65330ab 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -17,6 +17,8 @@
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.Cell.Context;
+import com.google.gwt.cell.client.IconCellDecorator;
+import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
@@ -30,6 +32,7 @@
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
@@ -41,6 +44,7 @@
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
@@ -59,21 +63,19 @@
* A tabular view that supports paging and columns.
*
* <p>
- * <h3>Columns</h3>
- * The {@link Column} class defines the {@link Cell} used to render a column.
- * Implement {@link Column#getValue(Object)} to retrieve the field value from
- * the row object that will be rendered in the {@link Cell}.
+ * <h3>Columns</h3> The {@link Column} class defines the {@link Cell} used to
+ * render a column. Implement {@link Column#getValue(Object)} to retrieve the
+ * field value from the row object that will be rendered in the {@link Cell}.
* </p>
*
* <p>
- * <h3>Headers and Footers</h3>
- * A {@link Header} can be placed at the top (header) or bottom (footer) of the
- * {@link CellTable}. You can specify a header as text using
- * {@link #addColumn(Column, String)}, or you can create a custom {@link Header}
- * that can change with the value of the cells, such as a column total. The
- * {@link Header} will be rendered every time the row data changes or the table
- * is redrawn. If you pass the same header instance (==) into adjacent columns,
- * the header will span the columns.
+ * <h3>Headers and Footers</h3> A {@link Header} can be placed at the top
+ * (header) or bottom (footer) of the {@link CellTable}. You can specify a
+ * header as text using {@link #addColumn(Column, String)}, or you can create a
+ * custom {@link Header} that can change with the value of the cells, such as a
+ * column total. The {@link Header} will be rendered every time the row data
+ * changes or the table is redrawn. If you pass the same header instance (==)
+ * into adjacent columns, the header will span the columns.
* </p>
*
* <p>
@@ -134,6 +136,20 @@
ImageResource cellTableSelectedBackground();
/**
+ * Icon used when a column is sorted in ascending order.
+ */
+ @Source("sortAscending.png")
+ @ImageOptions(flipRtl = true)
+ ImageResource cellTableSortAscending();
+
+ /**
+ * Icon used when a column is sorted in descending order.
+ */
+ @Source("sortDescending.png")
+ @ImageOptions(flipRtl = true)
+ ImageResource cellTableSortDescending();
+
+ /**
* The styles used in this widget.
*/
@Source(Style.DEFAULT_CSS)
@@ -256,6 +272,21 @@
String cellTableSelectedRowCell();
/**
+ * Applied to header cells that are sortable.
+ */
+ String cellTableSortableHeader();
+
+ /**
+ * Applied to header cells that are sorted in ascending order.
+ */
+ String cellTableSortedHeaderAscending();
+
+ /**
+ * Applied to header cells that are sorted in descending order.
+ */
+ String cellTableSortedHeaderDescending();
+
+ /**
* Applied to the table.
*/
String cellTableWidget();
@@ -476,15 +507,18 @@
private int keyboardSelectedColumn = 0;
+ private final Resources resources;
private RowStyles<T> rowStyles;
+ private IconCellDecorator<SafeHtml> sortAscDecorator;
+ private IconCellDecorator<SafeHtml> sortDescDecorator;
+ private final ColumnSortList sortList;
private final Style style;
private final TableElement table;
private final TableSectionElement tbody;
private final TableSectionElement tbodyLoading;
-
private final TableSectionElement tfoot;
-
private final TableSectionElement thead;
+ private boolean updatingSortList;
/**
* Constructs a table with a default page size of 15.
@@ -554,9 +588,19 @@
if (template == null) {
template = GWT.create(Template.class);
}
+ this.resources = resources;
this.style = resources.cellTableStyle();
this.style.ensureInjected();
+ // Create the ColumnSortList and delegate.
+ sortList = new ColumnSortList(new ColumnSortList.Delegate() {
+ public void onModification() {
+ if (!updatingSortList) {
+ createHeaders(false);
+ }
+ }
+ });
+
table = getElement().cast();
table.setCellSpacing(0);
colgroup = Document.get().createColGroupElement();
@@ -669,6 +713,17 @@
}
/**
+ * Add a handler to handle {@link ColumnSortEvent}s.
+ *
+ * @param handler the {@link ColumnSortEvent.Handler} to add
+ * @return a {@link HandlerRegistration} to remove the handler
+ */
+ public HandlerRegistration addColumnSortHandler(
+ ColumnSortEvent.Handler handler) {
+ return addHandler(handler, ColumnSortEvent.getType());
+ }
+
+ /**
* Add a style name to the {@link TableColElement} at the specified index,
* creating it if necessary.
*
@@ -710,6 +765,27 @@
}
/**
+ * Get the index of the specified column.
+ *
+ * @param column the column to search for
+ * @return the index of the column, or -1 if not found
+ */
+ public int getColumnIndex(Column<T,?> column) {
+ return columns.indexOf(column);
+ }
+
+ /**
+ * Get the {@link ColumnSortList} that specifies which columns are sorted.
+ * Modifications to the {@link ColumnSortList} will be reflected in the table
+ * header.
+ *
+ * @return the {@link ColumnSortList}
+ */
+ public ColumnSortList getColumnSortList() {
+ return sortList;
+ }
+
+ /**
* Return the height of the table header.
*
* @return an int representing the header height
@@ -1054,12 +1130,25 @@
TableSectionElement section = TableSectionElement.as(sectionElem);
// Forward the event to the associated header, footer, or column.
+ boolean isClick = "click".equals(eventType);
int col = tableCell.getCellIndex();
if (section == thead) {
Header<?> header = headers.get(col);
- if (header != null && cellConsumesEventType(header.getCell(), eventType)) {
- Context context = new Context(0, col, header.getKey());
- header.onBrowserEvent(context, tableCell, event);
+ if (header != null) {
+ // Fire the event to the header.
+ if (cellConsumesEventType(header.getCell(), eventType)) {
+ Context context = new Context(0, col, header.getKey());
+ header.onBrowserEvent(context, tableCell, event);
+ }
+
+ // Sort the header.
+ Column<T, ?> column = columns.get(col);
+ if (isClick && column.isSortable()) {
+ updatingSortList = true;
+ sortList.push(column);
+ updatingSortList = false;
+ ColumnSortEvent.fire(this, sortList);
+ }
}
} else if (section == tfoot) {
Header<?> footer = footers.get(col);
@@ -1069,7 +1158,6 @@
}
} else if (section == tbody) {
// Update the hover state.
- boolean isClick = "click".equals(eventType);
int row = tr.getSectionRowIndex();
if ("mouseover".equals(eventType)) {
// Unstyle the old row if it is still part of the table.
@@ -1339,19 +1427,41 @@
TableSectionElement section = isFooter ? tfoot : thead;
String className = isFooter ? style.cellTableFooter()
: style.cellTableHeader();
+ String firstColumnStyle = " "
+ + (isFooter ? style.cellTableFirstColumnFooter()
+ : style.cellTableFirstColumnHeader());
+ String lastColumnStyle = " "
+ + (isFooter ? style.cellTableLastColumnFooter()
+ : style.cellTableLastColumnHeader());
+ String sortableStyle = " " + style.cellTableSortableHeader();
+ String sortedAscStyle = " " + style.cellTableSortedHeaderAscending();
+ String sortedDescStyle = " " + style.cellTableSortedHeaderDescending();
boolean hasHeader = false;
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("<tr>");
int columnCount = columns.size();
if (columnCount > 0) {
+ // Get information about the sorted column.
+ ColumnSortInfo sortedInfo = (sortList.size() == 0) ? null
+ : sortList.get(0);
+ Column<?, ?> sortedColumn = (sortedInfo == null) ? null
+ : sortedInfo.getColumn();
+ boolean isSortAscending = (sortedInfo == null) ? false
+ : sortedInfo.isAscending();
+
// Setup the first column.
Header<?> prevHeader = theHeaders.get(0);
+ Column<T, ?> column = columns.get(0);
int prevColspan = 1;
+ boolean isSortable = false;
+ boolean isSorted = false;
StringBuilder classesBuilder = new StringBuilder(className);
- classesBuilder.append(" ");
- classesBuilder.append(isFooter ? style.cellTableFirstColumnFooter()
- : style.cellTableFirstColumnHeader());
+ classesBuilder.append(firstColumnStyle);
+ if (!isFooter && column.isSortable()) {
+ isSortable = true;
+ isSorted = (column == sortedColumn);
+ }
// Loop through all column headers.
int curColumn;
@@ -1360,41 +1470,86 @@
if (header != prevHeader) {
// The header has changed, so append the previous one.
- SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
+ SafeHtml headerHtml = SafeHtmlUtils.EMPTY_SAFE_HTML;
if (prevHeader != null) {
hasHeader = true;
+
+ // Build the header.
+ SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
Context context = new Context(0, curColumn - prevColspan,
prevHeader.getKey());
prevHeader.render(context, headerBuilder);
+
+ // Wrap the header with a sort icon.
+ if (isSorted) {
+ SafeHtml unwrappedHeader = headerBuilder.toSafeHtml();
+ headerBuilder = new SafeHtmlBuilder();
+ getSortDecorator(isSortAscending).render(null, unwrappedHeader,
+ headerBuilder);
+ }
+ headerHtml = headerBuilder.toSafeHtml();
+ }
+ if (isSortable) {
+ classesBuilder.append(sortableStyle);
+ }
+ if (isSorted) {
+ classesBuilder.append(isSortAscending ? sortedAscStyle
+ : sortedDescStyle);
}
sb.append(template.th(prevColspan, classesBuilder.toString(),
- headerBuilder.toSafeHtml()));
+ headerHtml));
// Reset the previous header.
prevHeader = header;
prevColspan = 1;
classesBuilder = new StringBuilder(className);
+ isSortable = false;
+ isSorted = false;
} else {
// Increment the colspan if the headers == each other.
prevColspan++;
}
+
+ // Update the sorted state.
+ column = columns.get(curColumn);
+ if (!isFooter && column.isSortable()) {
+ isSortable = true;
+ isSorted = (column == sortedColumn);
+ }
}
// Append the last header.
- SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
+ SafeHtml headerHtml = SafeHtmlUtils.EMPTY_SAFE_HTML;
if (prevHeader != null) {
hasHeader = true;
+
+ // Build the header.
+ SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
Context context = new Context(0, curColumn - prevColspan,
prevHeader.getKey());
prevHeader.render(context, headerBuilder);
+
+ // Wrap the header with a sort icon.
+ if (isSorted) {
+ SafeHtml unwrappedHeader = headerBuilder.toSafeHtml();
+ headerBuilder = new SafeHtmlBuilder();
+ getSortDecorator(isSortAscending).render(null, unwrappedHeader,
+ headerBuilder);
+ }
+ headerHtml = headerBuilder.toSafeHtml();
+ }
+ if (isSortable) {
+ classesBuilder.append(sortableStyle);
+ }
+ if (isSorted) {
+ classesBuilder.append(isSortAscending ? sortedAscStyle
+ : sortedDescStyle);
}
// The first and last columns could be the same column.
classesBuilder.append(" ");
- classesBuilder.append(isFooter ? style.cellTableLastColumnFooter()
- : style.cellTableLastColumnHeader());
- sb.append(template.th(prevColspan, classesBuilder.toString(),
- headerBuilder.toSafeHtml()));
+ classesBuilder.append(lastColumnStyle);
+ sb.append(template.th(prevColspan, classesBuilder.toString(), headerHtml));
}
sb.appendHtmlConstant("</tr>");
@@ -1522,6 +1677,28 @@
return element.clientHeight;
}-*/;
+ /**
+ * Get the {@link IconCellDecorator} used to decorate sorted column headers.
+ *
+ * @param ascending true if ascending, false if descending
+ * @return the {@link IconCellDecorator}
+ */
+ private IconCellDecorator<SafeHtml> getSortDecorator(boolean ascending) {
+ if (ascending) {
+ if (sortAscDecorator == null) {
+ sortAscDecorator = new IconCellDecorator<SafeHtml>(
+ resources.cellTableSortAscending(), new SafeHtmlCell());
+ }
+ return sortAscDecorator;
+ } else {
+ if (sortDescDecorator == null) {
+ sortDescDecorator = new IconCellDecorator<SafeHtml>(
+ resources.cellTableSortDescending(), new SafeHtmlCell());
+ }
+ return sortDescDecorator;
+ }
+ }
+
private boolean handleKey(Event event) {
HasDataPresenter<T> presenter = getPresenter();
int oldRow = getKeyboardSelectedRow();
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css b/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
index bca708b..f143f6d 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
@@ -69,6 +69,23 @@
}
+.cellTableSortableHeader {
+ cursor: pointer;
+ cursor: hand;
+}
+
+.cellTableSortableHeader:hover {
+ color: #6c6b6b;
+}
+
+.cellTableSortedHeaderAscending {
+
+}
+
+.cellTableSortedHeaderDescending {
+
+}
+
.cellTableEvenRow {
background: #ffffff;
}
diff --git a/user/src/com/google/gwt/user/cellview/client/Column.java b/user/src/com/google/gwt/user/cellview/client/Column.java
index f74f5da..5a6f96c 100644
--- a/user/src/com/google/gwt/user/cellview/client/Column.java
+++ b/user/src/com/google/gwt/user/cellview/client/Column.java
@@ -46,6 +46,7 @@
*/
private FieldUpdater<T, C> fieldUpdater;
+ private boolean isSortable = false;
private HorizontalAlignmentConstant hAlign = null;
private VerticalAlignmentConstant vAlign = null;
@@ -91,6 +92,15 @@
}
/**
+ * Check if the column is sortable.
+ *
+ * @return true if sortable, false if not
+ */
+ public boolean isSortable() {
+ return isSortable;
+ }
+
+ /**
* Handle a browser event that took place within the column.
*
* @param context the cell context
@@ -144,6 +154,16 @@
}
/**
+ * Set whether or not the column can be sorted. The change will take effect
+ * the next time the table is redrawn.
+ *
+ * @param sortable true to make sortable, false to make unsortable
+ */
+ public void setSortable(boolean sortable) {
+ this.isSortable = sortable;
+ }
+
+ /**
* {@inheritDoc}
*
* <p>
diff --git a/user/src/com/google/gwt/user/cellview/client/ColumnSortEvent.java b/user/src/com/google/gwt/user/cellview/client/ColumnSortEvent.java
new file mode 100644
index 0000000..7e5c524
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/ColumnSortEvent.java
@@ -0,0 +1,191 @@
+/*
+ * 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.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HasHandlers;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a column sort event.
+ */
+public class ColumnSortEvent extends GwtEvent<ColumnSortEvent.Handler> {
+
+ /**
+ * Handler for {@link ColumnSortEvent}.
+ */
+ public static interface Handler extends EventHandler {
+
+ /**
+ * Called when {@link ColumnSortEvent} is fired.
+ *
+ * @param event the {@link ColumnSortEvent} that was fired
+ */
+ void onColumnSort(ColumnSortEvent event);
+ }
+
+ /**
+ * <p>
+ * A default handler used to sort a {@link List} backing a table. If the
+ * sorted column has an associated {@link Comparator}, the list is sorted
+ * using the comparator.
+ * </p>
+ *
+ * <p>
+ * This can be used in conjunction with
+ * {@link com.google.gwt.view.client.ListDataProvider}.
+ * </p>
+ *
+ * @param <T> the data type of the list
+ */
+ public static class ListHandler<T> implements Handler {
+ private final Map<Column<?, ?>, Comparator<T>> comparators = new HashMap<Column<?, ?>, Comparator<T>>();
+ private final List<T> list;
+
+ public ListHandler(List<T> list) {
+ this.list = list;
+ }
+
+ public List<T> getList() {
+ return list;
+ }
+
+ public void onColumnSort(ColumnSortEvent event) {
+ // Get the sorted column.
+ Column<?, ?> column = event.getColumn();
+ if (column == null) {
+ return;
+ }
+
+ // Get the comparator.
+ final Comparator<T> comparator = comparators.get(column);
+ if (comparator == null) {
+ return;
+ }
+
+ // Sort using the comparator.
+ if (event.isSortAcsending()) {
+ Collections.sort(list, comparator);
+ } else {
+ Collections.sort(list, new Comparator<T>() {
+ public int compare(T o1, T o2) {
+ return -comparator.compare(o1, o2);
+ }
+ });
+ }
+ }
+
+ /**
+ * Set the comparator used to sort the specified column in ascending order.
+ *
+ * @param column the {@link Column}
+ * @param comparator the {@link Comparator} to use for the {@link Column}
+ */
+ public void setComparator(Column<T, ?> column, Comparator<T> comparator) {
+ comparators.put(column, comparator);
+ }
+ }
+
+ /**
+ * Handler type.
+ */
+ private static Type<Handler> TYPE;
+
+ /**
+ * Fires a cell preview event on all registered handlers in the handler
+ * manager. If no such handlers exist, this implementation will do nothing.
+ *
+ * @param source the source of the event
+ * @param sortList the {@link ColumnSortList} of sorted columns
+ * @return the {@link ColumnSortEvent} that was fired
+ */
+ public static ColumnSortEvent fire(HasHandlers source, ColumnSortList sortList) {
+ ColumnSortEvent event = new ColumnSortEvent(sortList);
+ if (TYPE != null) {
+ source.fireEvent(event);
+ }
+ return event;
+ }
+
+ /**
+ * Gets the type associated with this event.
+ *
+ * @return returns the handler type
+ */
+ public static Type<Handler> getType() {
+ if (TYPE == null) {
+ TYPE = new Type<Handler>();
+ }
+ return TYPE;
+ }
+
+ private final ColumnSortList sortList;
+
+ /**
+ * Construct a new {@link ColumnSortEvent}.
+ *
+ * @param sortList the {@link ColumnSortList}
+ */
+ protected ColumnSortEvent(ColumnSortList sortList) {
+ this.sortList = sortList;
+ }
+
+ @Override
+ public Type<Handler> getAssociatedType() {
+ return TYPE;
+ }
+
+ /**
+ * Get the {@link Column} that was sorted.
+ *
+ * @return the sorted {@link Column}, or null if not sorted
+ */
+ public Column<?, ?> getColumn() {
+ return (sortList == null || sortList.size() == 0) ? null
+ : sortList.get(0).getColumn();
+ }
+
+ /**
+ * Get the {@link ColumnSortList} that contains the ordered list of sorted
+ * columns.
+ *
+ * @return the {@link ColumnSortList}
+ */
+ public ColumnSortList getColumnSortList() {
+ return sortList;
+ }
+
+ /**
+ * Check if the {@link Column} is sorted in ascending order.
+ *
+ * @return true if ascending, false if descending or not sorted
+ */
+ public boolean isSortAcsending() {
+ return (sortList == null || sortList.size() == 0) ? false
+ : sortList.get(0).isAscending();
+ }
+
+ @Override
+ protected void dispatch(Handler handler) {
+ handler.onColumnSort(this);
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/ColumnSortList.java b/user/src/com/google/gwt/user/cellview/client/ColumnSortList.java
new file mode 100644
index 0000000..e2c2985
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/ColumnSortList.java
@@ -0,0 +1,270 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An ordered list containing the sort history of {@link Column}s in a table.
+ * The 0th item is the {@link ColumnSortInfo} of the most recently sorted
+ * column.
+ */
+public class ColumnSortList {
+
+ /**
+ * Information about the sort order of a specific column in a table.
+ */
+ public static class ColumnSortInfo {
+
+ private final boolean ascending;
+ private final Column<?, ?> column;
+
+ /**
+ * Construct a new {@link ColumnSortInfo}.
+ *
+ * @param column the column index
+ * @param ascending true if sorted ascending
+ */
+ public ColumnSortInfo(Column<?, ?> column, boolean ascending) {
+ this.column = column;
+ this.ascending = ascending;
+ }
+
+ /**
+ * Default constructor used for RPC.
+ */
+ ColumnSortInfo() {
+ this(null, true);
+ }
+
+ /**
+ * Check if this object is equal to another. The objects are equal if the
+ * column and ascending values are the equal.
+ *
+ * @param obj the object to check for equality
+ * @return true if objects are the same
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof ColumnSortInfo)) {
+ return false;
+ }
+
+ ColumnSortInfo other = (ColumnSortInfo) obj;
+ return equalsOrBothNull(getColumn(), other.getColumn())
+ && isAscending() == other.isAscending();
+ }
+
+ /**
+ * Get the {@link Column} that was sorted.
+ *
+ * @return the {@link Column}
+ */
+ public Column<?, ?> getColumn() {
+ return column;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * (column == null ? 0 : column.hashCode())
+ + (ascending ? 1 : 0);
+ }
+
+ /**
+ * Check if the column was sorted in ascending or descending order.
+ *
+ * @return true if ascending, false if descending
+ */
+ public boolean isAscending() {
+ return ascending;
+ }
+
+ private boolean equalsOrBothNull(Object a, Object b) {
+ return a == null ? b == null : a.equals(b);
+ }
+ }
+
+ /**
+ * The delegate that handles modifications to the list.
+ */
+ public static interface Delegate {
+
+ /**
+ * Called when the list is modified.
+ */
+ void onModification();
+ }
+
+ /**
+ * The delegate that handles modifications.
+ */
+ private final Delegate delegate;
+
+ /**
+ * A List used to manage the insertion/removal of {@link ColumnSortInfo}.
+ */
+ private final List<ColumnSortInfo> infos = new ArrayList<ColumnSortInfo>();
+
+ /**
+ * Construct a new {@link ColumnSortList} without a {@link Delegate}.
+ */
+ public ColumnSortList() {
+ this(null);
+ }
+
+ /**
+ * Construct a new {@link ColumnSortList} with the specified {@link Delegate}.
+ *
+ * @param delegate the {@link Delegate} to inform of modifications
+ */
+ public ColumnSortList(Delegate delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * Removes all of the elements from this list.
+ */
+ public void clear() {
+ infos.clear();
+ fireDelegate();
+ }
+
+ /**
+ * Check if the specified object equals this list. Two {@link ColumnSortList}
+ * are equals if they are the same size, and all entries are
+ * <code>equals</code> and in the same order.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof ColumnSortList)) {
+ return false;
+ }
+
+ // Check the size of the lists.
+ ColumnSortList other = (ColumnSortList) obj;
+ return infos.equals(other.infos);
+ }
+
+ /**
+ * Get the {@link ColumnSortInfo} at the specified index.
+ *
+ * @param index the index
+ * @return the {@link ColumnSortInfo}
+ */
+ public ColumnSortInfo get(int index) {
+ return infos.get(index);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * infos.hashCode() + 13;
+ }
+
+ /**
+ * Inserts the specified {@link ColumnSortInfo} at the specified position in
+ * this list. If the column already exists in the sort info, the index will be
+ * adjusted to account for any removed entries.
+ *
+ * @param sortInfo the {@link ColumnSortInfo} to add
+ */
+ public void insert(int index, ColumnSortInfo sortInfo) {
+ if (sortInfo == null) {
+ throw new IllegalArgumentException("sortInfo cannot be null");
+ }
+
+ // Remove sort info for duplicate columns
+ Column<?, ?> column = sortInfo.getColumn();
+ for (int i = 0; i < infos.size(); i++) {
+ ColumnSortInfo curInfo = infos.get(i);
+ if (curInfo.getColumn() == column) {
+ infos.remove(i);
+ if (i < index) {
+ index--;
+ }
+ i--;
+ }
+ }
+
+ // Insert the new sort info
+ infos.add(index, sortInfo);
+ fireDelegate();
+ }
+
+ /**
+ * Push a {@link Column} onto the list at index zero, setting ascending to
+ * true. If the column already exists, it will be removed from its current
+ * position and placed at the start of the list. If the Column is already at
+ * the start of the list, its ascending bit will be flipped (ascending to
+ * descending and vice versa).
+ *
+ * @param column the {@link Column} to push
+ * @return the {@link ColumnSortInfo} that was pushed
+ */
+ public ColumnSortInfo push(Column<?, ?> column) {
+ // If the column matches the primary column, toggle the order.
+ boolean ascending = true;
+ if (size() > 0 && get(0).getColumn() == column) {
+ ascending = !get(0).isAscending();
+ }
+
+ // Push the new column.
+ ColumnSortInfo toRet = new ColumnSortInfo(column, ascending);
+ push(toRet);
+ return toRet;
+ }
+
+ /**
+ * Push a {@link ColumnSortInfo} onto the list at index zero. If the column
+ * already exists, it will be removed from its current position and placed at
+ * the start of the list.
+ *
+ * @param sortInfo the {@link ColumnSortInfo} to push
+ */
+ public void push(ColumnSortInfo sortInfo) {
+ insert(0, sortInfo);
+ }
+
+ /**
+ * Remove a {@link ColumnSortInfo} from the list.
+ *
+ * @param sortInfo the {@link ColumnSortInfo} to remove
+ */
+ public boolean remove(ColumnSortInfo sortInfo) {
+ boolean toRet = infos.remove(sortInfo);
+ fireDelegate();
+ return toRet;
+ }
+
+ /**
+ * Get the size of the list.
+ *
+ * @return the number of {@link ColumnSortInfo} in the list
+ */
+ public int size() {
+ return infos.size();
+ }
+
+ private void fireDelegate() {
+ if (delegate != null) {
+ delegate.onModification();
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/sortAscending.png b/user/src/com/google/gwt/user/cellview/client/sortAscending.png
new file mode 100644
index 0000000..816198f
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/sortAscending.png
Binary files differ
diff --git a/user/src/com/google/gwt/user/cellview/client/sortDescending.png b/user/src/com/google/gwt/user/cellview/client/sortDescending.png
new file mode 100644
index 0000000..05b34f3
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/sortDescending.png
Binary files differ
diff --git a/user/test/com/google/gwt/user/cellview/CellViewSuite.java b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
index e2e8370..3dd2f6e 100644
--- a/user/test/com/google/gwt/user/cellview/CellViewSuite.java
+++ b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
@@ -23,6 +23,8 @@
import com.google.gwt.user.cellview.client.CellTableTest;
import com.google.gwt.user.cellview.client.CellTreeTest;
import com.google.gwt.user.cellview.client.CellWidgetTest;
+import com.google.gwt.user.cellview.client.ColumnSortInfoTest;
+import com.google.gwt.user.cellview.client.ColumnSortListTest;
import com.google.gwt.user.cellview.client.ColumnTest;
import com.google.gwt.user.cellview.client.HasDataPresenterTest;
import com.google.gwt.user.cellview.client.PageSizePagerTest;
@@ -44,6 +46,8 @@
suite.addTestSuite(CellTableTest.class);
suite.addTestSuite(CellTreeTest.class);
suite.addTestSuite(CellWidgetTest.class);
+ suite.addTestSuite(ColumnSortInfoTest.class);
+ suite.addTestSuite(ColumnSortListTest.class);
suite.addTestSuite(ColumnTest.class);
suite.addTestSuite(HasDataPresenterTest.class);
suite.addTestSuite(PageSizePagerTest.class);
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
index 6d3115c..6585f0e 100644
--- a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
@@ -162,6 +162,26 @@
RootPanel.get().remove(table);
}
+ public void testGetColumnIndex() {
+ CellTable<String> table = new CellTable<String>();
+ Column<String, String> col0 = new IdentityColumn<String>(new TextCell());
+ table.addColumn(col0);
+ Column<String, String> col1 = new IdentityColumn<String>(new TextCell());
+ table.addColumn(col1);
+ Column<String, String> col2 = new IdentityColumn<String>(new TextCell());
+ table.addColumn(col2);
+ assertEquals(0, table.getColumnIndex(col0));
+ assertEquals(1, table.getColumnIndex(col1));
+ assertEquals(2, table.getColumnIndex(col2));
+
+ // Test a column that is not in the table.
+ Column<String, String> other = new IdentityColumn<String>(new TextCell());
+ assertEquals(-1, table.getColumnIndex(other));
+
+ // Test null.
+ assertEquals(-1, table.getColumnIndex(null));
+ }
+
public void testGetColumnOutOfBounds() {
CellTable<String> table = new CellTable<String>();
@@ -363,6 +383,54 @@
styleLastColumn));
}
+ public void testSortableColumn() {
+ CellTable<String> table = createAbstractHasData(new TextCell());
+ table.getColumn(0).setSortable(true);
+ table.getPresenter().flush();
+ RootPanel.get().add(table);
+
+ // Add a column sort handler.
+ final List<Column<?, ?>> lastSorted = new ArrayList<Column<?, ?>>();
+ table.addColumnSortHandler(new ColumnSortEvent.Handler() {
+ public void onColumnSort(ColumnSortEvent event) {
+ lastSorted.clear();
+ lastSorted.add(event.getColumn());
+ }
+ });
+
+ // Default sort order is empty.
+ ColumnSortList sortList = table.getColumnSortList();
+ assertEquals(0, sortList.size());
+
+ // Sort a column that is sortable.
+ NativeEvent click = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
+ getHeaderElement(table, 0).dispatchEvent(click);
+ assertEquals(1, sortList.size());
+ assertEquals(table.getColumn(0), sortList.get(0).getColumn());
+ assertTrue(sortList.get(0).isAscending());
+ assertEquals(1, lastSorted.size());
+ lastSorted.clear();
+
+ // Sort the same column again.
+ getHeaderElement(table, 0).dispatchEvent(click);
+ assertEquals(1, sortList.size());
+ assertEquals(table.getColumn(0), sortList.get(0).getColumn());
+ assertFalse(sortList.get(0).isAscending());
+ assertEquals(1, lastSorted.size());
+ lastSorted.clear();
+
+ // Sort a column that is not sortable.
+ getHeaderElement(table, 1).dispatchEvent(click);
+ assertEquals(1, sortList.size());
+ assertEquals(table.getColumn(0), sortList.get(0).getColumn());
+ assertFalse(sortList.get(0).isAscending());
+ assertEquals(0, lastSorted.size());
+
+ // Cleanup.
+ RootPanel.get().remove(table);
+ }
+
@Override
protected CellTable<String> createAbstractHasData(Cell<String> cell) {
CellTable<String> table = new CellTable<String>();
@@ -371,13 +439,13 @@
public String getValue(String object) {
return object;
}
- });
+ }, "Column 0");
table.addColumn(new Column<String, String>(new TextCell()) {
@Override
public String getValue(String object) {
return object + "-2";
}
- });
+ }, "Column 1");
return table;
}
diff --git a/user/test/com/google/gwt/user/cellview/client/ColumnSortInfoTest.java b/user/test/com/google/gwt/user/cellview/client/ColumnSortInfoTest.java
new file mode 100644
index 0000000..bc4f49d
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/ColumnSortInfoTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cell.client.TextCell;
+import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link ColumnSortInfo}.
+ */
+public class ColumnSortInfoTest extends TestCase {
+
+ public void testAccessors() {
+ Column<String, String> column = new IdentityColumn<String>(new TextCell());
+ ColumnSortInfo info = new ColumnSortInfo(column, true);
+ assertEquals(column, info.getColumn());
+ assertTrue(info.isAscending());
+ }
+
+ public void testEquals() {
+ // Test equals.
+ Column<String, String> column0 = new IdentityColumn<String>(new TextCell());
+ ColumnSortInfo info0a = new ColumnSortInfo(column0, true);
+ ColumnSortInfo info0b = new ColumnSortInfo(column0, true);
+ assertTrue(info0a.equals(info0b));
+ assertTrue(info0b.equals(info0a));
+ assertEquals(info0a.hashCode(), info0b.hashCode());
+
+ // Test null.
+ assertFalse(info0a.equals(null));
+
+ // Test different object.
+ assertFalse(info0a.equals("not a ColumnSortInfo"));
+
+ // Test different sort order.
+ ColumnSortInfo info0desc = new ColumnSortInfo(column0, false);
+ assertFalse(info0a.equals(info0desc));
+ assertFalse(info0desc.equals(info0a));
+ assertTrue(info0a.hashCode() != info0desc.hashCode());
+
+ // Test different column.
+ Column<String, String> column1 = new IdentityColumn<String>(new TextCell());
+ ColumnSortInfo info1 = new ColumnSortInfo(column1, true);
+ assertFalse(info0a.equals(info1));
+ assertFalse(info1.equals(info0a));
+ assertTrue(info0a.hashCode() != info1.hashCode());
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/user/cellview/client/ColumnSortListTest.java b/user/test/com/google/gwt/user/cellview/client/ColumnSortListTest.java
new file mode 100644
index 0000000..57f30df
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/ColumnSortListTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.cell.client.TextCell;
+import com.google.gwt.user.cellview.client.ColumnSortList.ColumnSortInfo;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link ColumnSortList}.
+ */
+public class ColumnSortListTest extends TestCase {
+
+ public void testClear() {
+ ColumnSortList list = new ColumnSortList();
+ assertEquals(0, list.size());
+
+ list.push(createColumnSortInfo());
+ list.push(createColumnSortInfo());
+ assertEquals(2, list.size());
+ list.clear();
+ assertEquals(0, list.size());
+ }
+
+ public void testEquals() {
+ ColumnSortList list0 = new ColumnSortList();
+ ColumnSortList list1 = new ColumnSortList();
+
+ // Compare empty lists.
+ assertTrue(list0.equals(list1));
+ assertTrue(list1.equals(list0));
+ assertEquals(list0.hashCode(), list1.hashCode());
+
+ // Compare with one item.
+ ColumnSortInfo info0 = createColumnSortInfo();
+ list0.push(info0);
+ list1.push(info0);
+ assertTrue(list0.equals(list1));
+ assertTrue(list1.equals(list0));
+ assertEquals(list0.hashCode(), list1.hashCode());
+
+ // Compare different sizes.
+ ColumnSortInfo info1 = createColumnSortInfo();
+ list0.push(info1);
+ assertFalse(list0.equals(list1));
+ assertFalse(list1.equals(list0));
+ assertFalse(list0.hashCode() == list1.hashCode());
+ list1.push(info1); // Make the lists equal again.
+
+ // Compare with different items that equals each other.
+ ColumnSortInfo info2a = createColumnSortInfo();
+ ColumnSortInfo info2b = new ColumnSortInfo(info2a.getColumn(),
+ info2a.isAscending());
+ list0.push(info2a);
+ list1.push(info2b);
+ assertTrue(list0.equals(list1));
+ assertTrue(list1.equals(list0));
+ assertEquals(list0.hashCode(), list1.hashCode());
+
+ // Compare same items, but out of order.
+ list0.push(info0);
+ assertFalse(list0.equals(list1));
+ assertFalse(list1.equals(list0));
+ assertFalse(list0.hashCode() == list1.hashCode());
+
+ // Compare to null.
+ assertFalse(list0.equals(null));
+ assertFalse(list1.equals(null));
+ }
+
+ public void testInsert() {
+ ColumnSortList list = new ColumnSortList();
+ assertEquals(0, list.size());
+
+ // Insert into an empty list.
+ ColumnSortInfo info0 = createColumnSortInfo();
+ list.insert(0, info0);
+ assertEquals(1, list.size());
+ assertEquals(info0, list.get(0));
+
+ // Insert null.
+ try {
+ list.insert(0, (ColumnSortInfo) null);
+ fail("Expected IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+
+ // Insert the same item.
+ list.insert(0, info0);
+ assertEquals(1, list.size());
+ assertEquals(info0, list.get(0));
+
+ // Insert a second item at index 0.
+ ColumnSortInfo info1 = createColumnSortInfo();
+ list.insert(0, info1);
+ assertEquals(2, list.size());
+ assertEquals(info1, list.get(0));
+ assertEquals(info0, list.get(1));
+
+ // Insert a third item at the last index.
+ ColumnSortInfo info2 = createColumnSortInfo();
+ list.insert(list.size(), info2);
+ assertEquals(3, list.size());
+ assertEquals(info1, list.get(0));
+ assertEquals(info0, list.get(1));
+ assertEquals(info2, list.get(2));
+
+ // Insert item0 again. It should move to the new index.
+ list.insert(list.size(), info0);
+ assertEquals(3, list.size());
+ assertEquals(info1, list.get(0));
+ assertEquals(info2, list.get(1));
+ assertEquals(info0, list.get(2));
+ }
+
+ public void testPushColumn() {
+ ColumnSortList list = new ColumnSortList();
+ assertEquals(0, list.size());
+
+ // Push an item.
+ Column<String, String> col0 = new IdentityColumn<String>(new TextCell());
+ ColumnSortInfo item0 = list.push(col0);
+ assertEquals(1, list.size());
+ assertEquals(item0, list.get(0));
+ assertEquals(col0, list.get(0).getColumn());
+ assertTrue(list.get(0).isAscending());
+
+ // Push the same item. Should change sort order.
+ ColumnSortInfo item0desc = list.push(col0);
+ assertEquals(1, list.size());
+ assertEquals(item0desc, list.get(0));
+ assertEquals(col0, list.get(0).getColumn());
+ assertFalse(list.get(0).isAscending());
+
+ // Push a second item.
+ Column<String, String> col1 = new IdentityColumn<String>(new TextCell());
+ list.push(col1);
+ assertEquals(2, list.size());
+ assertEquals(col1, list.get(0).getColumn());
+ assertTrue(list.get(0).isAscending());
+ assertEquals(col0, list.get(1).getColumn());
+ assertFalse(list.get(1).isAscending());
+
+ // Push a third item.
+ Column<String, String> col2 = new IdentityColumn<String>(new TextCell());
+ list.push(col2);
+ assertEquals(3, list.size());
+ assertEquals(col2, list.get(0).getColumn());
+ assertTrue(list.get(0).isAscending());
+ assertEquals(col1, list.get(1).getColumn());
+ assertTrue(list.get(1).isAscending());
+ assertEquals(col0, list.get(2).getColumn());
+ assertFalse(list.get(2).isAscending());
+
+ // Push col0 again. Should move back to the front in ascending order.
+ list.push(col0);
+ assertEquals(3, list.size());
+ assertEquals(col0, list.get(0).getColumn());
+ assertTrue(list.get(0).isAscending());
+ assertEquals(col2, list.get(1).getColumn());
+ assertTrue(list.get(1).isAscending());
+ assertEquals(col1, list.get(2).getColumn());
+ assertTrue(list.get(2).isAscending());
+ }
+
+ public void testPushColumnSortInfo() {
+ ColumnSortList list = new ColumnSortList();
+ assertEquals(0, list.size());
+
+ // Push null.
+ try {
+ list.push((ColumnSortInfo) null);
+ fail("Expected IllegalArgumentException.");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+
+ // Push an item.
+ ColumnSortInfo info0 = createColumnSortInfo();
+ list.push(info0);
+ assertEquals(1, list.size());
+ assertEquals(info0, list.get(0));
+
+ // Push the same item.
+ list.push(info0);
+ assertEquals(1, list.size());
+ assertEquals(info0, list.get(0));
+
+ // Push a second item.
+ ColumnSortInfo info1 = createColumnSortInfo();
+ list.push(info1);
+ assertEquals(2, list.size());
+ assertEquals(info1, list.get(0));
+ assertEquals(info0, list.get(1));
+
+ // Push a third item.
+ ColumnSortInfo info2 = createColumnSortInfo();
+ list.push(info2);
+ assertEquals(3, list.size());
+ assertEquals(info2, list.get(0));
+ assertEquals(info1, list.get(1));
+ assertEquals(info0, list.get(2));
+
+ // Push item0 again. Should move back to the front
+ list.push(info0);
+ assertEquals(3, list.size());
+ assertEquals(info0, list.get(0));
+ assertEquals(info2, list.get(1));
+ assertEquals(info1, list.get(2));
+
+ // Push a fourth item with the same column as item1. Should remove item1.
+ ColumnSortInfo info1b = new ColumnSortInfo(info1.getColumn(), false);
+ list.push(info1b);
+ assertEquals(3, list.size());
+ assertEquals(info1b, list.get(0));
+ assertEquals(info0, list.get(1));
+ assertEquals(info2, list.get(2));
+ }
+
+ /**
+ * Verify that the Column can be null.
+ */
+ public void testPushNullColumn() {
+ ColumnSortList list = new ColumnSortList();
+ assertEquals(0, list.size());
+
+ // Push an null column.
+ ColumnSortInfo info0 = list.push((Column<?, ?>) null);
+ assertEquals(1, list.size());
+ assertNull(info0.getColumn());
+ assertTrue(info0.isAscending());
+
+ // Push null again.
+ ColumnSortInfo info1 = list.push((Column<?, ?>) null);
+ assertEquals(1, list.size());
+ assertNull(info1.getColumn());
+ assertFalse(info1.isAscending());
+
+ // Push a non-null value.
+ ColumnSortInfo info2 = createColumnSortInfo();
+ list.push(info2);
+ assertEquals(2, list.size());
+ assertNull(list.get(1).getColumn());
+
+ // Push null again.
+ list.push((Column<?, ?>) null);
+ assertEquals(2, list.size());
+ assertNull(list.get(0).getColumn());
+ assertEquals(info2, list.get(1));
+ }
+
+ public void testRemove() {
+ ColumnSortList list = new ColumnSortList();
+
+ // Remove the only item.
+ ColumnSortInfo info = createColumnSortInfo();
+ list.push(info);
+ assertEquals(1, list.size());
+ assertTrue(list.remove(info));
+ assertEquals(0, list.size());
+
+ // Remove a middle item.
+ ColumnSortInfo info0 = createColumnSortInfo();
+ ColumnSortInfo info1 = createColumnSortInfo();
+ ColumnSortInfo info2 = createColumnSortInfo();
+ list.push(info0);
+ list.push(info1);
+ list.push(info2);
+ assertEquals(3, list.size());
+ assertTrue(list.remove(info1));
+ assertEquals(2, list.size());
+ assertEquals(info2, list.get(0));
+ assertEquals(info0, list.get(1));
+
+ // Remove an item that doesn't exist.
+ assertFalse(list.remove(createColumnSortInfo()));
+ }
+
+ /**
+ * Create a {@link ColumnSortInfo} with a unique column and cell.
+ *
+ * @return a new {@link ColumnSortInfo}
+ */
+ private ColumnSortInfo createColumnSortInfo() {
+ return new ColumnSortInfo(new IdentityColumn<String>(new TextCell()), true);
+ }
+
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/user/cellview/client/ColumnTest.java b/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
index 6190c90..5c9817f 100644
--- a/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
@@ -207,4 +207,16 @@
column.render(context, "test", sb);
assertEquals("test", sb.toSafeHtml().asString());
}
+
+ public void testSetSortable() {
+ TextCell cell = new TextCell();
+ Column<String, String> column = new IdentityColumn<String>(cell);
+ assertFalse(column.isSortable());
+
+ column.setSortable(true);
+ assertTrue(column.isSortable());
+
+ column.setSortable(false);
+ assertFalse(column.isSortable());
+ }
}