blob: 8ec75af9943819d653ca35f98545732c54ee6155 [file] [log] [blame]
/*
* 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.EventTarget;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
/**
* An editable text cell. Click to edit, escape to cancel, return to commit.
*
* <p>
* Note: This class is new and its interface subject to change.
* </p>
*
* Important TODO: This cell still treats its value as HTML for rendering
* 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> {
/**
* The view data object used by this cell. We need to store both the text and
* the state because this cell is rendered differently in edit mode. If we did
* not store the edit state, refreshing the cell with view data would always
* put us in to edit state, rendering a text box instead of the new text
* string.
*/
static class ViewData {
/**
* Keep track of the original value at the start of the edit, which might be
* the edited value from the previous edit and NOT the actual value.
*/
private String original;
private String text;
private boolean isEditing;
/**
* If true, this is not the first edit.
*/
private boolean isEditingAgain;
/**
* Construct a new ViewData in editing mode.
*
* @param text the text to edit
*/
public ViewData(String text) {
this.original = text;
this.text = text;
this.isEditing = true;
this.isEditingAgain = false;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
ViewData vd = (ViewData) o;
return equalsOrBothNull(original, vd.original)
&& equalsOrBothNull(text, vd.text) && isEditing == vd.isEditing
&& isEditingAgain == vd.isEditingAgain;
}
public String getOriginal() {
return original;
}
public String getText() {
return text;
}
@Override
public int hashCode() {
return original.hashCode() + text.hashCode()
+ Boolean.valueOf(isEditing).hashCode() * 29
+ Boolean.valueOf(isEditingAgain).hashCode();
}
public boolean isEditing() {
return isEditing;
}
public boolean isEditingAgain() {
return isEditingAgain;
}
public void setEditing(boolean isEditing) {
boolean wasEditing = this.isEditing;
this.isEditing = isEditing;
// This is a subsequent edit, so start from where we left off.
if (!wasEditing && isEditing) {
isEditingAgain = true;
original = text;
}
}
public void setText(String text) {
this.text = text;
}
private boolean equalsOrBothNull(Object o1, Object o2) {
return (o1 == null) ? o2 == null : o1.equals(o2);
}
}
public EditTextCell() {
super("click", "keyup", "keydown", "blur");
}
@Override
public boolean isEditing(Element element, String value, Object key) {
ViewData viewData = getViewData(key);
return viewData == null ? false : viewData.isEditing();
}
@Override
public void onBrowserEvent(Element parent, String value, Object key,
NativeEvent event, ValueUpdater<String> valueUpdater) {
ViewData viewData = getViewData(key);
if (viewData != null && viewData.isEditing()) {
// Handle the edit event.
editEvent(parent, key, viewData, event, valueUpdater);
} else {
String type = event.getType();
int keyCode = event.getKeyCode();
boolean enterPressed = "keyup".equals(type) &&
keyCode == KeyCodes.KEY_ENTER;
if ("click".equals(type) || enterPressed) {
// Go into edit mode.
if (viewData == null) {
viewData = new ViewData(value);
setViewData(key, viewData);
} else {
viewData.setEditing(true);
}
edit(parent, value, key);
}
}
}
@Override
public void render(String value, Object key, StringBuilder sb) {
// Get the view data.
ViewData viewData = getViewData(key);
if (viewData != null && !viewData.isEditing() && value != null
&& value.equals(viewData.getText())) {
clearViewData(key);
viewData = null;
}
if (viewData != null) {
if (viewData.isEditing()) {
sb.append(
"<input type='text' value='" + viewData.getText() + "'></input>");
} else {
// The user pressed enter, but view data still exists.
sb.append(viewData.getText());
}
} else if (value != null) {
sb.append(value);
}
}
/**
* Convert the cell to edit mode.
*
* @param parent the parent element
* @param value the current value
* @param key the key of the row object
*/
protected void edit(Element parent, String value, Object key) {
setValue(parent, value, key);
InputElement input = (InputElement) parent.getFirstChild();
input.focus();
input.select();
}
/**
* Convert the cell to non-edit mode.
*
* @param parent the parent element
* @param value the current value
*/
private void cancel(Element parent, String value) {
setValue(parent, value, null);
}
/**
* 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) {
String value = updateViewData(parent, viewData, false);
setValue(parent, value, viewData);
valueUpdater.update(value);
}
private void editEvent(Element parent, Object key, ViewData viewData,
NativeEvent event, ValueUpdater<String> valueUpdater) {
String type = event.getType();
boolean keyUp = "keyup".equals(type);
boolean keyDown = "keydown".equals(type);
if (keyUp || keyDown) {
int keyCode = event.getKeyCode();
if (keyUp && keyCode == KeyCodes.KEY_ENTER) {
// Commit the change.
commit(parent, viewData, valueUpdater);
} else if (keyUp && keyCode == KeyCodes.KEY_ESCAPE) {
// Cancel edit mode.
String originalText = viewData.getOriginal();
if (viewData.isEditingAgain()) {
viewData.setText(originalText);
viewData.setEditing(false);
} else {
setViewData(key, null);
}
cancel(parent, originalText);
} else {
// Update the text in the view data on each key.
updateViewData(parent, viewData, true);
}
} else if ("blur".equals(type)) {
// Commit the change. Ensure that we are blurring the input element and
// not the parent element itself.
EventTarget eventTarget = event.getEventTarget();
if (Element.is(eventTarget)) {
Element target = Element.as(eventTarget);
if ("input".equals(target.getTagName().toLowerCase())) {
commit(parent, viewData, valueUpdater);
}
}
}
}
/**
* Update the view data based on the current value.
*
* @param parent the parent element
* @param viewData the {@link ViewData} object to update
* @param isEditing true if in edit mode
* @return the new value
*/
private String updateViewData(
Element parent, ViewData viewData, boolean isEditing) {
InputElement input = (InputElement) parent.getFirstChild();
String value = input.getValue();
viewData.setText(value);
viewData.setEditing(isEditing);
return value;
}
}