Implement a selection column, sortable columns, and row hovering in MailRecipe
Review at http://gwt-code-reviews.appspot.com/356801
Review by: jgw@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7936 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java
new file mode 100644
index 0000000..3a1e872
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java
@@ -0,0 +1,56 @@
+/*
+ * 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.bikeshed.cells.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * A {@link Cell} used to render text. Clicking on the call causes its
+ * @{link ValueUpdater} to be called.
+ */
+public class ClickableTextCell extends Cell<String, Void> {
+
+ private static ClickableTextCell instance;
+
+ public static ClickableTextCell getInstance() {
+ if (instance == null) {
+ instance = new ClickableTextCell();
+ }
+ return instance;
+ }
+
+ private ClickableTextCell() {
+ }
+
+ @Override
+ public Void onBrowserEvent(Element parent, String value, Void viewData,
+ NativeEvent event, ValueUpdater<String, Void> valueUpdater) {
+ String type = event.getType();
+ System.out.println(type);
+ if (type.equals("click")) {
+ valueUpdater.update(value, null);
+ }
+ return null;
+ }
+
+ @Override
+ public void render(String value, Void viewData, StringBuilder sb) {
+ if (value != null) {
+ sb.append(value);
+ }
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
index e3f6ba4..01b41d3 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
@@ -90,8 +90,7 @@
}
public void render(T object, StringBuilder sb) {
- C value = getValue(object);
- cell.render(value, viewDataMap.get(object), sb);
+ cell.render(getValue(object), viewDataMap.get(object), sb);
}
public void setFieldUpdater(FieldUpdater<T, C, V> fieldUpdater) {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
index 37a5f29..35f2936 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
@@ -25,32 +25,31 @@
*
* @param <H> the {#link Cell} type
*/
-public class Header<H> {
+public abstract class Header<H> {
+
private final Cell<H, Void> cell;
+
private ValueUpdater<H, Void> updater;
- private H value;
public Header(Cell<H, Void> cell) {
this.cell = cell;
}
- public H getValue() {
- return value;
+ public boolean dependsOnSelection() {
+ return false;
}
+ public abstract H getValue();
+
public void onBrowserEvent(Element elem, NativeEvent event) {
- cell.onBrowserEvent(elem, value, null, event, updater);
+ cell.onBrowserEvent(elem, getValue(), null, event, updater);
}
public void render(StringBuilder sb) {
- cell.render(value, null, sb);
+ cell.render(getValue(), null, sb);
}
public void setUpdater(ValueUpdater<H, Void> updater) {
this.updater = updater;
}
-
- public void setValue(H value) {
- this.value = value;
- }
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
index 703f902..48048be 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
@@ -60,14 +60,13 @@
private Delegate<T> delegate;
private List<Header<?>> footers = new ArrayList<Header<?>>();
private List<Header<?>> headers = new ArrayList<Header<?>>();
+ private TableRowElement hoveringRow;
private int numPages;
private int pageSize;
private ProvidesKey<T> providesKey;
private HandlerRegistration selectionHandler;
private SelectionModel<T> selectionModel;
-
private TableElement table;
-
private TableSectionElement tbody;
private TableSectionElement tfoot;
private TableSectionElement thead;
@@ -171,6 +170,18 @@
}
} else if (section == tbody) {
int row = tr.getSectionRowIndex();
+
+ if (event.getType().equals("mouseover")) {
+ if (hoveringRow != null) {
+ hoveringRow.removeClassName("hover");
+ }
+ hoveringRow = tr;
+ tr.addClassName("hover");
+ } else if (event.getType().equals("mouseout")) {
+ hoveringRow = null;
+ tr.removeClassName("hover");
+ }
+
T value = data.get(row);
Column<T, ?, ?> column = columns.get(col);
@@ -205,6 +216,18 @@
}
public void refreshSelection() {
+ // Refresh headers
+ Element th = thead.getFirstChild().getFirstChild().cast();
+ for (Header<?> header : headers) {
+ if (header.dependsOnSelection()) {
+ StringBuilder sb = new StringBuilder();
+ header.render(sb);
+ th.setInnerHTML(sb.toString());
+ }
+ th = th.getNextSibling().cast();
+ }
+
+ // Refresh body
NodeList<TableRowElement> rows = tbody.getRows();
for (int indexOnPage = 0; indexOnPage < pageSize; indexOnPage++) {
TableRowElement row = rows.getItem(indexOnPage);
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
index cc59ef7..0890c3b 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
@@ -22,8 +22,15 @@
*/
public class TextHeader extends Header<String> {
+ private String text;
+
public TextHeader(String text) {
super(TextCell.getInstance());
- setValue(text);
+ this.text = text;
+ }
+
+ @Override
+ public String getValue() {
+ return text;
}
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java
index 4124ab0..1d862c3 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java
@@ -33,6 +33,53 @@
*/
public class ListViewAdapter<T> extends AbstractListViewAdapter<T> {
+ private class WrappedListIterator implements ListIterator<T> {
+
+ int index;
+ private ListWrapper wrapper;
+
+ public WrappedListIterator(ListWrapper listWrapper, int index) {
+ this.wrapper = listWrapper;
+ this.index = index;
+ }
+
+ public void add(T o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasNext() {
+ return index < wrapper.size();
+ }
+
+ public boolean hasPrevious() {
+ return index > 0;
+ }
+
+ public T next() {
+ return wrapper.get(index++);
+ }
+
+ public int nextIndex() {
+ return index;
+ }
+
+ public T previous() {
+ return wrapper.get(--index);
+ }
+
+ public int previousIndex() {
+ return index - 1;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void set(T o) {
+ wrapper.set(index, o);
+ }
+ }
+
/**
* A wrapper around a list that updates the model on any change.
*/
@@ -188,13 +235,11 @@
}
public ListIterator<T> listIterator() {
- // TODO(jlabanca): Wrap the iterator
- return list.listIterator();
+ return new WrappedListIterator(this, 0);
}
public ListIterator<T> listIterator(int index) {
- // TODO(jlabanca): Wrap the iterator
- return list.listIterator(index);
+ return new WrappedListIterator(this, index);
}
public T remove(int index) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
index 11b5e49..00b555b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
@@ -61,8 +61,12 @@
}
EditTextColumn column = new EditTextColumn();
- Header<String> header = new Header<String>(TextCell.getInstance());
- header.setValue("<b>item</b>");
+ Header<String> header = new Header<String>(TextCell.getInstance()) {
+ @Override
+ public String getValue() {
+ return "<b>item</b>";
+ }
+ };
table.addColumn(column, header);
column.setFieldUpdater(new FieldUpdater<String, String, String>() {
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 a099a0a..de642da 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
@@ -16,13 +16,17 @@
package com.google.gwt.sample.bikeshed.cookbook.client;
import com.google.gwt.bikeshed.cells.client.ButtonCell;
+import com.google.gwt.bikeshed.cells.client.Cell;
import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.DatePickerCell;
+import com.google.gwt.bikeshed.cells.client.ClickableTextCell;
+import com.google.gwt.bikeshed.cells.client.DateCell;
import com.google.gwt.bikeshed.cells.client.FieldUpdater;
+import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.bikeshed.cells.client.ValueUpdater;
import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.Header;
import com.google.gwt.bikeshed.list.client.PagingTableListView;
import com.google.gwt.bikeshed.list.client.SimpleColumn;
-import com.google.gwt.bikeshed.list.client.TextColumn;
import com.google.gwt.bikeshed.list.shared.DefaultSelectionModel;
import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
import com.google.gwt.bikeshed.list.shared.ProvidesKey;
@@ -39,6 +43,8 @@
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -51,6 +57,10 @@
*/
public class MailRecipe extends Recipe implements ClickHandler {
+ static interface GetValue<T, C> {
+ C getValue(T object);
+ }
+
static class MailSelectionModel extends DefaultSelectionModel<Message> {
enum Type {
ALL(), NONE(), READ(), SENDER(), SUBJECT(), UNREAD();
@@ -77,6 +87,10 @@
return keyProvider;
}
+ public String getType() {
+ return type.toString();
+ }
+
@Override
public boolean isDefaultSelected(Message object) {
switch (type) {
@@ -165,11 +179,11 @@
// Hashing, comparison, and equality are based on the message id
static class Message {
+ Date date;
int id;
boolean isRead;
String sender;
String subject;
- Date date;
public Message(int id, String sender, String subject, Date date) {
super();
@@ -229,14 +243,14 @@
"Kaan Boulier", "Emilee Naoma", "Atino Alice", "Debby Renay",
"Versie Nereida", "Ramon Erikson", "Karole Crissy", "Nelda Olsen",
"Mariana Dann", "Reda Cheyenne", "Edelmira Jody", "Agueda Shante",
- "Marla Dorris"
- };
+ "Marla Dorris"};
private static final String[] subjects = {
"GWT rocks", "What's a widget?", "Money in Nigeria",
"Impress your colleagues with bling-bling", "Degree available",
- "Rolex Watches", "Re: Re: yo bud", "Important notice"
- };
+ "Rolex Watches", "Re: Re: yo bud", "Important notice"};
+
+ private List<Message> messages;
private MailSelectionModel selectionModel = new MailSelectionModel();
@@ -264,7 +278,8 @@
@Override
protected Widget createWidget() {
ListViewAdapter<Message> adapter = new ListViewAdapter<Message>();
- List<Message> messages = adapter.getList();
+ messages = adapter.getList();
+
Date now = new Date();
Random rand = new Random();
for (int i = 0; i < 1000; i++) {
@@ -272,60 +287,82 @@
long dateOffset = rand.nextInt(60 * 60 * 24 * 90) * 1000L;
Message message = new Message(10000 + i,
senders[rand.nextInt(senders.length)],
- subjects[rand.nextInt(subjects.length)],
- new Date(now.getTime() - dateOffset));
+ subjects[rand.nextInt(subjects.length)], new Date(now.getTime()
+ - dateOffset));
message.isRead = rand.nextBoolean();
messages.add(message);
}
+ final Comparator<Message> idComparator = new Comparator<Message>() {
+ public int compare(Message o1, Message o2) {
+ // Integer comparison
+ return o1.id - o2.id;
+ }
+ };
+
+ final Comparator<Message> dateComparator = new Comparator<Message>() {
+ public int compare(Message o1, Message o2) {
+ long cmp = o1.date.getTime() - o2.date.getTime();
+ if (cmp < 0) {
+ return -1;
+ } else if (cmp > 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ sortMessages(idComparator, true);
+
table = new PagingTableListView<Message>(adapter, 10);
table.setSelectionModel(selectionModel);
adapter.addView(table);
- // The state of the checkbox is taken from the selection model
- SimpleColumn<Message, Boolean> selectedColumn = new SimpleColumn<Message, Boolean>(
- new CheckboxCell()) {
+ // The state of the checkbox is synchronized with the selection model
+ SelectionColumn<Message> selectedColumn = new SelectionColumn<Message>(
+ selectionModel);
+ Header<Boolean> selectedHeader = new Header<Boolean>(new CheckboxCell()) {
@Override
public boolean dependsOnSelection() {
return true;
}
@Override
- public Boolean getValue(Message object) {
- return selectionModel.isSelected(object);
+ public Boolean getValue() {
+ return selectionModel.getType().equals("ALL");
}
};
- // Update the selection model when the checkbox is changed manually
- selectedColumn.setFieldUpdater(new FieldUpdater<Message, Boolean, Void>() {
- public void update(int index, Message object, Boolean value, Void viewData) {
- selectionModel.setSelected(object, value);
+ selectedHeader.setUpdater(new ValueUpdater<Boolean, Void>() {
+ public void update(Boolean value, Void viewData) {
+ if (value == true) {
+ selectionModel.setType("ALL");
+ } else if (value == false) {
+ selectionModel.setType("NONE");
+ }
}
});
- table.addColumn(selectedColumn, "Selected");
+ table.addColumn(selectedColumn, selectedHeader);
- TextColumn<Message> idColumn = new TextColumn<Message>() {
- @Override
- public String getValue(Message object) {
- return "" + object.id;
- }
- };
- table.addColumn(idColumn, "ID");
+ addColumn(table, "ID", TextCell.getInstance(),
+ new GetValue<Message, String>() {
+ public String getValue(Message object) {
+ return "" + object.id;
+ }
+ }, idComparator);
- TextColumn<Message> isReadColumn = new TextColumn<Message>() {
- @Override
+ addColumn(table, "Read", new GetValue<Message, String>() {
public String getValue(Message object) {
return object.isRead ? "read" : "unread";
}
- };
- table.addColumn(isReadColumn, "Read");
+ });
- Column<Message, Date, Void> dateColumn =
- new Column<Message, Date, Void>(new DatePickerCell<Void>()) {
- @Override
- public Date getValue(Message object) {
- return object.getDate();
- }
- };
+ Column<Message, Date, Void> dateColumn = addColumn(table, "Date",
+ new DateCell(), new GetValue<Message, Date>() {
+ public Date getValue(Message object) {
+ return object.date;
+ }
+ }, dateComparator);
dateColumn.setFieldUpdater(new FieldUpdater<Message, Date, Void>() {
public void update(int index, Message object, Date value, Void viewData) {
Window.alert("Changed date from " + object.date + " to " + value);
@@ -333,26 +370,21 @@
table.refresh();
}
});
- table.addColumn(dateColumn, "Date");
- TextColumn<Message> senderColumn = new TextColumn<Message>() {
- @Override
+ addColumn(table, "Sender", new GetValue<Message, String>() {
public String getValue(Message object) {
return object.getSender();
}
- };
- table.addColumn(senderColumn, "Sender");
+ });
- TextColumn<Message> subjectColumn = new TextColumn<Message>() {
- @Override
+ addColumn(table, "Subject", new GetValue<Message, String>() {
public String getValue(Message object) {
return object.getSubject();
}
- };
- table.addColumn(subjectColumn, "Subject");
+ });
- SimpleColumn<Message, String> toggleColumn =
- new SimpleColumn<Message, String>(ButtonCell.getInstance()) {
+ SimpleColumn<Message, String> toggleColumn = new SimpleColumn<Message, String>(
+ ButtonCell.getInstance()) {
@Override
public String getValue(Message object) {
return object.isRead ? "Mark Unread" : "Mark Read";
@@ -395,10 +427,64 @@
return p;
}
+ private Column<Message, String, Void> addColumn(
+ PagingTableListView<Message> table, final String text,
+ final GetValue<Message, String> getter) {
+ return addColumn(table, text, TextCell.getInstance(), getter, null);
+ }
+
+ private <C extends Comparable<C>> Column<Message, C, Void> addColumn(
+ PagingTableListView<Message> table, final String text,
+ final Cell<C, Void> cell, final GetValue<Message, C> getter,
+ final Comparator<Message> comparator) {
+ Column<Message, C, Void> column = new Column<Message, C, Void>(cell) {
+ @Override
+ public C getValue(Message object) {
+ return getter.getValue(object);
+ }
+ };
+ Header<String> header = new Header<String>(ClickableTextCell.getInstance()) {
+ @Override
+ public String getValue() {
+ return text;
+ }
+ };
+ header.setUpdater(new ValueUpdater<String, Void>() {
+ boolean sortUp = true;
+
+ public void update(String value, Void viewData) {
+ if (comparator == null) {
+ sortMessages(new Comparator<Message>() {
+ public int compare(Message o1, Message o2) {
+ return getter.getValue(o1).compareTo(getter.getValue(o2));
+ }
+ }, sortUp);
+ } else {
+ sortMessages(comparator, sortUp);
+ }
+ sortUp = !sortUp;
+ }
+ });
+ table.addColumn(column, header);
+ return column;
+ }
+
private Button makeButton(String label, String id) {
Button button = new Button(label);
button.getElement().setId(id);
button.addClickHandler(this);
return button;
}
+
+ private void sortMessages(final Comparator<Message> comparator, boolean sortUp) {
+ if (sortUp) {
+ Collections.sort(messages, comparator);
+ } else {
+ Collections.sort(messages, new Comparator<Message>() {
+ public int compare(Message o1, Message o2) {
+ return -comparator.compare(o1, o2);
+ }
+ });
+ }
+ }
}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java
new file mode 100644
index 0000000..3f71a7e
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.bikeshed.cookbook.client;
+
+import com.google.gwt.bikeshed.cells.client.CheckboxCell;
+import com.google.gwt.bikeshed.cells.client.FieldUpdater;
+import com.google.gwt.bikeshed.list.client.SimpleColumn;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
+
+/**
+ * A column that displays a checkbox that is synchronized with a given
+ * selection model.
+ *
+ * @param <T> the record data type, used by the row and the selection model
+ */
+public class SelectionColumn<T> extends SimpleColumn<T, Boolean> {
+
+ private final SelectionModel<T> selectionModel;
+
+ public SelectionColumn(final SelectionModel<T> selectionModel) {
+ super(new CheckboxCell());
+ setFieldUpdater(new FieldUpdater<T, Boolean, Void>() {
+ public void update(int index, T object, Boolean value, Void viewData) {
+ selectionModel.setSelected(object, value);
+ }
+ });
+ this.selectionModel = selectionModel;
+ }
+
+ @Override
+ public boolean dependsOnSelection() {
+ return true;
+ }
+
+ @Override
+ public Boolean getValue(T object) {
+ return selectionModel.isSelected(object);
+ }
+}
diff --git a/bikeshed/war/Cookbook.css b/bikeshed/war/Cookbook.css
index e63c422..de7f1b6 100644
--- a/bikeshed/war/Cookbook.css
+++ b/bikeshed/war/Cookbook.css
@@ -24,6 +24,10 @@
background-color: rgb(220, 220, 220);
}
+tr.hover {
+ background-color: rgb(255, 0, 0);
+}
+
div.gwt-sstree-column {
overflow-y: scroll;
overflow-x: auto;