Adds additional keyboard support in Cell Widgets. CellList and CellTable now
support page down/up, home, and end. CellBrowser and CellTree support
left/right to navigate into/out of child nodes. All widgets support SPACE to
select the current item in the SelectionModel. Selection can also be tied to
keyboard selection using the KeyboardSelectionPolicy.BOUND. Pressing the ENTER
key puts the cell in edit mode (by convention defined in AbstractCell), if the
cell supports it. CellList and CellTable support various KeyboardPagingPolicy
that determine how the list pages in response to keyboard movement outside the
current page. The widget can change the page, increase the range, or restrict
the user to the current page. All cell widgets support tabIndex and accessKey.
We now have more styles defined for CellTable, which allows us to add a border
around the current keyboard selected cell.
This change also adds a new API method Cell#resetFocus() that Cells can use
to reset focus after the widget is redrawn. This allows apps to refresh a table
in the background without interupting user interactions. This patch also fixes
some bugs in IE and in the expenses app.
Review at http://gwt-code-reviews.appspot.com/906801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8847 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
index 2878262..f7a8c31 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -31,6 +31,7 @@
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockLayoutPanel;
@@ -129,6 +130,7 @@
table.addColumnStyleName(1, style.thirty());
table.setRowCount(numRows, false);
table.setSelectionModel(selectionModel);
+ table.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
EntityProxyChange.registerForProxyType(eventBus, PersonProxy.class,
new EntityProxyChange.Handler<PersonProxy>() {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
index 783f24d..d745c3d 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetails.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,7 +15,7 @@
*/
package com.google.gwt.sample.expenses.client;
-import com.google.gwt.cell.client.AbstractEditableCell;
+import com.google.gwt.cell.client.AbstractInputCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.DateCell;
import com.google.gwt.cell.client.FieldUpdater;
@@ -55,7 +55,6 @@
import com.google.gwt.sample.expenses.client.request.ReportProxy;
import com.google.gwt.sample.expenses.client.style.Styles;
import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
@@ -108,13 +107,13 @@
interface Template extends SafeHtmlTemplates {
@Template("<select style=\"background-color:white;border:1px solid "
- + "#707172;width:10em;margin-right:10px;\" disabled=\"true\"><option>"
- + "</option>{0}{1}</select>")
+ + "#707172;width:10em;margin-right:10px;\" disabled=\"true\" tabindex=\"-1\">"
+ + "<option></option>{0}{1}</select>")
SafeHtml disabled(SafeHtml approvedOption, SafeHtml deniedOption);
@Template("<select style=\"background-color:white;border:1px solid "
- + "#707172;width:10em;margin-right:10px;\"><option></option>{0}{1}"
- + "</select>")
+ + "#707172;width:10em;margin-right:10px;\" tabindex=\"-1\">"
+ + "<option></option>{0}{1}</select>")
SafeHtml enabled(SafeHtml approvedOption, SafeHtml deniedOption);
}
@@ -122,7 +121,7 @@
* The cell used for approval status.
*/
private class ApprovalCell extends
- AbstractEditableCell<String, ApprovalViewData> {
+ AbstractInputCell<String, ApprovalViewData> {
private final String approvedText = Expenses.Approval.APPROVED.getText();
private final String deniedText = Expenses.Approval.DENIED.getText();
@@ -137,20 +136,25 @@
// Cache the html string for the error icon.
ImageResource errorIcon = Styles.resources().errorIcon();
- AbstractImagePrototype errorImg = AbstractImagePrototype.create(
- errorIcon);
+ AbstractImagePrototype errorImg = AbstractImagePrototype.create(errorIcon);
errorIconHtml = SafeHtmlUtils.fromTrustedString(errorImg.getHTML());
// Cache the html string for the pending icon.
ImageResource pendingIcon = Styles.resources().pendingCommit();
- AbstractImagePrototype pendingImg = AbstractImagePrototype.create(
- pendingIcon);
+ AbstractImagePrototype pendingImg = AbstractImagePrototype.create(pendingIcon);
pendingIconHtml = SafeHtmlUtils.fromTrustedString(pendingImg.getHTML());
}
@Override
+ public boolean isEditing(Element parent, String value, Object key) {
+ return super.isEditing(parent, value, key) || denialPopup.isShowing();
+ }
+
+ @Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+
String type = event.getType();
ApprovalViewData viewData = getViewData(key);
if ("change".equals(type)) {
@@ -170,6 +174,7 @@
String pendingValue = select.getOptions().getItem(index).getValue();
viewData = new ApprovalViewData(pendingValue);
setViewData(key, viewData);
+ finishEditing(parent, pendingValue, key, valueUpdater);
// Update the value updater.
if (valueUpdater != null) {
@@ -231,8 +236,7 @@
if (isRejected) {
// Add error icon if viewData does not match.
sb.append(errorIconHtml);
- sb.appendHtmlConstant(
- "<a style='padding-left:3px;color:red;' href='javascript:;'>Error!</a>");
+ sb.appendHtmlConstant("<a style='padding-left:3px;color:red;' href='javascript:;'>Error!</a>");
} else if (pendingValue != null) {
// Add refresh icon if pending.
sb.append(pendingIconHtml);
@@ -284,18 +288,20 @@
* The popup used to enter the rejection reason.
*/
private class DenialPopup extends PopupPanel {
- private final Button cancelButton = new Button("Cancel", new ClickHandler() {
- public void onClick(ClickEvent event) {
- reasonDenied = "";
- hide();
- }
- });
- private final Button confirmButton = new Button("Confirm", new ClickHandler() {
- public void onClick(ClickEvent event) {
- reasonDenied = reasonBox.getText();
- hide();
- }
- });
+ private final Button cancelButton = new Button("Cancel",
+ new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ reasonDenied = "";
+ hide();
+ }
+ });
+ private final Button confirmButton = new Button("Confirm",
+ new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ reasonDenied = reasonBox.getText();
+ hide();
+ }
+ });
private ExpenseProxy expenseRecord;
private final TextBox reasonBox = new TextBox();
@@ -356,8 +362,7 @@
*/
private static final int REFRESH_INTERVAL = 5000;
- private static ExpenseDetailsUiBinder uiBinder = GWT.create(
- ExpenseDetailsUiBinder.class);
+ private static ExpenseDetailsUiBinder uiBinder = GWT.create(ExpenseDetailsUiBinder.class);
@UiField
Element approvedLabel;
@@ -387,7 +392,7 @@
Anchor reportsLink;
@UiField(provided = true)
- CellTable<ExpenseProxy> table = new CellTable<ExpenseProxy>(Expenses.EXPENSE_RECORD_KEY_PROVIDER);
+ CellTable<ExpenseProxy> table;
@UiField
Element unreconciledLabel;
@@ -402,6 +407,11 @@
private Comparator<ExpenseProxy> defaultComparator;
/**
+ * The popup used when something is denied.
+ */
+ private final DenialPopup denialPopup = new DenialPopup();
+
+ /**
* The popup used to display errors to the user.
*/
private final PopupPanel errorPopup = new PopupPanel(false, true);
@@ -453,8 +463,10 @@
public ExpenseDetails() {
createErrorPopup();
+ initTable();
initWidget(uiBinder.createAndBindUi(this));
- items = new ListDataProvider<ExpenseProxy>(Expenses.EXPENSE_RECORD_KEY_PROVIDER);
+ items = new ListDataProvider<ExpenseProxy>(
+ Expenses.EXPENSE_RECORD_KEY_PROVIDER);
items.addDataDisplay(table);
// Switch to edit notes.
@@ -521,7 +533,7 @@
@Override
public void onSuccess(ExpenseProxy newRecord) {
list.set(i, newRecord);
-
+
// Update the view data if the approval has been updated.
ApprovalViewData avd = approvalCell.getViewData(proxyId);
if (avd != null
@@ -544,14 +556,14 @@
EntityProxyId<ReportProxy> changed = event.getProxyId();
if (report != null && report.getId().equals(changed)) {
// Request the updated report.
- expensesRequestFactory.reportRequest().findReport(
- report.getId()).fire(new Receiver<ReportProxy>() {
- @Override
- public void onSuccess(ReportProxy response) {
- report = response;
- setNotesEditState(false, false, response.getNotes());
- }
- });
+ expensesRequestFactory.reportRequest().findReport(report.getId()).fire(
+ new Receiver<ReportProxy>() {
+ @Override
+ public void onSuccess(ReportProxy response) {
+ report = response;
+ setNotesEditState(false, false, response.getNotes());
+ }
+ });
}
}
@@ -562,7 +574,7 @@
/**
* Set the {@link ReportProxy} to show.
- *
+ *
* @param report the {@link ReportProxy}
* @param department the selected department
* @param employee the selected employee
@@ -598,112 +610,9 @@
requestExpenses();
}
- @UiFactory
- CellTable<ExpenseProxy> createTable() {
- CellTable.Resources resources = GWT.create(TableResources.class);
- CellTable<ExpenseProxy> view = new CellTable<ExpenseProxy>(100, resources);
- Styles.Common common = Styles.common();
- view.addColumnStyleName(0, common.spacerColumn());
- view.addColumnStyleName(1, common.expenseDetailsDateColumn());
- view.addColumnStyleName(3, common.expenseDetailsCategoryColumn());
- view.addColumnStyleName(4, common.expenseDetailsAmountColumn());
- view.addColumnStyleName(5, common.expenseDetailsApprovalColumn());
- view.addColumnStyleName(6, common.spacerColumn());
-
- // Spacer column.
- view.addColumn(new SpacerColumn<ExpenseProxy>());
-
- // Created column.
- GetValue<ExpenseProxy, Date> createdGetter = new GetValue<ExpenseProxy, Date>() {
- public Date getValue(ExpenseProxy object) {
- return object.getCreated();
- }
- };
- defaultComparator = createColumnComparator(createdGetter, false);
- Comparator<ExpenseProxy> createdDesc = createColumnComparator(
- createdGetter, true);
- addColumn(view, "Created",
- new DateCell(DateTimeFormat.getFormat("MMM dd yyyy")), createdGetter,
- defaultComparator, createdDesc);
- lastComparator = defaultComparator;
-
- // Description column.
- addColumn(view, "Description", new TextCell(),
- new GetValue<ExpenseProxy, String>() {
- public String getValue(ExpenseProxy object) {
- return object.getDescription();
- }
- });
-
- // Category column.
- addColumn(view, "Category", new TextCell(),
- new GetValue<ExpenseProxy, String>() {
- public String getValue(ExpenseProxy object) {
- return object.getCategory();
- }
- });
-
- // Amount column.
- final GetValue<ExpenseProxy, Double> amountGetter = new GetValue<ExpenseProxy, Double>() {
- public Double getValue(ExpenseProxy object) {
- return object.getAmount();
- }
- };
- Comparator<ExpenseProxy> amountAsc = createColumnComparator(amountGetter,
- false);
- Comparator<ExpenseProxy> amountDesc = createColumnComparator(amountGetter,
- true);
- addColumn(view, "Amount", new NumberCell(NumberFormat.getCurrencyFormat()),
- new GetValue<ExpenseProxy, Number>() {
- public Number getValue(ExpenseProxy object) {
- return amountGetter.getValue(object);
- }
- }, amountAsc, amountDesc);
-
- // Dialog box to obtain a reason for a denial
- final DenialPopup denialPopup = new DenialPopup();
- denialPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
- public void onClose(CloseEvent<PopupPanel> event) {
- String reasonDenied = denialPopup.getReasonDenied();
- ExpenseProxy record = denialPopup.getExpenseRecord();
- if (reasonDenied == null || reasonDenied.length() == 0) {
- // We need to redraw the table to reset the select box.
- syncCommit(record, null);
- } else {
- updateExpenseRecord(record, "Denied", reasonDenied);
- }
- }
- });
-
- // Approval column.
- approvalCell = new ApprovalCell();
- Column<ExpenseProxy, String> approvalColumn = addColumn(view,
- "Approval Status", approvalCell, new GetValue<ExpenseProxy, String>() {
- public String getValue(ExpenseProxy object) {
- return object.getApproval();
- }
- });
- approvalColumn.setFieldUpdater(new FieldUpdater<ExpenseProxy, String>() {
- public void update(int index, final ExpenseProxy object, String value) {
- if ("Denied".equals(value)) {
- denialPopup.setExpenseRecord(object);
- denialPopup.setReasonDenied(object.getReasonDenied());
- denialPopup.popup();
- } else {
- updateExpenseRecord(object, value, "");
- }
- }
- });
-
- // Spacer column.
- view.addColumn(new SpacerColumn<ExpenseProxy>());
-
- return view;
- }
-
/**
* Add a column of a {@link Comparable} type using default comparators.
- *
+ *
* @param <C> the column type
* @param table the table
* @param text the header text
@@ -721,7 +630,7 @@
/**
* Add a column with the specified comparators.
- *
+ *
* @param <C> the column type
* @param table the table
* @param text the header text
@@ -771,7 +680,7 @@
/**
* Create a comparator for the column.
- *
+ *
* @param <C> the column type
* @param getter the {@link GetValue} used to get the cell value
* @param descending true if descending, false if ascending
@@ -812,8 +721,7 @@
private void createErrorPopup() {
errorPopup.setGlassEnabled(true);
errorPopup.setStyleName(Styles.common().popupPanel());
- errorPopupMessage.addStyleName(
- Styles.common().expenseDetailsErrorPopupMessage());
+ errorPopupMessage.addStyleName(Styles.common().expenseDetailsErrorPopupMessage());
Button closeButton = new Button("Dismiss", new ClickHandler() {
public void onClick(ClickEvent event) {
@@ -831,7 +739,7 @@
/**
* Return a formatted currency string.
- *
+ *
* @param amount the amount in dollars
* @return a formatted string
*/
@@ -861,8 +769,120 @@
* Get the columns displayed in the expense table.
*/
private String[] getExpenseColumns() {
- return new String[]{ "amount", "approval", "category", "created",
- "description", "reasonDenied"};
+ return new String[]{
+ "amount", "approval", "category", "created", "description",
+ "reasonDenied"};
+ }
+
+ private CellTable<ExpenseProxy> initTable() {
+ CellTable.Resources resources = GWT.create(TableResources.class);
+ table = new CellTable<ExpenseProxy>(100, resources,
+ Expenses.EXPENSE_RECORD_KEY_PROVIDER);
+ Styles.Common common = Styles.common();
+
+ table.addColumnStyleName(0, common.spacerColumn());
+ table.addColumnStyleName(1, common.expenseDetailsDateColumn());
+ table.addColumnStyleName(3, common.expenseDetailsCategoryColumn());
+ table.addColumnStyleName(4, common.expenseDetailsAmountColumn());
+ table.addColumnStyleName(5, common.expenseDetailsApprovalColumn());
+ table.addColumnStyleName(6, common.spacerColumn());
+
+ // Spacer column.
+ table.addColumn(new SpacerColumn<ExpenseProxy>());
+
+ // Created column.
+ GetValue<ExpenseProxy, Date> createdGetter = new GetValue<ExpenseProxy, Date>() {
+ public Date getValue(ExpenseProxy object) {
+ return object.getCreated();
+ }
+ };
+ defaultComparator = createColumnComparator(createdGetter, false);
+ Comparator<ExpenseProxy> createdDesc = createColumnComparator(
+ createdGetter, true);
+ addColumn(table, "Created",
+ new DateCell(DateTimeFormat.getFormat("MMM dd yyyy")), createdGetter,
+ defaultComparator, createdDesc);
+ lastComparator = defaultComparator;
+
+ // Description column.
+ addColumn(table, "Description", new TextCell(),
+ new GetValue<ExpenseProxy, String>() {
+ public String getValue(ExpenseProxy object) {
+ return object.getDescription();
+ }
+ });
+
+ // Category column.
+ addColumn(table, "Category", new TextCell(),
+ new GetValue<ExpenseProxy, String>() {
+ public String getValue(ExpenseProxy object) {
+ return object.getCategory();
+ }
+ });
+
+ // Amount column.
+ final GetValue<ExpenseProxy, Double> amountGetter = new GetValue<ExpenseProxy, Double>() {
+ public Double getValue(ExpenseProxy object) {
+ return object.getAmount();
+ }
+ };
+ Comparator<ExpenseProxy> amountAsc = createColumnComparator(amountGetter,
+ false);
+ Comparator<ExpenseProxy> amountDesc = createColumnComparator(amountGetter,
+ true);
+ addColumn(table, "Amount",
+ new NumberCell(NumberFormat.getCurrencyFormat()),
+ new GetValue<ExpenseProxy, Number>() {
+ public Number getValue(ExpenseProxy object) {
+ return amountGetter.getValue(object);
+ }
+ }, amountAsc, amountDesc);
+
+ // Dialog box to obtain a reason for a denial
+ denialPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
+ public void onClose(CloseEvent<PopupPanel> event) {
+ String reasonDenied = denialPopup.getReasonDenied();
+ ExpenseProxy record = denialPopup.getExpenseRecord();
+ if (reasonDenied == null || reasonDenied.length() == 0) {
+ // Clear the view data.
+ final Object key = items.getKey(record);
+ approvalCell.clearViewData(key);
+
+ // We need to redraw the table to reset the select box.
+ syncCommit(record, null);
+ } else {
+ updateExpenseRecord(record, "Denied", reasonDenied);
+ }
+
+ // Return focus to the table.
+ table.setFocus(true);
+ }
+ });
+
+ // Approval column.
+ approvalCell = new ApprovalCell();
+ Column<ExpenseProxy, String> approvalColumn = addColumn(table,
+ "Approval Status", approvalCell, new GetValue<ExpenseProxy, String>() {
+ public String getValue(ExpenseProxy object) {
+ return object.getApproval();
+ }
+ });
+ approvalColumn.setFieldUpdater(new FieldUpdater<ExpenseProxy, String>() {
+ public void update(int index, final ExpenseProxy object, String value) {
+ if ("Denied".equals(value)) {
+ denialPopup.setExpenseRecord(object);
+ denialPopup.setReasonDenied(object.getReasonDenied());
+ denialPopup.popup();
+ } else {
+ updateExpenseRecord(object, value, "");
+ }
+ }
+ });
+
+ // Spacer column.
+ table.addColumn(new SpacerColumn<ExpenseProxy>());
+
+ return table;
}
/**
@@ -928,8 +948,8 @@
}
};
- expensesRequestFactory.expenseRequest().findExpensesByReport(
- report.getId()).with(getExpenseColumns()).fire(lastReceiver);
+ expensesRequestFactory.expenseRequest().findExpensesByReport(report.getId()).with(
+ getExpenseColumns()).fire(lastReceiver);
}
/**
@@ -960,7 +980,7 @@
/**
* Set the state of the notes section.
- *
+ *
* @param editable true for edit state, false for view state
* @param pending true if changes are pending, false if not
* @param notesText the current notes
@@ -979,7 +999,7 @@
/**
* Show the error popup.
- *
+ *
* @param errorMessage the error message
*/
private void showErrorPopup(String errorMessage) {
@@ -995,7 +1015,7 @@
/**
* Update the state of a pending approval change.
- *
+ *
* @param record the {@link ExpenseProxy} to sync
* @param message the error message if rejected, or null if accepted
*/
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetailsCellTable.css b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetailsCellTable.css
index fe295aa..e9443fe 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetailsCellTable.css
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseDetailsCellTable.css
@@ -23,7 +23,7 @@
}
.cellTableCell {
- padding: 4px 9px;
+ padding: 2px 9px;
}
.cellTableFirstColumn {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
index 02788dc..99e7b00 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -49,6 +49,7 @@
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.SimplePager;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
@@ -84,7 +85,7 @@
/**
* Called when the user selects a report.
- *
+ *
* @param report the selected report
*/
void onReportSelected(ReportProxy report);
@@ -197,7 +198,7 @@
/**
* Utility method to get the first part of the breadcrumb based on the
* department and employee.
- *
+ *
* @param department the selected department
* @param employee the selected employee
* @return the breadcrumb
@@ -362,7 +363,7 @@
/**
* Set the current department and employee to filter on.
- *
+ *
* @param department the department, or null if none selected
* @param employee the employee, or null if none selected
*/
@@ -394,7 +395,7 @@
/**
* Add a sortable column to the table.
- *
+ *
* @param <C> the data type for the column
* @param text the header text
* @param cell the cell used to render the column
@@ -457,6 +458,7 @@
private void createTable() {
CellTable.Resources resources = GWT.create(TableResources.class);
table = new CellTable<ReportProxy>(20, resources);
+ table.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
Styles.Common common = Styles.common();
table.addColumnStyleName(0, common.spacerColumn());
table.addColumnStyleName(1, common.expenseListPurposeColumn());
@@ -517,7 +519,7 @@
/**
* Send a request for reports in the current range.
- *
+ *
* @param isPolling true if this request is caused by polling
*/
private void requestReports(boolean isPolling) {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseListCellTable.css b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseListCellTable.css
index b7d49ba..3d7e167 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseListCellTable.css
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseListCellTable.css
@@ -24,6 +24,7 @@
.cellTableCell {
padding: 4px 9px;
+ border-width: 0;
}
.cellTableFirstColumn {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseDetails.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseDetails.java
index 0442b50..0ba748d 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseDetails.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseDetails.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -75,11 +75,6 @@
initWidget(BINDER.createAndBindUi(this));
}
- @Override
- public Widget asWidget() {
- return this;
- }
-
public String getPageTitle() {
return expense != null ? expense.getDescription() : "";
}
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
index 2640598..1b4f8f5 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileExpenseList.java
@@ -25,6 +25,7 @@
import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
import com.google.gwt.sample.expenses.client.request.ReportProxy;
import com.google.gwt.user.cellview.client.CellList;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
@@ -142,6 +143,7 @@
};
expenseList = new CellList<ExpenseProxy>(new ExpenseCell());
+ expenseList.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
expenseSelection = new NoSelectionModel<ExpenseProxy>();
expenseList.setSelectionModel(expenseSelection);
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
index 323e797..dc50155 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportList.java
@@ -22,6 +22,7 @@
import com.google.gwt.sample.expenses.client.request.ExpensesRequestFactory;
import com.google.gwt.sample.expenses.client.request.ReportProxy;
import com.google.gwt.user.cellview.client.CellList;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.AsyncDataProvider;
@@ -79,6 +80,7 @@
sb.appendHtmlConstant("</div>");
}
});
+ reportList.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
reportSelection = new NoSelectionModel<ReportProxy>(Expenses.REPORT_RECORD_KEY_PROVIDER);
reportSelection.addSelectionChangeHandler(
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/ShowcaseShell.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/ShowcaseShell.java
index f713e9e..6561ae8 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/ShowcaseShell.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/ShowcaseShell.java
@@ -31,6 +31,7 @@
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.CellTree;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
@@ -176,6 +177,7 @@
// Create the cell tree.
mainMenu = new CellTree(treeModel, null);
mainMenu.setAnimationEnabled(true);
+ mainMenu.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
// Initialize the ui binder.
initWidget(uiBinder.createAndBindUi(this));
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellList.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellList.java
index c236738..6df55c7 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellList.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellList.java
@@ -32,6 +32,7 @@
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.cellview.client.CellList;
+import com.google.gwt.user.cellview.client.HasKeyboardPagingPolicy.KeyboardPagingPolicy;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Button;
@@ -169,26 +170,28 @@
// Set a key provider that provides a unique key for each contact. If key is
// used to identify contacts when fields (such as the name and address)
// change.
- cellList = new CellList<ContactInfo>(contactCell, ContactDatabase.ContactInfo.KEY_PROVIDER);
+ cellList = new CellList<ContactInfo>(contactCell,
+ ContactDatabase.ContactInfo.KEY_PROVIDER);
cellList.setPageSize(30);
+ cellList.setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE);
// Add a selection model so we can select cells.
- final SingleSelectionModel<ContactInfo> selectionModel = new SingleSelectionModel<ContactInfo>(ContactDatabase.ContactInfo.KEY_PROVIDER);
+ final SingleSelectionModel<ContactInfo> selectionModel = new SingleSelectionModel<ContactInfo>(
+ ContactDatabase.ContactInfo.KEY_PROVIDER);
cellList.setSelectionModel(selectionModel);
- selectionModel.addSelectionChangeHandler(
- new SelectionChangeEvent.Handler() {
- public void onSelectionChange(SelectionChangeEvent event) {
- contactForm.setContact(selectionModel.getSelectedObject());
- }
- });
-
- // Add the CellList to the data provider in the database.
- ContactDatabase.get().addDataDisplay(cellList);
+ selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
+ public void onSelectionChange(SelectionChangeEvent event) {
+ contactForm.setContact(selectionModel.getSelectedObject());
+ }
+ });
// Create the UiBinder.
Binder uiBinder = GWT.create(Binder.class);
Widget widget = uiBinder.createAndBindUi(this);
+ // Add the CellList to the data provider in the database.
+ ContactDatabase.get().addDataDisplay(cellList);
+
// Set the cellList as the display of the pagers. This example has two
// pagers. pagerPanel is a scrollable pager that extends the range when the
// user scrolls to the bottom. rangeLabelPager is a pager that displays the
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
index e3d498b..97c9be7 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellValidation.java
@@ -15,7 +15,7 @@
*/
package com.google.gwt.sample.showcase.client.content.cell;
-import com.google.gwt.cell.client.AbstractEditableCell;
+import com.google.gwt.cell.client.AbstractInputCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.cell.client.ValueUpdater;
@@ -63,7 +63,7 @@
}
interface Template extends SafeHtmlTemplates {
- @Template("<input type=\"text\" value=\"{0}\" style=\"color:{1}\"/>")
+ @Template("<input type=\"text\" value=\"{0}\" style=\"color:{1}\" tabindex=\"-1\"/>")
SafeHtml input(String value, String color);
}
@@ -71,8 +71,8 @@
* An input cell that changes color based on the validation status.
*/
@ShowcaseSource
- private static class ValidatableInputCell extends AbstractEditableCell<
- String, ValidationData> {
+ private static class ValidatableInputCell extends
+ AbstractInputCell<String, ValidationData> {
private SafeHtml errorMessage;
@@ -87,9 +87,17 @@
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
- ValidationData viewData = getViewData(key);
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
- if (event.getType().equals("change")) {
+ // Ignore events that don't target the input.
+ Element target = event.getEventTarget().cast();
+ if (!parent.getFirstChildElement().isOrHasChild(target)) {
+ return;
+ }
+
+ ValidationData viewData = getViewData(key);
+ String eventType = event.getType();
+ if ("change".equals(eventType)) {
InputElement input = parent.getFirstChild().cast();
// Mark cell as containing a pending change
@@ -102,6 +110,7 @@
}
String newValue = input.getValue();
viewData.setValue(newValue);
+ finishEditing(parent, newValue, key, valueUpdater);
// Update the value updater, which updates the field updater.
if (valueUpdater != null) {
@@ -137,6 +146,17 @@
sb.appendHtmlConstant("</span>");
}
}
+
+ @Override
+ protected void onEnterKeyDown(Element parent, String value, Object key,
+ NativeEvent event, ValueUpdater<String> valueUpdater) {
+ Element target = event.getEventTarget().cast();
+ if (getInputElement(parent).isOrHasChild(target)) {
+ finishEditing(parent, value, key, valueUpdater);
+ } else {
+ super.onEnterKeyDown(parent, value, key, event, valueUpdater);
+ }
+ }
}
/**
@@ -222,7 +242,8 @@
@Override
public Widget onInitialize() {
// Create a table.
- final CellTable<ContactInfo> table = new CellTable<ContactInfo>(10, ContactInfo.KEY_PROVIDER);
+ final CellTable<ContactInfo> table = new CellTable<ContactInfo>(10,
+ ContactInfo.KEY_PROVIDER);
// Add the Name column.
table.addColumn(new Column<ContactInfo, String>(new TextCell()) {
@@ -244,8 +265,7 @@
};
table.addColumn(addressColumn, constants.cwCellValidationColumnAddress());
addressColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
- public void update(
- int index, final ContactInfo object, final String value) {
+ public void update(int index, final ContactInfo object, final String value) {
// Perform validation after 2 seconds to simulate network delay.
new Timer() {
@Override
@@ -259,8 +279,7 @@
ContactDatabase.get().refreshDisplays();
} else {
// Update the view data to mark the pending value as invalid.
- ValidationData viewData = addressCell.getViewData(
- ContactInfo.KEY_PROVIDER.getKey(object));
+ ValidationData viewData = addressCell.getViewData(ContactInfo.KEY_PROVIDER.getKey(object));
viewData.setInvalid(true);
// We only modified the cell, so do a local redraw.
diff --git a/user/src/com/google/gwt/cell/client/AbstractCell.java b/user/src/com/google/gwt/cell/client/AbstractCell.java
index 88c0121..9bfa026 100644
--- a/user/src/com/google/gwt/cell/client/AbstractCell.java
+++ b/user/src/com/google/gwt/cell/client/AbstractCell.java
@@ -17,6 +17,7 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import java.util.Collections;
@@ -81,23 +82,42 @@
* Returns false. Subclasses that support editing should override this method
* to return the current editing status.
*/
- public boolean isEditing(Element element, C value, Object key) {
+ public boolean isEditing(Element parent, C value, Object key) {
return false;
}
/**
* {@inheritDoc}
*
- * This method is a no-op in {@link AbstractCell}. If you override this method
- * to add support for events, remember to pass the event types that the cell
- * expects into the constructor.
+ * <p>
+ * If you override this method to add support for events, remember to pass the
+ * event types that the cell expects into the constructor.
+ * </p>
*/
public void onBrowserEvent(Element parent, C value, Object key,
NativeEvent event, ValueUpdater<C> valueUpdater) {
+ String eventType = event.getType();
+ // Special case the ENTER key for a unified user experience.
+ if ("keydown".equals(eventType) && event.getKeyCode() == KeyCodes.KEY_ENTER) {
+ onEnterKeyDown(parent, value, key, event, valueUpdater);
+ }
}
public abstract void render(C value, Object key, SafeHtmlBuilder sb);
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * This method is a no-op and returns false. If your cell is editable or can
+ * be focused by the user, override this method to reset focus when the
+ * containing widget is refreshed.
+ * </p>
+ */
+ public boolean resetFocus(Element parent, C value, Object key) {
+ return false;
+ }
+
public void setValue(Element parent, C value, Object key) {
SafeHtmlBuilder sb = new SafeHtmlBuilder();
render(value, key, sb);
@@ -105,6 +125,23 @@
}
/**
+ * Called when the user triggers a <code>keydown</code> event with the ENTER
+ * key while focused on the cell. If your cell interacts with the user, you
+ * should override this method to provide a consistent user experience. Your
+ * widget must consume <code>keydown</code> events for this method to be
+ * called.
+ *
+ * @param parent the parent Element
+ * @param value the value associated with the cell
+ * @param key the unique key associated with the row object
+ * @param event the native browser event
+ * @param valueUpdater a {@link ValueUpdater}, or null if not specified
+ */
+ protected void onEnterKeyDown(Element parent, C value, Object key,
+ NativeEvent event, ValueUpdater<C> valueUpdater) {
+ }
+
+ /**
* Initialize the cell.
*
* @param consumedEvents the events that the cell consumes
diff --git a/user/src/com/google/gwt/cell/client/AbstractEditableCell.java b/user/src/com/google/gwt/cell/client/AbstractEditableCell.java
index a5a1caf..b115413 100644
--- a/user/src/com/google/gwt/cell/client/AbstractEditableCell.java
+++ b/user/src/com/google/gwt/cell/client/AbstractEditableCell.java
@@ -16,7 +16,6 @@
package com.google.gwt.cell.client;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
import java.util.HashMap;
import java.util.Map;
@@ -81,9 +80,18 @@
return (key == null) ? null : viewDataMap.get(key);
}
+ /**
+ * Returns true if the cell is currently editing the data identified by the
+ * given element and key. While a cell is editing, widgets containing the cell
+ * may choose to pass keystrokes directly to the cell rather than using them
+ * for navigation purposes.
+ *
+ * @param parent the parent Element
+ * @param value the value associated with the cell
+ * @param key the unique key associated with the row object
+ */
@Override
- public abstract void onBrowserEvent(Element parent, C value, Object key,
- NativeEvent event, ValueUpdater<C> valueUpdater);
+ public abstract boolean isEditing(Element parent, C value, Object key);
/**
* Associate view data with the specified key. If the key is null, the view
diff --git a/user/src/com/google/gwt/cell/client/AbstractInputCell.java b/user/src/com/google/gwt/cell/client/AbstractInputCell.java
new file mode 100644
index 0000000..e67ccf5
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/AbstractInputCell.java
@@ -0,0 +1,160 @@
+/*
+ * 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.cell.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An {@link AbstractCell} used to render input elements that can receive focus.
+ *
+ * @param <C> the type that this Cell represents
+ * @param <V> the data type of the view data state
+ */
+public abstract class AbstractInputCell<C, V> extends
+ AbstractEditableCell<C, V> {
+
+ /**
+ * Get the events consumed by the input cell.
+ *
+ * @param userEvents the events consumed by the subclass
+ * @return the events
+ */
+ private static Set<String> getConsumedEventsImpl(Set<String> userEvents) {
+ Set<String> events = new HashSet<String>();
+ events.add("focus");
+ events.add("blur");
+ events.add("keydown");
+ if (userEvents != null && userEvents.size() > 0) {
+ events.addAll(userEvents);
+ }
+ return events;
+ }
+
+ /**
+ * Get the events consumed by the input cell.
+ *
+ * @param userEvents the events consumed by the subclass
+ * @return the events
+ */
+ private static Set<String> getConsumedEventsImpl(String... consumedEvents) {
+ Set<String> userEvents = new HashSet<String>();
+ if (consumedEvents != null) {
+ for (String event : consumedEvents) {
+ userEvents.add(event);
+ }
+ }
+ return getConsumedEventsImpl(userEvents);
+ }
+
+ /**
+ * The currently focused value key. Only one key can be focused at any time.
+ */
+ private Object focusedKey;
+
+ /**
+ * Construct a new {@link AbstractInputCell} with the specified consumed
+ * events.
+ *
+ * @param consumedEvents the events that this cell consumes
+ */
+ public AbstractInputCell(String... consumedEvents) {
+ super(getConsumedEventsImpl(consumedEvents));
+ }
+
+ /**
+ * Construct a new {@link AbstractInputCell} with the specified consumed
+ * events.
+ *
+ * @param consumedEvents the events that this cell consumes
+ */
+ public AbstractInputCell(Set<String> consumedEvents) {
+ super(getConsumedEventsImpl(consumedEvents));
+ }
+
+ @Override
+ public boolean isEditing(Element parent, C value, Object key) {
+ return focusedKey != null && focusedKey.equals(key);
+ }
+
+ @Override
+ public void onBrowserEvent(Element parent, C value, Object key,
+ NativeEvent event, ValueUpdater<C> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+
+ // Ignore events that don't target the input.
+ Element target = event.getEventTarget().cast();
+ if (!getInputElement(parent).isOrHasChild(target)) {
+ return;
+ }
+
+ String eventType = event.getType();
+ if ("focus".equals(eventType)) {
+ focusedKey = key;
+ } else if ("blur".equals(eventType)) {
+ focusedKey = null;
+ }
+ }
+
+ @Override
+ public boolean resetFocus(Element parent, C value, Object key) {
+ if (isEditing(parent, value, key)) {
+ getInputElement(parent).focus();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Call this method when editing is complete.
+ *
+ * @param parent the parent Element
+ * @param value the value associated with the cell
+ * @param key the unique key associated with the row object
+ * @param valueUpdater the value update to fire
+ */
+ protected void finishEditing(Element parent, C value, Object key,
+ ValueUpdater<C> valueUpdater) {
+ focusedKey = null;
+ getInputElement(parent).blur();
+ }
+
+ /**
+ * Get the input element.
+ *
+ * @param parent the cell parent element
+ * @return the input element
+ */
+ protected Element getInputElement(Element parent) {
+ return parent.getFirstChildElement();
+ }
+
+ @Override
+ protected void onEnterKeyDown(Element parent, C value, Object key,
+ NativeEvent event, ValueUpdater<C> valueUpdater) {
+ Element input = getInputElement(parent);
+ Element target = event.getEventTarget().cast();
+ if (getInputElement(parent).isOrHasChild(target)) {
+ finishEditing(parent, value, key, valueUpdater);
+ } else {
+ focusedKey = key;
+ input.focus();
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/cell/client/ActionCell.java b/user/src/com/google/gwt/cell/client/ActionCell.java
index bcc09e5..c870378 100644
--- a/user/src/com/google/gwt/cell/client/ActionCell.java
+++ b/user/src/com/google/gwt/cell/client/ActionCell.java
@@ -52,10 +52,11 @@
* @param delegate the delegate that will handle events
*/
public ActionCell(SafeHtml message, Delegate<C> delegate) {
- super("click");
+ super("click", "keydown");
this.delegate = delegate;
- this.html = new SafeHtmlBuilder().appendHtmlConstant("<button>").append(
- message).appendHtmlConstant("</button>").toSafeHtml();
+ this.html = new SafeHtmlBuilder().appendHtmlConstant(
+ "<button type=\"button\" tabindex=\"-1\">").append(message).appendHtmlConstant(
+ "</button>").toSafeHtml();
}
/**
@@ -72,8 +73,9 @@
@Override
public void onBrowserEvent(Element parent, C value, Object key,
NativeEvent event, ValueUpdater<C> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
if ("click".equals(event.getType())) {
- delegate.execute(value);
+ onEnterKeyDown(parent, value, key, event, valueUpdater);
}
}
@@ -81,4 +83,10 @@
public void render(C value, Object key, SafeHtmlBuilder sb) {
sb.append(html);
}
+
+ @Override
+ protected void onEnterKeyDown(Element parent, C value, Object key,
+ NativeEvent event, ValueUpdater<C> valueUpdater) {
+ delegate.execute(value);
+ }
}
diff --git a/user/src/com/google/gwt/cell/client/ButtonCell.java b/user/src/com/google/gwt/cell/client/ButtonCell.java
index 8372819..d64631b 100644
--- a/user/src/com/google/gwt/cell/client/ButtonCell.java
+++ b/user/src/com/google/gwt/cell/client/ButtonCell.java
@@ -32,27 +32,36 @@
public class ButtonCell extends AbstractSafeHtmlCell<String> {
public ButtonCell() {
- super(SimpleSafeHtmlRenderer.getInstance(), "mouseup");
+ this(SimpleSafeHtmlRenderer.getInstance());
}
public ButtonCell(SafeHtmlRenderer<String> renderer) {
- super(renderer, "mouseup");
+ super(renderer, "click", "keydown");
}
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
- if (valueUpdater != null && "mouseup".equals(event.getType())) {
- valueUpdater.update(value);
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+ if ("click".equals(event.getType())) {
+ onEnterKeyDown(parent, value, key, event, valueUpdater);
}
}
@Override
public void render(SafeHtml data, Object key, SafeHtmlBuilder sb) {
- sb.appendHtmlConstant("<button>");
+ sb.appendHtmlConstant("<button type=\"button\" tabindex=\"-1\">");
if (data != null) {
sb.append(data);
}
sb.appendHtmlConstant("</button>");
}
+
+ @Override
+ protected void onEnterKeyDown(Element parent, String value, Object key,
+ NativeEvent event, ValueUpdater<String> valueUpdater) {
+ if (valueUpdater != null) {
+ valueUpdater.update(value);
+ }
+ }
}
diff --git a/user/src/com/google/gwt/cell/client/Cell.java b/user/src/com/google/gwt/cell/client/Cell.java
index d14d8d0..bbe169b 100644
--- a/user/src/com/google/gwt/cell/client/Cell.java
+++ b/user/src/com/google/gwt/cell/client/Cell.java
@@ -42,8 +42,9 @@
/**
* <p>
* Get the set of events that this cell consumes. The container that uses this
- * cell should only pass these events to {@link #onBrowserEvent(Element,
- * Object, Object, NativeEvent, ValueUpdater)}.
+ * cell should only pass these events to
+ * {@link #onBrowserEvent(Element, Object, Object, NativeEvent, ValueUpdater)}
+ * .
* </p>
* <p>
* The returned value should not be modified, and may be an unmodifiable set.
@@ -90,6 +91,13 @@
/**
* Render a cell as HTML into a StringBuilder, suitable for passing to
* {@link Element#setInnerHTML} on a container element.
+ *
+ * <p>
+ * Note: If your cell contains natively focusable elements, such as buttons or
+ * input elements, be sure to set the tabIndex to -1 so that they do not steal
+ * focus away from the containing widget.
+ * </p>
+ *
* @param value the cell value to be rendered
* @param key the unique key associated with the row object
* @param sb the {@link SafeHtmlBuilder} to be written to
@@ -97,6 +105,14 @@
void render(C value, Object key, SafeHtmlBuilder sb);
/**
+ * Reset focus on the Cell. This method is called if the cell has focus when
+ * it is refreshed.
+ *
+ * @return true if focus is taken, false if not
+ */
+ boolean resetFocus(Element parent, C value, Object key);
+
+ /**
* This method may be used by cell containers to set the value on a single
* cell directly, rather than using {@link Element#setInnerHTML(String)}. See
* {@link AbstractCell#setValue(Element, Object, Object)} for a default
diff --git a/user/src/com/google/gwt/cell/client/CheckboxCell.java b/user/src/com/google/gwt/cell/client/CheckboxCell.java
index 5c87972..38def3e 100644
--- a/user/src/com/google/gwt/cell/client/CheckboxCell.java
+++ b/user/src/com/google/gwt/cell/client/CheckboxCell.java
@@ -36,14 +36,12 @@
/**
* An html string representation of a checked input box.
*/
- private static final SafeHtml INPUT_CHECKED =
- SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" checked/>");
+ private static final SafeHtml INPUT_CHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\" checked/>");
/**
* An html string representation of an unchecked input box.
*/
- private static final SafeHtml INPUT_UNCHECKED =
- SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\"/>");
+ private static final SafeHtml INPUT_UNCHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\"/>");
private final boolean isSelectBox;
@@ -60,7 +58,7 @@
* @param isSelectBox true if the cell controls the selection state
*/
public CheckboxCell(boolean isSelectBox) {
- super("change", "keyup");
+ super("change", "keydown");
this.isSelectBox = isSelectBox;
}
@@ -75,11 +73,18 @@
}
@Override
+ public boolean isEditing(Element parent, Boolean value, Object key) {
+ // A checkbox is never in "edit mode". There is no intermediate state
+ // between checked and unchecked.
+ return false;
+ }
+
+ @Override
public void onBrowserEvent(Element parent, Boolean value, Object key,
NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
String type = event.getType();
- boolean enterPressed = "keyup".equals(type)
+ boolean enterPressed = "keydown".equals(type)
&& event.getKeyCode() == KeyCodes.KEY_ENTER;
if ("change".equals(type) || enterPressed) {
InputElement input = parent.getFirstChild().cast();
diff --git a/user/src/com/google/gwt/cell/client/ClickableTextCell.java b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
index cbda346..2b373ac 100644
--- a/user/src/com/google/gwt/cell/client/ClickableTextCell.java
+++ b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
@@ -33,18 +33,26 @@
public class ClickableTextCell extends AbstractSafeHtmlCell<String> {
public ClickableTextCell() {
- super(SimpleSafeHtmlRenderer.getInstance(), "click");
+ this(SimpleSafeHtmlRenderer.getInstance());
}
public ClickableTextCell(SafeHtmlRenderer<String> renderer) {
- super(renderer, "click");
+ super(renderer, "click", "keydown");
}
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
- String type = event.getType();
- if (valueUpdater != null && type.equals("click")) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+ if ("click".equals(event.getType())) {
+ onEnterKeyDown(parent, value, key, event, valueUpdater);
+ }
+ }
+
+ @Override
+ protected void onEnterKeyDown(Element parent, String value, Object key,
+ NativeEvent event, ValueUpdater<String> valueUpdater) {
+ if (valueUpdater != null) {
valueUpdater.update(value);
}
}
diff --git a/user/src/com/google/gwt/cell/client/CompositeCell.java b/user/src/com/google/gwt/cell/client/CompositeCell.java
index b86d66d..edcc8ad 100644
--- a/user/src/com/google/gwt/cell/client/CompositeCell.java
+++ b/user/src/com/google/gwt/cell/client/CompositeCell.java
@@ -63,7 +63,7 @@
/**
* The cells that compose this {@link Cell}.
*
- * NOTE: Do not add add/insert/remove hasCells methods to the API. This cell
+ * NOTE: Do not add add/insert/remove hasCells methods to the API. This cell
* assumes that the index of the cellParent corresponds to the index in the
* hasCells array.
*/
@@ -127,8 +127,8 @@
Element wrapper = container.getFirstChildElement();
while (wrapper != null) {
if (wrapper.isOrHasChild(target)) {
- onBrowserEventImpl(
- wrapper, value, key, event, valueUpdater, hasCells.get(index));
+ onBrowserEventImpl(wrapper, value, key, event, valueUpdater,
+ hasCells.get(index));
}
index++;
@@ -145,8 +145,22 @@
}
@Override
+ public boolean resetFocus(Element parent, C value, Object key) {
+ Element curChild = getContainerElement(parent).getFirstChildElement();
+ for (HasCell<C, ?> hasCell : hasCells) {
+ // The first child that takes focus wins. Only one child should ever be in
+ // edit mode, so this is safe.
+ if (resetFocusImpl(curChild, value, key, hasCell)) {
+ return true;
+ }
+ curChild = curChild.getNextSiblingElement();
+ }
+ return false;
+ }
+
+ @Override
public void setValue(Element parent, C object, Object key) {
- Element curChild = parent.getFirstChildElement();
+ Element curChild = getContainerElement(parent).getFirstChildElement();
for (HasCell<C, ?> hasCell : hasCells) {
setValueImpl(curChild, object, key, hasCell);
curChild = curChild.getNextSiblingElement();
@@ -165,8 +179,8 @@
return parent;
}
- protected <X> void render(
- C value, Object key, SafeHtmlBuilder sb, HasCell<C, X> hasCell) {
+ protected <X> void render(C value, Object key, SafeHtmlBuilder sb,
+ HasCell<C, X> hasCell) {
Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<span>");
cell.render(hasCell.getValue(value), key, sb);
@@ -189,12 +203,18 @@
};
}
Cell<X> cell = hasCell.getCell();
- cell.onBrowserEvent(
- parent, hasCell.getValue(object), key, event, tempUpdater);
+ cell.onBrowserEvent(parent, hasCell.getValue(object), key, event,
+ tempUpdater);
}
- private <X> void setValueImpl(
- Element cellParent, C object, Object key, HasCell<C, X> hasCell) {
+ private <X> boolean resetFocusImpl(Element cellParent, C value, Object key,
+ HasCell<C, X> hasCell) {
+ X cellValue = hasCell.getValue(value);
+ return hasCell.getCell().resetFocus(cellParent, cellValue, key);
+ }
+
+ private <X> void setValueImpl(Element cellParent, C object, Object key,
+ HasCell<C, X> hasCell) {
hasCell.getCell().setValue(cellParent, hasCell.getValue(object), key);
}
}
diff --git a/user/src/com/google/gwt/cell/client/DatePickerCell.java b/user/src/com/google/gwt/cell/client/DatePickerCell.java
index 532a084..5e56364 100644
--- a/user/src/com/google/gwt/cell/client/DatePickerCell.java
+++ b/user/src/com/google/gwt/cell/client/DatePickerCell.java
@@ -17,9 +17,12 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
@@ -90,7 +93,7 @@
* {@link SafeHtmlRenderer}.
*/
public DatePickerCell(SafeHtmlRenderer<String> renderer) {
- this(DateTimeFormat.getFullDateFormat(), renderer);
+ this(DateTimeFormat.getFormat(PredefinedFormat.DATE_FULL), renderer);
}
/**
@@ -98,7 +101,7 @@
* {@link SafeHtmlRenderer}.
*/
public DatePickerCell(DateTimeFormat format, SafeHtmlRenderer<String> renderer) {
- super("click");
+ super("click", "keydown");
if (format == null) {
throw new IllegalArgumentException("format == null");
}
@@ -120,6 +123,18 @@
}
}
};
+ panel.addCloseHandler(new CloseHandler<PopupPanel>() {
+ public void onClose(CloseEvent<PopupPanel> event) {
+ lastKey = null;
+ lastValue = null;
+ if (lastParent != null && !event.isAutoClosed()) {
+ // Refocus on the containing cell after the user selects a value, but
+ // not if the popup is auto closed.
+ lastParent.focus();
+ }
+ lastParent = null;
+ }
+ });
panel.add(datePicker);
// Hide the panel and call valueUpdater.update when a date is selected
@@ -137,24 +152,16 @@
}
@Override
- public void onBrowserEvent(final Element parent, Date value, Object key,
- NativeEvent event, ValueUpdater<Date> valueUpdater) {
- if (value != null && event.getType().equals("click")) {
- this.lastKey = key;
- this.lastParent = parent;
- this.lastValue = value;
- this.valueUpdater = valueUpdater;
+ public boolean isEditing(Element parent, Date value, Object key) {
+ return lastKey != null && lastKey.equals(key);
+ }
- Date viewData = getViewData(key);
- Date date = (viewData == null) ? value : viewData;
- datePicker.setCurrentMonth(date);
- datePicker.setValue(date);
- panel.setPopupPositionAndShow(new PositionCallback() {
- public void setPosition(int offsetWidth, int offsetHeight) {
- panel.setPopupPosition(parent.getAbsoluteLeft() + offsetX,
- parent.getAbsoluteTop() + offsetY);
- }
- });
+ @Override
+ public void onBrowserEvent(Element parent, Date value, Object key,
+ NativeEvent event, ValueUpdater<Date> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+ if ("click".equals(event.getType())) {
+ onEnterKeyDown(parent, value, key, event, valueUpdater);
}
}
@@ -177,4 +184,24 @@
sb.append(renderer.render(s));
}
}
+
+ @Override
+ protected void onEnterKeyDown(final Element parent, Date value, Object key,
+ NativeEvent event, ValueUpdater<Date> valueUpdater) {
+ this.lastKey = key;
+ this.lastParent = parent;
+ this.lastValue = value;
+ this.valueUpdater = valueUpdater;
+
+ Date viewData = getViewData(key);
+ Date date = (viewData == null) ? value : viewData;
+ datePicker.setCurrentMonth(date);
+ datePicker.setValue(date);
+ panel.setPopupPositionAndShow(new PositionCallback() {
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ panel.setPopupPosition(parent.getAbsoluteLeft() + offsetX,
+ parent.getAbsoluteTop() + offsetY);
+ }
+ });
+ }
}
diff --git a/user/src/com/google/gwt/cell/client/EditTextCell.java b/user/src/com/google/gwt/cell/client/EditTextCell.java
index e008190..e9c540d 100644
--- a/user/src/com/google/gwt/cell/client/EditTextCell.java
+++ b/user/src/com/google/gwt/cell/client/EditTextCell.java
@@ -38,11 +38,11 @@
* purposes, which is entirely wrong. It should be able to treat it as a proper
* string (especially since that's all the user can enter).
*/
-public class EditTextCell extends AbstractEditableCell<
- String, EditTextCell.ViewData> {
+public class EditTextCell extends
+ AbstractEditableCell<String, EditTextCell.ViewData> {
interface Template extends SafeHtmlTemplates {
- @Template("<input type=\"text\" value=\"{0}\"></input>")
+ @Template("<input type=\"text\" value=\"{0}\" tabindex=\"-1\"></input>")
SafeHtml input(String value);
}
@@ -171,8 +171,8 @@
} else {
String type = event.getType();
int keyCode = event.getKeyCode();
- boolean enterPressed = "keyup".equals(type) &&
- keyCode == KeyCodes.KEY_ENTER;
+ boolean enterPressed = "keyup".equals(type)
+ && keyCode == KeyCodes.KEY_ENTER;
if ("click".equals(type) || enterPressed) {
// Go into edit mode.
if (viewData == null) {
@@ -212,6 +212,15 @@
}
}
+ @Override
+ public boolean resetFocus(Element parent, String value, Object key) {
+ if (isEditing(parent, value, key)) {
+ getInputElement(parent).focus();
+ return true;
+ }
+ return false;
+ }
+
/**
* Convert the cell to edit mode.
*
@@ -221,7 +230,7 @@
*/
protected void edit(Element parent, String value, Object key) {
setValue(parent, value, key);
- InputElement input = (InputElement) parent.getFirstChild();
+ InputElement input = getInputElement(parent);
input.focus();
input.select();
}
@@ -233,19 +242,35 @@
* @param value the current value
*/
private void cancel(Element parent, String value) {
+ clearInput(getInputElement(parent));
setValue(parent, value, null);
}
/**
+ * Clear selected from the input element. Both Firefox and IE fire spurious
+ * onblur events after the input is removed from the DOM if selection is not
+ * cleared.
+ *
+ * @param input the input element
+ */
+ private native void clearInput(Element input) /*-{
+ if (input.selectionEnd)
+ input.selectionEnd = input.selectionStart;
+ else if ($doc.selection)
+ $doc.selection.clear();
+ }-*/;
+
+ /**
* Commit the current value.
*
* @param parent the parent element
* @param viewData the {@link ViewData} object
* @param valueUpdater the {@link ValueUpdater}
*/
- private void commit(
- Element parent, ViewData viewData, ValueUpdater<String> valueUpdater) {
+ private void commit(Element parent, ViewData viewData,
+ ValueUpdater<String> valueUpdater) {
String value = updateViewData(parent, viewData, false);
+ clearInput(getInputElement(parent));
setValue(parent, value, viewData);
valueUpdater.update(value);
}
@@ -288,6 +313,13 @@
}
/**
+ * Get the input element in edit mode.
+ */
+ private InputElement getInputElement(Element parent) {
+ return parent.getFirstChild().<InputElement> cast();
+ }
+
+ /**
* Update the view data based on the current value.
*
* @param parent the parent element
@@ -295,8 +327,8 @@
* @param isEditing true if in edit mode
* @return the new value
*/
- private String updateViewData(
- Element parent, ViewData viewData, boolean isEditing) {
+ private String updateViewData(Element parent, ViewData viewData,
+ boolean isEditing) {
InputElement input = (InputElement) parent.getFirstChild();
String value = input.getValue();
viewData.setText(value);
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
index 34af82f..185b785 100644
--- a/user/src/com/google/gwt/cell/client/IconCellDecorator.java
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -42,7 +42,8 @@
interface Template extends SafeHtmlTemplates {
@Template("<div style=\"position:relative;padding-{0}:{1}px;\">{2}<div>{3}</div></div>")
- SafeHtml outerDiv(String direction, int width, SafeHtml icon, SafeHtml cellContents);
+ SafeHtml outerDiv(String direction, int width, SafeHtml icon,
+ SafeHtml cellContents);
@Template("<div style=\"position:absolute;{0}:0px;top:0px;height:100%;width:{1}px;\"></div>")
SafeHtml imagePlaceholder(String direction, int width);
@@ -52,7 +53,8 @@
private final Cell<C> cell;
- private final String direction = LocaleInfo.getCurrentLocale().isRTL() ? "right" : "left";
+ private final String direction = LocaleInfo.getCurrentLocale().isRTL()
+ ? "right" : "left";
private final SafeHtml iconHtml;
@@ -119,6 +121,10 @@
? getIconHtml(value) : placeHolderHtml, cellBuilder.toSafeHtml()));
}
+ public boolean resetFocus(Element parent, C value, Object key) {
+ return cell.resetFocus(getCellParent(parent), value, key);
+ }
+
public void setValue(Element parent, C value, Object key) {
cell.setValue(getCellParent(parent), value, key);
}
diff --git a/user/src/com/google/gwt/cell/client/SelectionCell.java b/user/src/com/google/gwt/cell/client/SelectionCell.java
index 6612f5c..d3e62af 100644
--- a/user/src/com/google/gwt/cell/client/SelectionCell.java
+++ b/user/src/com/google/gwt/cell/client/SelectionCell.java
@@ -34,11 +34,12 @@
* Note: This class is new and its interface subject to change.
* </p>
*/
-public class SelectionCell extends AbstractEditableCell<String, String> {
+public class SelectionCell extends AbstractInputCell<String, String> {
interface Template extends SafeHtmlTemplates {
@Template("<option value=\"{0}\">{0}</option>")
SafeHtml deselected(String option);
+
@Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
SafeHtml selected(String option);
}
@@ -69,11 +70,13 @@
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
String type = event.getType();
if ("change".equals(type)) {
SelectElement select = parent.getFirstChild().cast();
String newValue = options.get(select.getSelectedIndex());
setViewData(key, newValue);
+ finishEditing(parent, newValue, key, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(newValue);
}
@@ -90,7 +93,7 @@
}
int selectedIndex = getSelectedIndex(viewData == null ? value : viewData);
- sb.appendHtmlConstant("<select>");
+ sb.appendHtmlConstant("<select tabindex=\"-1\">");
int index = 0;
for (String option : options) {
if (index++ == selectedIndex) {
diff --git a/user/src/com/google/gwt/cell/client/TextInputCell.java b/user/src/com/google/gwt/cell/client/TextInputCell.java
index e117f78..b66e857 100644
--- a/user/src/com/google/gwt/cell/client/TextInputCell.java
+++ b/user/src/com/google/gwt/cell/client/TextInputCell.java
@@ -32,13 +32,75 @@
* Note: This class is new and its interface subject to change.
* </p>
*/
-public class TextInputCell extends AbstractEditableCell<String, String> {
+public class TextInputCell extends
+ AbstractInputCell<String, TextInputCell.ViewData> {
interface Template extends SafeHtmlTemplates {
- @Template("<input type=\"text\" value=\"{0}\"></input>")
+ @Template("<input type=\"text\" value=\"{0}\" tabindex=\"-1\"></input>")
SafeHtml input(String value);
}
+ /**
+ * The ViewData for this cell.
+ */
+ public static class ViewData {
+ /**
+ * The last value that was updated.
+ */
+ private String lastValue;
+
+ /**
+ * The current value.
+ */
+ private String curValue;
+
+ public ViewData(String value) {
+ this.lastValue = value;
+ this.curValue = value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof ViewData)) {
+ return false;
+ }
+ ViewData vd = (ViewData) other;
+ return equalsOrNull(lastValue, vd.lastValue)
+ && equalsOrNull(curValue, vd.curValue);
+ }
+
+ /**
+ * @return the current value of the input element
+ */
+ public String getCurrentValue() {
+ return curValue;
+ }
+
+ /**
+ * @return the last value sent to the {@link ValueUpdater}
+ */
+ public String getLastValue() {
+ return lastValue;
+ }
+
+ @Override
+ public int hashCode() {
+ return (lastValue + "_*!@HASH_SEPERATOR@!*_" + curValue).hashCode();
+ }
+
+ protected void setCurrentValue(String curValue) {
+ this.curValue = curValue;
+ }
+
+ protected void setLastValue(String lastValue) {
+ this.lastValue = lastValue;
+ }
+
+ private boolean equalsOrNull(Object a, Object b) {
+ return (a != null) ? a.equals(b) : ((b == null) ? true : false);
+ }
+ }
+
private static Template template;
private final SafeHtmlRenderer<String> renderer;
@@ -51,8 +113,8 @@
}
/**
- * Constructs a TextInputCell that renders its text using the
- * given {@link SafeHtmlRenderer}.
+ * Constructs a TextInputCell that renders its text using the given
+ * {@link SafeHtmlRenderer}.
*
* @param renderer a non-null SafeHtmlRenderer
*/
@@ -70,37 +132,73 @@
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
+ super.onBrowserEvent(parent, value, key, event, valueUpdater);
+
+ // Ignore events that don't target the input.
+ InputElement input = getInputElement(parent);
+ Element target = event.getEventTarget().cast();
+ if (!input.isOrHasChild(target)) {
+ return;
+ }
+
String eventType = event.getType();
if ("change".equals(eventType)) {
- InputElement input = parent.getFirstChild().cast();
- String newValue = input.getValue();
- setViewData(key, newValue);
- if (valueUpdater != null) {
- valueUpdater.update(newValue);
- }
+ finishEditing(parent, value, key, valueUpdater);
} else if ("keyup".equals(eventType)) {
// Record keys as they are typed.
- InputElement input = parent.getFirstChild().cast();
- setViewData(key, input.getValue());
+ ViewData vd = getViewData(key);
+ if (vd == null) {
+ vd = new ViewData(value);
+ setViewData(key, vd);
+ }
+ vd.setCurrentValue(input.getValue());
}
}
@Override
public void render(String value, Object key, SafeHtmlBuilder sb) {
// Get the view data.
- String viewData = getViewData(key);
- if (viewData != null && viewData.equals(value)) {
+ ViewData viewData = getViewData(key);
+ if (viewData != null && viewData.getCurrentValue().equals(value)) {
clearViewData(key);
viewData = null;
}
- String s = (viewData != null) ? viewData : (value != null ? value : null);
+ String s = (viewData != null) ? viewData.getCurrentValue() : value;
if (s != null) {
SafeHtml html = renderer.render(s);
// Note: template will not treat SafeHtml specially
sb.append(template.input(html.asString()));
} else {
- sb.appendHtmlConstant("<input type=\"text\"></input>");
+ sb.appendHtmlConstant("<input type=\"text\" tabindex=\"-1\"></input>");
}
}
+
+ @Override
+ protected void finishEditing(Element parent, String value, Object key,
+ ValueUpdater<String> valueUpdater) {
+ String newValue = getInputElement(parent).getValue();
+
+ // Get the view data.
+ ViewData vd = getViewData(key);
+ if (vd == null) {
+ vd = new ViewData(value);
+ setViewData(key, vd);
+ }
+ vd.setCurrentValue(newValue);
+
+ // Fire the value updater if the value has changed.
+ if (valueUpdater != null && !vd.getCurrentValue().equals(vd.getLastValue())) {
+ vd.setLastValue(newValue);
+ valueUpdater.update(newValue);
+ }
+
+ // Blur the element.
+ super.finishEditing(parent, newValue, key, valueUpdater);
+ }
+
+ @Override
+ protected InputElement getInputElement(Element parent) {
+ return super.getInputElement(parent).<InputElement> cast();
+ }
}
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractCellTree.java b/user/src/com/google/gwt/user/cellview/client/AbstractCellTree.java
index 649bf0e..0d5ede03 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractCellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTree.java
@@ -30,8 +30,11 @@
* An abstract representation of a tree widget that renders items using
* {@link com.google.gwt.cell.client.Cell}s.
*/
-public abstract class AbstractCellTree extends Composite
- implements HasOpenHandlers<TreeNode>, HasCloseHandlers<TreeNode> {
+public abstract class AbstractCellTree extends Composite implements
+ HasOpenHandlers<TreeNode>, HasCloseHandlers<TreeNode>,
+ HasKeyboardSelectionPolicy {
+
+ private KeyboardSelectionPolicy keyboardSelectionPolicy = KeyboardSelectionPolicy.ENABLED;
/**
* The {@link TreeViewModel} that backs the tree.
@@ -56,6 +59,10 @@
return addHandler(handler, OpenEvent.getType());
}
+ public KeyboardSelectionPolicy getKeyboardSelectionPolicy() {
+ return keyboardSelectionPolicy;
+ }
+
/**
* Get the root {@link TreeNode}.
*
@@ -72,6 +79,13 @@
return viewModel;
}
+ public void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy) {
+ if (policy == null) {
+ throw new NullPointerException("KeyboardSelectionPolicy cannot be null");
+ }
+ this.keyboardSelectionPolicy = policy;
+ }
+
/**
* Get the {@link NodeInfo} that will provide the information to retrieve and
* display the children of the specified value.
@@ -84,6 +98,15 @@
}
/**
+ * Check if keyboard selection is disabled.
+ *
+ * @return true if disabled, false if enabled.
+ */
+ protected boolean isKeyboardSelectionDisabled() {
+ return KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy;
+ }
+
+ /**
* Check if the value is known to be a leaf node.
*
* @param value the value at the node
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
index c5f74e9..4300d31 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractHasData.java
@@ -15,8 +15,12 @@
*/
package com.google.gwt.user.cellview.client;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventHandler;
@@ -27,7 +31,10 @@
import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.HasKeyProvider;
import com.google.gwt.view.client.ProvidesKey;
@@ -37,15 +44,17 @@
import com.google.gwt.view.client.SelectionModel;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* An abstract {@link Widget} that implements {@link HasData}.
*
* @param <T> the data type of each row
*/
-public abstract class AbstractHasData<T> extends Widget
- implements HasData<T>, HasKeyProvider<T> {
+public abstract class AbstractHasData<T> extends Widget implements HasData<T>,
+ HasKeyProvider<T>, Focusable, HasKeyboardPagingPolicy {
/**
* Implementation of {@link HasDataPresenter.View} used by this widget.
@@ -55,13 +64,14 @@
private static class View<T> implements HasDataPresenter.View<T> {
private final AbstractHasData<T> hasData;
+ private boolean wasFocused;
public View(AbstractHasData<T> hasData) {
this.hasData = hasData;
}
- public <H extends EventHandler> HandlerRegistration addHandler(
- H handler, Type<H> type) {
+ public <H extends EventHandler> HandlerRegistration addHandler(H handler,
+ Type<H> type) {
return hasData.addHandler(handler, type);
}
@@ -74,8 +84,8 @@
}
public ElementIterator getChildIterator() {
- return new HasDataPresenter.DefaultElementIterator(
- this, hasData.getChildContainer().getFirstChildElement());
+ return new HasDataPresenter.DefaultElementIterator(this,
+ hasData.getChildContainer().getFirstChildElement());
}
public void onUpdateSelection() {
@@ -88,29 +98,49 @@
}
public void replaceAllChildren(List<T> values, SafeHtml html) {
+ // Removing elements can fire a blur event, which we ignore.
+ wasFocused = hasData.isFocused;
+ hasData.isRefreshing = true;
hasData.replaceAllChildren(values, html);
+ hasData.isRefreshing = false;
fireValueChangeEvent();
}
public void replaceChildren(List<T> values, int start, SafeHtml html) {
+ // Removing elements can fire a blur event, which we ignore.
+ wasFocused = hasData.isFocused;
+ hasData.isRefreshing = true;
hasData.replaceChildren(values, start, html);
+ hasData.isRefreshing = false;
fireValueChangeEvent();
}
public void resetFocus() {
- hasData.resetFocus();
+ if (wasFocused) {
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ if (!hasData.resetFocusOnCell()) {
+ Element elem = hasData.getKeyboardSelectedElement();
+ if (elem != null) {
+ elem.focus();
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public void setKeyboardSelected(int index, boolean seleted,
+ boolean stealFocus) {
+ hasData.setKeyboardSelected(index, seleted, stealFocus);
}
public void setLoadingState(LoadingState state) {
+ hasData.isRefreshing = true;
hasData.setLoadingState(state);
+ hasData.isRefreshing = false;
}
- /**
- * Update an element to reflect its selected state.
- *
- * @param elem the element to update
- * @param selected true if selected, false if not
- */
public void setSelected(Element elem, boolean selected) {
hasData.setSelected(elem, selected);
}
@@ -122,9 +152,9 @@
// Use an anonymous class to override ValueChangeEvents's protected
// constructor. We can't call ValueChangeEvent.fire() because this class
// doesn't implement HasValueChangeHandlers.
- hasData.fireEvent(
- new ValueChangeEvent<List<T>>(hasData.getDisplayedItems()) {
- });
+ hasData.fireEvent(new ValueChangeEvent<List<T>>(
+ hasData.getDisplayedItems()) {
+ });
}
}
@@ -141,8 +171,8 @@
* @param tmpElem a temporary element
* @return the parent element
*/
- static Element convertToElements(
- Widget widget, com.google.gwt.user.client.Element tmpElem, SafeHtml html) {
+ static Element convertToElements(Widget widget,
+ com.google.gwt.user.client.Element tmpElem, SafeHtml html) {
// Attach an event listener so we can catch synchronous load events from
// cached images.
DOM.setEventListener(tmpElem, widget);
@@ -162,8 +192,8 @@
* @param childContainer the container that holds the contents
* @param html the html to set
*/
- static void replaceAllChildren(
- Widget widget, Element childContainer, SafeHtml html) {
+ static void replaceAllChildren(Widget widget, Element childContainer,
+ SafeHtml html) {
// If the widget is not attached, attach an event listener so we can catch
// synchronous load events from cached images.
if (!widget.isAttached()) {
@@ -225,24 +255,39 @@
}
/**
- * The presenter.
+ * A boolean indicating that the widget has focus.
*/
- private final HasDataPresenter<T> presenter;
+ boolean isFocused;
+
+ private char accessKey = 0;
/**
- * If null, each T will be used as its own key.
+ * A boolean indicating that the widget is refreshing, so all events should be
+ * ignored.
*/
- private ProvidesKey<T> providesKey;
+ private boolean isRefreshing;
+
+ private final HasDataPresenter<T> presenter;
+ private int tabIndex;
/**
* Constructs an {@link AbstractHasData} with the given page size.
*
* @param pageSize the page size
*/
- public AbstractHasData(Element elem, final int pageSize, final ProvidesKey<T> keyProvider) {
+ public AbstractHasData(Element elem, final int pageSize,
+ final ProvidesKey<T> keyProvider) {
setElement(elem);
- this.presenter = new HasDataPresenter<T>(this, new View<T>(this), pageSize);
- this.providesKey = keyProvider;
+ this.presenter = new HasDataPresenter<T>(this, new View<T>(this), pageSize,
+ keyProvider);
+
+ // Sink events.
+ Set<String> eventTypes = new HashSet<String>();
+ eventTypes.add("focus");
+ eventTypes.add("blur");
+ eventTypes.add("keydown");
+ eventTypes.add("mousedown"); // Used by subclasses to steal focus.
+ CellBasedWidgetImpl.get().sinkEvents(this, eventTypes);
}
public HandlerRegistration addRangeChangeHandler(
@@ -256,11 +301,20 @@
}
/**
+ * Get the access key.
+ *
+ * @return the access key, or -1 if not set
+ */
+ public char getAccessKey() {
+ return accessKey;
+ }
+
+ /**
* Get the row value at the specified visible index. Index 0 corresponds to
* the first item on the page.
*
* @param indexOnPage the index on the page
- * @return the row vaule
+ * @return the row value
*/
public T getDisplayedItem(int indexOnPage) {
checkRowBounds(indexOnPage);
@@ -274,8 +328,16 @@
return new ArrayList<T>(presenter.getRowData());
}
+ public KeyboardPagingPolicy getKeyboardPagingPolicy() {
+ return presenter.getKeyboardPagingPolicy();
+ }
+
+ public KeyboardSelectionPolicy getKeyboardSelectionPolicy() {
+ return presenter.getKeyboardSelectionPolicy();
+ }
+
public ProvidesKey<T> getKeyProvider() {
- return providesKey;
+ return presenter.getKeyProvider();
}
/**
@@ -302,6 +364,10 @@
return presenter.getSelectionModel();
}
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
public Range getVisibleRange() {
return presenter.getVisibleRange();
}
@@ -311,12 +377,114 @@
}
/**
+ * Handle browser events. Subclasses should override
+ * {@link #onBrowserEvent2(Event)} if they want to extend browser event
+ * handling.
+ *
+ * @see #onBrowserEvent2(Event)
+ */
+ @Override
+ public final void onBrowserEvent(Event event) {
+ CellBasedWidgetImpl.get().onBrowserEvent(this, event);
+
+ // Ignore spurious events (such as onblur) while we refresh the table.
+ if (isRefreshing) {
+ return;
+ }
+
+ // Verify that the target is still a child of this widget. IE fires focus
+ // events even after the element has been removed from the DOM.
+ EventTarget eventTarget = event.getEventTarget();
+ if (!Element.is(eventTarget)
+ || !getElement().isOrHasChild(Element.as(eventTarget))) {
+ return;
+ }
+ super.onBrowserEvent(event);
+
+ String eventType = event.getType();
+ if ("focus".equals(eventType)) {
+ // Remember the focus state.
+ isFocused = true;
+ onFocus();
+ } else if ("blur".equals(eventType)) {
+ // Remember the blur state.
+ isFocused = false;
+ onBlur();
+ } else if ("keydown".equals(eventType) && !isKeyboardNavigationSuppressed()) {
+ // A key event indicates that we have focus.
+ isFocused = true;
+
+ // Handle keyboard navigation. Prevent default on navigation events to
+ // prevent default scrollbar behavior.
+ int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyCodes.KEY_DOWN:
+ presenter.keyboardNext();
+ event.preventDefault();
+ return;
+ case KeyCodes.KEY_UP:
+ presenter.keyboardPrev();
+ event.preventDefault();
+ return;
+ case KeyCodes.KEY_PAGEDOWN:
+ presenter.keyboardNextPage();
+ event.preventDefault();
+ return;
+ case KeyCodes.KEY_PAGEUP:
+ presenter.keyboardPrevPage();
+ event.preventDefault();
+ return;
+ case KeyCodes.KEY_HOME:
+ presenter.keyboardHome();
+ event.preventDefault();
+ return;
+ case KeyCodes.KEY_END:
+ presenter.keyboardEnd();
+ event.preventDefault();
+ return;
+ case 32:
+ // Select the node on space.
+ presenter.keyboardToggleSelect();
+ event.preventDefault();
+ return;
+ }
+ }
+
+ // Let subclasses handle the event now.
+ onBrowserEvent2(event);
+ }
+
+ /**
* Redraw the widget using the existing data.
*/
public void redraw() {
presenter.redraw();
}
+ public void setAccessKey(char key) {
+ this.accessKey = key;
+ setKeyboardSelected(getKeyboardSelectedRow(), true, false);
+ }
+
+ public void setFocus(boolean focused) {
+ Element elem = getKeyboardSelectedElement();
+ if (elem != null) {
+ if (focused) {
+ elem.focus();
+ } else {
+ elem.blur();
+ }
+ }
+ }
+
+ public void setKeyboardPagingPolicy(KeyboardPagingPolicy policy) {
+ presenter.setKeyboardPagingPolicy(policy);
+ }
+
+ public void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy) {
+ presenter.setKeyboardSelectionPolicy(policy);
+ }
+
/**
* Set the number of rows per page and refresh the view.
*
@@ -355,6 +523,11 @@
presenter.setSelectionModel(selectionModel);
}
+ public void setTabIndex(int index) {
+ this.tabIndex = index;
+ setKeyboardSelected(getKeyboardSelectedRow(), true, false);
+ }
+
public final void setVisibleRange(int start, int length) {
setVisibleRange(new Range(start, length));
}
@@ -363,26 +536,138 @@
presenter.setVisibleRange(range);
}
- public void setVisibleRangeAndClearData(
- Range range, boolean forceRangeChangeEvent) {
+ public void setVisibleRangeAndClearData(Range range,
+ boolean forceRangeChangeEvent) {
presenter.setVisibleRangeAndClearData(range, forceRangeChangeEvent);
}
/**
+ * Check if a cell consumes the specified event type.
+ *
+ * @param cell the cell
+ * @param eventType the event type to check
+ * @return true if consumed, false if not
+ */
+ protected boolean cellConsumesEventType(Cell<?> cell, String eventType) {
+ Set<String> consumedEvents = cell.getConsumedEvents();
+ return consumedEvents != null && consumedEvents.contains(eventType);
+ }
+
+ /**
* Checks that the row is within the correct bounds.
*
* @param row row index to check
* @throws IndexOutOfBoundsException
*/
protected void checkRowBounds(int row) {
- int rowCount = getChildCount();
- if ((row >= rowCount) || (row < 0)) {
- throw new IndexOutOfBoundsException(
- "Row index: " + row + ", Row size: " + rowCount);
+ if (!isRowWithinBounds(row)) {
+ throw new IndexOutOfBoundsException("Row index: " + row + ", Row size: "
+ + getRowCount());
}
}
/**
+ * Convert the specified HTML into DOM elements and return the parent of the
+ * DOM elements.
+ *
+ * @param html the HTML to convert
+ * @return the parent element
+ */
+ protected Element convertToElements(SafeHtml html) {
+ return convertToElements(this, getTmpElem(), html);
+ }
+
+ /**
+ * Check whether or not the cells in the view depend on the selection state.
+ *
+ * @return true if cells depend on selection, false if not
+ */
+ protected abstract boolean dependsOnSelection();
+
+ /**
+ * @return the element that holds the rendered cells
+ */
+ protected abstract Element getChildContainer();
+
+ /**
+ * Get the element that has keyboard selection.
+ *
+ * @return the keyboard selected element
+ */
+ protected abstract Element getKeyboardSelectedElement();
+
+ /**
+ * Get the row index of the keyboard selected row.
+ *
+ * @return the row index
+ */
+ protected int getKeyboardSelectedRow() {
+ return presenter.getKeyboardSelectedRow();
+ }
+
+ /**
+ * Get the key for the specified value.
+ *
+ * @param value the value
+ * @return the key
+ */
+ protected Object getValueKey(T value) {
+ ProvidesKey<T> keyProvider = getKeyProvider();
+ return keyProvider == null ? value : keyProvider.getKey(value);
+ }
+
+ /**
+ * Check if keyboard navigation is being suppressed, such as when the user is
+ * editing a cell.
+ *
+ * @return true if suppressed, false if not
+ */
+ protected abstract boolean isKeyboardNavigationSuppressed();
+
+ /**
+ * Checks that the row is within the correct bounds.
+ *
+ * @param row row index to check
+ * @return true if within bounds, false if not
+ */
+ protected boolean isRowWithinBounds(int row) {
+ return row >= 0 && row < getChildCount()
+ && row < presenter.getRowData().size();
+ }
+
+ /**
+ * Called when the widget is blurred.
+ */
+ protected void onBlur() {
+ }
+
+ /**
+ * Called after {@link #onBrowserEvent(Event)} completes.
+ *
+ * @param event the event that was fired
+ */
+ protected void onBrowserEvent2(Event event) {
+ }
+
+ /**
+ * Called when the widget is focused.
+ */
+ protected void onFocus() {
+ }
+
+ @Override
+ protected void onUnload() {
+ isFocused = false;
+ super.onUnload();
+ }
+
+ /**
+ * Called when selection changes.
+ */
+ protected void onUpdateSelection() {
+ }
+
+ /**
* Render all row values into the specified {@link SafeHtmlBuilder}.
*
* @param sb the {@link SafeHtmlBuilder} to render into
@@ -394,6 +679,79 @@
int start, SelectionModel<? super T> selectionModel);
/**
+ * Replace all children with the specified html.
+ *
+ * @param values the values of the new children
+ * @param html the html to render in the child
+ */
+ protected void replaceAllChildren(List<T> values, SafeHtml html) {
+ replaceAllChildren(this, getChildContainer(), html);
+ }
+
+ /**
+ * Convert the specified HTML into DOM elements and replace the existing
+ * elements starting at the specified index. If the number of children
+ * specified exceeds the existing number of children, the remaining children
+ * should be appended.
+ *
+ * @param values the values of the new children
+ * @param start the start index to be replaced
+ * @param html the HTML to convert
+ */
+ protected void replaceChildren(List<T> values, int start, SafeHtml html) {
+ Element newChildren = convertToElements(html);
+ replaceChildren(this, getChildContainer(), newChildren, start, html);
+ }
+
+ /**
+ * Reset focus on the currently focused cell.
+ *
+ * @return true if focus is taken, false if not
+ */
+ protected abstract boolean resetFocusOnCell();
+
+ /**
+ * Make an element focusable or not.
+ *
+ * @param elem the element
+ * @param focusable true to make focusable, false to make unfocusable
+ */
+ protected void setFocusable(Element elem, boolean focusable) {
+ if (focusable) {
+ FocusImpl focusImpl = FocusImpl.getFocusImplForWidget();
+ com.google.gwt.user.client.Element rowElem = elem.cast();
+ focusImpl.setTabIndex(rowElem, getTabIndex());
+ if (accessKey != 0) {
+ focusImpl.setAccessKey(rowElem, accessKey);
+ }
+ } else {
+ // Chrome: Elements remain focusable after removing the tabIndex, so set
+ // it to -1 first.
+ elem.setTabIndex(-1);
+ elem.removeAttribute("tabIndex");
+ elem.removeAttribute("accessKey");
+ }
+ }
+
+ /**
+ * Update an element to reflect its keyboard selected state.
+ *
+ * @param index the index of the element
+ * @param selected true if selected, false if not
+ * @param stealFocus true if the row should steal focus, false if not
+ */
+ protected abstract void setKeyboardSelected(int index, boolean selected,
+ boolean stealFocus);
+
+ /**
+ * Update an element to reflect its selected state.
+ *
+ * @param elem the element to update
+ * @param selected true if selected, false if not
+ */
+ protected abstract void setSelected(Element elem, boolean selected);
+
+ /**
* Add a {@link ValueChangeHandler} that is called when the display values
* change. Used by {@link CellBrowser} to detect when the displayed data
* changes.
@@ -407,36 +765,16 @@
}
/**
- * Convert the specified HTML into DOM elements and return the parent of the
- * DOM elements.
- *
- * @param html the HTML to convert
- * @return the parent element
- */
- // TODO(jlabanca): Which of the following methods should we expose.
- Element convertToElements(SafeHtml html) {
- return convertToElements(this, getTmpElem(), html);
- }
-
- /**
- * Check whether or not the cells in the view depend on the selection state.
- *
- * @return true if cells depend on selection, false if not
- */
- abstract boolean dependsOnSelection();
-
- /**
- * @return the element that holds the rendered cells
- */
- abstract Element getChildContainer();
-
- /**
* @return the number of child elements
*/
int getChildCount() {
return getChildContainer().getChildCount();
}
+ HasDataPresenter<T> getPresenter() {
+ return presenter;
+ }
+
/**
* Get the first index of a displayed item according to its key.
*
@@ -467,54 +805,10 @@
}
/**
- * Called when selection changes.
- */
- void onUpdateSelection() {
- }
-
- /**
- * Replace all children with the specified html.
- *
- * @param values the values of the new children
- * @param html the html to render in the child
- */
- void replaceAllChildren(List<T> values, SafeHtml html) {
- replaceAllChildren(this, getChildContainer(), html);
- }
-
- /**
- * Convert the specified HTML into DOM elements and replace the existing
- * elements starting at the specified index. If the number of children
- * specified exceeds the existing number of children, the remaining children
- * should be appended.
- *
- * @param values the values of the new children
- * @param start the start index to be replaced
- * @param html the HTML to convert
- */
- void replaceChildren(List<T> values, int start, SafeHtml html) {
- Element newChildren = convertToElements(html);
- replaceChildren(this, getChildContainer(), newChildren, start, html);
- }
-
- /**
- * Re-establish focus on an element within the view if desired.
- */
- void resetFocus() {
- }
-
- /**
* Set the current loading state of the data.
*
* @param state the loading state
*/
- abstract void setLoadingState(LoadingState state);
-
- /**
- * Update an element to reflect its selected state.
- *
- * @param elem the element to update
- * @param selected true if selected, false if not
- */
- abstract void setSelected(Element elem, boolean selected);
+ void setLoadingState(LoadingState state) {
+ }
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
index 3de6640..d8dde3a 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImpl.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.cellview.client;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
@@ -67,6 +68,15 @@
}
/**
+ * Reset focus on an element.
+ *
+ * @param command the command to execute when resetting focus
+ */
+ public void resetFocus(ScheduledCommand command) {
+ command.execute();
+ }
+
+ /**
* Sink events on the widget.
*
* @param widget the {@link Widget} that will handle the events
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
index 0ef49c7..cd0cf53 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBasedWidgetImplTrident.java
@@ -94,8 +94,8 @@
/**
* Dispatch an event through the normal GWT mechanism.
*/
- private static native void dispatchEvent(
- Event evt, Element elem, EventListener listener) /*-{
+ private static native void dispatchEvent(Event evt, Element elem,
+ EventListener listener) /*-{
@com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/user/client/Element;Lcom/google/gwt/user/client/EventListener;)(evt, elem, listener);
}-*/;
@@ -124,8 +124,8 @@
}-*/;
/**
- * Used by {@link #initFocusEventSystem()} and
- * {@link #initLoadEvents(String)}.
+ * Used by {@link #initFocusEventSystem()} and {@link #initLoadEvents(String)}
+ * to handle non bubbling events .
*
* @param event
*/
@@ -243,7 +243,6 @@
// Initialize the change event triggers.
changeEventTriggers = new HashSet<String>();
changeEventTriggers.add("mouseup");
- changeEventTriggers.add("keyup");
changeEventTriggers.add("mousewheel");
}
@@ -286,12 +285,12 @@
// load/error listeners.
if (loadEventsInitialized && html != null) {
String moduleName = GWT.getModuleName();
- String listener = "__gwt_CellBasedWidgetImplLoadListeners[\"" + moduleName
- + "\"]();";
+ String listener = "__gwt_CellBasedWidgetImplLoadListeners[\""
+ + moduleName + "\"]();";
String htmlString = html.asString();
- htmlString = htmlString.replaceAll("(<img)([\\s/>])",
- "<img onload='" + listener + "' onerror='" + listener + "'$2");
+ htmlString = htmlString.replaceAll("(<img)([\\s/>])", "<img onload='"
+ + listener + "' onerror='" + listener + "'$2");
// We assert that the resulting string is safe
html = SafeHtmlUtils.fromTrustedString(htmlString);
@@ -300,6 +299,12 @@
}
@Override
+ public void resetFocus(ScheduledCommand command) {
+ // IE will not focus an element that was created in this event loop.
+ Scheduler.get().scheduleDeferred(command);
+ }
+
+ @Override
protected int sinkEvent(Widget widget, String typeName) {
if ("change".equals(typeName) || "focus".equals(typeName)
|| "blur".equals(typeName)) {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.css b/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
index acfdff6..6428be8 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
@@ -25,16 +25,16 @@
}
-.cellBrowserItem {
+.cellBrowserEvenItem {
padding: 8px;
}
-@sprite .cellBrowserOpenItem {
- gwt-image: 'cellBrowserOpenBackground';
- background-color: #7b7b7b;
- color: white;
- height: auto;
- overflow: auto;
+.cellBrowserOddItem {
+ padding: 8px;
+}
+
+.cellBrowserKeyboardSelectedItem {
+ background: #ffc;
}
@sprite .cellBrowserSelectedItem {
@@ -42,5 +42,13 @@
background-color: #628cd5;
color: white;
height: auto;
- overflow: auto;
+ overflow: hidden;
+}
+
+@sprite .cellBrowserOpenItem {
+ gwt-image: 'cellBrowserOpenBackground';
+ background-color: #7b7b7b;
+ color: white;
+ height: auto;
+ overflow: hidden;
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
index 1bb919a..2b7aee2 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
@@ -17,15 +17,14 @@
import com.google.gwt.animation.client.Animation;
import com.google.gwt.cell.client.Cell;
-import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
@@ -58,9 +57,7 @@
import com.google.gwt.view.client.TreeViewModel.NodeInfo;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* A "browsable" view of a tree in which only a single node per level may be
@@ -72,14 +69,8 @@
* declaration.
* </p>
*/
-public class CellBrowser extends AbstractCellTree
- implements ProvidesResize, RequiresResize, HasAnimation {
-
- interface Template extends SafeHtmlTemplates {
- @Template("<div style=\"position:relative;padding-right:{0}px;\" class="
- + "\"{1}\">{2}<div>{3}</div></div>")
- SafeHtml div(int imageWidth, String classes, SafeHtml image, SafeHtml cellContents);
- }
+public class CellBrowser extends AbstractCellTree implements ProvidesResize,
+ RequiresResize, HasAnimation {
/**
* A ClientBundle that provides images for this widget.
@@ -133,14 +124,24 @@
String cellBrowserColumn();
/**
+ * Applied to even list items.
+ */
+ String cellBrowserEvenItem();
+
+ /**
* Applied to the first column.
*/
String cellBrowserFirstColumn();
- /**
- * Applied to all list items.
+ /***
+ * Applied to keyboard selected items.
*/
- String cellBrowserItem();
+ String cellBrowserKeyboardSelectedItem();
+
+ /**
+ * Applied to odd list items.
+ */
+ String cellBrowserOddItem();
/***
* Applied to open items.
@@ -158,31 +159,26 @@
String cellBrowserWidget();
}
- /**
- * We override the Resources in {@link CellList} so that the styles in
- * {@link CellList} don't conflict with the styles in {@link CellBrowser}.
- */
- interface CellListResources extends CellList.Resources {
- @Source("CellBrowserOverride.css")
- CellList.Style cellListStyle();
+ interface Template extends SafeHtmlTemplates {
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\">{3}<div>{4}</div></div>")
+ SafeHtml div(int idx, String classes, int imageWidth, SafeHtml imageHtml,
+ SafeHtml cellContents);
+
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\" tabindex=\"{3}\">{4}<div>{5}</div></div>")
+ SafeHtml divFocusable(int idx, String classes, int imageWidth,
+ int tabIndex, SafeHtml imageHtml, SafeHtml cellContents);
+
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"position:relative;padding-right:{2}px;outline:none;\" tabindex=\"{3}\" accessKey=\"{4}\">{5}<div>{6}</div></div>")
+ SafeHtml divFocusableWithKey(int idx, String classes, int imageWidth,
+ int tabIndex, char accessKey, SafeHtml imageHtml, SafeHtml cellContents);
}
/**
- * A wrapper around a cell that adds an open button.
+ * A custom version of cell list used by the browser.
*
- * @param <C> the data type of the cell
+ * @param <T> the data type of list items
*/
- private class CellDecorator<C> implements Cell<C> {
-
- /**
- * The cell used to render the inner contents.
- */
- private final Cell<C> cell;
-
- /**
- * The events consumed by this cell.
- */
- private final Set<String> consumedEvents = new HashSet<String>();
+ private class BrowserCellList<T> extends CellList<T> {
/**
* The level of this list view.
@@ -197,119 +193,225 @@
/**
* The value of the currently open item.
*/
- private C openValue;
+ private T openValue;
- /**
- * The key provider for the node.
- */
- private final ProvidesKey<C> providesKey;
-
- /**
- * The selection model for the node.
- */
- private final SelectionModel<? super C> selectionModel;
-
- /**
- * Construct a new {@link CellDecorator}.
- *
- * @param nodeInfo the {@link NodeInfo} associated with the cell
- * @param level the level of items rendered by this decorator
- */
- public CellDecorator(NodeInfo<C> nodeInfo, int level) {
- this.cell = nodeInfo.getCell();
+ public BrowserCellList(final Cell<T> cell, int level,
+ ProvidesKey<T> keyProvider) {
+ super(cell, cellListResources, keyProvider);
this.level = level;
- this.providesKey = nodeInfo.getProvidesKey();
- this.selectionModel = nodeInfo.getSelectionModel();
+ }
- // Save the consumed events.
- consumedEvents.add("mousedown");
- Set<String> cellEvents = cell.getConsumedEvents();
- if (cellEvents != null) {
- consumedEvents.addAll(cellEvents);
+ @Override
+ protected Element getCellParent(Element item) {
+ return item.getFirstChildElement().getNextSiblingElement();
+ }
+
+ @Override
+ protected void onBrowserEvent2(Event event) {
+ super.onBrowserEvent2(event);
+
+ // Handle keyboard navigation between lists.
+ String eventType = event.getType();
+ if ("keydown".equals(eventType) && !isKeyboardNavigationSuppressed()) {
+ int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyCodes.KEY_LEFT:
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ keyboardNavigateDeep();
+ } else {
+ keyboardNavigateShallow();
+ }
+ return;
+ case KeyCodes.KEY_RIGHT:
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ keyboardNavigateShallow();
+ } else {
+ keyboardNavigateDeep();
+ }
+ return;
+ }
}
}
- public boolean dependsOnSelection() {
- return cell.dependsOnSelection();
- }
+ @Override
+ protected void onFocus() {
+ super.onFocus();
- public Set<String> getConsumedEvents() {
- return consumedEvents;
- }
-
- public boolean handlesSelection() {
- return cell.handlesSelection();
- }
-
- public boolean isEditing(Element element, C value, Object key) {
- return cell.isEditing(element, value, key);
- }
-
- public void onBrowserEvent(Element parent, C value, Object key,
- NativeEvent event, ValueUpdater<C> valueUpdater) {
-
- // Fire the event to the inner cell.
- cell.onBrowserEvent(
- getCellParent(parent), value, key, event, valueUpdater);
-
- // Open child nodes.
- if (Event.getTypeInt(event.getType()) == Event.ONMOUSEDOWN) {
- setChildState(this, value, true, true);
+ // Open the selected row.
+ int selectedRow = getKeyboardSelectedRow();
+ if (isRowWithinBounds(selectedRow)) {
+ T value = getDisplayedItem(selectedRow);
+ setChildState(this, value, true, true, true);
}
}
- public void render(C value, Object viewData, SafeHtmlBuilder sb) {
- boolean isOpen = (openKey == null) ? false
- : openKey.equals(getValueKey(value));
- boolean isSelected = (selectionModel == null) ? false
- : selectionModel.isSelected(value);
+ @Override
+ protected void renderRowValues(SafeHtmlBuilder sb, List<T> values,
+ int start, SelectionModel<? super T> selectionModel) {
+ Cell<T> cell = getCell();
+ String keyboardSelectedItem = " "
+ + style.cellBrowserKeyboardSelectedItem();
+ String selectedItem = " " + style.cellBrowserSelectedItem();
+ String openItem = " " + style.cellBrowserOpenItem();
+ String evenItem = style.cellBrowserEvenItem();
+ String oddItem = style.cellBrowserOddItem();
+ int keyboardSelectedRow = Math.max(0, getKeyboardSelectedRow()
+ + getPageStart());
+ int length = values.size();
+ int end = start + length;
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ Object key = getValueKey(value);
+ boolean isSelected = selectionModel == null ? false
+ : selectionModel.isSelected(value);
+ boolean isOpen = (openKey == null) ? false : openKey.equals(key);
+ StringBuilder classesBuilder = new StringBuilder();
+ classesBuilder.append(i % 2 == 0 ? evenItem : oddItem);
+ if (isOpen) {
+ classesBuilder.append(openItem);
+ }
+ if (isSelected) {
+ classesBuilder.append(selectedItem);
+ }
- StringBuilder classesBuilder = new StringBuilder();
- classesBuilder.append(style.cellBrowserItem());
- if (isOpen) {
- classesBuilder.append(" ").append(style.cellBrowserOpenItem());
- }
- if (isSelected) {
- classesBuilder.append(" ").append(style.cellBrowserSelectedItem());
- }
- String classes = classesBuilder.toString();
+ SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+ cell.render(value, null, cellBuilder);
- SafeHtml image;
- if (isOpen) {
- image = openImageHtml;
- } else if (isLeaf(value)) {
- image = LEAF_IMAGE;
- } else {
- image = closedImageHtml;
- }
- SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
- cell.render(value, viewData, cellBuilder);
- sb.append(template.div(imageWidth, classes, image,
- cellBuilder.toSafeHtml()));
- }
+ // Figure out which image to use.
+ SafeHtml image;
+ if (isOpen) {
+ image = openImageHtml;
+ } else if (isLeaf(value)) {
+ image = LEAF_IMAGE;
+ } else {
+ image = closedImageHtml;
+ }
- public void setValue(Element parent, C value, Object viewData) {
- cell.setValue(getCellParent(parent), value, viewData);
+ if (i == keyboardSelectedRow) {
+ // This is the focused item.
+ if (isFocused) {
+ classesBuilder.append(keyboardSelectedItem);
+ }
+ char accessKey = getAccessKey();
+ if (accessKey != 0) {
+ sb.append(template.divFocusableWithKey(i,
+ classesBuilder.toString(), imageWidth, getTabIndex(),
+ getAccessKey(), image, cellBuilder.toSafeHtml()));
+ } else {
+ sb.append(template.divFocusable(i, classesBuilder.toString(),
+ imageWidth, getTabIndex(), image, cellBuilder.toSafeHtml()));
+ }
+ } else {
+ sb.append(template.div(i, classesBuilder.toString(), imageWidth,
+ image, cellBuilder.toSafeHtml()));
+ }
+ }
}
/**
- * Get the parent element of the decorated cell.
- *
- * @param parent the parent of this cell
- * @return the decorated cell's parent
+ * Navigate to a deeper node.
*/
- private Element getCellParent(Element parent) {
- return parent.getFirstChildElement().getChild(1).cast();
+ private void keyboardNavigateDeep() {
+ if (isKeyboardSelectionDisabled()) {
+ return;
+ }
+
+ // Move to the child node.
+ if (level < treeNodes.size() - 1) {
+ TreeNodeImpl<?> treeNode = treeNodes.get(level + 1);
+ treeNode.display.setFocus(true);
+
+ // Select the element.
+ int selected = getKeyboardSelectedRow();
+ if (isRowWithinBounds(selected)) {
+ T value = getDisplayedItem(selected);
+ setChildState(this, value, true, true, true);
+ }
+ }
}
/**
- * Get the key for the specified value.
- *
- * @param value the value
- * @return the key
+ * Navigate to a shallower node.
*/
- private Object getValueKey(C value) {
- return (providesKey == null) ? value : providesKey.getKey(value);
+ private void keyboardNavigateShallow() {
+ if (isKeyboardSelectionDisabled()) {
+ return;
+ }
+
+ // Move to the parent node.
+ if (level > 0) {
+ TreeNodeImpl<?> treeNode = treeNodes.get(level - 1);
+ treeNode.display.setFocus(true);
+ }
+ }
+ }
+
+ /**
+ * An implementation of {@link CellList.Resources} that delegates to
+ * {@link CellBrowser.Resources}.
+ */
+ private static class CellListResourcesImpl implements CellList.Resources {
+
+ private final CellBrowser.Resources delegate;
+ private final CellListStyleImpl style;
+
+ public CellListResourcesImpl(CellBrowser.Resources delegate) {
+ this.delegate = delegate;
+ this.style = new CellListStyleImpl(delegate.cellBrowserStyle());
+ }
+
+ public ImageResource cellListSelectedBackground() {
+ return delegate.cellBrowserSelectedBackground();
+ }
+
+ public CellList.Style cellListStyle() {
+ return style;
+ }
+ }
+
+ /**
+ * An implementation of {@link CellList.Style} that delegates to
+ * {@link CellBrowser.Style}.
+ */
+ private static class CellListStyleImpl implements CellList.Style {
+
+ private final CellBrowser.Style delegate;
+
+ public CellListStyleImpl(CellBrowser.Style delegate) {
+ this.delegate = delegate;
+ }
+
+ public String cellListEvenItem() {
+ return delegate.cellBrowserEvenItem();
+ }
+
+ public String cellListKeyboardSelectedItem() {
+ return delegate.cellBrowserKeyboardSelectedItem();
+ }
+
+ public String cellListOddItem() {
+ return delegate.cellBrowserOddItem();
+ }
+
+ public String cellListSelectedItem() {
+ return delegate.cellBrowserSelectedItem();
+ }
+
+ public String cellListWidget() {
+ // Do not apply any style to the list itself.
+ return null;
+ }
+
+ public boolean ensureInjected() {
+ return delegate.ensureInjected();
+ }
+
+ public String getName() {
+ return delegate.getName();
+ }
+
+ public String getText() {
+ return delegate.getText();
}
}
@@ -363,8 +465,7 @@
* @param <C> the data type of the children of the node
*/
private class TreeNodeImpl<C> implements TreeNode {
- private final CellDecorator<C> cell;
- private final AbstractHasData<C> display;
+ private final BrowserCellList<C> display;
private NodeInfo<C> nodeInfo;
private final Object value;
private final HandlerRegistration valueChangeHandler;
@@ -377,37 +478,34 @@
* @param value the value of the node
* @param display the display associated with the node
* @param cell the {@link Cell} used to render the data
- * @param widget the widget that represents the list view
+ * @param widget the widget that wraps the display
*/
public TreeNodeImpl(final NodeInfo<C> nodeInfo, Object value,
- AbstractHasData<C> display, final CellDecorator<C> cell,
- Widget widget) {
- this.cell = cell;
+ final BrowserCellList<C> display, Widget widget) {
this.display = display;
this.nodeInfo = nodeInfo;
this.value = value;
this.widget = widget;
// Trim to the current level if the open node disappears.
- valueChangeHandler = display.addValueChangeHandler(
- new ValueChangeHandler<List<C>>() {
- public void onValueChange(ValueChangeEvent<List<C>> event) {
- Object openKey = cell.openKey;
- if (openKey != null) {
- boolean stillExists = false;
- List<C> displayValues = event.getValue();
- for (C displayValue : displayValues) {
- if (openKey.equals(cell.getValueKey(displayValue))) {
- stillExists = true;
- break;
- }
- }
- if (!stillExists) {
- trimToLevel(cell.level);
- }
+ valueChangeHandler = display.addValueChangeHandler(new ValueChangeHandler<List<C>>() {
+ public void onValueChange(ValueChangeEvent<List<C>> event) {
+ Object openKey = display.openKey;
+ if (openKey != null) {
+ boolean stillExists = false;
+ List<C> displayValues = event.getValue();
+ for (C displayValue : displayValues) {
+ if (openKey.equals(display.getValueKey(displayValue))) {
+ stillExists = true;
+ break;
}
}
- });
+ if (!stillExists) {
+ trimToLevel(display.level);
+ }
+ }
+ }
+ });
}
public int getChildCount() {
@@ -429,7 +527,7 @@
public TreeNodeImpl<?> getParent() {
assertNotDestroyed();
- return (cell.level == 0) ? null : treeNodes.get(cell.level - 1);
+ return (display.level == 0) ? null : treeNodes.get(display.level - 1);
}
public Object getValue() {
@@ -445,8 +543,8 @@
public boolean isChildOpen(int index) {
assertNotDestroyed();
checkChildBounds(index);
- return (cell.openKey == null) ? false : cell.openKey.equals(
- cell.getValueKey(getChildValue(index)));
+ return (display.openKey == null) ? false
+ : display.openKey.equals(display.getValueKey(getChildValue(index)));
}
public boolean isDestroyed() {
@@ -460,7 +558,8 @@
public TreeNode setChildOpen(int index, boolean open, boolean fireEvents) {
assertNotDestroyed();
checkChildBounds(index);
- return setChildState(cell, getChildValue(index), open, fireEvents);
+ return setChildState(display, getChildValue(index), open, fireEvents,
+ true);
}
/**
@@ -501,35 +600,19 @@
* @return the index of the open item, or -1 if not found
*/
private int getOpenIndex() {
- return display.indexOf(cell.openValue);
+ return display.indexOf(display.openValue);
}
}
- /**
- * The element used in place of an image when a node has no children.
- */
- private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant(
- "<div style='position:absolute;display:none;'></div>");
-
private static Resources DEFAULT_RESOURCES;
/**
- * The override styles used in {@link CellList}.
+ * The element used in place of an image when a node has no children.
*/
- private static CellListResources cellListResource;
+ private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant("<div style='position:absolute;display:none;'></div>");
private static Template template;
- /**
- * Get the {@link CellList.Resources} overrides.
- */
- private static CellListResources getCellListResources() {
- if (cellListResource == null) {
- cellListResource = GWT.create(CellListResources.class);
- }
- return cellListResource;
- }
-
private static Resources getDefaultResources() {
if (DEFAULT_RESOURCES == null) {
DEFAULT_RESOURCES = GWT.create(Resources.class);
@@ -543,9 +626,9 @@
private final ScrollAnimation animation = new ScrollAnimation();
/**
- * The default width of new columns.
+ * The resources used by the {@link CellList}.
*/
- private int defaultWidth = 200;
+ private final CellList.Resources cellListResources;
/**
* The HTML used to generate the closed image.
@@ -553,6 +636,16 @@
private final SafeHtml closedImageHtml;
/**
+ * The default width of new columns.
+ */
+ private int defaultWidth = 200;
+
+ /**
+ * The maximum width of the open and closed images.
+ */
+ private final int imageWidth;
+
+ /**
* A boolean indicating whether or not animations are enabled.
*/
private boolean isAnimationEnabled;
@@ -563,30 +656,24 @@
private int minWidth;
/**
- * The maximum width of the open and closed images.
- */
- private final int imageWidth;
-
- /**
* The HTML used to generate the open image.
*/
private final SafeHtml openImageHtml;
/**
- * The styles used by this widget.
- */
- private final Style style;
-
- /**
* The element used to maintain the scrollbar when columns are removed.
*/
private Element scrollLock;
/**
+ * The styles used by this widget.
+ */
+ private final Style style;
+
+ /**
* The visible {@link TreeNodeImpl}s.
*/
- private final List<TreeNodeImpl<?>> treeNodes = new ArrayList<
- TreeNodeImpl<?>>();
+ private final List<TreeNodeImpl<?>> treeNodes = new ArrayList<TreeNodeImpl<?>>();
/**
* Construct a new {@link CellBrowser}.
@@ -607,14 +694,15 @@
* @param rootValue the hidden root value of the tree
* @param resources the {@link Resources} used for images
*/
- public <T> CellBrowser(
- TreeViewModel viewModel, T rootValue, Resources resources) {
+ public <T> CellBrowser(TreeViewModel viewModel, T rootValue,
+ Resources resources) {
super(viewModel);
if (template == null) {
template = GWT.create(Template.class);
}
this.style = resources.cellBrowserStyle();
this.style.ensureInjected();
+ this.cellListResources = new CellListResourcesImpl(resources);
initWidget(new SplitLayoutPanel());
getElement().getStyle().setOverflow(Overflow.AUTO);
setStyleName(this.style.cellBrowserWidget());
@@ -704,6 +792,14 @@
this.defaultWidth = width;
}
+ @Override
+ public void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy) {
+ super.setKeyboardSelectionPolicy(policy);
+ for (TreeNodeImpl<?> treeNode : treeNodes) {
+ treeNode.display.setKeyboardSelectionPolicy(policy);
+ }
+ }
+
/**
* Set the minimum width of columns.
*
@@ -714,24 +810,10 @@
}
/**
- * Create a {@link HasData} that will display items. The {@link HasData} must
- * extend {@link Widget}.
*
- * @param <C> the item type in the list view
- * @param nodeInfo the node info with child data
- * @param cell the cell to use in the list view
- * @return the {@link HasData}
- */
- // TODO(jlabanca): Move createDisplay into constructor factory arg?
- protected <C> AbstractHasData<C> createDisplay(
- NodeInfo<C> nodeInfo, Cell<C> cell) {
- CellList<C> display = new CellList<C>(cell, getCellListResources(),
- nodeInfo.getProvidesKey());
- display.setValueUpdater(nodeInfo.getValueUpdater());
- return display;
- }
-
- /**
+ *
+ *
+ *
* Create a pager to control the list view.
*
* @param <C> the item type in the list view
@@ -770,11 +852,13 @@
private <C> void appendTreeNode(final NodeInfo<C> nodeInfo, Object value) {
// Create the list view.
final int level = treeNodes.size();
- final CellDecorator<C> cell = new CellDecorator<C>(nodeInfo, level);
- final AbstractHasData<C> view = createDisplay(nodeInfo, cell);
+ final BrowserCellList<C> view = createDisplay(nodeInfo, level);
- // Create a pager and wrap the components in a scrollable container.
+ // Create a pager and wrap the components in a scrollable container. Set the
+ // tabIndex to -1 so the user can tab between lists without going through
+ // the scrollable.
ScrollPanel scrollable = new ScrollPanel();
+ scrollable.getElement().setTabIndex(-1);
final Widget pager = createPager(view);
if (pager != null) {
FlowPanel flowPanel = new FlowPanel();
@@ -790,8 +874,8 @@
}
// Create a TreeNode.
- TreeNodeImpl<C> treeNode = new TreeNodeImpl<C>(
- nodeInfo, value, view, cell, scrollable);
+ TreeNodeImpl<C> treeNode = new TreeNodeImpl<C>(nodeInfo, value, view,
+ scrollable);
treeNodes.add(treeNode);
// Attach the view to the selection model and node info.
@@ -809,6 +893,23 @@
}
/**
+ * Create a {@link HasData} that will display items. The {@link HasData} must
+ * extend {@link Widget}.
+ *
+ * @param <C> the item type in the list view
+ * @param nodeInfo the node info with child data
+ * @param level the level of the list
+ * @return the {@link HasData}
+ */
+ private <C> BrowserCellList<C> createDisplay(NodeInfo<C> nodeInfo, int level) {
+ BrowserCellList<C> display = new BrowserCellList<C>(nodeInfo.getCell(),
+ level, nodeInfo.getProvidesKey());
+ display.setValueUpdater(nodeInfo.getValueUpdater());
+ display.setKeyboardSelectionPolicy(getKeyboardSelectionPolicy());
+ return display;
+ }
+
+ /**
* Get the HTML representation of an image.
*
* @param res the {@link ImageResource} to render as HTML
@@ -851,8 +952,8 @@
* @param fireEvents true to fireEvents
* @return the open {@link TreeNode}, or null if not opened
*/
- private <C> TreeNode setChildState(
- CellDecorator<C> cell, C value, boolean open, boolean fireEvents) {
+ private <C> TreeNode setChildState(BrowserCellList<C> cellList, C value,
+ boolean open, boolean fireEvents, boolean redraw) {
// Early exit if the node is a leaf.
if (isLeaf(value)) {
@@ -860,20 +961,20 @@
}
// Get the key of the value to open.
- Object newKey = cell.getValueKey(value);
+ Object newKey = cellList.getValueKey(value);
if (open) {
if (newKey == null) {
// Early exit if opening but the specified node has no key.
return null;
- } else if (newKey.equals(cell.openKey)) {
+ } else if (newKey.equals(cellList.openKey)) {
// Early exit if opening but the specified node is already open.
- return treeNodes.get(cell.level + 1);
+ return treeNodes.get(cellList.level + 1);
}
// Close the currently open node.
- if (cell.openKey != null) {
- setChildState(cell, cell.openValue, false, fireEvents);
+ if (cellList.openKey != null) {
+ setChildState(cellList, cellList.openValue, false, fireEvents, false);
}
// Get the child node info.
@@ -883,33 +984,37 @@
}
// Update the cell so it renders the styles correctly.
- cell.openValue = value;
- cell.openKey = cell.getValueKey(value);
-
- // Refresh the display to update the styles for this node.
- treeNodes.get(cell.level).display.redraw();
+ cellList.openValue = value;
+ cellList.openKey = cellList.getValueKey(value);
// Add the child node.
appendTreeNode(childNodeInfo, value);
- if (fireEvents) {
- OpenEvent.fire(this, treeNodes.get(cell.level + 1));
+ // Refresh the display to update the styles for this node.
+ if (redraw) {
+ treeNodes.get(cellList.level).display.redraw();
}
- return treeNodes.get(cell.level + 1);
+
+ if (fireEvents) {
+ OpenEvent.fire(this, treeNodes.get(cellList.level + 1));
+ }
+ return treeNodes.get(cellList.level + 1);
} else {
// Early exit if closing and the specified node or all nodes are closed.
- if (cell.openKey == null || !cell.openKey.equals(newKey)) {
+ if (cellList.openKey == null || !cellList.openKey.equals(newKey)) {
return null;
}
// Close the node.
- TreeNode closedNode = treeNodes.get(cell.level + 1);
- trimToLevel(cell.level);
- cell.openKey = null;
- cell.openValue = null;
+ TreeNode closedNode = treeNodes.get(cellList.level + 1);
+ trimToLevel(cellList.level);
+ cellList.openKey = null;
+ cellList.openValue = null;
// Refresh the display to update the styles for this node.
- treeNodes.get(cell.level).display.redraw();
+ if (redraw) {
+ treeNodes.get(cellList.level).display.redraw();
+ }
if (fireEvents) {
CloseEvent.fire(this, closedNode);
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.css b/user/src/com/google/gwt/user/cellview/client/CellList.css
index 7241573..cd7546e 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellList.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellList.css
@@ -14,7 +14,7 @@
* the License.
*/
.cellListWidget {
-
+
}
.cellListEvenItem {
@@ -25,10 +25,14 @@
cursor: pointer;
}
+.cellListKeyboardSelectedItem {
+ background: #ffc;
+}
+
@sprite .cellListSelectedItem {
gwt-image: 'cellListSelectedBackground';
background-color: #628cd5;
color: white;
height: auto;
- overflow: auto;
+ overflow: visible;
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.java b/user/src/com/google/gwt/user/cellview/client/CellList.java
index e1d3793..7eaaa66 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellList.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -18,9 +18,11 @@
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
@@ -37,13 +39,12 @@
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionModel;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A single column list of cells.
- *
+ *
* @param <T> the data type of list items
*/
public class CellList<T> extends AbstractHasData<T> {
@@ -81,6 +82,11 @@
String cellListEvenItem();
/**
+ * Applied to the keyboard selected item.
+ */
+ String cellListKeyboardSelectedItem();
+
+ /**
* Applied to odd items.
*/
String cellListOddItem();
@@ -97,8 +103,16 @@
}
interface Template extends SafeHtmlTemplates {
- @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\">{2}</div>")
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"outline:none;\">{2}</div>")
SafeHtml div(int idx, String classes, SafeHtml cellContents);
+
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"outline:none;\" tabindex=\"{2}\">{3}</div>")
+ SafeHtml divFocusable(int idx, String classes, int tabIndex,
+ SafeHtml cellContents);
+
+ @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"outline:none;\" tabindex=\"{2}\" accesskey=\"{3}\">{4}</div>")
+ SafeHtml divFocusableWithKey(int idx, String classes, int tabIndex,
+ char accessKey, SafeHtml cellContents);
}
/**
@@ -118,6 +132,7 @@
}
private final Cell<T> cell;
+ private boolean cellIsEditing;
private final Element childContainer;
private SafeHtml emptyListMessage = SafeHtmlUtils.fromSafeConstant("");
@@ -129,7 +144,7 @@
/**
* Construct a new {@link CellList}.
- *
+ *
* @param cell the cell used to render each item
*/
public CellList(final Cell<T> cell) {
@@ -138,7 +153,7 @@
/**
* Construct a new {@link CellList} with the specified {@link Resources}.
- *
+ *
* @param cell the cell used to render each item
* @param resources the resources used for this widget
*/
@@ -148,7 +163,7 @@
/**
* Construct a new {@link CellList} with the specified {@link ProvidesKey key provider}.
- *
+ *
* @param cell the cell used to render each item
* @param keyProvider an instance of ProvidesKey<T>, or null if the record
* object should act as its own key
@@ -160,7 +175,7 @@
/**
* Construct a new {@link CellList} with the specified {@link Resources}
* and {@link ProvidesKey key provider}.
- *
+ *
* @param cell the cell used to render each item
* @param resources the resources used for this widget
* @param keyProvider an instance of ProvidesKey<T>, or null if the record
@@ -171,7 +186,12 @@
this.cell = cell;
this.style = resources.cellListStyle();
this.style.ensureInjected();
- addStyleName(this.style.cellListWidget());
+
+ String widgetStyle = this.style.cellListWidget();
+ if (widgetStyle != null) {
+ // The widget style is null when used in CellBrowser.
+ addStyleName(widgetStyle);
+ }
// Create the DOM hierarchy.
childContainer = Document.get().createDivElement();
@@ -184,18 +204,12 @@
outerDiv.appendChild(emptyMessageElem);
// Sink events that the cell consumes.
- Set<String> eventsToSink = new HashSet<String>();
- eventsToSink.add("click");
- Set<String> consumedEvents = cell.getConsumedEvents();
- if (consumedEvents != null) {
- eventsToSink.addAll(consumedEvents);
- }
- CellBasedWidgetImpl.get().sinkEvents(this, eventsToSink);
+ CellBasedWidgetImpl.get().sinkEvents(this, cell.getConsumedEvents());
}
/**
* Get the message that is displayed when there is no data.
- *
+ *
* @return the empty message
*/
public SafeHtml getEmptyListMessage() {
@@ -205,7 +219,7 @@
/**
* Get the {@link Element} for the specified index. If the element has not
* been created, null is returned.
- *
+ *
* @param indexOnPage the index on the page
* @return the element, or null if it doesn't exists
* @throws IndexOutOfBoundsException if the index is outside of the current
@@ -219,13 +233,104 @@
return null;
}
+ /**
+ * Set the message to display when there is no data.
+ *
+ * @param html the message to display when there are no results
+ */
+ public void setEmptyListMessage(SafeHtml html) {
+ this.emptyListMessage = html;
+ emptyMessageElem.setInnerHTML(html.asString());
+ }
+
+ /**
+ * Set the value updater to use when cells modify items.
+ *
+ * @param valueUpdater the {@link ValueUpdater}
+ */
+ public void setValueUpdater(ValueUpdater<T> valueUpdater) {
+ this.valueUpdater = valueUpdater;
+ }
+
@Override
- public void onBrowserEvent(Event event) {
- CellBasedWidgetImpl.get().onBrowserEvent(this, event);
- super.onBrowserEvent(event);
+ protected boolean dependsOnSelection() {
+ return cell.dependsOnSelection();
+ }
+
+ /**
+ * Fire an event to the cell.
+ *
+ * @param event the event that was fired
+ * @param parent the parent of the cell
+ * @param value the value of the cell
+ */
+ protected void fireEventToCell(Event event, Element parent, T value) {
+ Set<String> consumedEvents = cell.getConsumedEvents();
+ if (consumedEvents != null && consumedEvents.contains(event.getType())) {
+ Object key = getValueKey(value);
+ boolean cellWasEditing = cell.isEditing(parent, value, key);
+ cell.onBrowserEvent(parent, value, key, event, valueUpdater);
+ cellIsEditing = cell.isEditing(parent, value, key);
+ if (cellWasEditing && !cellIsEditing) {
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ setFocus(true);
+ }
+ });
+ }
+ }
+ }
+
+ protected Cell<T> getCell() {
+ return cell;
+ }
+
+ /**
+ * Get the parent element that wraps the cell from the list item. Override
+ * this method if you add structure to the element.
+ *
+ * @param item the row element that wraps the list item
+ * @return the parent element of the cell
+ */
+ protected Element getCellParent(Element item) {
+ return item;
+ }
+
+ @Override
+ protected Element getChildContainer() {
+ return childContainer;
+ }
+
+ @Override
+ protected Element getKeyboardSelectedElement() {
+ int rowIndex = getKeyboardSelectedRow();
+ return isRowWithinBounds(rowIndex) ? getRowElement(rowIndex) : null;
+ }
+
+ @Override
+ protected boolean isKeyboardNavigationSuppressed() {
+ return cellIsEditing;
+ }
+
+ @Override
+ protected void onBlur() {
+ // Remove the keyboard selection style.
+ Element elem = getKeyboardSelectedElement();
+ if (elem != null) {
+ elem.removeClassName(style.cellListKeyboardSelectedItem());
+ }
+ }
+
+ @Override
+ protected void onBrowserEvent2(Event event) {
+ // Get the event target.
+ EventTarget eventTarget = event.getEventTarget();
+ if (!Element.is(eventTarget)) {
+ return;
+ }
+ Element target = event.getEventTarget().cast();
// Forward the event to the cell.
- Element target = event.getEventTarget().cast();
String idxString = "";
while ((target != null)
&& ((idxString = target.getAttribute("__idx")).length() == 0)) {
@@ -236,74 +341,119 @@
// before firing the event to the cell in case the cell operates on the
// currently selected item.
String eventType = event.getType();
+ boolean isMouseDown = "mousedown".equals(eventType);
int idx = Integer.parseInt(idxString);
- T value = getDisplayedItem(idx - getPageStart());
+ int indexOnPage = idx - getPageStart();
+ if (!isRowWithinBounds(indexOnPage)) {
+ // If the event causes us to page, then the index will be out of bounds.
+ return;
+ }
+ T value = getDisplayedItem(indexOnPage);
SelectionModel<? super T> selectionModel = getSelectionModel();
- if (selectionModel != null && "click".equals(eventType)
- && !cell.handlesSelection()) {
+ if (selectionModel != null && isMouseDown && !cell.handlesSelection()) {
selectionModel.setSelected(value, true);
}
- // Fire the event to the cell.
- Set<String> consumedEvents = cell.getConsumedEvents();
- if (consumedEvents != null && consumedEvents.contains(eventType)) {
- cell.onBrowserEvent(target, value, getKey(value), event, valueUpdater);
+ // Focus on the cell.
+ if ("focus".equals(eventType) || isMouseDown) {
+ isFocused = true;
+ if (getPresenter().getKeyboardSelectedRow() != indexOnPage) {
+ getPresenter().setKeyboardSelectedRow(indexOnPage, false);
+ }
}
+
+ // Fire the event to the cell if the list has not been refreshed.
+ fireEventToCell(event, getCellParent(target), value);
}
}
- /**
- * Set the message to display when there is no data.
- *
- * @param html the message to display when there are no results
- */
- public void setEmptyListMessage(SafeHtml html) {
- this.emptyListMessage = html;
- emptyMessageElem.setInnerHTML(html.asString());
- }
-
- /**
- * Set the value updater to use when cells modify items.
- *
- * @param valueUpdater the {@link ValueUpdater}
- */
- public void setValueUpdater(ValueUpdater<T> valueUpdater) {
- this.valueUpdater = valueUpdater;
+ @Override
+ protected void onFocus() {
+ // Add the keyboard selection style.
+ Element elem = getKeyboardSelectedElement();
+ if (elem != null) {
+ elem.addClassName(style.cellListKeyboardSelectedItem());
+ }
}
@Override
protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
SelectionModel<? super T> selectionModel) {
+ String keyboardSelectedItem = " " + style.cellListKeyboardSelectedItem();
+ String selectedItem = " " + style.cellListSelectedItem();
+ String evenItem = style.cellListEvenItem();
+ String oddItem = style.cellListOddItem();
+ int keyboardSelectedRow = getKeyboardSelectedRow() + getPageStart();
int length = values.size();
int end = start + length;
for (int i = start; i < end; i++) {
T value = values.get(i - start);
boolean isSelected = selectionModel == null ? false
: selectionModel.isSelected(value);
- // TODO(jlabanca): Factor out __idx because rows can move.
+
StringBuilder classesBuilder = new StringBuilder();
- classesBuilder.append(i % 2 == 0 ? style.cellListEvenItem()
- : style.cellListOddItem());
+ classesBuilder.append(i % 2 == 0 ? evenItem : oddItem);
if (isSelected) {
- classesBuilder.append(" ").append(style.cellListSelectedItem());
+ classesBuilder.append(selectedItem);
}
SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
cell.render(value, null, cellBuilder);
- sb.append(TEMPLATE.div(i, classesBuilder.toString(),
- cellBuilder.toSafeHtml()));
+ if (i == keyboardSelectedRow) {
+ // This is the focused item.
+ if (isFocused) {
+ classesBuilder.append(keyboardSelectedItem);
+ }
+ char accessKey = getAccessKey();
+ if (accessKey != 0) {
+ sb.append(TEMPLATE.divFocusableWithKey(i, classesBuilder.toString(),
+ getTabIndex(), accessKey, cellBuilder.toSafeHtml()));
+ } else {
+ sb.append(TEMPLATE.divFocusable(i, classesBuilder.toString(),
+ getTabIndex(), cellBuilder.toSafeHtml()));
+ }
+ } else {
+ sb.append(TEMPLATE.div(i, classesBuilder.toString(),
+ cellBuilder.toSafeHtml()));
+ }
}
}
@Override
- boolean dependsOnSelection() {
- return cell.dependsOnSelection();
+ protected boolean resetFocusOnCell() {
+ int row = getKeyboardSelectedRow();
+ if (isRowWithinBounds(row)) {
+ Element rowElem = getKeyboardSelectedElement();
+ Element cellParent = getCellParent(rowElem);
+ T value = getDisplayedItem(row);
+ Object key = getValueKey(value);
+ return cell.resetFocus(cellParent, value, key);
+ }
+ return false;
}
@Override
- Element getChildContainer() {
- return childContainer;
+ protected void setKeyboardSelected(int index, boolean selected,
+ boolean stealFocus) {
+ if (!isRowWithinBounds(index)) {
+ return;
+ }
+
+ Element elem = getRowElement(index);
+ if (!selected || isFocused || stealFocus) {
+ setStyleName(elem, style.cellListKeyboardSelectedItem(), selected);
+ }
+ setFocusable(elem, selected);
+ if (selected && stealFocus) {
+ elem.focus();
+ onFocus();
+ }
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(elem, style.cellListSelectedItem(), selected);
}
@Override
@@ -312,26 +462,9 @@
// TODO(jlabanca): Add a loading icon.
}
- @Override
- void setSelected(Element elem, boolean selected) {
- setStyleName(elem, style.cellListSelectedItem(), selected);
- }
-
- /**
- * Get the key for a given value. Defaults to the value if new
- * {@link ProvidesKey} is specified.
- *
- * @param value the value
- * @return the key for the value
- */
- private Object getKey(T value) {
- ProvidesKey<T> keyProvider = getKeyProvider();
- return keyProvider == null ? value : keyProvider.getKey(value);
- }
-
/**
* Show or hide an element.
- *
+ *
* @param element the element
* @param show true to show, false to hide
*/
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 4d22876..36cab91 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.css
@@ -13,6 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
+@def selectionBorderWidth 2px;
.cellTableWidget {
}
@@ -42,15 +43,15 @@
}
.cellTableCell {
- padding: 4px 15px;
+ padding: 2px 15px;
}
.cellTableFirstColumnFooter {
- border-left: 0px;
+
}
.cellTableFirstColumnHeader {
- border-left: 0px;
+
}
.cellTableLastColumnFooter {
@@ -62,33 +63,56 @@
}
.cellTableEvenRow {
- background-color: #ffffff;
+ background: #ffffff;
+}
+
+.cellTableEvenRowCell {
+ border: selectionBorderWidth solid #ffffff;
}
.cellTableOddRow {
- background-color: #f3f7fb;
+ background: #f3f7fb;
+}
+
+.cellTableOddRowCell {
+ border: selectionBorderWidth solid #f3f7fb;
}
.cellTableHoveredRow {
- background-color: #eee;
+ background: #eee;
+}
+
+.cellTableHoveredRowCell {
+ border: selectionBorderWidth solid #eee;
+}
+
+.cellTableKeyboardSelectedRow {
+ background: #ffc;
+}
+
+.cellTableKeyboardSelectedRowCell {
+ border: selectionBorderWidth solid #ffc;
}
.cellTableSelectedRow {
- background-color: #628cd5;
+ background: #628cd5;
color: white;
height: auto;
overflow: auto;
}
-.cellTableKeyboardSelectedRow {
-
+.cellTableSelectedRowCell {
+ border: selectionBorderWidth solid #628cd5;
}
+/**
+ * The keyboard selected cell is visible over selection.
+ */
.cellTableKeyboardSelectedCell {
-
+ border: selectionBorderWidth solid #d7dde8;
}
@sprite .cellTableLoading {
gwt-image: 'cellTableLoading';
- margin: 3px;
+ margin: 30px;
}
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 80fc682..7e62d70 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -27,7 +27,9 @@
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
@@ -50,12 +52,23 @@
/**
* A list view that supports paging and columns.
- *
+ *
* @param <T> the data type of each row
*/
public class CellTable<T> extends AbstractHasData<T> {
/**
+ * Resources that match the GWT standard style theme.
+ */
+ public interface BasicResources extends Resources {
+ /**
+ * The styles used in this widget.
+ */
+ @Source(BasicStyle.DEFAULT_CSS)
+ BasicStyle cellTableStyle();
+ }
+
+ /**
* A ClientBundle that provides images for this widget.
*/
public interface Resources extends ClientBundle {
@@ -90,17 +103,6 @@
}
/**
- * Resources that match the GWT standard style theme.
- */
- public interface BasicResources extends Resources {
- /**
- * The styles used in this widget.
- */
- @Source(BasicStyle.DEFAULT_CSS)
- BasicStyle cellTableStyle();
- }
-
- /**
* Styles used by this widget.
*/
@ImportedWithPrefix("gwt-CellTable")
@@ -121,6 +123,11 @@
String cellTableEvenRow();
/**
+ * Applied to cells in even rows.
+ */
+ String cellTableEvenRowCell();
+
+ /**
* Applied to the first column.
*/
String cellTableFirstColumn();
@@ -151,6 +158,11 @@
String cellTableHoveredRow();
/**
+ * Applied to the cells in the hovered row.
+ */
+ String cellTableHoveredRowCell();
+
+ /**
* Applied to the keyboard selected cell.
*/
String cellTableKeyboardSelectedCell();
@@ -161,6 +173,11 @@
String cellTableKeyboardSelectedRow();
/**
+ * Applied to the cells in the keyboard selected row.
+ */
+ String cellTableKeyboardSelectedRowCell();
+
+ /**
* Applied to the last column.
*/
String cellTableLastColumn();
@@ -186,11 +203,21 @@
String cellTableOddRow();
/**
+ * Applied to cells in odd rows.
+ */
+ String cellTableOddRowCell();
+
+ /**
* Applied to selected rows.
*/
String cellTableSelectedRow();
/**
+ * Applied to cells in selected rows.
+ */
+ String cellTableSelectedRowCell();
+
+ /**
* Applied to the table.
*/
String cellTableWidget();
@@ -214,9 +241,16 @@
@Template("<table><tbody>{0}</tbody></table>")
SafeHtml tbody(SafeHtml rowHtml);
- @Template("<td class=\"{0}\"><div>{1}</div></td>")
+ @Template("<td class=\"{0}\"><div style=\"outline:none;\">{1}</div></td>")
SafeHtml td(String classes, SafeHtml contents);
+ @Template("<td class=\"{0}\"><div style=\"outline:none;\" tabindex=\"{1}\">{2}</div></td>")
+ SafeHtml tdFocusable(String classes, int tabIndex, SafeHtml contents);
+
+ @Template("<td class=\"{0}\"><div style=\"outline:none;\" tabindex=\"{1}\" accessKey=\"{2}\">{3}</div></td>")
+ SafeHtml tdFocusableWithKey(String classes, int tabIndex, char accessKey,
+ SafeHtml contents);
+
@Template("<table><tfoot>{0}</tfoot></table>")
SafeHtml tfoot(SafeHtml rowHtml);
@@ -235,12 +269,11 @@
*/
private static class Impl {
- private final com.google.gwt.user.client.Element tmpElem =
- Document.get().createDivElement().cast();
+ private final com.google.gwt.user.client.Element tmpElem = Document.get().createDivElement().cast();
/**
* Convert the rowHtml into Elements wrapped by the specified table section.
- *
+ *
* @param table the {@link CellTable}
* @param sectionTag the table section tag
* @param rowHtml the Html for the rows
@@ -286,7 +319,7 @@
/**
* Render a table section in the table.
- *
+ *
* @param table the {@link CellTable}
* @param section the {@link TableSectionElement} to replace
* @param html the html to render
@@ -370,10 +403,16 @@
private final List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
+ /**
+ * Indicates that at least one column depends on selection.
+ */
private boolean dependsOnSelection;
private final List<Header<?>> footers = new ArrayList<Header<?>>();
+ /**
+ * Indicates that at least one column handles selection.
+ */
private boolean handlesSelection;
private final List<Header<?>> headers = new ArrayList<Header<?>>();
@@ -385,9 +424,12 @@
private TableRowElement hoveringRow;
- private int keyboardSelectedColumn = 0;
+ /**
+ * Indicates that at least one column is interactive.
+ */
+ private boolean isInteractive;
- private int keyboardSelectedRow = 0;
+ private int keyboardSelectedColumn = 0;
/**
* Indicates whether or not the scheduled redraw has been cancelled.
@@ -407,6 +449,7 @@
redraw();
}
};
+
/**
* Indicates whether or not a redraw is scheduled.
*/
@@ -429,7 +472,7 @@
/**
* Constructs a table with the given page size.
- *
+ *
* @param pageSize the page size
*/
public CellTable(final int pageSize) {
@@ -437,19 +480,20 @@
}
/**
- * Constructs a table with a default page size of 15, and the given {@link ProvidesKey key provider}.
- *
+ * Constructs a table with a default page size of 15, and the given
+ * {@link ProvidesKey key provider}.
+ *
* @param keyProvider an instance of ProvidesKey<T>, or null if the record
- * object should act as its own key
+ * object should act as its own key
*/
public CellTable(ProvidesKey<T> keyProvider) {
this(DEFAULT_PAGESIZE, getDefaultResources(), keyProvider);
}
-
+
/**
* Constructs a table with the given page size with the specified
* {@link Resources}.
- *
+ *
* @param pageSize the page size
* @param resources the resources to use for this widget
*/
@@ -458,11 +502,12 @@
}
/**
- * Constructs a table with the given page size and the given {@link ProvidesKey key provider}.
- *
+ * Constructs a table with the given page size and the given
+ * {@link ProvidesKey key provider}.
+ *
* @param pageSize the page size
* @param keyProvider an instance of ProvidesKey<T>, or null if the record
- * object should act as its own key
+ * object should act as its own key
*/
public CellTable(final int pageSize, ProvidesKey<T> keyProvider) {
this(pageSize, getDefaultResources(), keyProvider);
@@ -471,13 +516,14 @@
/**
* Constructs a table with the given page size, the specified
* {@link Resources}, and the given key provider.
- *
+ *
* @param pageSize the page size
* @param resources the resources to use for this widget
* @param keyProvider an instance of ProvidesKey<T>, or null if the record
- * object should act as its own key
+ * object should act as its own key
*/
- public CellTable(final int pageSize, Resources resources, ProvidesKey<T> keyProvider) {
+ public CellTable(final int pageSize, Resources resources,
+ ProvidesKey<T> keyProvider) {
super(Document.get().createTableElement(), pageSize, keyProvider);
if (TABLE_IMPL == null) {
TABLE_IMPL = GWT.create(Impl.class);
@@ -510,8 +556,11 @@
}
// Sink events.
- sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
- | Event.ONKEYUP | Event.ONKEYDOWN);
+ Set<String> eventTypes = new HashSet<String>();
+ eventTypes.add("click");
+ eventTypes.add("mouseover");
+ eventTypes.add("mouseout");
+ CellBasedWidgetImpl.get().sinkEvents(this, eventTypes);
}
/**
@@ -535,8 +584,15 @@
headers.add(header);
footers.add(footer);
columns.add(col);
+ boolean wasinteractive = isInteractive;
updateDependsOnSelection();
+ // Move the keyboard selected column if the current column is not
+ // interactive.
+ if (!wasinteractive && isInteractive) {
+ keyboardSelectedColumn = columns.size() - 1;
+ }
+
// Sink events used by the new column.
Set<String> consumedEvents = new HashSet<String>();
{
@@ -597,7 +653,7 @@
/**
* Add a style name to the {@link TableColElement} at the specified index,
* creating it if necessary.
- *
+ *
* @param index the column index
* @param styleName the style name to add
*/
@@ -618,7 +674,7 @@
/**
* Get the {@link TableRowElement} for the specified row. If the row element
* has not been created, null is returned.
- *
+ *
* @param row the row index
* @return the row element, or null if it doesn't exists
* @throws IndexOutOfBoundsException if the row index is outside of the
@@ -630,31 +686,140 @@
return rows.getLength() > row ? rows.getItem(row) : null;
}
- @Override
- public void onBrowserEvent(Event event) {
- CellBasedWidgetImpl.get().onBrowserEvent(this, event);
- super.onBrowserEvent(event);
+ public void redrawFooters() {
+ createHeaders(true);
+ }
- String eventType = event.getType();
- boolean keyUp = "keyup".equals(eventType);
- boolean keyDown = "keydown".equals(eventType);
+ public void redrawHeaders() {
+ createHeaders(false);
+ }
- // Ignore keydown events unless the cell is in edit mode
- if (keyDown && !cellIsEditing) {
+ /**
+ * Remove a column.
+ *
+ * @param col the column to remove
+ */
+ public void removeColumn(Column<T, ?> col) {
+ int index = columns.indexOf(col);
+ if (index < 0) {
+ throw new IllegalArgumentException(
+ "The specified column is not part of this table.");
+ }
+ removeColumn(index);
+ }
+
+ /**
+ * Remove a column.
+ *
+ * @param index the column index
+ */
+ public void removeColumn(int index) {
+ if (index < 0 || index >= columns.size()) {
+ throw new IndexOutOfBoundsException(
+ "The specified column index is out of bounds.");
+ }
+ columns.remove(index);
+ headers.remove(index);
+ footers.remove(index);
+ updateDependsOnSelection();
+ headersStale = true;
+
+ // Find an interactive column. Stick with 0 if no column is interactive.
+ if (index <= keyboardSelectedColumn) {
+ keyboardSelectedColumn = 0;
+ if (isInteractive) {
+ for (int i = 0; i < columns.size(); i++) {
+ if (isColumnInteractive(columns.get(i))) {
+ keyboardSelectedColumn = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // Redraw the table asynchronously.
+ scheduleRedraw();
+
+ // We don't unsink events because other handlers or user code may have sunk
+ // them intentionally.
+ }
+
+ /**
+ * Remove a style from the {@link TableColElement} at the specified index.
+ *
+ * @param index the column index
+ * @param styleName the style name to remove
+ */
+ public void removeColumnStyleName(int index, String styleName) {
+ if (index >= colgroup.getChildCount()) {
return;
}
- if (keyUp && !cellIsEditing) {
+ ensureTableColElement(index).removeClassName(styleName);
+ }
+
+ @Override
+ protected Element convertToElements(SafeHtml html) {
+ return TABLE_IMPL.convertToSectionElement(CellTable.this, "tbody", html);
+ }
+
+ @Override
+ protected boolean dependsOnSelection() {
+ return dependsOnSelection;
+ }
+
+ @Override
+ protected Element getChildContainer() {
+ return tbody;
+ }
+
+ @Override
+ protected Element getKeyboardSelectedElement() {
+ int rowIndex = getKeyboardSelectedRow();
+ if (isRowWithinBounds(rowIndex) && columns.size() > 0) {
+ TableRowElement tr = getRowElement(rowIndex);
+ TableCellElement td = tr.getCells().getItem(keyboardSelectedColumn);
+ return getCellParent(td);
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean isKeyboardNavigationSuppressed() {
+ return cellIsEditing;
+ }
+
+ @Override
+ protected void onBlur() {
+ Element elem = getKeyboardSelectedElement();
+ if (elem != null) {
+ TableCellElement td = elem.getParentElement().cast();
+ TableRowElement tr = td.getParentElement().cast();
+ td.removeClassName(style.cellTableKeyboardSelectedCell());
+ setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
+ style.cellTableKeyboardSelectedRowCell(), false);
+ }
+ }
+
+ @Override
+ protected void onBrowserEvent2(Event event) {
+ // Get the event target.
+ EventTarget eventTarget = event.getEventTarget();
+ if (!Element.is(eventTarget)) {
+ return;
+ }
+ Element target = event.getEventTarget().cast();
+
+ // Ignore keydown events unless the cell is in edit mode
+ String eventType = event.getType();
+ if ("keydown".equals(eventType) && !isKeyboardNavigationSuppressed()
+ && KeyboardSelectionPolicy.DISABLED != getKeyboardSelectionPolicy()) {
if (handleKey(event)) {
return;
}
}
// Find the cell where the event occurred.
- EventTarget eventTarget = event.getEventTarget();
- TableCellElement tableCell = null;
- if (eventTarget != null && Element.is(eventTarget)) {
- tableCell = findNearestParentCell(Element.as(eventTarget));
- }
+ TableCellElement tableCell = findNearestParentCell(target);
if (tableCell == null) {
return;
}
@@ -679,29 +844,50 @@
if (section == thead) {
Header<?> header = headers.get(col);
if (header != null && cellConsumesEventType(header.getCell(), eventType)) {
+
header.onBrowserEvent(tableCell, event);
}
} else if (section == tfoot) {
Header<?> footer = footers.get(col);
if (footer != null && cellConsumesEventType(footer.getCell(), eventType)) {
+
footer.onBrowserEvent(tableCell, event);
}
} else if (section == tbody) {
// Update the hover state.
+ boolean isMouseDown = "mousedown".equals(eventType);
int row = tr.getSectionRowIndex();
if ("mouseover".equals(eventType)) {
if (hoveringRow != null) {
- hoveringRow.removeClassName(style.cellTableHoveredRow());
+ setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
+ style.cellTableHoveredRowCell(), false);
}
hoveringRow = tr;
- tr.addClassName(style.cellTableHoveredRow());
- } else if ("mouseout".equals(eventType)) {
+ setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
+ style.cellTableHoveredRowCell(), true);
+ } else if ("mouseout".equals(eventType) && hoveringRow != null) {
+ setRowStyleName(hoveringRow, style.cellTableHoveredRow(),
+ style.cellTableHoveredRowCell(), false);
hoveringRow = null;
- tr.removeClassName(style.cellTableHoveredRow());
+ } else if ("focus".equals(eventType) || isMouseDown) {
+ // Move keyboard focus. Since the user clicked, allow focus to go to a
+ // non-interactive column.
+ isFocused = true;
+ if (getPresenter().getKeyboardSelectedRow() != row
+ || keyboardSelectedColumn != col) {
+ deselectKeyboardRow(getKeyboardSelectedRow());
+ keyboardSelectedColumn = col;
+ getPresenter().setKeyboardSelectedRow(row, false);
+ }
}
// Update selection. Selection occurs before firing the event to the cell
// in case the cell operates on the currently selected item.
+ if (!isRowWithinBounds(row)) {
+ // If the event causes us to page, then the physical index will be out
+ // of bounds of the underlying data.
+ return;
+ }
T value = getDisplayedItem(row);
SelectionModel<? super T> selectionModel = getSelectionModel();
if (selectionModel != null && "click".equals(eventType)
@@ -709,132 +895,24 @@
selectionModel.setSelected(value, true);
}
- fireEventToCell(event, eventType, tableCell, value, col, row);
- }
- }
-
- public void redrawFooters() {
- createHeaders(true);
- }
-
- public void redrawHeaders() {
- createHeaders(false);
- }
-
- /**
- * Remove a column.
- *
- * @param col the column to remove
- */
- public void removeColumn(Column<T, ?> col) {
- int index = columns.indexOf(col);
- if (index < 0) {
- throw new IllegalArgumentException(
- "The specified column is not part of this table.");
- }
- removeColumn(index);
- }
-
- /**
- * Remove a column.
- *
- * @param index the column index
- */
- public void removeColumn(int index) {
- if (index < 0 || index >= columns.size()) {
- throw new IndexOutOfBoundsException(
- "The specified column index is out of bounds.");
- }
- columns.remove(index);
- headers.remove(index);
- footers.remove(index);
- updateDependsOnSelection();
- headersStale = true;
- scheduleRedraw();
-
- // We don't unsink events because other handlers or user code may have sunk
- // them intentionally.
- }
-
- /**
- * Remove a style from the {@link TableColElement} at the specified index.
- *
- * @param index the column index
- * @param styleName the style name to remove
- */
- public void removeColumnStyleName(int index, String styleName) {
- if (index >= colgroup.getChildCount()) {
- return;
- }
- ensureTableColElement(index).removeClassName(styleName);
- }
-
- @Override
- protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
- SelectionModel<? super T> selectionModel) {
- createHeadersAndFooters();
-
- ProvidesKey<T> keyProvider = getKeyProvider();
- String evenRowStyle = style.cellTableEvenRow();
- String oddRowStyle = style.cellTableOddRow();
- String cellStyle = style.cellTableCell();
- String firstColumnStyle = " " + style.cellTableFirstColumn();
- String lastColumnStyle = " " + style.cellTableLastColumn();
- String selectedRowStyle = " " + style.cellTableSelectedRow();
- int columnCount = columns.size();
- int length = values.size();
- int end = start + length;
- for (int i = start; i < end; i++) {
- T value = values.get(i - start);
- boolean isSelected = (selectionModel == null || value == null) ? false
- : selectionModel.isSelected(value);
- String trClasses = i % 2 == 0 ? evenRowStyle : oddRowStyle;
- if (isSelected) {
- trClasses += selectedRowStyle;
- }
-
- SafeHtmlBuilder trBuilder = new SafeHtmlBuilder();
- int curColumn = 0;
- for (Column<T, ?> column : columns) {
- String tdClasses = cellStyle;
- if (curColumn == 0) {
- tdClasses += firstColumnStyle;
- }
- // The first and last column could be the same column.
- if (curColumn == columnCount - 1) {
- tdClasses += lastColumnStyle;
- }
-
- SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
- if (value != null) {
- column.render(value, keyProvider, cellBuilder);
- }
-
- trBuilder.append(template.td(tdClasses, cellBuilder.toSafeHtml()));
- curColumn++;
- }
-
- sb.append(template.tr(trClasses, trBuilder.toSafeHtml()));
+ fireEventToCell(event, eventType, tableCell, value, row, columns.get(col));
}
}
@Override
- Element convertToElements(SafeHtml html) {
- return TABLE_IMPL.convertToSectionElement(CellTable.this, "tbody", html);
+ protected void onFocus() {
+ Element elem = getKeyboardSelectedElement();
+ if (elem != null) {
+ TableCellElement td = elem.getParentElement().cast();
+ TableRowElement tr = td.getParentElement().cast();
+ td.addClassName(style.cellTableKeyboardSelectedCell());
+ setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
+ style.cellTableKeyboardSelectedRowCell(), true);
+ }
}
@Override
- boolean dependsOnSelection() {
- return dependsOnSelection;
- }
-
- @Override
- Element getChildContainer() {
- return tbody;
- }
-
- @Override
- void onUpdateSelection() {
+ protected void onUpdateSelection() {
// Refresh headers.
for (Header<?> header : headers) {
if (header != null && header.getCell().dependsOnSelection()) {
@@ -853,7 +931,92 @@
}
@Override
- void replaceAllChildren(List<T> values, SafeHtml html) {
+ protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
+ SelectionModel<? super T> selectionModel) {
+ createHeadersAndFooters();
+
+ int keyboardSelectedRow = getKeyboardSelectedRow() + getPageStart();
+ ProvidesKey<T> keyProvider = getKeyProvider();
+ String evenRowStyle = style.cellTableEvenRow();
+ String oddRowStyle = style.cellTableOddRow();
+ String cellStyle = style.cellTableCell();
+ String evenCellStyle = " " + style.cellTableEvenRowCell();
+ String oddCellStyle = " " + style.cellTableOddRowCell();
+ String firstColumnStyle = " " + style.cellTableFirstColumn();
+ String lastColumnStyle = " " + style.cellTableLastColumn();
+ String selectedRowStyle = " " + style.cellTableSelectedRow();
+ String selectedCellStyle = " " + style.cellTableSelectedRowCell();
+ String keyboardRowStyle = " " + style.cellTableKeyboardSelectedRow();
+ String keyboardRowCellStyle = " "
+ + style.cellTableKeyboardSelectedRowCell();
+ String keyboardCellStyle = " " + style.cellTableKeyboardSelectedCell();
+ int columnCount = columns.size();
+ int length = values.size();
+ int end = start + length;
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ boolean isSelected = (selectionModel == null || value == null) ? false
+ : selectionModel.isSelected(value);
+ boolean isEven = i % 2 == 0;
+ boolean isKeyboardSelected = i == keyboardSelectedRow && isFocused;
+ String trClasses = isEven ? evenRowStyle : oddRowStyle;
+ if (isSelected) {
+ trClasses += selectedRowStyle;
+ }
+ if (isKeyboardSelected) {
+ trClasses += keyboardRowStyle;
+ }
+
+ SafeHtmlBuilder trBuilder = new SafeHtmlBuilder();
+ int curColumn = 0;
+ for (Column<T, ?> column : columns) {
+ String tdClasses = cellStyle;
+ tdClasses += isEven ? evenCellStyle : oddCellStyle;
+ if (curColumn == 0) {
+ tdClasses += firstColumnStyle;
+ }
+ if (isSelected) {
+ tdClasses += selectedCellStyle;
+ }
+ if (isKeyboardSelected) {
+ tdClasses += keyboardRowCellStyle;
+ }
+ // The first and last column could be the same column.
+ if (curColumn == columnCount - 1) {
+ tdClasses += lastColumnStyle;
+ }
+
+ SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+ if (value != null) {
+ column.render(value, keyProvider, cellBuilder);
+ }
+
+ if (i == keyboardSelectedRow && curColumn == keyboardSelectedColumn) {
+ // This is the focused cell.
+ if (isFocused) {
+ tdClasses += keyboardCellStyle;
+ }
+ char accessKey = getAccessKey();
+ if (accessKey != 0) {
+ trBuilder.append(template.tdFocusableWithKey(tdClasses,
+ getTabIndex(), accessKey, cellBuilder.toSafeHtml()));
+ } else {
+ trBuilder.append(template.tdFocusable(tdClasses, getTabIndex(),
+ cellBuilder.toSafeHtml()));
+ }
+ } else {
+ trBuilder.append(template.td(tdClasses, cellBuilder.toSafeHtml()));
+ }
+
+ curColumn++;
+ }
+
+ sb.append(template.tr(trClasses, trBuilder.toSafeHtml()));
+ }
+ }
+
+ @Override
+ protected void replaceAllChildren(List<T> values, SafeHtml html) {
// Cancel any pending redraw.
if (redrawScheduled) {
redrawCancelled = true;
@@ -863,20 +1026,46 @@
}
@Override
- void resetFocus() {
- int pageStart = getPageStart();
- int offset = keyboardSelectedRow - pageStart;
- // Check that both the row and column still exist. The column may not exist
- // if a column is removed.
- if (offset >= 0 && offset < getChildCount()
- && keyboardSelectedColumn < columns.size()) {
- TableRowElement tr = getRowElement(offset);
- TableCellElement td = tr.getCells().getItem(keyboardSelectedColumn);
- tr.addClassName(style.cellTableKeyboardSelectedRow());
- td.addClassName(style.cellTableKeyboardSelectedCell());
- td.setTabIndex(0);
- td.focus(); // TODO (rice) only focus if we were focused previously
+ protected boolean resetFocusOnCell() {
+ int row = getKeyboardSelectedRow();
+ if (isRowWithinBounds(row) && columns.size() > 0) {
+ Column<T, ?> column = columns.get(keyboardSelectedColumn);
+ return resetFocusOnCellImpl(row, column);
}
+ return false;
+ }
+
+ @Override
+ protected void setKeyboardSelected(int index, boolean selected,
+ boolean stealFocus) {
+ if (KeyboardSelectionPolicy.DISABLED == getKeyboardSelectionPolicy()
+ || !isRowWithinBounds(index) || columns.size() == 0) {
+ return;
+ }
+
+ TableRowElement tr = getRowElement(index);
+ TableCellElement td = tr.getCells().getItem(keyboardSelectedColumn);
+ final com.google.gwt.user.client.Element cellParent = getCellParent(td).cast();
+ if (!selected || isFocused || stealFocus) {
+ setRowStyleName(tr, style.cellTableKeyboardSelectedRow(),
+ style.cellTableKeyboardSelectedRowCell(), selected);
+ setStyleName(td, style.cellTableKeyboardSelectedCell(), selected);
+ }
+ setFocusable(cellParent, selected);
+ if (selected && stealFocus) {
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ cellParent.focus();
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ TableRowElement tr = elem.cast();
+ setRowStyleName(tr, style.cellTableSelectedRow(),
+ style.cellTableSelectedRowCell(), selected);
}
@Override
@@ -884,26 +1073,9 @@
setLoadingIconVisible(state == LoadingState.LOADING);
}
- @Override
- void setSelected(Element elem, boolean selected) {
- setStyleName(elem, style.cellTableSelectedRow(), selected);
- }
-
- /**
- * Check if a cell consumes the specified event type.
- *
- * @param cell the cell
- * @param eventType the event type to check
- * @return true if consumed, false if not
- */
- private boolean cellConsumesEventType(Cell<?> cell, String eventType) {
- Set<String> consumedEvents = cell.getConsumedEvents();
- return consumedEvents != null && consumedEvents.contains(eventType);
- }
-
/**
* Render the header or footer.
- *
+ *
* @param isFooter true if this is the footer table, false if the header table
*/
private void createHeaders(boolean isFooter) {
@@ -958,10 +1130,14 @@
}
}
+ private void deselectKeyboardRow(int row) {
+ setKeyboardSelected(row, false, false);
+ }
+
/**
* Get the {@link TableColElement} at the specified index, creating it if
* necessary.
- *
+ *
* @param index the column index
* @return the {@link TableColElement}
*/
@@ -974,9 +1150,49 @@
}
/**
+ * Find and return the index of the next interactive column. If no column is
+ * interactive, 0 is returned. If the start index is the only interactive
+ * column, it is returned.
+ *
+ * @param start the start index, exclusive unless it is the only option
+ * @param reverse true to do a reverse search
+ * @return the interactive column index, or 0 if not interactive
+ */
+ private int findInteractiveColumn(int start, boolean reverse) {
+ if (!isInteractive) {
+ return 0;
+ } else if (reverse) {
+ for (int i = start - 1; i >= 0; i--) {
+ if (isColumnInteractive(columns.get(i))) {
+ return i;
+ }
+ }
+ // Wrap to the end.
+ for (int i = columns.size() - 1; i >= start; i--) {
+ if (isColumnInteractive(columns.get(i))) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = start + 1; i < columns.size(); i++) {
+ if (isColumnInteractive(columns.get(i))) {
+ return i;
+ }
+ }
+ // Wrap to the start.
+ for (int i = 0; i <= start; i++) {
+ if (isColumnInteractive(columns.get(i))) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
* Find the cell that contains the element. Note that the TD element is not
* the parent. The parent is the div inside the TD cell.
- *
+ *
* @param elem the element
* @return the parent cell
*/
@@ -996,93 +1212,111 @@
/**
* Fire an event to the Cell within the specified {@link TableCellElement}.
*/
- @SuppressWarnings("unchecked")
private <C> void fireEventToCell(Event event, String eventType,
- TableCellElement tableCell, T value, int col, int row) {
- Column<T, C> column = (Column<T, C>) columns.get(col);
+ TableCellElement tableCell, T value, int row, Column<T, C> column) {
Cell<C> cell = column.getCell();
if (cellConsumesEventType(cell, eventType)) {
C cellValue = column.getValue(value);
ProvidesKey<T> providesKey = getKeyProvider();
- Object key = providesKey == null ? value : providesKey.getKey(value);
- Element parentElem = tableCell.getFirstChildElement();
+ Object key = getValueKey(value);
+ Element parentElem = getCellParent(tableCell);
boolean cellWasEditing = cell.isEditing(parentElem, cellValue, key);
column.onBrowserEvent(parentElem, getPageStart() + row, value, event,
providesKey);
cellIsEditing = cell.isEditing(parentElem, cellValue, key);
if (cellWasEditing && !cellIsEditing) {
- resetFocus();
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ setFocus(true);
+ }
+ });
}
}
}
+ /**
+ * Get the parent element that is passed to the {@link Cell} from the table
+ * cell element.
+ *
+ * @param td the table cell
+ * @return the parent of the {@link Cell}
+ */
+ private Element getCellParent(TableCellElement td) {
+ return td.getFirstChildElement();
+ }
+
private native int getClientHeight(Element element) /*-{
return element.clientHeight;
}-*/;
private boolean handleKey(Event event) {
+ HasDataPresenter<T> presenter = getPresenter();
+ int oldRow = getKeyboardSelectedRow();
+ boolean isRtl = LocaleInfo.getCurrentLocale().isRTL();
+ int keyCodeLineEnd = isRtl ? KeyCodes.KEY_LEFT : KeyCodes.KEY_RIGHT;
+ int keyCodeLineStart = isRtl ? KeyCodes.KEY_RIGHT : KeyCodes.KEY_LEFT;
int keyCode = event.getKeyCode();
- int oldColumn = keyboardSelectedColumn;
- int oldRow = keyboardSelectedRow;
- int numColumns = columns.size();
- int numRows = getRowCount();
- switch (keyCode) {
- case KeyCodes.KEY_UP:
- if (keyboardSelectedRow > 0) {
- --keyboardSelectedRow;
+ if (keyCode == keyCodeLineEnd) {
+ int nextColumn = findInteractiveColumn(keyboardSelectedColumn, false);
+ if (nextColumn <= keyboardSelectedColumn) {
+ // Wrap to the next row.
+ if (presenter.hasKeyboardNext()) {
+ deselectKeyboardRow(oldRow);
+ keyboardSelectedColumn = nextColumn;
+ presenter.keyboardNext();
+ event.preventDefault();
+ return true;
}
- break;
- case KeyCodes.KEY_DOWN:
- if (keyboardSelectedRow < numRows - 1) {
- ++keyboardSelectedRow;
- }
- break;
- case KeyCodes.KEY_LEFT:
- if (keyboardSelectedColumn == 0 && keyboardSelectedRow > 0) {
- keyboardSelectedColumn = numColumns - 1;
- --keyboardSelectedRow;
- } else if (keyboardSelectedColumn > 0) {
- --keyboardSelectedColumn;
- }
- break;
- case KeyCodes.KEY_RIGHT:
- if (keyboardSelectedColumn == numColumns - 1
- && keyboardSelectedRow < numRows - 1) {
- keyboardSelectedColumn = 0;
- ++keyboardSelectedRow;
- } else if (keyboardSelectedColumn < numColumns - 1) {
- ++keyboardSelectedColumn;
- }
- break;
- }
-
- if (keyboardSelectedColumn != oldColumn || keyboardSelectedRow != oldRow) {
- int pageStart = getPageStart();
- int pageSize = getPageSize();
-
- // Remove old selection markers
- TableRowElement row = getRowElement(oldRow - pageStart);
- row.removeClassName(style.cellTableKeyboardSelectedRow());
- TableCellElement td = row.getCells().getItem(oldColumn);
- td.removeClassName(style.cellTableKeyboardSelectedCell());
- td.removeAttribute("tabIndex");
-
- // Move page start if needed
- if (keyboardSelectedRow >= pageStart + pageSize) {
- setPageStart(keyboardSelectedRow - pageSize + 1);
- } else if (keyboardSelectedRow < pageStart) {
- setPageStart(keyboardSelectedRow);
+ } else {
+ // Reselect the row to move the selected column.
+ deselectKeyboardRow(oldRow);
+ keyboardSelectedColumn = nextColumn;
+ setKeyboardSelected(oldRow, true, true);
+ event.preventDefault();
+ return true;
}
-
- // Add new selection markers and re-establish focus
- resetFocus();
- return true;
+ } else if (keyCode == keyCodeLineStart) {
+ int prevColumn = findInteractiveColumn(keyboardSelectedColumn, true);
+ if (prevColumn >= keyboardSelectedColumn) {
+ // Wrap to the previous row.
+ if (presenter.hasKeyboardPrev()) {
+ deselectKeyboardRow(oldRow);
+ keyboardSelectedColumn = prevColumn;
+ presenter.keyboardPrev();
+ event.preventDefault();
+ return true;
+ }
+ } else {
+ // Reselect the row to move the selected column.
+ deselectKeyboardRow(oldRow);
+ keyboardSelectedColumn = prevColumn;
+ setKeyboardSelected(oldRow, true, true);
+ event.preventDefault();
+ return true;
+ }
}
return false;
}
/**
+ * Check if a column consumes events.
+ */
+ private boolean isColumnInteractive(Column<T, ?> column) {
+ Set<String> consumedEvents = column.getCell().getConsumedEvents();
+ return consumedEvents != null && consumedEvents.size() > 0;
+ }
+
+ private <C> boolean resetFocusOnCellImpl(int row, Column<T, C> column) {
+ Element parent = getKeyboardSelectedElement();
+ T value = getDisplayedItem(row);
+ Object key = getValueKey(value);
+ C cellValue = column.getValue(value);
+ Cell<C> cell = column.getCell();
+ return cell.resetFocus(parent, cellValue, key);
+ }
+
+ /**
* Schedule a redraw for the end of the event loop.
*/
private void scheduleRedraw() {
@@ -1095,13 +1329,15 @@
/**
* Show or hide the loading icon.
- *
+ *
* @param visible true to show, false to hide.
*/
private void setLoadingIconVisible(boolean visible) {
// Clear the current data.
if (visible) {
- tbody.setInnerText("");
+ tbody.getStyle().setDisplay(Display.NONE);
+ } else {
+ tbody.getStyle().clearDisplay();
}
// Update the colspan.
@@ -1112,11 +1348,29 @@
}
/**
+ * Apply a style to a row and all cells in the row.
+ *
+ * @param tr the row element
+ * @param rowStyle the style to apply to the row
+ * @param cellStyle the style to apply to the cells
+ * @param add true to add the style, false to remove
+ */
+ private void setRowStyleName(TableRowElement tr, String rowStyle,
+ String cellStyle, boolean add) {
+ setStyleName(tr, rowStyle, add);
+ NodeList<TableCellElement> cells = tr.getCells();
+ for (int i = 0; i < cells.getLength(); i++) {
+ setStyleName(cells.getItem(i), cellStyle, add);
+ }
+ }
+
+ /**
* Update the dependsOnSelection and handlesSelection booleans.
*/
private void updateDependsOnSelection() {
dependsOnSelection = false;
handlesSelection = false;
+ isInteractive = false;
for (Column<T, ?> column : columns) {
Cell<?> cell = column.getCell();
if (cell.dependsOnSelection()) {
@@ -1125,6 +1379,9 @@
if (cell.handlesSelection()) {
handlesSelection = true;
}
+ if (isColumnInteractive(column)) {
+ isInteractive = true;
+ }
}
}
}
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 ed89781..bca708b 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTableBasic.css
@@ -70,22 +70,34 @@
}
.cellTableEvenRow {
- background-color: #ffffff;
+ background: #ffffff;
}
-.cellTableOddRow {
- background-color: #eef4fb;
-}
-
-.cellTableHoveredRow {
- background-color: #88b0f2;
-}
-
-.cellTableKeyboardSelectedRow {
+.cellTableEvenRowCell {
}
-.cellTableKeyboardSelectedCell {
+.cellTableOddRow {
+ background: #eef4fb;
+}
+
+.cellTableOddRowCell {
+
+}
+
+.cellTableHoveredRow {
+ background: #eee;
+}
+
+.cellTableHoveredRowCell {
+
+}
+
+.cellTableKeyboardSelectedRow {
+ background: #ffc;
+}
+
+.cellTableKeyboardSelectedRowCell {
}
@@ -97,6 +109,14 @@
overflow: auto;
}
+.cellTableSelectedRowCell {
+ background: #628cd5;
+}
+
+.cellTableKeyboardSelectedCell {
+ background: #d7dde8;
+}
+
@sprite .cellTableLoading {
gwt-image: 'cellTableLoading';
margin: 30px;
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.css b/user/src/com/google/gwt/user/cellview/client/CellTree.css
index 1e8c886..23c83d1 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.css
@@ -14,70 +14,69 @@
* the License.
*/
.cellTreeWidget {
-
+
}
.cellTreeEmptyMessage {
- padding-left: 16px;
- font-style: italic;
+ padding-left: 16px;
+ font-style: italic;
}
.cellTreeItem {
- padding-top: 4px;
- padding-bottom: 4px;
- cursor: hand;
- cursor: pointer;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ cursor: hand;
+ cursor: pointer;
}
.cellTreeItemImage {
-
+
}
.cellTreeItemImageValue {
-
+
}
.cellTreeItemValue {
- padding-left: 3px;
- padding-right: 3px;
-}
-
-/*
-div:focus { outline: none; }
-*/
-.cellTreeKeyboardSelectedItem {
- /* background-color: #ffff00; */
+ padding-left: 3px;
+ padding-right: 3px;
+ outline: none;
}
.cellTreeOpenItem {
-
+
}
.cellTreeTopItem {
- font-weight: bold;
- color: #4b4a4a;
- margin-top: 20px;
- padding: 3px 13px 3px 10px !important;
+ font-weight: bold;
+ color: #4b4a4a;
+ margin-top: 20px;
+ padding: 3px 13px 3px 10px !important;
}
.cellTreeTopItemImage {
-
+
}
.cellTreeTopItemImageValue {
- border-bottom: 1px solid #6f7277;
- padding-bottom: 1px;
+ border-bottom: 1px solid #6f7277;
+ padding-bottom: 1px;
+}
+
+.cellTreeKeyboardSelectedItem {
+ background-color: #ffc;
+ outline: none;
}
@sprite .cellTreeSelectedItem {
- gwt-image: 'cellTreeSelectedBackground';
- background-color: #628cd5;
- color: white;
- height: auto;
- overflow: auto;
+ gwt-image: 'cellTreeSelectedBackground';
+ background-color: #628cd5;
+ color: white;
+ height: auto;
+ overflow: visible;
}
.cellTreeShowMoreButton {
- padding-left: 16px;
- outline: none;
+ padding-left: 16px;
+ outline: none;
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.java b/user/src/com/google/gwt/user/cellview/client/CellTree.java
index 5895dea..8dd1de2 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -17,6 +17,7 @@
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
@@ -32,11 +33,14 @@
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.view.client.TreeViewModel;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
/**
* A view of a tree.
@@ -47,7 +51,8 @@
* declaration.
* </p>
*/
-public class CellTree extends AbstractCellTree implements HasAnimation {
+public class CellTree extends AbstractCellTree implements HasAnimation,
+ Focusable {
/**
* Resources that match the GWT standard style theme.
@@ -426,12 +431,28 @@
}
/**
+ * A boolean indicating whether or not a cell is being edited.
+ */
+ boolean cellIsEditing;
+
+ /**
+ * A boolean indicating that the widget has focus.
+ */
+ boolean isFocused;
+
+ /**
+ * Set to true while the elements are being refreshed. Events are ignored
+ * during this time.
+ */
+ boolean isRefreshing;
+
+ private char accessKey = 0;
+
+ /**
* The animation.
*/
private NodeAnimation animation;
- private boolean cellIsEditing;
-
/**
* The HTML used to generate the closed image.
*/
@@ -488,6 +509,8 @@
*/
private final Style style;
+ private int tabIndex;
+
/**
* Construct a new {@link CellTree}.
*
@@ -507,8 +530,7 @@
* @param rootValue the hidden root value of the tree
* @param resources the resources used to render the tree
*/
- public <T> CellTree(
- TreeViewModel viewModel, T rootValue, Resources resources) {
+ public <T> CellTree(TreeViewModel viewModel, T rootValue, Resources resources) {
super(viewModel);
if (template == null) {
template = GWT.create(Template.class);
@@ -534,11 +556,16 @@
setAnimation(SlideAnimation.create());
// Add event handlers.
- sinkEvents(Event.ONCLICK | Event.ONKEYDOWN | Event.ONKEYUP);
+ Set<String> eventTypes = new HashSet<String>();
+ eventTypes.add("focus");
+ eventTypes.add("blur");
+ eventTypes.add("keydown");
+ eventTypes.add("mousedown");
+ CellBasedWidgetImpl.get().sinkEvents(this, eventTypes);
// Associate a view with the item.
- CellTreeNodeView<T> root = new CellTreeNodeView<T>(
- this, null, null, getElement(), rootValue);
+ CellTreeNodeView<T> root = new CellTreeNodeView<T>(this, null, null,
+ getElement(), rootValue);
keyboardSelectedNode = rootNode = root;
root.setOpen(true, false);
}
@@ -568,6 +595,10 @@
return rootNode.getTreeNode();
}
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
@@ -575,30 +606,43 @@
@Override
public void onBrowserEvent(Event event) {
CellBasedWidgetImpl.get().onBrowserEvent(this, event);
+ if (isRefreshing) {
+ // Ignore spurious events (onblur) while replacing elements.
+ return;
+ }
super.onBrowserEvent(event);
String eventType = event.getType();
-
- // Keep track of whether the user has focused on the widget
- if ("blur".equals(eventType)) {
- keyboardSelectedNode.keyboardBlur();
- return;
- }
if ("focus".equals(eventType)) {
- keyboardSelectedNode.keyboardFocus();
- return;
- }
+ // Remember the focus state.
+ isFocused = true;
+ onFocus();
+ } else if ("blur".equals(eventType)) {
+ // Remember the blur state.
+ isFocused = false;
+ onBlur();
+ } else if ("keydown".equals(eventType) && !cellIsEditing) {
+ int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ // Handle keyboard navigation.
+ case KeyCodes.KEY_DOWN:
+ case KeyCodes.KEY_UP:
+ case KeyCodes.KEY_RIGHT:
+ case KeyCodes.KEY_LEFT:
+ handleKeyNavigation(keyCode);
- boolean keyUp = "keyup".equals(eventType);
- boolean keyDown = "keydown".equals(eventType);
+ // Prevent scrollbars from scrolling.
+ event.preventDefault();
+ return;
+ case 32:
+ // Handle space bar selection.
+ if (KeyboardSelectionPolicy.ENABLED == getKeyboardSelectionPolicy()) {
+ keyboardSelectedNode.setSelected(!keyboardSelectedNode.isSelected());
- // Ignore keydown events unless the cell is in edit mode
- if (keyDown && !cellIsEditing) {
- return;
- }
- if (keyUp && !cellIsEditing) {
- if (handleKey(event)) {
- return;
+ // Prevent scrollbars from scrolling.
+ event.preventDefault();
+ }
+ return;
}
}
@@ -606,35 +650,48 @@
ArrayList<Element> chain = new ArrayList<Element>();
collectElementChain(chain, getElement(), target);
+ boolean isMouseDown = "mousedown".equals(eventType);
CellTreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
if (nodeView != null && nodeView != rootNode) {
- if ("click".equals(eventType)) {
- // Open the node when the open image is clicked.
+ if (isMouseDown) {
Element showMoreElem = nodeView.getShowMoreElement();
if (nodeView.getImageElement().isOrHasChild(target)) {
+ // Open the node when the open image is clicked.
nodeView.setOpen(!nodeView.isOpen(), true);
return;
} else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
+ // Show more rows when clicked.
nodeView.showMore();
return;
}
- } else if ("mouseup".equals(eventType)) {
- // Move the keyboard focus to the clicked item
- keyboardSelectedNode.keyboardExit();
- keyboardSelectedNode = nodeView.getParentNode();
- keyboardSelectedNode.keyboardEnter(target, true);
}
// Forward the event to the cell
- if (nodeView.getCellParent().isOrHasChild(target)
- || (eventType.startsWith("key")
- && nodeView.getCellParent().getParentElement() == target)) {
+ if (nodeView.getCellParent().isOrHasChild(target)) {
+ // Move the keyboard focus to the clicked item.
+ if ("focus".equals(eventType) || isMouseDown) {
+ isFocused = true;
+ keyboardSelect(nodeView, false);
+ }
+
nodeView.fireEventToCell(event);
}
}
}
/**
+ * {@inheritDoc}
+ *
+ * <p>
+ * Setting the key to (int) 0 will disable the access key.
+ * </p>
+ */
+ public void setAccessKey(char key) {
+ this.accessKey = key;
+ keyboardSelectedNode.setKeyboardSelected(true, false);
+ }
+
+ /**
* Set the animation used to open and close nodes in this tree. You must call
* {@link #setAnimationEnabled(boolean)} to enable or disable animation.
*
@@ -665,6 +722,32 @@
this.defaultNodeSize = defaultNodeSize;
}
+ public void setFocus(boolean focused) {
+ keyboardSelectedNode.setKeyboardSelected(true, true);
+ }
+
+ public void setTabIndex(int index) {
+ this.tabIndex = index;
+ keyboardSelectedNode.setKeyboardSelected(true, false);
+ }
+
+ /**
+ * Get the access key.
+ *
+ * @return the access key, or -1 if not set
+ */
+ protected char getAccessKey() {
+ return accessKey;
+ }
+
+ protected void onBlur() {
+ keyboardSelectedNode.setKeyboardSelectedStyle(false);
+ }
+
+ protected void onFocus() {
+ keyboardSelectedNode.setKeyboardSelectedStyle(true);
+ }
+
/**
* Cancel a pending animation.
*/
@@ -692,6 +775,13 @@
}
/**
+ * @return the node that has keyboard selection.
+ */
+ CellTreeNodeView<?> getKeyboardSelectedNode() {
+ return keyboardSelectedNode;
+ }
+
+ /**
* @return the HTML to render the loading image.
*/
SafeHtml getLoadingImageHtml() {
@@ -716,22 +806,54 @@
}
/**
+ * Select a node using the keyboard.
+ *
+ * @param node the new node to select
+ * @param stealFocus true to steal focus, false not to
+ */
+ void keyboardSelect(CellTreeNodeView<?> node, boolean stealFocus) {
+ if (isKeyboardSelectionDisabled()) {
+ return;
+ }
+
+ // Deselect the old node if it not destroyed.
+ if (keyboardSelectedNode != null && !keyboardSelectedNode.isDestroyed()) {
+ keyboardSelectedNode.setKeyboardSelected(false, false);
+ }
+ keyboardSelectedNode = node;
+ keyboardSelectedNode.setKeyboardSelected(true, stealFocus);
+ }
+
+ /**
* Animate the current state of a {@link CellTreeNodeView} in this tree.
*
* @param node the node to animate
*/
void maybeAnimateTreeNode(CellTreeNodeView<?> node) {
if (animation != null) {
- animation.animate(node,
- node.consumeAnimate() && isAnimationEnabled() && !node.isRootNode());
+ animation.animate(node, node.consumeAnimate() && isAnimationEnabled()
+ && !node.isRootNode());
}
}
/**
+ * If this widget has focus, reset it.
+ */
+ void resetFocus() {
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ if (isFocused && !keyboardSelectedNode.resetFocusOnCell()) {
+ keyboardSelectedNode.setKeyboardSelected(true, true);
+ }
+ }
+ });
+ }
+
+ /**
* Collects parents going up the element tree, terminated at the tree root.
*/
- private void collectElementChain(
- ArrayList<Element> chain, Element hRoot, Element hElem) {
+ private void collectElementChain(ArrayList<Element> chain, Element hRoot,
+ Element hElem) {
if ((hElem == null) || (hElem == hRoot)) {
return;
}
@@ -740,8 +862,8 @@
chain.add(hElem);
}
- private CellTreeNodeView<?> findItemByChain(
- ArrayList<Element> chain, int idx, CellTreeNodeView<?> parent) {
+ private CellTreeNodeView<?> findItemByChain(ArrayList<Element> chain,
+ int idx, CellTreeNodeView<?> parent) {
if (idx == chain.size()) {
return parent;
}
@@ -780,83 +902,109 @@
} else {
direction = "left";
}
- return template.image(classesBuilder.toString(), direction, res.getHeight(),
- res.getWidth(), res.getURL());
+ return template.image(classesBuilder.toString(), direction,
+ res.getHeight(), res.getWidth(), res.getURL());
}
/**
- * @return true if the key event was consumed by navigation, false if it
- * should be passed on to the underlying Cell.
+ * Handle keyboard navigation.
+ *
+ * @param keyCode the key code that was pressed
*/
- private boolean handleKey(Event event) {
- int keyCode = event.getKeyCode();
-
- CellTreeNodeView<?> child = null;
- int keyboardSelectedIndex = keyboardSelectedNode.getKeyboardSelectedIndex();
- if (keyboardSelectedIndex != -1
- && keyboardSelectedNode.getChildCount() > keyboardSelectedIndex) {
- child = keyboardSelectedNode.getChildNode(keyboardSelectedIndex);
- }
-
+ private void handleKeyNavigation(int keyCode) {
CellTreeNodeView<?> parent = keyboardSelectedNode.getParentNode();
+ int parentChildCount = (parent == null) ? 0 : parent.getChildCount();
+ int index = (parent == null) ? 0 : parent.indexOf(keyboardSelectedNode);
+ int childCount = keyboardSelectedNode.getChildCount();
+
switch (keyCode) {
- case KeyCodes.KEY_UP:
- if (keyboardSelectedNode.getKeyboardSelectedIndex() == 0) {
- if (!keyboardSelectedNode.isRootNode()) {
- if (parent != null) {
- keyboardSelectedNode.keyboardExit();
- parent.keyboardEnter(parent.indexOf(keyboardSelectedNode), true);
- keyboardSelectedNode = parent;
- }
- }
- } else {
- keyboardSelectedNode.keyboardUp();
- // Descend into open nodes, go to bottom of leaf node
- int index = keyboardSelectedNode.getKeyboardSelectedIndex();
- while ((child = keyboardSelectedNode.getChildNode(index)).isOpen()) {
- keyboardSelectedNode.keyboardExit();
- index = child.getChildCount() - 1;
- child.keyboardEnter(index, true);
- keyboardSelectedNode = child;
- }
- }
- return true;
-
case KeyCodes.KEY_DOWN:
- if (child != null && child.isOpen()) {
- keyboardSelectedNode.keyboardExit();
- child.keyboardEnter(0, true);
- keyboardSelectedNode = child;
- } else if (!keyboardSelectedNode.keyboardDown()) {
- if (parent != null) {
- keyboardSelectedNode.keyboardExit();
- parent.keyboardEnter(parent.indexOf(keyboardSelectedNode), true);
- // If already at last node of a given level, go up
- while (!parent.keyboardDown()) {
- CellTreeNodeView<?> newParent = parent.getParentNode();
- if (newParent != null) {
- parent.keyboardExit();
- newParent.keyboardEnter(newParent.indexOf(parent) + 1, true);
- parent = newParent;
- }
+ if (keyboardSelectedNode.isOpen() && childCount > 0) {
+ // Select first child.
+ keyboardSelect(keyboardSelectedNode.getChildNode(0), true);
+ } else if (index < parentChildCount - 1) {
+ // Next sibling.
+ keyboardSelect(parent.getChildNode(index + 1), true);
+ } else {
+ // Next available sibling of parent hierarchy.
+ CellTreeNodeView<?> curParent = parent;
+ CellTreeNodeView<?> nextSibling = null;
+ while (curParent != null && curParent != rootNode) {
+ CellTreeNodeView<?> grandparent = curParent.getParentNode();
+ if (grandparent == null) {
+ break;
}
- keyboardSelectedNode = parent;
+ int curParentIndex = grandparent.indexOf(curParent);
+ if (curParentIndex < grandparent.getChildCount() - 1) {
+ nextSibling = grandparent.getChildNode(curParentIndex + 1);
+ break;
+ }
+ curParent = grandparent;
+ }
+ if (nextSibling != null) {
+ keyboardSelect(nextSibling, true);
}
}
- return true;
-
- case KeyCodes.KEY_LEFT:
+ break;
+ case KeyCodes.KEY_UP:
+ if (index > 0) {
+ // Deepest node of previous sibling hierarchy.
+ CellTreeNodeView<?> prevSibling = parent.getChildNode(index - 1);
+ if (prevSibling.isOpen() && prevSibling.getChildCount() > 0) {
+ prevSibling = prevSibling.getChildNode(prevSibling.getChildCount() - 1);
+ }
+ keyboardSelect(prevSibling, true);
+ } else if (parent != null && parent != rootNode) {
+ // Parent.
+ keyboardSelect(parent, true);
+ }
+ break;
case KeyCodes.KEY_RIGHT:
- case KeyCodes.KEY_ENTER:
- // TODO(rice) - try different key bahavior mappings such as
- // left=close, right=open, enter=toggle.
- if (child != null && !child.isLeaf()) {
- child.setOpen(!child.isOpen(), true);
- return true;
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ keyboardNavigateShallow();
+ } else {
+ keyboardNavigateDeep();
+ }
+ break;
+ case KeyCodes.KEY_LEFT:
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ keyboardNavigateDeep();
+ } else {
+ keyboardNavigateShallow();
}
break;
}
+ }
- return false;
+ /**
+ * Navigate to a deeper node. If the node is closed, open it. If it is open,
+ * move to the first child.
+ */
+ private void keyboardNavigateDeep() {
+ if (!keyboardSelectedNode.isLeaf()) {
+ boolean isOpen = keyboardSelectedNode.isOpen();
+ if (isOpen && keyboardSelectedNode.getChildCount() > 0) {
+ // First child.
+ keyboardSelect(keyboardSelectedNode.getChildNode(0), true);
+ } else if (!isOpen) {
+ // Open the node.
+ keyboardSelectedNode.setOpen(true, true);
+ }
+ }
+ }
+
+ /**
+ * Navigate to a shallower node. If the node is open, close it. If it is
+ * closed, move to the parent.
+ */
+ private void keyboardNavigateShallow() {
+ CellTreeNodeView<?> parent = keyboardSelectedNode.getParentNode();
+ if (keyboardSelectedNode.isOpen()) {
+ // Close the node.
+ keyboardSelectedNode.setOpen(false, true);
+ } else if (parent != null && parent != rootNode) {
+ // Select the parent.
+ keyboardSelect(parent, true);
+ }
}
}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css b/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
index d40bd73..f008a26 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
@@ -40,13 +40,7 @@
.cellTreeItemValue {
padding-left: 3px;
padding-right: 3px;
-}
-
-/*
-div:focus { outline: none; }
-*/
-.cellTreeKeyboardSelectedItem {
- /* background-color: #ffff00; */
+ outline: none;
}
.cellTreeOpenItem {
@@ -65,12 +59,17 @@
}
+.cellTreeKeyboardSelectedItem {
+ background-color: #ffc;
+ outline: none;
+}
+
@sprite .cellTreeSelectedItem {
gwt-image: 'cellTreeSelectedBackground';
background-color: #628cd5;
color: white;
height: auto;
- overflow: auto;
+ overflow: visible;
}
.cellTreeShowMoreButton {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
index 6652d33..a165244 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeNodeView.java
@@ -17,6 +17,7 @@
import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
@@ -28,9 +29,9 @@
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
@@ -38,7 +39,9 @@
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
@@ -65,11 +68,12 @@
interface Template extends SafeHtmlTemplates {
@Template("<div onclick=\"\" style=\"position:relative;padding-{0}:{1}px;"
+ "\" class=\"{2}\">{3}<div class=\"{4}\">{5}</div></div>")
- SafeHtml innerDiv(String paddingDirection, int imageWidth, String classes, SafeHtml image,
- String itemValueStyle, SafeHtml cellContents);
+ SafeHtml innerDiv(String paddingDirection, int imageWidth, String classes,
+ SafeHtml image, String itemValueStyle, SafeHtml cellContents);
@Template("<div><div style=\"padding-{0}:{1}px;\" class=\"{2}\">{3}</div></div>")
- SafeHtml outerDiv(String paddingDirection, int paddingAmount, String classes, SafeHtml content);
+ SafeHtml outerDiv(String paddingDirection, int paddingAmount,
+ String classes, SafeHtml content);
}
private static final Template template = GWT.create(Template.class);
@@ -185,12 +189,12 @@
SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
cell.render(value, null, cellBuilder);
- SafeHtml innerDiv = template.innerDiv(paddingDirection,
- imageWidth, innerClasses.toString(), image, itemValueStyle,
+ SafeHtml innerDiv = template.innerDiv(paddingDirection, imageWidth,
+ innerClasses.toString(), image, itemValueStyle,
cellBuilder.toSafeHtml());
- sb.append(template.outerDiv(paddingDirection,
- paddingAmount, outerClasses.toString(), innerDiv));
+ sb.append(template.outerDiv(paddingDirection, paddingAmount,
+ outerClasses.toString(), innerDiv));
}
}
@@ -201,8 +205,10 @@
}
// Replace the child nodes.
+ nodeView.tree.isRefreshing = true;
Map<Object, CellTreeNodeView<?>> savedViews = saveChildState(values, 0);
AbstractHasData.replaceAllChildren(nodeView.tree, childContainer, html);
+ nodeView.tree.isRefreshing = false;
// Trim the list of children.
int size = values.size();
@@ -215,6 +221,13 @@
// Reattach the open nodes.
loadChildState(values, 0, savedViews);
+ // If this is the root node, move keyboard focus to the first child.
+ if (nodeView.isRootNode()
+ && nodeView.tree.getKeyboardSelectedNode() == nodeView
+ && values.size() > 0) {
+ nodeView.tree.keyboardSelect(nodeView.children.get(0), false);
+ }
+
// Animate the child container open.
if (nodeView.tree.isAnimationEnabled()) {
nodeView.tree.maybeAnimateTreeNode(nodeView);
@@ -224,19 +237,26 @@
public void replaceChildren(List<C> values, int start, SafeHtml html) {
Map<Object, CellTreeNodeView<?>> savedViews = saveChildState(values, 0);
+ nodeView.tree.isRefreshing = true;
Element newChildren = AbstractHasData.convertToElements(nodeView.tree,
getTmpElem(), html);
AbstractHasData.replaceChildren(nodeView.tree, childContainer,
newChildren, start, html);
+ nodeView.tree.isRefreshing = false;
loadChildState(values, 0, savedViews);
}
public void resetFocus() {
- if (nodeView.keyboardSelectedIndex != -1) {
- nodeView.keyboardEnter(nodeView.keyboardSelectedIndex,
- nodeView.keyboardFocused);
- }
+ nodeView.tree.resetFocus();
+ }
+
+ public void setKeyboardSelected(int index, boolean selected,
+ boolean stealFocus) {
+ // Keyboard selection is handled by CellTree.
+ Element elem = childContainer.getChild(index).cast();
+ setStyleName(getSelectionElement(elem),
+ nodeView.tree.getStyle().cellTreeKeyboardSelectedItem(), selected);
}
public void setLoadingState(LoadingState state) {
@@ -265,6 +285,7 @@
Element container = nodeView.ensureChildContainer();
Element childElem = container.getFirstChildElement();
+ CellTreeNodeView<?> keyboardSelected = nodeView.tree.getKeyboardSelectedNode();
for (int i = start; i < end; i++) {
C childValue = values.get(i - start);
CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
@@ -294,10 +315,27 @@
// NodeInfo, which would inevitably cause the NodeInfo to push
// new data.
child.listView = savedChild.listView;
- child.listView.nodeView = child;
+ if (child.listView != null) {
+ child.listView.nodeView = child;
+ }
+
+ // Set the new parent of the grandchildren.
+ if (child.children != null) {
+ for (CellTreeNodeView<?> grandchild : child.children) {
+ grandchild.parentNode = child;
+ }
+ }
+
+ // Transfer the keyboard selected node.
+ if (keyboardSelected == savedChild) {
+ keyboardSelected = child;
+ }
// Copy the child container element to the new child
child.getElement().appendChild(savedChild.ensureAnimationFrame());
+
+ // Mark the old child as destroy without actually destroying it.
+ savedChild.isDestroyed = true;
}
if (childCount > i) {
@@ -307,6 +345,17 @@
}
childElem = childElem.getNextSiblingElement();
}
+
+ // Move the keyboard selected node if it is this node or a child of this
+ // node.
+ CellTreeNodeView<?> curNode = keyboardSelected;
+ while (curNode != null) {
+ if (curNode == nodeView) {
+ nodeView.tree.keyboardSelect(keyboardSelected, false);
+ break;
+ }
+ curNode = curNode.parentNode;
+ }
}
/**
@@ -329,11 +378,12 @@
int len = values.size();
int end = start + len;
int childCount = nodeView.getChildCount();
+ CellTreeNodeView<?> keyboardSelected = nodeView.tree.getKeyboardSelectedNode();
Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<Object, CellTreeNodeView<?>>();
for (int i = start; i < end && i < childCount; i++) {
CellTreeNodeView<?> child = nodeView.getChildNode(i);
- if (child.isOpen()) {
- // Save child nodes that are open.
+ if (child.isOpen() || child == keyboardSelected) {
+ // Save child nodes that are open or keyboard selected.
openNodes.put(child.getValueKey(), child);
} else {
// Cleanup child nodes that are closed.
@@ -380,7 +430,7 @@
cell = nodeInfo.getCell();
presenter = new HasDataPresenter<C>(this, new View(
- nodeView.ensureChildContainer()), pageSize);
+ nodeView.ensureChildContainer()), pageSize, nodeInfo.getProvidesKey());
// Use a pager to update buttons.
presenter.addRowCountChangeHandler(new RowCountChangeEvent.Handler() {
@@ -556,8 +606,7 @@
/**
* The element used in place of an image when a node has no children.
*/
- private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant(
- "<div style='position:absolute;display:none;'></div>");
+ private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils.fromSafeConstant("<div style='position:absolute;display:none;'></div>");
/**
* The temporary element used to render child items.
@@ -662,21 +711,6 @@
private boolean isDestroyed;
/**
- * True if the keyboard selection has focus.
- */
- private boolean keyboardFocused;
-
- /**
- * The index of the keyboard selection within this node's children.
- */
- private int keyboardSelectedIndex = -1;
-
- /**
- * The parent element of the current keyboard selection, or null.
- */
- private Element keyboardSelection;
-
- /**
* The list view used to display the nodes.
*/
private NodeCellList<?> listView;
@@ -699,7 +733,7 @@
/**
* The parent {@link CellTreeNodeView}.
*/
- private final CellTreeNodeView<?> parentNode;
+ private CellTreeNodeView<?> parentNode;
/**
* The {@link NodeInfo} of the parent node.
@@ -820,7 +854,8 @@
showOrHide(showMoreElem, false);
showOrHide(emptyMessageElem, false);
if (!isRootNode()) {
- setStyleName(getCellParent(), tree.getStyle().cellTreeOpenItem(), true);
+ setStyleName(getCellParent(), tree.getStyle().cellTreeOpenItem(),
+ true);
}
ensureAnimationFrame().getStyle().setProperty("display", "");
onOpen(nodeInfo);
@@ -839,8 +874,16 @@
cleanup(false);
tree.maybeAnimateTreeNode(this);
updateImage(false);
- keyboardExit();
- keyboardSelectedIndex = -1;
+
+ // Keyboard select this node if the open node was a child.
+ CellTreeNodeView<?> keySelected = tree.getKeyboardSelectedNode();
+ while (keySelected != null) {
+ if (keySelected == this) {
+ tree.keyboardSelect(this, true);
+ break;
+ }
+ keySelected = keySelected.getParentNode();
+ }
// Fire an event.
if (fireEvents) {
@@ -875,6 +918,13 @@
// Destroy this node.
if (destroy) {
isDestroyed = true;
+
+ // If this is the keyboard selected node, select the parent. The children
+ // have already been cleaned, so the selected node cannot be under this
+ // node.
+ if (this == tree.getKeyboardSelectedNode()) {
+ tree.keyboardSelect(parentNode, false);
+ }
}
}
@@ -906,25 +956,36 @@
* @param event the native event
*/
protected void fireEventToCell(NativeEvent event) {
- if (parentNodeInfo != null) {
- Cell<T> parentCell = parentNodeInfo.getCell();
- String eventType = event.getType();
- SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+ if (parentNodeInfo == null) {
+ return;
+ }
- // Update selection.
- if (selectionModel != null && "click".equals(eventType)
- && !parentCell.handlesSelection()) {
- // TODO(jlabanca): Should we toggle? Only when ctrl is pressed?
- selectionModel.setSelected(value, true);
- }
+ Cell<T> parentCell = parentNodeInfo.getCell();
+ String eventType = event.getType();
+ boolean isMouseDown = "mousedown".equals(eventType);
+ SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+ Element cellParent = getCellParent();
+ Object key = getValueKey();
- // Forward the event to the cell.
- Element cellParent = getCellParent();
- Object key = getValueKey();
- Set<String> consumedEvents = parentCell.getConsumedEvents();
- if (consumedEvents != null && consumedEvents.contains(eventType)) {
- parentCell.onBrowserEvent(cellParent, value, key, event,
- parentNodeInfo.getValueUpdater());
+ // Update selection.
+ if (selectionModel != null && isMouseDown && !parentCell.handlesSelection()) {
+ // TODO(jlabanca): Should we toggle? Only when ctrl is pressed?
+ selectionModel.setSelected(value, true);
+ }
+
+ // Forward the event to the cell.
+ Set<String> consumedEvents = parentCell.getConsumedEvents();
+ if (consumedEvents != null && consumedEvents.contains(eventType)) {
+ boolean cellWasEditing = parentCell.isEditing(cellParent, value, key);
+ parentCell.onBrowserEvent(cellParent, value, key, event,
+ parentNodeInfo.getValueUpdater());
+ tree.cellIsEditing = parentCell.isEditing(cellParent, value, key);
+ if (cellWasEditing && !tree.cellIsEditing) {
+ CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
+ public void execute() {
+ tree.setFocus(true);
+ }
+ });
}
}
}
@@ -1010,7 +1071,8 @@
// TODO(jlabanca): I18N no data string.
emptyMessageElem = Document.get().createDivElement();
emptyMessageElem.setInnerHTML("no data");
- setStyleName(emptyMessageElem, tree.getStyle().cellTreeEmptyMessage(), true);
+ setStyleName(emptyMessageElem, tree.getStyle().cellTreeEmptyMessage(),
+ true);
showOrHide(emptyMessageElem, false);
contentContainer.appendChild(emptyMessageElem);
@@ -1024,10 +1086,6 @@
return contentContainer;
}
- int getKeyboardSelectedIndex() {
- return keyboardSelectedIndex;
- }
-
/**
* Return the parent node, or null if this node is the root.
*/
@@ -1055,6 +1113,10 @@
return children.indexOf(child);
}
+ boolean isDestroyed() {
+ return isDestroyed;
+ }
+
/**
* Check if this node is a root node.
*
@@ -1065,113 +1127,101 @@
}
/**
- * The user has "tabbed" away from the node -- don't force refocus when
- * re-rendering.
- */
- void keyboardBlur() {
- keyboardFocused = false;
- }
-
- /**
- * Handle a keyboard navigation event to move down one item. Returns true if
- * movement was possible (the current item was not the last child of its
- * parent).
+ * Check if the value of this node is selected.
*
- * @return true if the selection moved
+ * @return true if selected, false if not
*/
- boolean keyboardDown() {
- Element next = keyboardSelection.getNextSiblingElement();
- if (next != null) {
- keyboardExit();
- keyboardEnterAtElement(next, true);
- keyboardSelectedIndex++;
- return true;
- }
- return false;
- }
-
- /**
- * Handle a keyboard event to move focus into the current item list at the
- * child that contains the given Element.
- *
- * @param focus if true, focus on the element
- */
- void keyboardEnter(Element element, boolean focus) {
- Element item = ensureChildContainer().getFirstChildElement();
- int index = 0;
- boolean found = false;
- for (int i = 0; i < getChildCount(); i++) {
- if (item.isOrHasChild(element)) {
- found = true;
- break;
+ boolean isSelected() {
+ if (parentNodeInfo != null) {
+ SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+ if (selectionModel != null) {
+ return selectionModel.isSelected(value);
}
- item = item.getNextSiblingElement();
- index++;
- }
- if (found) {
- keyboardEnterAtElement(item, focus);
- keyboardSelectedIndex = index;
- }
- }
-
- /**
- * Handle a keyboard event to move focus into the current item list at the
- * given child index.
- *
- * @param focus if true, focus on the element
- */
- void keyboardEnter(int index, boolean focus) {
- if (index < 0 || index >= getChildCount()) {
- throw new IllegalArgumentException("Index out of range: " + index);
- }
- Element item = ensureChildContainer().getChild(index).cast();
- keyboardEnterAtElement(item, focus);
- keyboardSelectedIndex = index;
- }
-
- /**
- * Handle a keyboard event to move focus away from the current item.
- */
- void keyboardExit() {
- if (keyboardSelection == null) {
- return;
- }
- Element parent = keyboardSelection.getFirstChildElement();
- Element child = parent.getFirstChildElement();
- child.removeAttribute("tabIndex");
- child.removeClassName(tree.getStyle().cellTreeKeyboardSelectedItem());
- keyboardSelection = null;
- keyboardFocused = false;
- }
-
- void keyboardFocus() {
- keyboardFocused = true;
- }
-
- /**
- * Handle a keyboard navigation event to move up one item. Returns true if
- * movement was possible (the current item was not the first child of its
- * parent).
- *
- * @return true if the selection moved
- */
- boolean keyboardUp() {
- Element prev = keyboardSelection.getParentElement().getFirstChildElement();
- Element next = prev.getNextSiblingElement();
- while (next != null && next != keyboardSelection) {
- prev = next;
- next = next.getNextSiblingElement();
- }
- if (next == keyboardSelection) {
- int index = keyboardSelectedIndex;
- keyboardExit();
- keyboardEnterAtElement(prev, true);
- keyboardSelectedIndex = index - 1;
- return true;
}
return false;
}
+ /**
+ * Reset focus on this node.
+ *
+ * @return true of the cell takes focus, false if not
+ */
+ boolean resetFocusOnCell() {
+ if (parentNodeInfo != null) {
+ Cell<T> cell = parentNodeInfo.getCell();
+ return cell.resetFocus(getCellParent(), value, getValueKey());
+ }
+ return false;
+ }
+
+ /**
+ * Select or deselect this node with the keyboard.
+ *
+ * @param selected true if selected, false if not
+ * @param stealFocus true to steal focus
+ */
+ void setKeyboardSelected(boolean selected, boolean stealFocus) {
+ // Apply the selected style.
+ if (!selected || tree.isFocused || stealFocus) {
+ setKeyboardSelectedStyle(selected);
+ }
+
+ // Make the node focusable or not.
+ Element cellParent = getCellParent();
+ if (!selected) {
+ // Chrome: Elements remain focusable after removing the tabIndex, so set
+ // it to -1 first.
+ cellParent.setTabIndex(-1);
+ cellParent.removeAttribute("tabIndex");
+ cellParent.removeAttribute("accessKey");
+ } else {
+ FocusImpl focusImpl = FocusImpl.getFocusImplForWidget();
+ com.google.gwt.user.client.Element cellElem = cellParent.cast();
+ focusImpl.setTabIndex(cellElem, tree.getTabIndex());
+ char accessKey = tree.getAccessKey();
+ if (accessKey != 0) {
+ focusImpl.setAccessKey(cellElem, accessKey);
+ }
+ if (stealFocus) {
+ cellElem.focus();
+ }
+ }
+
+ // Update the selection model.
+ if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == tree.getKeyboardSelectionPolicy()) {
+ setSelected(selected);
+ }
+ }
+
+ /**
+ * Add or remove the keyboard selected style.
+ *
+ * @param selected true if selected, false if not
+ */
+ void setKeyboardSelectedStyle(boolean selected) {
+ if (!isRootNode()) {
+ Element selectionElem = getSelectionElement(getElement());
+ if (selectionElem != null) {
+ setStyleName(selectionElem,
+ tree.getStyle().cellTreeKeyboardSelectedItem(), selected);
+ }
+ }
+ }
+
+ /**
+ * Select or deselect this node.
+ *
+ * @param selected true to select, false to deselect
+ */
+ void setSelected(boolean selected) {
+ if (parentNodeInfo != null) {
+ SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+ if (selectionModel != null) {
+ selectionModel.setSelected(value, selected);
+ }
+ }
+ }
+
void showFewer() {
Range range = listView.getVisibleRange();
int defaultPageSize = listView.getDefaultPageSize();
@@ -1185,19 +1235,6 @@
listView.setVisibleRange(range.getStart(), pageSize);
}
- private void keyboardEnterAtElement(Element item, boolean focus) {
- if (item != null) {
- Element child = item.getFirstChildElement().getFirstChildElement();
- child.addClassName(tree.getStyle().cellTreeKeyboardSelectedItem());
- child.setTabIndex(0);
- if (focus) {
- child.focus();
- }
- keyboardSelection = item;
- keyboardFocused = focus;
- }
- }
-
/**
* Update the image based on the current state.
*
diff --git a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
index c922a17..b1bb09f0 100644
--- a/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
+++ b/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java
@@ -22,6 +22,8 @@
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.view.client.HasData;
+import com.google.gwt.view.client.HasKeyProvider;
+import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.view.client.RowCountChangeEvent;
@@ -51,7 +53,8 @@
*
* @param <T> the data type of items in the list
*/
-class HasDataPresenter<T> implements HasData<T> {
+class HasDataPresenter<T> implements HasData<T>, HasKeyProvider<T>,
+ HasKeyboardPagingPolicy {
/**
* Default iterator over DOM elements.
@@ -134,8 +137,8 @@
* @param handler the handler to add
* @param type the event type
*/
- <H extends EventHandler> HandlerRegistration addHandler(
- final H handler, GwtEvent.Type<H> type);
+ <H extends EventHandler> HandlerRegistration addHandler(final H handler,
+ GwtEvent.Type<H> type);
/**
* Check whether or not the cells in the view depend on the selection state.
@@ -196,11 +199,21 @@
void replaceChildren(List<T> values, int start, SafeHtml html);
/**
- * Re-establish focus on an element within the view if desired.
+ * Re-establish focus on an element within the view if the view already had
+ * focus.
*/
void resetFocus();
/**
+ * Update an element to reflect its keyboard selected state.
+ *
+ * @param index the index of the element relative to page start
+ * @param selected true if selected, false if not
+ * @param stealFocus true if the row should steal focus, false if not
+ */
+ void setKeyboardSelected(int index, boolean selected, boolean stealFocus);
+
+ /**
* Set the current loading state of the data.
*
* @param state the loading state
@@ -216,9 +229,32 @@
void setSelected(Element elem, boolean selected);
}
+ /**
+ * The number of rows to jump when PAGE_UP or PAGE_DOWN is pressed and the
+ * {@link KeyboardSelectionPolicy} is
+ * {@link KeyboardSelectionPolicy.INCREMENT_PAGE}.
+ */
+ static final int PAGE_INCREMENT = 30;
+
private final HasData<T> display;
/**
+ * The current keyboard selected row relative to page start. This value should
+ * never be negative.
+ */
+ private int keyboardSelectedRow = 0;
+
+ /**
+ * The last row value that was selected with the keyboard.
+ */
+ private T keyboardSelectedRowValue;
+
+ private KeyboardPagingPolicy keyboardPagingPolicy = KeyboardPagingPolicy.CHANGE_PAGE;
+ private KeyboardSelectionPolicy keyboardSelectionPolicy = KeyboardSelectionPolicy.ENABLED;
+
+ private final ProvidesKey<T> keyProvider;
+
+ /**
* As an optimization, keep track of the last HTML string that we rendered. If
* the contents do not change the next time we render, then we don't have to
* set inner html.
@@ -262,10 +298,12 @@
* @param view the view implementation
* @param pageSize the default page size
*/
- public HasDataPresenter(HasData<T> display, View<T> view, int pageSize) {
+ public HasDataPresenter(HasData<T> display, View<T> view, int pageSize,
+ ProvidesKey<T> keyProvider) {
this.display = display;
this.view = view;
this.pageSize = pageSize;
+ this.keyProvider = keyProvider;
}
public HandlerRegistration addRangeChangeHandler(
@@ -307,6 +345,28 @@
return Math.min(pageSize, rowCount - pageStart);
}
+ public KeyboardPagingPolicy getKeyboardPagingPolicy() {
+ return keyboardPagingPolicy;
+ }
+
+ /**
+ * Get the index of the keyboard selected row relative to the page start.
+ *
+ * @return the row index, or -1 if disabled
+ */
+ public int getKeyboardSelectedRow() {
+ return KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy ? -1
+ : keyboardSelectedRow;
+ }
+
+ public KeyboardSelectionPolicy getKeyboardSelectionPolicy() {
+ return keyboardSelectionPolicy;
+ }
+
+ public ProvidesKey<T> getKeyProvider() {
+ return keyProvider;
+ }
+
/**
* Get the overall data size.
*
@@ -338,11 +398,118 @@
return new Range(pageStart, pageSize);
}
+ /**
+ * Check if the next call to {@link #keyboardNext()} would succeed.
+ *
+ * @return true if there is another row accessible by the keyboard
+ */
+ public boolean hasKeyboardNext() {
+ if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
+ return false;
+ } else if (keyboardSelectedRow < rowData.size() - 1) {
+ return true;
+ } else if (!keyboardPagingPolicy.isLimitedToRange()
+ && (keyboardSelectedRow + pageStart < rowCount - 1 || !rowCountIsExact)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if the next call to {@link #keyboardPrevious()} would succeed.
+ *
+ * @return true if there is a previous row accessible by the keyboard
+ */
+ public boolean hasKeyboardPrev() {
+ if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
+ return false;
+ } else if (keyboardSelectedRow > 0) {
+ return true;
+ } else if (!keyboardPagingPolicy.isLimitedToRange() && pageStart > 0) {
+ return true;
+ }
+ return false;
+ }
+
public boolean isRowCountExact() {
return rowCountIsExact;
}
/**
+ * Move keyboard selection to the last row.
+ */
+ public void keyboardEnd() {
+ if (!keyboardPagingPolicy.isLimitedToRange()) {
+ setKeyboardSelectedRow(rowCount - 1, true);
+ }
+ }
+
+ /**
+ * Move keyboard selection to the absolute 0th row.
+ */
+ public void keyboardHome() {
+ if (!keyboardPagingPolicy.isLimitedToRange()) {
+ setKeyboardSelectedRow(-pageStart, true);
+ }
+ }
+
+ /**
+ * Move keyboard selection to the next row.
+ */
+ public void keyboardNext() {
+ if (hasKeyboardNext()) {
+ setKeyboardSelectedRow(keyboardSelectedRow + 1, true);
+ }
+ }
+
+ /**
+ * Move keyboard selection to the next page.
+ */
+ public void keyboardNextPage() {
+ if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
+ // 0th index of next page.
+ setKeyboardSelectedRow(pageSize, true);
+ } else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
+ setKeyboardSelectedRow(keyboardSelectedRow + PAGE_INCREMENT, true);
+ }
+ }
+
+ /**
+ * Move keyboard selection to the previous row.
+ */
+ public void keyboardPrev() {
+ if (hasKeyboardPrev()) {
+ setKeyboardSelectedRow(keyboardSelectedRow - 1, true);
+ }
+ }
+
+ /**
+ * Move keyboard selection to the previous page.
+ */
+ public void keyboardPrevPage() {
+ if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
+ // 0th index of previous page.
+ setKeyboardSelectedRow(-pageSize, true);
+ } else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
+ setKeyboardSelectedRow(keyboardSelectedRow - PAGE_INCREMENT, true);
+ }
+ }
+
+ /**
+ * Toggle selection of the current keyboard row in the {@link SelectionModel}.
+ */
+ public void keyboardToggleSelect() {
+ if (KeyboardSelectionPolicy.ENABLED == keyboardSelectionPolicy
+ && selectionModel != null && keyboardSelectedRow >= 0
+ && keyboardSelectedRow < rowData.size()) {
+ T value = rowData.get(keyboardSelectedRow);
+ if (value != null) {
+ selectionModel.setSelected(value, !selectionModel.isSelected(value));
+ }
+ }
+ }
+
+ /**
* Redraw the list with the current data.
*/
public void redraw() {
@@ -350,6 +517,109 @@
setRowData(pageStart, rowData);
}
+ public void setKeyboardPagingPolicy(KeyboardPagingPolicy policy) {
+ if (policy == null) {
+ throw new NullPointerException("KeyboardPagingPolicy cannot be null");
+ }
+ this.keyboardPagingPolicy = policy;
+ }
+
+ /**
+ * Set the row index of the keyboard selected element.
+ *
+ * @param index the row index
+ * @param stealFocus true to steal focus
+ */
+ public void setKeyboardSelectedRow(int index, boolean stealFocus) {
+ // Early exit if disabled.
+ if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
+ return;
+ }
+ boolean isBound = KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy;
+
+ // Deselect the old index.
+ if (keyboardSelectedRow >= 0 && keyboardSelectedRow < view.getChildCount()) {
+ view.setKeyboardSelected(keyboardSelectedRow, false, false);
+ if (isBound) {
+ deselectKeyboardValue();
+ }
+ }
+
+ // Trim to within bounds.
+ int absIndex = pageStart + index;
+ if (absIndex < 0) {
+ absIndex = 0;
+ } else if (absIndex >= rowCount && rowCountIsExact) {
+ absIndex = rowCount - 1;
+ }
+ index = absIndex - pageStart;
+ if (keyboardPagingPolicy.isLimitedToRange()) {
+ index = Math.max(0, Math.min(index, pageSize - 1));
+ }
+
+ // Select the new index.
+ int newPageStart = pageStart;
+ int newPageSize = pageSize;
+ keyboardSelectedRow = 0;
+ if (index >= 0 && index < pageSize) {
+ keyboardSelectedRow = index;
+ if (isBound) {
+ selectKeyboardValue(index);
+ }
+ view.setKeyboardSelected(index, true, stealFocus);
+ return;
+ } else if (KeyboardPagingPolicy.CHANGE_PAGE == keyboardPagingPolicy) {
+ // Go to previous page.
+ while (index < 0) {
+ newPageStart -= pageSize;
+ index += pageSize;
+ }
+
+ // Go to next page.
+ while (index >= pageSize) {
+ newPageStart += pageSize;
+ index -= pageSize;
+ }
+ } else if (KeyboardPagingPolicy.INCREASE_RANGE == keyboardPagingPolicy) {
+ // Increase range at the beginning.
+ while (index < 0) {
+ newPageSize += PAGE_INCREMENT;
+ newPageStart -= PAGE_INCREMENT;
+ index += PAGE_INCREMENT;
+ }
+ if (newPageStart < 0) {
+ index += newPageStart;
+ newPageSize += newPageStart;
+ newPageStart = 0;
+ }
+
+ // Increase range at the end.
+ while (index >= newPageSize) {
+ newPageSize += PAGE_INCREMENT;
+ }
+ if (isRowCountExact()) {
+ newPageSize = Math.min(newPageSize, rowCount - newPageStart);
+ if (index >= rowCount) {
+ index = rowCount - 1;
+ }
+ }
+ }
+
+ // Update the range if it changed.
+ if (newPageStart != pageStart || newPageSize != pageSize) {
+ deselectKeyboardValue();
+ keyboardSelectedRow = index;
+ setVisibleRange(new Range(newPageStart, newPageSize), false, false);
+ }
+ }
+
+ public void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy) {
+ if (policy == null) {
+ throw new NullPointerException("KeyboardSelectionPolicy cannot be null");
+ }
+ this.keyboardSelectionPolicy = policy;
+ }
+
/**
* @throws UnsupportedOperationException
*/
@@ -367,6 +637,11 @@
this.rowCountIsExact = isExact;
updateLoadingState();
+ // Update the keyboardSelectedRow.
+ if (keyboardSelectedRow >= count) {
+ keyboardSelectedRow = Math.max(0, count - 1);
+ }
+
// Redraw the current page if it is affected by the new data size.
if (updateCachedData()) {
redraw();
@@ -402,6 +677,24 @@
rowData.add(null);
}
+ // If the keyboard selected row is within the data set, clear it out. If the
+ // key still exists, it will be reset below at its new index.
+ Object keyboardSelectedKey = null;
+ int keyboardSelectedAbsoluteRow = pageStart + keyboardSelectedRow;
+ boolean keyboardSelectedInRange = false;
+ boolean keyboardSelectedStillExists = false;
+ if (keyboardSelectedAbsoluteRow >= boundedStart
+ && keyboardSelectedAbsoluteRow < boundedEnd) {
+ keyboardSelectedInRange = true;
+
+ // If the value is null, then we will select whatever value is at the
+ // selected row.
+ if (keyboardSelectedRowValue != null) {
+ keyboardSelectedKey = getRowValueKey(keyboardSelectedRowValue);
+ keyboardSelectedRow = 0; // Will be set to a non-negative number later.
+ }
+ }
+
// Insert the new values into the data array.
for (int i = boundedStart; i < boundedEnd; i++) {
T value = values.get(i - start);
@@ -420,6 +713,13 @@
selectedRows.remove(i);
}
}
+
+ // Update the keyboard selected index.
+ if (keyboardSelectedKey != null && value != null
+ && keyboardSelectedKey.equals(getRowValueKey(value))) {
+ keyboardSelectedRow = i - pageStart;
+ keyboardSelectedStillExists = true;
+ }
}
// Construct a run of elements within the range of the data and the page.
@@ -427,8 +727,8 @@
// boundedSize = the number of items to replace.
boundedStart = pageStartChangedSinceRender ? pageStart : boundedStart;
boundedStart -= cacheOffset;
- List<T> boundedValues = rowData.subList(
- boundedStart - pageStart, boundedEnd - pageStart);
+ List<T> boundedValues = rowData.subList(boundedStart - pageStart,
+ boundedEnd - pageStart);
int boundedSize = boundedValues.size();
SafeHtmlBuilder sb = new SafeHtmlBuilder();
view.render(sb, boundedValues, boundedStart, selectionModel);
@@ -439,25 +739,42 @@
// Replace the DOM elements with the new rendered cells.
int childCount = view.getChildCount();
if (boundedStart == pageStart
- && (boundedSize >= childCount || boundedSize >= getCurrentPageSize()
- || rowData.size() < childCount)) {
+ && (boundedSize >= childCount || boundedSize >= getCurrentPageSize() || rowData.size() < childCount)) {
// If the contents have not changed, we're done.
SafeHtml newContents = sb.toSafeHtml();
if (!newContents.equals(lastContents)) {
lastContents = newContents;
view.replaceAllChildren(boundedValues, newContents);
}
+
+ // Allow the view to reestablish focus after being re-rendered.
+ view.resetFocus();
} else {
lastContents = null;
- view.replaceChildren(
- boundedValues, boundedStart - pageStart, sb.toSafeHtml());
- }
+ view.replaceChildren(boundedValues, boundedStart - pageStart,
+ sb.toSafeHtml());
- // Allow the view to reestablish focus after being re-rendered
- view.resetFocus();
+ // Only reset focus if needed.
+ if (keyboardSelectedStillExists) {
+ view.resetFocus();
+ }
+ }
// Reset the pageStartChanged boolean.
pageStartChangedSinceRender = false;
+
+ // Update the keyboard selected value.
+ if (keyboardSelectedInRange && !keyboardSelectedStillExists) {
+ if (keyboardSelectedKey != null) {
+ // We had a value, but its lost.
+ deselectKeyboardValue();
+ }
+
+ // Select the selected row based off the row index.
+ if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy) {
+ selectKeyboardValue(keyboardSelectedRow);
+ }
+ }
}
/**
@@ -473,24 +790,22 @@
setVisibleRange(range, false, false);
}
- public void setVisibleRangeAndClearData(
- Range range, boolean forceRangeChangeEvent) {
+ public void setVisibleRangeAndClearData(Range range,
+ boolean forceRangeChangeEvent) {
setVisibleRange(range, true, forceRangeChangeEvent);
}
- public void setSelectionModel(
- final SelectionModel<? super T> selectionModel) {
+ public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
clearSelectionModel();
// Set the new selection model.
this.selectionModel = selectionModel;
if (selectionModel != null) {
- selectionHandler = selectionModel.addSelectionChangeHandler(
- new SelectionChangeEvent.Handler() {
- public void onSelectionChange(SelectionChangeEvent event) {
- updateSelection();
- }
- });
+ selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
+ public void onSelectionChange(SelectionChangeEvent event) {
+ updateSelection();
+ }
+ });
}
// Update the current selection state based on the new model.
@@ -498,6 +813,42 @@
}
/**
+ * Deselect the keyboard selected value.
+ */
+ private void deselectKeyboardValue() {
+ if (selectionModel != null && keyboardSelectedRowValue != null) {
+ T curValue = keyboardSelectedRowValue;
+ keyboardSelectedRow = 0;
+ keyboardSelectedRowValue = null;
+ selectionModel.setSelected(curValue, false);
+ }
+ }
+
+ /**
+ * Get the key for the specified row value.
+ *
+ * @param rowValue the row value
+ * @return the key
+ */
+ private Object getRowValueKey(T rowValue) {
+ return keyProvider == null ? rowValue : keyProvider.getKey(rowValue);
+ }
+
+ /**
+ * Select the value at the keyboard selected row.
+ *
+ * @param row the row index
+ */
+ private void selectKeyboardValue(int row) {
+ if (selectionModel != null && row >= 0 && row < rowData.size()) {
+ keyboardSelectedRowValue = rowData.get(row);
+ if (keyboardSelectedRowValue != null) {
+ selectionModel.setSelected(keyboardSelectedRowValue, true);
+ }
+ }
+ }
+
+ /**
* Set the visible {@link Range}, optionally clearing data and/or firing a
* {@link RangeChangeEvent}.
*
@@ -505,10 +856,13 @@
* @param clearData true to clear all data
* @param forceRangeChangeEvent true to force a {@link RangeChangeEvent}
*/
- private void setVisibleRange(
- Range range, boolean clearData, boolean forceRangeChangeEvent) {
+ private void setVisibleRange(Range range, boolean clearData,
+ boolean forceRangeChangeEvent) {
final int start = range.getStart();
final int length = range.getLength();
+ if (length < 0) {
+ throw new IllegalArgumentException("Range length cannot be less than 1");
+ }
// Update the page start.
final boolean pageStartChanged = (pageStart != start);
@@ -578,8 +932,8 @@
*/
private boolean updateCachedData() {
boolean updated = false;
- int expectedLastIndex = Math.max(
- 0, Math.min(pageSize, rowCount - pageStart));
+ int expectedLastIndex = Math.max(0,
+ Math.min(pageSize, rowCount - pageStart));
int lastIndex = rowData.size() - 1;
while (lastIndex >= expectedLastIndex) {
rowData.remove(lastIndex);
@@ -626,8 +980,8 @@
children.next();
// Update the selection state.
- boolean selected = selectionModel == null
- ? false : selectionModel.isSelected(value);
+ boolean selected = selectionModel == null ? false
+ : selectionModel.isSelected(value);
if (selected != selectedRows.contains(row)) {
refreshRequired = true;
if (selected) {
diff --git a/user/src/com/google/gwt/user/cellview/client/HasKeyboardPagingPolicy.java b/user/src/com/google/gwt/user/cellview/client/HasKeyboardPagingPolicy.java
new file mode 100644
index 0000000..62d7b87
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/HasKeyboardPagingPolicy.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+/**
+ * Implemented by widgets that have a
+ * {@link HasKeyboardPagingPolicy.KeyboardPagingPolicy}.
+ */
+public interface HasKeyboardPagingPolicy extends HasKeyboardSelectionPolicy {
+
+ /**
+ * The policy that determines how keyboard paging will work.
+ */
+ enum KeyboardPagingPolicy {
+ /**
+ * Users cannot navigate past the current page.
+ */
+ CURRENT_PAGE(true),
+
+ /**
+ * Users can navigate between pages.
+ */
+ CHANGE_PAGE(false),
+
+ /**
+ * If the user navigates to the beginning or end of the current range, the
+ * range is increased.
+ */
+ INCREASE_RANGE(false);
+
+ private final boolean isLimitedToRange;
+
+ private KeyboardPagingPolicy(boolean isLimitedToRange) {
+ this.isLimitedToRange = isLimitedToRange;
+ }
+
+ boolean isLimitedToRange() {
+ return isLimitedToRange;
+ }
+ }
+
+ /**
+ * Get the {@link KeyboardPagingPolicy}.
+ */
+ KeyboardPagingPolicy getKeyboardPagingPolicy();
+
+ /**
+ * Set the {@link KeyboardPagingPolicy}.
+ */
+ void setKeyboardPagingPolicy(KeyboardPagingPolicy policy);
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/HasKeyboardSelectionPolicy.java b/user/src/com/google/gwt/user/cellview/client/HasKeyboardSelectionPolicy.java
new file mode 100644
index 0000000..23713bdb0
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/HasKeyboardSelectionPolicy.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * Implemented by widgets that have a
+ * {@link HasKeyboardSelectionPolicy.KeyboardSelectionPolicy}.
+ */
+public interface HasKeyboardSelectionPolicy {
+
+ /**
+ * The policy that determines how keyboard selection will work.
+ */
+ enum KeyboardSelectionPolicy {
+ /**
+ * Keyboard selection is disabled.
+ */
+ DISABLED,
+
+ /**
+ * Keyboard selection is enabled.
+ */
+ ENABLED,
+
+ /**
+ * Keyboard selection is bound to the
+ * {@link com.google.gwt.view.client.SelectionModel}.
+ */
+ BOUND_TO_SELECTION
+ }
+
+ /**
+ * Get the {@link KeyboardSelectionPolicy}.
+ */
+ KeyboardSelectionPolicy getKeyboardSelectionPolicy();
+
+ /**
+ * Set the {@link KeyboardSelectionPolicy}.
+ */
+ void setKeyboardSelectionPolicy(KeyboardSelectionPolicy policy);
+}
diff --git a/user/test/com/google/gwt/cell/client/ActionCellTest.java b/user/test/com/google/gwt/cell/client/ActionCellTest.java
index 1167994..b8cdfa7 100644
--- a/user/test/com/google/gwt/cell/client/ActionCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ActionCellTest.java
@@ -47,8 +47,8 @@
MockDelegate<String> delegate = new MockDelegate<String>();
ActionCell<String> cell = new ActionCell<String>("hello", delegate);
Element parent = Document.get().createDivElement();
- NativeEvent event = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
cell.onBrowserEvent(parent, "test", DEFAULT_KEY, event, null);
delegate.assertLastObject("test");
}
@@ -71,17 +71,16 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"click"};
+ return new String[]{"click", "keydown"};
}
@Override
protected String getExpectedInnerHtml() {
- return "<button>clickme</button>";
+ return "<button type=\"button\" tabindex=\"-1\">clickme</button>";
}
@Override
protected String getExpectedInnerHtmlNull() {
- // ActionCell always renders the same message.
- return "<button>clickme</button>";
+ return getExpectedInnerHtml();
}
}
diff --git a/user/test/com/google/gwt/cell/client/ButtonCellTest.java b/user/test/com/google/gwt/cell/client/ButtonCellTest.java
index e855df3..103d98f 100644
--- a/user/test/com/google/gwt/cell/client/ButtonCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ButtonCellTest.java
@@ -24,8 +24,8 @@
public class ButtonCellTest extends CellTestBase<String> {
public void testOnBrowserEvent() {
- NativeEvent event = Document.get().createMouseUpEvent(
- 0, 0, 0, 0, 0, false, false, false, false, NativeEvent.BUTTON_LEFT);
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
testOnBrowserEvent(getExpectedInnerHtml(), event, "clickme", "clickme");
}
@@ -46,16 +46,16 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"mouseup"};
+ return new String[]{"click", "keydown"};
}
@Override
protected String getExpectedInnerHtml() {
- return "<button>clickme</button>";
+ return "<button type=\"button\" tabindex=\"-1\">clickme</button>";
}
@Override
protected String getExpectedInnerHtmlNull() {
- return "<button></button>";
+ return "<button type=\"button\" tabindex=\"-1\"></button>";
}
}
diff --git a/user/test/com/google/gwt/cell/client/CellTestBase.java b/user/test/com/google/gwt/cell/client/CellTestBase.java
index c9b0cc7..28c6082 100644
--- a/user/test/com/google/gwt/cell/client/CellTestBase.java
+++ b/user/test/com/google/gwt/cell/client/CellTestBase.java
@@ -20,6 +20,9 @@
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
import java.util.Set;
@@ -41,8 +44,8 @@
private T lastEventValue;
private final T updateValue;
- public MockCell(
- boolean isSelectable, T updateValue, String... consumedEvents) {
+ public MockCell(boolean isSelectable, T updateValue,
+ String... consumedEvents) {
super(consumedEvents);
this.isSelectable = isSelectable;
this.updateValue = updateValue;
@@ -129,18 +132,6 @@
assertFalse(createCell().handlesSelection());
}
- public void testOnBrowserEventNullValueUpdater() {
- Cell<T> cell = createCell();
- T value = createCellValue();
- NativeEvent event = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
- Element parent = Document.get().createDivElement();
- parent.setInnerHTML(getExpectedInnerHtml());
-
- cell.onBrowserEvent(parent, value, DEFAULT_KEY, event, null);
- // Make sure that no exceptions occur.
- }
-
/**
* Test rendering the cell with a valid value and no view data.
*/
@@ -206,8 +197,9 @@
protected abstract String getExpectedInnerHtmlNull();
/**
- * Test {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent,
- * ValueUpdater)} with the specified conditions.
+ * Test
+ * {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent, ValueUpdater)}
+ * with the specified conditions.
*
* @param startHtml the innerHTML of the cell before the test starts
* @param event the event to fire
@@ -216,17 +208,39 @@
* null if none expected
* @return the parent element
*/
- protected Element testOnBrowserEvent(
- String startHtml, NativeEvent event, T value, T expectedValue) {
+ protected Element testOnBrowserEvent(String startHtml, NativeEvent event,
+ final T value, T expectedValue) {
// Setup the parent element.
- Element parent = Document.get().createDivElement();
+ final com.google.gwt.user.client.Element parent = Document.get().createDivElement().cast();
parent.setInnerHTML(startHtml);
+ Document.get().getBody().appendChild(parent);
+
+ // If the element has a child, use it as the event target.
+ Element child = parent.getFirstChildElement();
+ Element target = (child == null) ? parent : child;
// Pass the event to the cell.
- MockValueUpdater updater = new MockValueUpdater();
- createCell().onBrowserEvent(parent, value, DEFAULT_KEY, event, updater);
- updater.assertLastValue(expectedValue);
+ final MockValueUpdater valueUpdater = new MockValueUpdater();
+ Event.setEventListener(parent, new EventListener() {
+ public void onBrowserEvent(Event event) {
+ try {
+ DOM.setEventListener(parent, null);
+ createCell().onBrowserEvent(parent, value, DEFAULT_KEY, event,
+ valueUpdater);
+ parent.removeFromParent();
+ } catch (Exception e) {
+ // We are in an event loop, so events may not propagate out to JUnit.
+ fail("An exception occured while handling the event: "
+ + e.getMessage());
+ }
+ }
+ });
+ Event.sinkEvents(target, Event.getTypeInt(event.getType()));
+ target.dispatchEvent(event);
+ assertNull(DOM.getEventListener(parent));
+ // Check the expected value and view data.
+ valueUpdater.assertLastValue(expectedValue);
return parent;
}
}
diff --git a/user/test/com/google/gwt/cell/client/CheckboxCellTest.java b/user/test/com/google/gwt/cell/client/CheckboxCellTest.java
index 073e7ce..67e1434 100644
--- a/user/test/com/google/gwt/cell/client/CheckboxCellTest.java
+++ b/user/test/com/google/gwt/cell/client/CheckboxCellTest.java
@@ -31,14 +31,14 @@
public void testOnBrowserEventChecked() {
NativeEvent event = Document.get().createChangeEvent();
- testOnBrowserEvent("<input type=\"checkbox\" checked/>", event, false, null,
- Boolean.TRUE, true);
+ testOnBrowserEvent("<input type=\"checkbox\" checked/>", event, false,
+ null, Boolean.TRUE, true);
}
public void testOnBrowserEventUnchecked() {
NativeEvent event = Document.get().createChangeEvent();
- testOnBrowserEvent(
- "<input type=\"checkbox\"/>", event, true, null, Boolean.FALSE, false);
+ testOnBrowserEvent("<input type=\"checkbox\"/>", event, true, null,
+ Boolean.FALSE, false);
}
@Override
@@ -63,21 +63,21 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"change", "keyup"};
+ return new String[]{"change", "keydown"};
}
@Override
protected String getExpectedInnerHtml() {
- return "<input type=\"checkbox\" checked/>";
+ return "<input type=\"checkbox\" tabindex=\"-1\" checked/>";
}
@Override
protected String getExpectedInnerHtmlNull() {
- return "<input type=\"checkbox\"/>";
+ return "<input type=\"checkbox\" tabindex=\"-1\"/>";
}
@Override
protected String getExpectedInnerHtmlViewData() {
- return "<input type=\"checkbox\"/>";
+ return "<input type=\"checkbox\" tabindex=\"-1\"/>";
}
}
diff --git a/user/test/com/google/gwt/cell/client/ClickableTextCellTest.java b/user/test/com/google/gwt/cell/client/ClickableTextCellTest.java
index 9352cbf..0df32f3 100644
--- a/user/test/com/google/gwt/cell/client/ClickableTextCellTest.java
+++ b/user/test/com/google/gwt/cell/client/ClickableTextCellTest.java
@@ -24,8 +24,8 @@
public class ClickableTextCellTest extends CellTestBase<String> {
public void testOnBrowserEvent() {
- NativeEvent event = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
testOnBrowserEvent(getExpectedInnerHtml(), event, "value", "value");
}
@@ -46,7 +46,7 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"click"};
+ return new String[]{"click", "keydown"};
}
@Override
diff --git a/user/test/com/google/gwt/cell/client/CompositeCellTest.java b/user/test/com/google/gwt/cell/client/CompositeCellTest.java
index d5bed92..a56e6ef 100644
--- a/user/test/com/google/gwt/cell/client/CompositeCellTest.java
+++ b/user/test/com/google/gwt/cell/client/CompositeCellTest.java
@@ -86,7 +86,8 @@
* Fire an event to no cell in particular.
*/
public void testOnBrowserEventNoCell() {
- NativeEvent event = Document.get().createChangeEvent();
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
testOnBrowserEvent(getExpectedInnerHtml(), event, "test", null);
}
@@ -115,8 +116,8 @@
DOM.setEventListener(parent, listener);
// Fire the event on one of the inner cells.
- NativeEvent event = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
Element.as(parent.getChild(1)).dispatchEvent(event);
innerCell.assertLastEventValue("test-1");
@@ -177,8 +178,7 @@
List<HasCell<String, ?>> cells = new ArrayList<HasCell<String, ?>>();
for (int i = 0; i < count; i++) {
final int index = i;
- final MockCell<String> inner = new MockCell<String>(
- false, "fromCell" + i);
+ final MockCell<String> inner = new MockCell<String>(false, "fromCell" + i);
cells.add(new HasCell<String, String>() {
public Cell<String> getCell() {
return inner;
diff --git a/user/test/com/google/gwt/cell/client/DatePickerCellTest.java b/user/test/com/google/gwt/cell/client/DatePickerCellTest.java
index e16074f..06a85ef 100644
--- a/user/test/com/google/gwt/cell/client/DatePickerCellTest.java
+++ b/user/test/com/google/gwt/cell/client/DatePickerCellTest.java
@@ -50,7 +50,7 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"click"};
+ return new String[]{"click", "keydown"};
}
@Override
diff --git a/user/test/com/google/gwt/cell/client/EditTextCellTest.java b/user/test/com/google/gwt/cell/client/EditTextCellTest.java
index 5c213ed..8925a5d 100644
--- a/user/test/com/google/gwt/cell/client/EditTextCellTest.java
+++ b/user/test/com/google/gwt/cell/client/EditTextCellTest.java
@@ -201,6 +201,6 @@
@Override
protected String getExpectedInnerHtmlViewData() {
- return "<input type=\"text\" value=\"newValue\"></input>";
+ return "<input type=\"text\" value=\"newValue\" tabindex=\"-1\"></input>";
}
}
diff --git a/user/test/com/google/gwt/cell/client/EditableCellTestBase.java b/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
index cf576bd..1eddda7 100644
--- a/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
+++ b/user/test/com/google/gwt/cell/client/EditableCellTestBase.java
@@ -19,6 +19,9 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
/**
* Base class for testing {@link AbstractEditableCell}s that can be modified.
@@ -61,8 +64,9 @@
protected abstract String getExpectedInnerHtmlViewData();
/**
- * Test {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent,
- * ValueUpdater)} with the specified conditions.
+ * Test
+ * {@link Cell#onBrowserEvent(Element, Object, Object, NativeEvent, ValueUpdater)}
+ * with the specified conditions.
*
* @param startHtml the innerHTML of the cell before the test starts
* @param event the event to fire
@@ -74,16 +78,38 @@
* @return the parent element
*/
protected Element testOnBrowserEvent(String startHtml, NativeEvent event,
- T value, V viewData, T expectedValue, V expectedViewData) {
+ final T value, V viewData, T expectedValue, V expectedViewData) {
// Setup the parent element.
- Element parent = Document.get().createDivElement();
+ final com.google.gwt.user.client.Element parent = Document.get().createDivElement().cast();
parent.setInnerHTML(startHtml);
+ Document.get().getBody().appendChild(parent);
+
+ // If the element has a child, use it as the event target.
+ Element child = parent.getFirstChildElement();
+ Element target = (child == null) ? parent : child;
// Pass the event to the cell.
- MockValueUpdater valueUpdater = new MockValueUpdater();
- AbstractEditableCell<T, V> cell = createCell();
+ final MockValueUpdater valueUpdater = new MockValueUpdater();
+ final AbstractEditableCell<T, V> cell = createCell();
cell.setViewData(DEFAULT_KEY, viewData);
- cell.onBrowserEvent(parent, value, DEFAULT_KEY, event, valueUpdater);
+ Event.setEventListener(parent, new EventListener() {
+ public void onBrowserEvent(Event event) {
+ try {
+ DOM.setEventListener(parent, null);
+ cell.onBrowserEvent(parent, value, DEFAULT_KEY, event, valueUpdater);
+ parent.removeFromParent();
+ } catch (Exception e) {
+ // We are in an event loop, so events may not propagate out to JUnit.
+ fail("An exception occured while handling the event: "
+ + e.getMessage());
+ }
+ }
+ });
+ Event.sinkEvents(target, Event.getTypeInt(event.getType()));
+ target.dispatchEvent(event);
+ assertNull(DOM.getEventListener(parent));
+
+ // Check the expected value and view data.
assertEquals(expectedViewData, cell.getViewData(DEFAULT_KEY));
valueUpdater.assertLastValue(expectedValue);
diff --git a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
index eacbf3a..79c656a 100644
--- a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
+++ b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
@@ -42,15 +42,16 @@
* Verify that events are sent to the inner cell.
*/
public void testOnBrowserEvent() {
- NativeEvent event = Document.get().createChangeEvent();
- testOnBrowserEvent(
- getExpectedInnerHtml(), event, "helloworld", "newValueFromInnerCell");
+ NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false,
+ false, false, false);
+ testOnBrowserEvent(getExpectedInnerHtml(), event, "helloworld",
+ "newValueFromInnerCell");
}
public void testRenderNoImage() {
Images images = getImages();
- MockCell<String> innerCell = new MockCell<String>(
- true, "newValueFromInnerCell", "click");
+ MockCell<String> innerCell = new MockCell<String>(true,
+ "newValueFromInnerCell", "click");
IconCellDecorator<String> cell = new IconCellDecorator<String>(
images.prettyPiccy(), innerCell) {
@Override
@@ -65,16 +66,16 @@
// Compare the expected render string.
String expected = "<div style=\"position:relative;padding-left:64px;\">";
- expected += cell.getImageHtml(
- images.prettyPiccy(), HasVerticalAlignment.ALIGN_MIDDLE, true).asString();
+ expected += cell.getImageHtml(images.prettyPiccy(),
+ HasVerticalAlignment.ALIGN_MIDDLE, true).asString();
expected += "<div>helloworld</div>";
expected += "</div>";
assertEquals(expected, sb.toSafeHtml().asString());
}
public void testSelectableDelegate() {
- MockCell<String> innerCell = new MockCell<String>(
- true, "newValueFromInnerCell", "click");
+ MockCell<String> innerCell = new MockCell<String>(true,
+ "newValueFromInnerCell", "click");
IconCellDecorator<String> iconCell = new IconCellDecorator<String>(
getImages().prettyPiccy(), innerCell);
assertTrue(iconCell.dependsOnSelection());
@@ -94,8 +95,8 @@
@Override
protected IconCellDecorator<String> createCell() {
- MockCell<String> innerCell = new MockCell<String>(
- false, "newValueFromInnerCell", "click");
+ MockCell<String> innerCell = new MockCell<String>(false,
+ "newValueFromInnerCell", "click");
IconCellDecorator<String> iconCell = new IconCellDecorator<String>(
getImages().prettyPiccy(), innerCell);
return iconCell;
diff --git a/user/test/com/google/gwt/cell/client/SelectionCellTest.java b/user/test/com/google/gwt/cell/client/SelectionCellTest.java
index 010ac8e..941b5ea 100644
--- a/user/test/com/google/gwt/cell/client/SelectionCellTest.java
+++ b/user/test/com/google/gwt/cell/client/SelectionCellTest.java
@@ -58,26 +58,26 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"change"};
+ return new String[]{"change", "keydown", "focus", "blur"};
}
@Override
protected String getExpectedInnerHtml() {
- return "<select><option value=\"option 0\">option 0</option>"
+ return "<select tabindex=\"-1\"><option value=\"option 0\">option 0</option>"
+ "<option value=\"option 1\" selected=\"selected\">option 1</option>"
+ "<option value=\"option 2\">option 2</option></select>";
}
@Override
protected String getExpectedInnerHtmlNull() {
- return "<select><option value=\"option 0\">option 0</option>"
+ return "<select tabindex=\"-1\"><option value=\"option 0\">option 0</option>"
+ "<option value=\"option 1\">option 1</option>"
+ "<option value=\"option 2\">option 2</option></select>";
}
@Override
protected String getExpectedInnerHtmlViewData() {
- return "<select><option value=\"option 0\">option 0</option>"
+ return "<select tabindex=\"-1\"><option value=\"option 0\">option 0</option>"
+ "<option value=\"option 1\">option 1</option>"
+ "<option value=\"option 2\" selected=\"selected\">option 2</option></select>";
}
diff --git a/user/test/com/google/gwt/cell/client/TextInputCellTest.java b/user/test/com/google/gwt/cell/client/TextInputCellTest.java
index b5eee3a..bcaa2df 100644
--- a/user/test/com/google/gwt/cell/client/TextInputCellTest.java
+++ b/user/test/com/google/gwt/cell/client/TextInputCellTest.java
@@ -21,19 +21,25 @@
/**
* Tests for {@link TextInputCell}.
*/
-public class TextInputCellTest extends EditableCellTestBase<String, String> {
+public class TextInputCellTest extends
+ EditableCellTestBase<String, TextInputCell.ViewData> {
public void testOnBrowserEventChange() {
NativeEvent event = Document.get().createChangeEvent();
- testOnBrowserEvent(
- getExpectedInnerHtml(), event, "oldValue", null, "hello", "hello");
+ TextInputCell.ViewData expected = new TextInputCell.ViewData("oldValue");
+ expected.setLastValue("hello");
+ expected.setCurrentValue("hello");
+ testOnBrowserEvent(getExpectedInnerHtml(), event, "oldValue", null,
+ "hello", expected);
}
public void testOnBrowserEventKeyUp() {
- NativeEvent event = Document.get().createKeyUpEvent(
- false, false, false, false, 0);
- testOnBrowserEvent(
- getExpectedInnerHtml(), event, "oldValue", null, null, "hello");
+ NativeEvent event = Document.get().createKeyUpEvent(false, false, false,
+ false, 0);
+ TextInputCell.ViewData expected = new TextInputCell.ViewData("oldValue");
+ expected.setCurrentValue("hello");
+ testOnBrowserEvent(getExpectedInnerHtml(), event, "oldValue", null, null,
+ expected);
}
@Override
@@ -47,8 +53,8 @@
}
@Override
- protected String createCellViewData() {
- return "newValue";
+ protected TextInputCell.ViewData createCellViewData() {
+ return new TextInputCell.ViewData("newValue");
}
@Override
@@ -58,21 +64,21 @@
@Override
protected String[] getConsumedEvents() {
- return new String[]{"change", "keyup"};
+ return new String[]{"change", "keyup", "keydown", "focus", "blur"};
}
@Override
protected String getExpectedInnerHtml() {
- return "<input type=\"text\" value=\"hello\"></input>";
+ return "<input type=\"text\" value=\"hello\" tabindex=\"-1\"></input>";
}
@Override
protected String getExpectedInnerHtmlNull() {
- return "<input type=\"text\"></input>";
+ return "<input type=\"text\" tabindex=\"-1\"></input>";
}
@Override
protected String getExpectedInnerHtmlViewData() {
- return "<input type=\"text\" value=\"newValue\"></input>";
+ return "<input type=\"text\" value=\"newValue\" tabindex=\"-1\"></input>";
}
}
diff --git a/user/test/com/google/gwt/user/cellview/CellViewSuite.java b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
index 2fe796b..1608780 100644
--- a/user/test/com/google/gwt/user/cellview/CellViewSuite.java
+++ b/user/test/com/google/gwt/user/cellview/CellViewSuite.java
@@ -19,6 +19,8 @@
import com.google.gwt.user.cellview.client.AbstractPagerTest;
import com.google.gwt.user.cellview.client.AnimatedCellTreeTest;
import com.google.gwt.user.cellview.client.CellBrowserTest;
+import com.google.gwt.user.cellview.client.CellListTest;
+import com.google.gwt.user.cellview.client.CellTableTest;
import com.google.gwt.user.cellview.client.CellTreeTest;
import com.google.gwt.user.cellview.client.ColumnTest;
import com.google.gwt.user.cellview.client.HasDataPresenterTest;
@@ -32,12 +34,13 @@
*/
public class CellViewSuite {
public static Test suite() {
- GWTTestSuite suite = new GWTTestSuite(
- "Test suite for all cellview classes");
+ GWTTestSuite suite = new GWTTestSuite("Test suite for all cellview classes");
suite.addTestSuite(AbstractPagerTest.class);
suite.addTestSuite(AnimatedCellTreeTest.class);
suite.addTestSuite(CellBrowserTest.class);
+ suite.addTestSuite(CellListTest.class);
+ suite.addTestSuite(CellTableTest.class);
suite.addTestSuite(CellTreeTest.class);
suite.addTestSuite(ColumnTest.class);
suite.addTestSuite(HasDataPresenterTest.class);
diff --git a/user/test/com/google/gwt/user/cellview/client/AbstractHasDataTestBase.java b/user/test/com/google/gwt/user/cellview/client/AbstractHasDataTestBase.java
new file mode 100644
index 0000000..f71ead7
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/AbstractHasDataTestBase.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.cellview.client;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.view.client.ListDataProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base tests for {@link AbstractHasData}.
+ */
+public abstract class AbstractHasDataTestBase extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.cellview.CellView";
+ }
+
+ public void testGetDisplayedItem() {
+ AbstractHasData<String> display = createAbstractHasData();
+ ListDataProvider<String> provider = new ListDataProvider<String>(
+ createData(0, 13));
+ provider.addDataDisplay(display);
+ display.setVisibleRange(10, 10);
+
+ // No items when no data is present.
+ assertEquals("test 10", display.getDisplayedItem(0));
+ assertEquals("test 11", display.getDisplayedItem(1));
+ assertEquals("test 12", display.getDisplayedItem(2));
+
+ // Out of range.
+ try {
+ assertEquals("test 10", display.getDisplayedItem(-1));
+ fail("Expected IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException e) {
+ // Expected.
+ }
+
+ // Within page range, but out of data range.
+ try {
+ assertEquals("test 10", display.getDisplayedItem(4));
+ fail("Expected IndexOutOfBoundsException");
+ } catch (IndexOutOfBoundsException e) {
+ // Expected.
+ }
+ }
+
+ public void testGetDisplayedItems() {
+ AbstractHasData<String> display = createAbstractHasData();
+ ListDataProvider<String> provider = new ListDataProvider<String>();
+ provider.addDataDisplay(display);
+ display.setVisibleRange(10, 3);
+
+ // No items when no data is present.
+ assertEquals(0, display.getDisplayedItems().size());
+
+ // Set some data.
+ provider.setList(createData(0, 13));
+ List<String> items = display.getDisplayedItems();
+ assertEquals(3, items.size());
+ assertEquals("test 10", items.get(0));
+ assertEquals("test 11", items.get(1));
+ assertEquals("test 12", items.get(2));
+ }
+
+ public void testSetTabIndex() {
+ AbstractHasData<String> display = createAbstractHasData();
+ ListDataProvider<String> provider = new ListDataProvider<String>(
+ createData(0, 10));
+ provider.addDataDisplay(display);
+
+ // Default tab index is 0.
+ assertEquals(0, display.getTabIndex());
+ assertEquals(0, display.getKeyboardSelectedElement().getTabIndex());
+
+ // Set tab index to 2.
+ display.setTabIndex(2);
+ assertEquals(2, display.getTabIndex());
+ assertEquals(2, display.getKeyboardSelectedElement().getTabIndex());
+
+ // Push new data.
+ provider.refresh();
+ assertEquals(2, display.getTabIndex());
+ assertEquals(2, display.getKeyboardSelectedElement().getTabIndex());
+ }
+
+ /**
+ * Create an {@link AbstractHasData} to test.
+ *
+ * @return the widget to tezst
+ */
+ protected abstract AbstractHasData<String> createAbstractHasData();
+
+ /**
+ * Create a list of data for testing.
+ *
+ * @param start the start index
+ * @param length the length
+ * @return a list of data
+ */
+ protected List<String> createData(int start, int length) {
+ List<String> toRet = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ toRet.add("test " + (i + start));
+ }
+ return toRet;
+ }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowserOverride.css b/user/test/com/google/gwt/user/cellview/client/CellListTest.java
similarity index 64%
rename from user/src/com/google/gwt/user/cellview/client/CellBrowserOverride.css
rename to user/test/com/google/gwt/user/cellview/client/CellListTest.java
index 7f1f715..be56b60 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowserOverride.css
+++ b/user/test/com/google/gwt/user/cellview/client/CellListTest.java
@@ -1,35 +1,29 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
+package com.google.gwt.user.cellview.client;
-/*
- * The following overrides are used by CellBrowser to disable the styles used
- * in CellList.
+import com.google.gwt.cell.client.TextCell;
+
+/**
+ * Tests for {@link CellList}.
*/
-.cellListWidget {
-
-}
+public class CellListTest extends AbstractHasDataTestBase {
-.cellListEvenItem {
-
-}
-
-.cellListOddItem {
-
-}
-
-.cellListSelectedItem {
-
+ @Override
+ protected CellList<String> createAbstractHasData() {
+ return new CellList<String>(new TextCell());
+ }
}
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
new file mode 100644
index 0000000..ad5a790
--- /dev/null
+++ b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Tests for {@link CellTable}.
+ */
+public class CellTableTest extends AbstractHasDataTestBase {
+
+ @Override
+ protected CellTable<String> createAbstractHasData() {
+ CellTable<String> table = new CellTable<String>();
+ table.addColumn(new Column<String, String>(new TextCell()) {
+ @Override
+ public String getValue(String object) {
+ return object;
+ }
+ });
+ table.addColumn(new Column<String, String>(new TextCell()) {
+ @Override
+ public String getValue(String object) {
+ return object + "-2";
+ }
+ });
+ return table;
+ }
+}
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 1b92c2b..c96a0a7 100644
--- a/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/ColumnTest.java
@@ -15,9 +15,9 @@
*/
package com.google.gwt.user.cellview.client;
+import com.google.gwt.cell.client.AbstractEditableCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
-import com.google.gwt.cell.client.TextInputCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
@@ -32,6 +32,23 @@
public class ColumnTest extends GWTTestCase {
/**
+ * A mock cell used for testing.
+ */
+ private static class MockEditableCell extends
+ AbstractEditableCell<String, String> {
+
+ @Override
+ public boolean isEditing(Element parent, String value, Object key) {
+ return false;
+ }
+
+ @Override
+ public void render(String value, Object key, SafeHtmlBuilder sb) {
+ sb.appendEscaped(value);
+ }
+ }
+
+ /**
* A mock {@link FieldUpdater} used for testing.
*
* @param <T> the field type
@@ -80,9 +97,9 @@
*/
public void testDelayedValueUpdaer() {
final Element theElem = Document.get().createDivElement();
- final NativeEvent theEvent = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
- final TextInputCell cell = new TextInputCell() {
+ final NativeEvent theEvent = Document.get().createClickEvent(0, 0, 0, 0, 0,
+ false, false, false, false);
+ final MockEditableCell cell = new MockEditableCell() {
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, final ValueUpdater<String> valueUpdater) {
@@ -96,8 +113,7 @@
}
};
final Column<String, String> column = new IdentityColumn<String>(cell);
- final MockFieldUpdater<String, String> fieldUpdater = new MockFieldUpdater<
- String, String>() {
+ final MockFieldUpdater<String, String> fieldUpdater = new MockFieldUpdater<String, String>() {
@Override
public void update(int index, String object, String value) {
assertEquals("newViewData", cell.getViewData("test"));
@@ -121,9 +137,9 @@
public void testOnBrowserEventWithFieldUpdater() {
final Element theElem = Document.get().createDivElement();
- final NativeEvent theEvent = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
- final TextInputCell cell = new TextInputCell() {
+ final NativeEvent theEvent = Document.get().createClickEvent(0, 0, 0, 0, 0,
+ false, false, false, false);
+ final MockEditableCell cell = new MockEditableCell() {
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
@@ -137,8 +153,7 @@
}
};
final Column<String, String> column = new IdentityColumn<String>(cell);
- final MockFieldUpdater<String, String> fieldUpdater = new MockFieldUpdater<
- String, String>() {
+ final MockFieldUpdater<String, String> fieldUpdater = new MockFieldUpdater<String, String>() {
@Override
public void update(int index, String object, String value) {
// The new view data should already be set.
@@ -159,9 +174,9 @@
public void testOnBrowserEventWithoutFieldUpdater() {
final Element theElem = Document.get().createDivElement();
- final NativeEvent theEvent = Document.get().createClickEvent(
- 0, 0, 0, 0, 0, false, false, false, false);
- final TextInputCell cell = new TextInputCell() {
+ final NativeEvent theEvent = Document.get().createClickEvent(0, 0, 0, 0, 0,
+ false, false, false, false);
+ final MockEditableCell cell = new MockEditableCell() {
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
diff --git a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
index e6eea72..5a1c40f 100644
--- a/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java
@@ -24,6 +24,8 @@
import com.google.gwt.user.cellview.client.HasDataPresenter.ElementIterator;
import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
import com.google.gwt.user.cellview.client.HasDataPresenter.View;
+import com.google.gwt.user.cellview.client.HasKeyboardPagingPolicy.KeyboardPagingPolicy;
+import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.MockHasData;
import com.google.gwt.view.client.MockHasData.MockRangeChangeHandler;
@@ -99,6 +101,8 @@
private int childCount;
private boolean dependsOnSelection;
+ private List<Integer> keyboardSelectedRow = new ArrayList<Integer>();
+ private List<Boolean> keyboardSelectedRowState = new ArrayList<Boolean>();
private SafeHtml lastHtml;
private LoadingState loadingState;
private boolean onUpdateSelectionFired;
@@ -106,11 +110,31 @@
private boolean replaceChildrenCalled;
private Set<Integer> selectedRows = new HashSet<Integer>();
- public <H extends EventHandler> HandlerRegistration addHandler(
- H handler, Type<H> type) {
+ public <H extends EventHandler> HandlerRegistration addHandler(H handler,
+ Type<H> type) {
throw new UnsupportedOperationException();
}
+ /**
+ * Assert the value of the oldest keyboard selected row and pop it.
+ *
+ * @param row the row index
+ * @param selected true if selected, false if not
+ */
+ public void assertKeyboardSelectedRow(int row, boolean selected) {
+ int actualRow = keyboardSelectedRow.remove(0);
+ boolean actualSelected = keyboardSelectedRowState.remove(0);
+ assertEquals(row, actualRow);
+ assertEquals(selected, actualSelected);
+ }
+
+ /**
+ * Assert that the keyboard selected row queue is empty.
+ */
+ public void assertKeyboardSelectedRowEmpty() {
+ assertEquals(0, keyboardSelectedRow.size());
+ }
+
public void assertLastHtml(String html) {
if (html == null) {
assertNull(lastHtml);
@@ -148,8 +172,7 @@
public void assertSelectedRows(Integer... rows) {
assertEquals(rows.length, selectedRows.size());
for (Integer row : rows) {
- assertTrue(
- "Row " + row + "is not selected", selectedRows.contains(row));
+ assertTrue("Row " + row + "is not selected", selectedRows.contains(row));
}
}
@@ -194,6 +217,12 @@
this.dependsOnSelection = dependsOnSelection;
}
+ public void setKeyboardSelected(int index, boolean selected,
+ boolean stealFocus) {
+ keyboardSelectedRow.add(index);
+ keyboardSelectedRowState.add(selected);
+ }
+
public void setLoadingState(LoadingState state) {
this.loadingState = state;
}
@@ -215,8 +244,8 @@
public void testAddRowCountChangeHandler() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
MockRowCountChangeHandler handler = new MockRowCountChangeHandler();
// Adding a handler should not invoke the handler.
@@ -257,8 +286,8 @@
public void testAddRangeChangeHandler() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
MockRangeChangeHandler handler = new MockRangeChangeHandler();
// Adding a handler should not invoke the handler.
@@ -293,12 +322,12 @@
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
view.setDependsOnSelection(true);
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
assertNull(presenter.getSelectionModel());
// Initialize some data.
- presenter.setRowData(0, createData(0, 10));
+ populatePresenter(presenter);
view.assertReplaceAllChildrenCalled(true);
view.assertReplaceChildrenCalled(false);
view.assertLastHtml("start=0,size=10");
@@ -325,8 +354,8 @@
public void testGetCurrentPageSize() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
presenter.setRowCount(35, true);
// First page.
@@ -337,15 +366,281 @@
assertEquals(5, presenter.getCurrentPageSize());
}
+ public void testKeyboardNavigationChangePage() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setRowCount(100, true);
+ presenter.setVisibleRange(new Range(50, 10));
+ populatePresenter(presenter);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.CHANGE_PAGE);
+
+ // keyboardPrev in middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+ assertTrue(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, true);
+
+ // keyboardPrev at beginning.
+ assertTrue(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ assertEquals(new Range(40, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNext in middle.
+ presenter.setKeyboardSelectedRow(8, false);
+ view.assertKeyboardSelectedRow(9, false);
+ view.assertKeyboardSelectedRow(8, true);
+ assertTrue(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(8, false);
+ view.assertKeyboardSelectedRow(9, true);
+
+ // keyboardNext at end.
+ assertTrue(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(9, false);
+ assertEquals(new Range(50, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardPrevPage.
+ presenter.setKeyboardSelectedRow(5, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(5, true);
+ presenter.keyboardPrevPage();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(5, false);
+ assertEquals(new Range(40, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNextPage.
+ presenter.setKeyboardSelectedRow(5, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(5, true);
+ presenter.keyboardNextPage();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(5, false);
+ assertEquals(new Range(50, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardHome.
+ presenter.keyboardHome();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ assertEquals(new Range(0, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardPrev at first row.
+ assertFalse(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardEnd.
+ presenter.keyboardEnd();
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ assertEquals(new Range(90, 10), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNext at last row.
+ assertFalse(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ view.assertKeyboardSelectedRowEmpty();
+ }
+
+ public void testKeyboardNavigationCurrentPage() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(50, 10));
+ populatePresenter(presenter);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.CURRENT_PAGE);
+
+ // keyboardPrev in middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+ assertTrue(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, true);
+
+ // keyboardPrev at beginning.
+ assertFalse(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardNext in middle.
+ presenter.setKeyboardSelectedRow(8, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(8, true);
+ assertTrue(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(8, false);
+ view.assertKeyboardSelectedRow(9, true);
+
+ // keyboardNext at end.
+ assertFalse(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardPrevPage.
+ presenter.keyboardPrevPage();
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardNextPage.
+ presenter.keyboardNextPage();
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardHome.
+ presenter.keyboardHome();
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardEnd.
+ presenter.keyboardEnd();
+ view.assertKeyboardSelectedRowEmpty();
+ }
+
+ public void testKeyboardNavigationIncreaseRange() {
+ int pageStart = 150;
+ int pageSize = 10;
+ int increment = HasDataPresenter.PAGE_INCREMENT;
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setRowCount(300, true);
+ presenter.setVisibleRange(new Range(pageStart, pageSize));
+ populatePresenter(presenter);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE);
+
+ // keyboardPrev in middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+ assertTrue(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(0, true);
+
+ // keyboardPrev at beginning.
+ assertTrue(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ view.assertKeyboardSelectedRow(0, false);
+ pageStart -= increment;
+ pageSize += increment;
+ assertEquals(increment - 1, presenter.getKeyboardSelectedRow());
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNext in middle.
+ presenter.setKeyboardSelectedRow(pageSize - 2, false);
+ view.assertKeyboardSelectedRow(increment - 1, false);
+ view.assertKeyboardSelectedRow(pageSize - 2, true);
+ assertTrue(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ assertEquals(pageSize - 1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(pageSize - 2, false);
+ view.assertKeyboardSelectedRow(pageSize - 1, true);
+
+ // keyboardNext at end.
+ assertTrue(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ view.assertKeyboardSelectedRow(pageSize - 1, false);
+ pageSize += increment;
+ assertEquals(pageSize - increment, presenter.getKeyboardSelectedRow());
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardPrevPage within range.
+ presenter.setKeyboardSelectedRow(increment, false);
+ view.assertKeyboardSelectedRow(pageSize - increment, false);
+ view.assertKeyboardSelectedRow(increment, true);
+ presenter.keyboardPrevPage();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(increment, false);
+ view.assertKeyboardSelectedRow(0, true);
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+
+ // keyboardPrevPage outside range.
+ presenter.keyboardPrevPage();
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ pageStart -= increment;
+ pageSize += increment;
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNextPage inside range.
+ presenter.keyboardNextPage();
+ assertEquals(increment, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(increment, true);
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNextPage outside range.
+ presenter.setKeyboardSelectedRow(pageSize - 1, false);
+ view.assertKeyboardSelectedRow(increment, false);
+ view.assertKeyboardSelectedRow(pageSize - 1, true);
+ presenter.keyboardNextPage();
+ view.assertKeyboardSelectedRow(pageSize - 1, false);
+ pageSize += increment;
+ assertEquals(pageSize - 1, presenter.getKeyboardSelectedRow());
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardHome.
+ presenter.keyboardHome();
+ view.assertKeyboardSelectedRow(pageSize - 1, false);
+ pageSize += pageStart;
+ pageStart = 0;
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ assertEquals(new Range(pageStart, pageSize), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardPrev at first row.
+ assertFalse(presenter.hasKeyboardPrev());
+ presenter.keyboardPrev();
+ view.assertKeyboardSelectedRowEmpty();
+
+ // keyboardEnd.
+ presenter.keyboardEnd();
+ view.assertKeyboardSelectedRow(0, false);
+ assertEquals(299, presenter.getKeyboardSelectedRow());
+ assertEquals(new Range(0, 300), presenter.getVisibleRange());
+ populatePresenter(presenter);
+
+ // keyboardNext at last row.
+ assertFalse(presenter.hasKeyboardNext());
+ presenter.keyboardNext();
+ view.assertKeyboardSelectedRowEmpty();
+ }
+
public void testRedraw() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Initialize some data.
presenter.setRowCount(10, true);
- presenter.setRowData(0, createData(0, 10));
+ populatePresenter(presenter);
assertEquals(10, presenter.getRowData().size());
assertEquals("test 0", presenter.getRowData().get(0));
view.assertReplaceAllChildrenCalled(true);
@@ -361,11 +656,226 @@
view.assertLoadingState(LoadingState.LOADED);
}
+ public void testSetKeyboardSelectedRowBound() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(0, 10));
+ populatePresenter(presenter);
+
+ // The default is ENABLED.
+ assertEquals(KeyboardSelectionPolicy.ENABLED,
+ presenter.getKeyboardSelectionPolicy());
+
+ // Change to bound with paging.
+ presenter.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.BOUND_TO_SELECTION);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.CHANGE_PAGE);
+
+ // Add a selection model.
+ MockSelectionModel<String> model = new MockSelectionModel<String>(null);
+ presenter.setSelectionModel(model);
+ assertEquals(0, model.getSelectedSet().size());
+
+ // Select an element.
+ presenter.setKeyboardSelectedRow(5, false);
+ assertEquals(1, model.getSelectedSet().size());
+ assertTrue(model.isSelected("test 5"));
+
+ // Select another element.
+ presenter.setKeyboardSelectedRow(9, false);
+ assertEquals(1, model.getSelectedSet().size());
+ assertTrue(model.isSelected("test 9"));
+
+ // Select an element on another page.
+ presenter.setKeyboardSelectedRow(11, false);
+ // Nothing is selected yet because we don't have data.
+ assertEquals(0, model.getSelectedSet().size());
+ populatePresenter(presenter);
+ // Once data is pushed, the selection model should be populated.
+ assertEquals(1, model.getSelectedSet().size());
+ assertTrue(model.isSelected("test 11"));
+ }
+
+ public void testSetKeyboardSelectedRowChangePage() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(10, 10));
+ populatePresenter(presenter);
+
+ // Default policy is CHANGE_PAGE.
+ assertEquals(KeyboardPagingPolicy.CHANGE_PAGE,
+ presenter.getKeyboardPagingPolicy());
+
+ // Default to row 0.
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ // Move to middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to same row (should not early out).
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to last row.
+ presenter.setKeyboardSelectedRow(9, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(9, true);
+ assertEquals(10, presenter.getVisibleRange().getStart());
+ assertEquals(10, presenter.getVisibleRange().getLength());
+
+ // Move to next page.
+ presenter.setKeyboardSelectedRow(10, false);
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(9, false);
+ // Select is not fired because there is no row data yet.
+ view.assertKeyboardSelectedRowEmpty();
+ assertEquals(20, presenter.getVisibleRange().getStart());
+ assertEquals(10, presenter.getVisibleRange().getLength());
+ populatePresenter(presenter);
+
+ // Negative index.
+ presenter.setKeyboardSelectedRow(-1, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ // Select is not fired because there is no row data yet.
+ assertEquals(10, presenter.getVisibleRange().getStart());
+ assertEquals(10, presenter.getVisibleRange().getLength());
+ }
+
+ public void testSetKeyboardSelectedRowCurrentPage() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(10, 10));
+ populatePresenter(presenter);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.CURRENT_PAGE);
+
+ // Default to row 0.
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ // Negative index (should remain at index 0).
+ presenter.setKeyboardSelectedRow(-1, false);
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(0, true);
+
+ // Move to middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to same row (should not early out).
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to last row.
+ presenter.setKeyboardSelectedRow(9, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(9, true);
+
+ // Move to next page (confined to page).
+ presenter.setKeyboardSelectedRow(10, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(9, false);
+ view.assertKeyboardSelectedRow(9, true);
+ view.assertKeyboardSelectedRowEmpty();
+ assertEquals(10, presenter.getVisibleRange().getStart());
+ assertEquals(10, presenter.getVisibleRange().getLength());
+ }
+
+ public void testSetKeyboardSelectedRowDisabled() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(10, 10));
+ populatePresenter(presenter);
+ presenter.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
+
+ assertEquals(-1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(-1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+ }
+
+ public void testSetKeyboardSelectedRowIncreaseRange() {
+ HasData<String> listView = new MockHasData<String>();
+ MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
+ presenter.setVisibleRange(new Range(10, 10));
+ populatePresenter(presenter);
+ presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE);
+ int pageSize = presenter.getVisibleRange().getLength();
+
+ // Default to row 0.
+ assertEquals(0, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRowEmpty();
+
+ // Move to middle.
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(0, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to same row (should not early out).
+ presenter.setKeyboardSelectedRow(1, false);
+ assertEquals(1, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(1, true);
+
+ // Move to last row.
+ presenter.setKeyboardSelectedRow(9, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(1, false);
+ view.assertKeyboardSelectedRow(9, true);
+ assertEquals(10, presenter.getVisibleRange().getStart());
+ assertEquals(pageSize, presenter.getVisibleRange().getLength());
+
+ // Move to next page.
+ presenter.setKeyboardSelectedRow(10, false);
+ assertEquals(10, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(9, false);
+ // Select is not fired because there is no row data yet.
+ view.assertKeyboardSelectedRowEmpty();
+ assertEquals(10, presenter.getVisibleRange().getStart());
+ pageSize += HasDataPresenter.PAGE_INCREMENT;
+ assertEquals(pageSize, presenter.getVisibleRange().getLength());
+ populatePresenter(presenter);
+
+ // Negative index near index 0.
+ presenter.setKeyboardSelectedRow(-1, false);
+ assertEquals(9, presenter.getKeyboardSelectedRow());
+ view.assertKeyboardSelectedRow(10, false);
+ // Select is not fired because there is no row data yet.
+ assertEquals(0, presenter.getVisibleRange().getStart());
+ pageSize += 10;
+ assertEquals(pageSize, presenter.getVisibleRange().getLength());
+ }
+
public void testSetRowCount() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
view.assertLoadingState(null);
// Set size to 100.
@@ -384,8 +894,8 @@
public void testSetRowCountNoBoolean() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
try {
presenter.setRowCount(100);
@@ -398,15 +908,15 @@
public void testSetRowCountTrimsCurrentPage() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
view.assertLoadingState(null);
// Initialize some data.
presenter.setRowCount(10, true);
presenter.setVisibleRange(new Range(0, 10));
assertEquals(new Range(0, 10), presenter.getVisibleRange());
- presenter.setRowData(0, createData(0, 10));
+ populatePresenter(presenter);
assertEquals(10, presenter.getRowData().size());
assertEquals("test 0", presenter.getRowData().get(0));
view.assertReplaceAllChildrenCalled(true);
@@ -429,8 +939,8 @@
public void testSetRowData() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
presenter.setVisibleRange(new Range(5, 10));
view.assertLoadingState(LoadingState.LOADING);
@@ -499,8 +1009,8 @@
public void testSetRowValuesChangesDataSize() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Set the initial data size.
presenter.setRowCount(10, true);
@@ -523,8 +1033,8 @@
public void testSetRowValuesEmptySet() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Set the initial data size.
presenter.setRowCount(10, true);
@@ -540,8 +1050,8 @@
public void testSetRowValuesOutsideRange() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
presenter.setVisibleRange(new Range(5, 10));
view.assertLoadingState(LoadingState.LOADING);
@@ -582,8 +1092,8 @@
public void testSetRowValuesSameContents() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
view.assertLoadingState(null);
// Initialize some data.
@@ -608,8 +1118,8 @@
public void testSetRowValuesSparse() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
view.assertLoadingState(null);
List<String> expectedData = createData(5, 3);
@@ -631,8 +1141,8 @@
public void testSetVisibleRange() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Set the range the first time.
presenter.setVisibleRange(new Range(0, 100));
@@ -656,8 +1166,8 @@
public void testSetVisibleRangeAndClearDataDifferentRange() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Add a range change handler.
final List<Range> events = new ArrayList<Range>();
@@ -692,8 +1202,8 @@
public void testSetVisibleRangeAndClearDataSameRange() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Add a range change handler.
final List<Range> events = new ArrayList<Range>();
@@ -727,8 +1237,8 @@
public void testSetVisibleRangeAndClearDataSameRangeForceEvent() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Add a range change handler.
final List<Range> events = new ArrayList<Range>();
@@ -762,8 +1272,8 @@
public void testSetVisibleRangeDecreasePageSize() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Initialize some data.
presenter.setVisibleRange(new Range(0, 10));
@@ -790,8 +1300,8 @@
public void testSetVisibleRangeDecreasePageStart() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Initialize some data.
presenter.setVisibleRange(new Range(10, 30));
@@ -820,8 +1330,8 @@
public void testSetVisibleRangeIncreasePageSize() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Initialize some data.
presenter.setVisibleRange(new Range(0, 10));
@@ -848,8 +1358,8 @@
public void testSetVisibleRangeIncreasePageStart() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
// Initialize some data.
presenter.setVisibleRange(new Range(0, 20));
@@ -876,8 +1386,8 @@
public void testSetVisibleRangeInts() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
try {
presenter.setVisibleRange(0, 100);
@@ -894,13 +1404,13 @@
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
view.setDependsOnSelection(true);
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
assertNull(presenter.getSelectionModel());
// Initialize some data.
presenter.setVisibleRange(new Range(0, 10));
- presenter.setRowData(0, createData(0, 10));
+ populatePresenter(presenter);
view.assertReplaceAllChildrenCalled(true);
view.assertReplaceChildrenCalled(false);
view.assertLastHtml("start=0,size=10");
@@ -939,13 +1449,13 @@
public void testSetSelectionModelDoesNotDependOnSelection() {
HasData<String> listView = new MockHasData<String>();
MockView<String> view = new MockView<String>();
- HasDataPresenter<String> presenter = new HasDataPresenter<String>(
- listView, view, 10);
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+ view, 10, null);
assertNull(presenter.getSelectionModel());
// Initialize some data.
presenter.setVisibleRange(new Range(0, 10));
- presenter.setRowData(0, createData(0, 10));
+ populatePresenter(presenter);
view.assertReplaceAllChildrenCalled(true);
view.assertReplaceChildrenCalled(false);
view.assertLastHtml("start=0,size=10");
@@ -991,4 +1501,16 @@
}
return toRet;
}
+
+ /**
+ * Populate the entire range of a presenter.
+ *
+ * @param presenter the presenter
+ */
+ private void populatePresenter(HasDataPresenter<String> presenter) {
+ Range range = presenter.getVisibleRange();
+ int start = range.getStart();
+ int length = range.getLength();
+ presenter.setRowData(start, createData(start, length));
+ }
}