Clean up mail sample app; simplify table refresh for selection changes
Review at http://gwt-code-reviews.appspot.com/324801
Review by: jgw@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7890 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 3c5eb67..1124b75 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
@@ -50,7 +50,7 @@
private class TableSelectionHandler implements SelectionChangeHandler {
public void onSelectionChange(SelectionChangeEvent event) {
- refresh();
+ refreshSelection();
}
}
@@ -185,6 +185,20 @@
updateRowVisibility();
}
+ public void refreshSelection() {
+ NodeList<TableRowElement> rows = tbody.getRows();
+ for (int indexOnPage = 0; indexOnPage < pageSize; indexOnPage++) {
+ TableRowElement row = rows.getItem(indexOnPage);
+ T q = data.get(indexOnPage);
+ if (q != null && selectionModel != null && selectionModel.isSelected(q)) {
+ row.setClassName("pagingTableListView selected");
+ } else {
+ row.setClassName("pagingTableListView "
+ + ((indexOnPage & 0x1) == 0 ? "evenRow" : "oddRow"));
+ }
+ }
+ }
+
/**
* Set the current visible page.
*
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java
new file mode 100644
index 0000000..31046a0
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java
@@ -0,0 +1,90 @@
+/*
+ * 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.list.shared;
+
+import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link SelectionModel} that allows records to be selected according to
+ * a subclass-defined rule, plus a list of positive or negative exceptions.
+ *
+ * @param <T> the data type of records in the list
+ */
+public abstract class DefaultSelectionModel<T> extends
+ AbstractSelectionModel<T> {
+
+ private final Map<T, Boolean> exceptions = new HashMap<T, Boolean>();
+
+ /**
+ * Removes all exceptions.
+ */
+ public void clearExceptions() {
+ exceptions.clear();
+ scheduleSelectionChangeEvent();
+ }
+
+ /**
+ * Returns true if the given object should be selected by default.
+ * Subclasses implement this method in order to define the default
+ * selection behavior.
+ */
+ public abstract boolean isDefaultSelected(T object);
+
+ /**
+ * If the given object is marked as an exception, return the exception
+ * value. Otherwise, return the value of isDefaultSelected for the given
+ * object.
+ */
+ public boolean isSelected(T object) {
+ // Check exceptions first
+ Boolean exception = exceptions.get(object);
+ if (exception != null) {
+ return exception.booleanValue();
+ }
+ // If not in exceptions, return the default
+ return isDefaultSelected(object);
+ }
+
+ /**
+ * Sets an object's selection state. If the object is currently marked
+ * as an exception, and the new selected state differs from the previous
+ * selected state, the object is removed from the list of exceptions.
+ * Otherwise, the object is added to the list of exceptions with the given
+ * selected state.
+ */
+ public void setSelected(T object, boolean selected) {
+ Boolean currentlySelected = exceptions.get(object);
+ if (currentlySelected != null
+ && currentlySelected.booleanValue() != selected) {
+ exceptions.remove(object);
+ } else {
+ exceptions.put(object, selected);
+ }
+
+ scheduleSelectionChangeEvent();
+ }
+
+ /**
+ * Copies the exceptions map into a user-supplied map.
+ */
+ protected void getExceptions(Map<T, Boolean> output) {
+ output.clear();
+ output.putAll(exceptions);
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java
index 19cda3c..f1ff4ff 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java
@@ -22,7 +22,7 @@
* <p>
* The key must implement a coherent set of {@link #equals(Object)} and
* {@link #hashCode()} methods. If the item type is a not uniquely identifiable,
- * such as a list of {@link String}, the index can be used a the key.
+ * such as a list of {@link String}, the index can be used as the key.
* </p>
*
* @param <T> the data type of records in the list
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
index 67b06d8..9fe4548 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
@@ -120,10 +120,6 @@
handlerManager.fireEvent(event);
}
- public void setSelected(T object, boolean selected) {
- scheduleSelectionChangeEvent();
- }
-
/**
* Schedules a {@link SelectionModel.SelectionChangeEvent} to fire at the
* end of the current event loop.
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml
index 19da671..eaf5297 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
-<module rename-to='mail'>
+<module rename-to='mailsample'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.bikeshed.list.List'/>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java
index e87140b..fa30a91 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java
@@ -15,13 +15,14 @@
*/
package com.google.gwt.sample.bikeshed.mail.client;
+import com.google.gwt.bikeshed.cells.client.ButtonCell;
import com.google.gwt.bikeshed.cells.client.CheckboxCell;
import com.google.gwt.bikeshed.cells.client.FieldUpdater;
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.ListListModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -34,6 +35,7 @@
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -44,122 +46,23 @@
*/
public class MailSample implements EntryPoint, ClickHandler {
- class MailSelectionModel extends AbstractSelectionModel<Message> {
- private static final int ALL = 0;
- private static final int NONE = 1;
- private static final int READ = 2;
- private static final int SENDER = 3;
- private static final int SUBJECT = 4;
- private static final int UNREAD = 5;
+ static class MailSelectionModel extends DefaultSelectionModel<Message> {
+ enum Type {
+ ALL(), NONE(), READ(), SENDER(), SUBJECT(), UNREAD();
- // Use a TreeMap in order to get sorted diagnostic output
- private Map<Integer, Boolean> exceptions = new TreeMap<Integer, Boolean>();
+ Type() {
+ typeMap.put(this.toString(), this);
+ }
+ }
+
+ // A map from enum names to their values
+ private static Map<String, Type> typeMap = new HashMap<String, Type>();
private String search;
- private int type = NONE;
-
- public boolean isSelected(Message object) {
- // Check exceptions
- int id = object.id;
- Boolean exception = exceptions.get(id);
- if (exception != null) {
- return exception.booleanValue();
- }
- // If not in exceptions, return the default
- return isDefaultSelected(object);
- }
-
- public void setSearch(String search) {
- this.search = canonicalize(search);
- updateListeners();
- }
+ private Type type = Type.NONE;
@Override
- public void setSelected(Message object, boolean selected) {
- addException(object.id, selected);
- updateListeners();
- }
-
- public void setType(int type) {
- this.type = type;
- exceptions.clear();
- updateListeners();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- switch (type) {
- case NONE:
- sb.append("NONE ");
- break;
- case ALL:
- sb.append("ALL ");
- break;
- case READ:
- sb.append("READ ");
- break;
- case UNREAD:
- sb.append("UNREAD ");
- break;
- case SENDER:
- sb.append("SENDER ");
- sb.append(search);
- sb.append(' ');
- break;
- case SUBJECT:
- sb.append("SUBJECT ");
- sb.append(search);
- sb.append(' ');
- break;
- }
-
- boolean first = true;
- for (int i : exceptions.keySet()) {
- if (exceptions.get(i) != Boolean.TRUE) {
- continue;
- }
-
- if (first) {
- first = false;
- sb.append("+msg(s) ");
- }
- sb.append(i);
- sb.append(' ');
- }
-
- first = true;
- for (int i : exceptions.keySet()) {
- if (exceptions.get(i) != Boolean.FALSE) {
- continue;
- }
-
- if (first) {
- first = false;
- sb.append("-msg(s) ");
- }
- sb.append(i);
- sb.append(' ');
- }
-
- return sb.toString();
- }
-
- private void addException(int id, boolean selected) {
- Boolean currentlySelected = exceptions.get(id);
- if (currentlySelected != null
- && currentlySelected.booleanValue() != selected) {
- exceptions.remove(id);
- } else {
- exceptions.put(id, selected);
- }
- }
-
- private String canonicalize(String input) {
- return input.toUpperCase();
- }
-
- private boolean isDefaultSelected(Message object) {
+ public boolean isDefaultSelected(Message object) {
switch (type) {
case NONE:
return false;
@@ -184,13 +87,67 @@
}
}
- private void updateListeners() {
- selectionLabel.setText("Selected " + this.toString());
+ public void setSearch(String search) {
+ this.search = canonicalize(search);
scheduleSelectionChangeEvent();
}
+
+ public void setType(String type) {
+ this.type = typeMap.get(type);
+ clearExceptions();
+ scheduleSelectionChangeEvent();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(type.name());
+ sb.append(' ');
+ if (type == Type.SENDER || type == Type.SUBJECT) {
+ sb.append(search);
+ sb.append(' ');
+ }
+
+ // Copy the exceptions into a TreeMap in order to sort by message id
+ TreeMap<Message, Boolean> exceptions = new TreeMap<Message, Boolean>();
+ getExceptions(exceptions);
+
+ appendExceptions(sb, exceptions, true);
+ appendExceptions(sb, exceptions, false);
+
+ return sb.toString();
+ }
+
+ protected void scheduleSelectionChangeEvent() {
+ selectionLabel.setText("Selected " + this.toString());
+ super.scheduleSelectionChangeEvent();
+ }
+
+ private void appendExceptions(StringBuilder sb,
+ Map<Message, Boolean> exceptions, boolean selected) {
+ boolean first = true;
+ for (Message message : exceptions.keySet()) {
+ if (exceptions.get(message) != selected) {
+ continue;
+ }
+
+ if (first) {
+ first = false;
+ sb.append(selected ? '+' : '-');
+ sb.append("msg(s) ");
+ }
+ sb.append(message.id);
+ sb.append(' ');
+ }
+ }
+
+ private String canonicalize(String input) {
+ return input.toUpperCase();
+ }
}
- class Message {
+ // Hashing, comparison, and equality are based on the message id
+ class Message implements Comparable<Message> {
int id;
boolean isRead;
String sender;
@@ -203,6 +160,10 @@
this.subject = subject;
}
+ public int compareTo(Message o) {
+ return id - o.id;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Message)) {
@@ -239,6 +200,8 @@
}
}
+ private static Label selectionLabel = new Label("Selected NONE");
+
private static final String[] senders = {
"test@example.com", "spam1@spam.com", "gwt@google.com", "Mai Oleta",
"Barbara Myles", "Celsa Ocie", "Elwood Holloway", "Bolanle Alford",
@@ -247,48 +210,31 @@
"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 Button allButton = new Button("Select All");
- private Button allOnPageButton = new Button("Select All On This Page");
- private Button noneButton = new Button("Select None");
- private Button readButton = new Button("Select Read");
- private Label selectionLabel = new Label();
private MailSelectionModel selectionModel = new MailSelectionModel();
- private Button senderButton = new Button("Search Senders");
- private Button subjectButton = new Button("Search Subject");
private PagingTableListView<Message> table;
- private Button unreadButton = new Button("Select Unread");
-
// Handle events for all buttons here in order to avoid creating multiple
// ClickHandlers
public void onClick(ClickEvent event) {
- Button source = (Button) event.getSource();
- if (source == noneButton) {
- selectionModel.setType(MailSelectionModel.NONE);
- } else if (source == allOnPageButton) {
- selectionModel.setType(MailSelectionModel.NONE);
+ String id = ((Button) event.getSource()).getElement().getId();
+ if ("PAGE".equals(id)) {
+ // selectionModel.setType(MailSelectionModel.NONE);
List<Message> selectedItems = table.getDisplayedItems();
for (Message item : selectedItems) {
selectionModel.setSelected(item, true);
}
- } else if (source == allButton) {
- selectionModel.setType(MailSelectionModel.ALL);
- } else if (source == readButton) {
- selectionModel.setType(MailSelectionModel.READ);
- } else if (source == unreadButton) {
- selectionModel.setType(MailSelectionModel.UNREAD);
- } else if (source == senderButton) {
- selectionModel.setType(MailSelectionModel.SENDER);
- } else if (source == subjectButton) {
- selectionModel.setType(MailSelectionModel.SUBJECT);
+ } else {
+ selectionModel.setType(id);
}
}
@@ -347,6 +293,21 @@
};
table.addColumn(subjectColumn, "Subject");
+ SimpleColumn<Message, String> toggleColumn =
+ new SimpleColumn<Message, String>(ButtonCell.getInstance()) {
+ @Override
+ public String getValue(Message object) {
+ return object.isRead ? "Mark Unread" : "Mark Read";
+ }
+ };
+ toggleColumn.setFieldUpdater(new FieldUpdater<Message, String, Void>() {
+ public void update(int index, Message object, String value, Void viewData) {
+ object.isRead = !object.isRead;
+ table.refresh();
+ }
+ });
+ table.addColumn(toggleColumn, "Toggle Read/Unread");
+
Label searchLabel = new Label("Search Sender or Subject:");
final TextBox searchBox = new TextBox();
searchBox.addKeyUpHandler(new KeyUpHandler() {
@@ -355,30 +316,29 @@
}
});
- noneButton.addClickHandler(this);
- allOnPageButton.addClickHandler(this);
- allButton.addClickHandler(this);
- readButton.addClickHandler(this);
- unreadButton.addClickHandler(this);
- senderButton.addClickHandler(this);
- subjectButton.addClickHandler(this);
-
HorizontalPanel panel = new HorizontalPanel();
panel.add(searchLabel);
panel.add(searchBox);
RootPanel.get().add(panel);
+ RootPanel.get().add(makeButton("Search Subject", "SUBJECT"));
+ RootPanel.get().add(makeButton("Search Senders", "SENDER"));
RootPanel.get().add(new HTML("<br>"));
RootPanel.get().add(table);
RootPanel.get().add(new HTML("<br>"));
- RootPanel.get().add(noneButton);
- RootPanel.get().add(allOnPageButton);
- RootPanel.get().add(allButton);
- RootPanel.get().add(readButton);
- RootPanel.get().add(unreadButton);
- RootPanel.get().add(subjectButton);
- RootPanel.get().add(senderButton);
+ RootPanel.get().add(makeButton("Select None", "NONE"));
+ RootPanel.get().add(makeButton("Select All On This Page", "PAGE"));
+ RootPanel.get().add(makeButton("Select All", "ALL"));
+ RootPanel.get().add(makeButton("Select Read", "READ"));
+ RootPanel.get().add(makeButton("Select Unread", "UNREAD"));
RootPanel.get().add(new HTML("<hr>"));
RootPanel.get().add(selectionLabel);
}
+
+ private Button makeButton(String label, String id) {
+ Button button = new Button(label);
+ button.getElement().setId(id);
+ button.addClickHandler(this);
+ return button;
+ }
}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
index 63c1fef..8f88176 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
@@ -44,7 +44,6 @@
return selectedSet.contains(object);
}
- @Override
public void setSelected(Object object, boolean selected) {
if (selected) {
selectedSet.add(object);
@@ -52,7 +51,7 @@
selectedSet.remove(object);
}
label.setText("Selected " + selectedSet.toString());
- super.setSelected(object, selected);
+ scheduleSelectionChangeEvent();
}
}
diff --git a/bikeshed/war/Mail.html b/bikeshed/war/Mail.html
index 6f3cf17..ce279e6 100644
--- a/bikeshed/war/Mail.html
+++ b/bikeshed/war/Mail.html
@@ -4,7 +4,7 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link type="text/css" rel="stylesheet" href="Mail.css">
<title>Mail Sample</title>
- <script type="text/javascript" language="javascript" src="mail/mail.nocache.js"></script>
+ <script type="text/javascript" language="javascript" src="mailsample/mailsample.nocache.js"></script>
</head>
<body>