/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.sample.showcase.client.content.cell; 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; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.i18n.client.Constants; import com.google.gwt.safecss.shared.SafeStyles; import com.google.gwt.safecss.shared.SafeStylesUtils; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SimpleHtmlSanitizer; import com.google.gwt.sample.showcase.client.ContentWidget; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseData; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseRaw; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseSource; import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.ContactInfo; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Widget; /** * Example file. */ @ShowcaseRaw({"ContactDatabase.java"}) public class CwCellValidation extends ContentWidget { /** * The constants used in this Content Widget. */ @ShowcaseSource public static interface CwConstants extends Constants { String cwCellValidationColumnAddress(); String cwCellValidationColumnName(); String cwCellValidationDescription(); String cwCellValidationError(); String cwCellValidationName(); } interface Template extends SafeHtmlTemplates { @Template("<input type=\"text\" value=\"{0}\" style=\"{1}\" tabindex=\"-1\"/>") SafeHtml input(String value, SafeStyles color); } /** * An input cell that changes color based on the validation status. */ @ShowcaseSource private static class ValidatableInputCell extends AbstractInputCell<String, ValidationData> { private SafeHtml errorMessage; public ValidatableInputCell(String errorMessage) { super("change"); if (template == null) { template = GWT.create(Template.class); } this.errorMessage = SimpleHtmlSanitizer.sanitizeHtml(errorMessage); } @Override public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) { super.onBrowserEvent(context, parent, value, event, valueUpdater); // Ignore events that don't target the input. Element target = event.getEventTarget().cast(); if (!parent.getFirstChildElement().isOrHasChild(target)) { return; } Object key = context.getKey(); ValidationData viewData = getViewData(key); String eventType = event.getType(); if ("change".equals(eventType)) { InputElement input = parent.getFirstChild().cast(); // Mark cell as containing a pending change input.getStyle().setColor("blue"); // Save the new value in the view data. if (viewData == null) { viewData = new ValidationData(); setViewData(key, viewData); } String newValue = input.getValue(); viewData.setValue(newValue); finishEditing(parent, newValue, key, valueUpdater); // Update the value updater, which updates the field updater. if (valueUpdater != null) { valueUpdater.update(newValue); } } } @Override public void render(Context context, String value, SafeHtmlBuilder sb) { // Get the view data. Object key = context.getKey(); ValidationData viewData = getViewData(key); if (viewData != null && viewData.getValue().equals(value)) { // Clear the view data if the value is the same as the current value. clearViewData(key); viewData = null; } /* * If viewData is null, just paint the contents black. If it is non-null, * show the pending value and paint the contents red if they are known to * be invalid. */ String pendingValue = (viewData == null) ? null : viewData.getValue(); boolean invalid = (viewData == null) ? false : viewData.isInvalid(); String color = pendingValue != null ? (invalid ? "red" : "blue") : "black"; SafeStyles safeColor = SafeStylesUtils.fromTrustedString("color: " + color + ";"); sb.append(template.input(pendingValue != null ? pendingValue : value, safeColor)); if (invalid) { sb.appendHtmlConstant(" <span style='color:red;'>"); sb.append(errorMessage); sb.appendHtmlConstant("</span>"); } } @Override protected void onEnterKeyDown(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) { Element target = event.getEventTarget().cast(); if (getInputElement(parent).isOrHasChild(target)) { finishEditing(parent, value, context.getKey(), valueUpdater); } else { super.onEnterKeyDown(context, parent, value, event, valueUpdater); } } } /** * The ViewData used by {@link ValidatableInputCell}. */ @ShowcaseSource private static class ValidationData { private boolean invalid; private String value; public String getValue() { return value; } public boolean isInvalid() { return invalid; } public void setInvalid(boolean invalid) { this.invalid = invalid; } public void setValue(String value) { this.value = value; } } // Used by ValidatableInputCell private static Template template; /** * Checks if an address is valid. A valid address consists of a number * followed by a street name, which may be composed of multiple words. * * @param address the address * @return true if valid, false if invalid */ @ShowcaseSource public static boolean isAddressValid(String address) { // Cannot be null. if (address == null) { return false; } // Must have two or more parts. String[] parts = address.split(" "); if (parts.length < 2) { return false; } // First part is a number. try { Integer.parseInt(parts[0]); } catch (NumberFormatException e) { return false; } // The remaining parts form the street name. return true; } /** * An instance of the constants. */ @ShowcaseData private final CwConstants constants; /** * Constructor. * * @param constants the constants */ public CwCellValidation(CwConstants constants) { super(constants.cwCellValidationName(), constants.cwCellValidationDescription(), false, "ContactDatabase.java"); this.constants = constants; } /** * Initialize this example. */ @ShowcaseSource @Override public Widget onInitialize() { // Create a table. final CellTable<ContactInfo> table = new CellTable<ContactInfo>(10, ContactInfo.KEY_PROVIDER); // Add the Name column. table.addColumn(new Column<ContactInfo, String>(new TextCell()) { @Override public String getValue(ContactInfo object) { return object.getFullName(); } }, constants.cwCellValidationColumnName()); // Add an editable address column. final ValidatableInputCell addressCell = new ValidatableInputCell( constants.cwCellValidationError()); Column<ContactInfo, String> addressColumn = new Column<ContactInfo, String>( addressCell) { @Override public String getValue(ContactInfo object) { return object.getAddress(); } }; table.addColumn(addressColumn, constants.cwCellValidationColumnAddress()); addressColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() { public void update(int index, final ContactInfo object, final String value) { // Perform validation after 2 seconds to simulate network delay. new Timer() { @Override public void run() { if (isAddressValid(value)) { // The cell will clear the view data when it sees the updated // value. object.setAddress(value); // Push the change to the views. ContactDatabase.get().refreshDisplays(); } else { // Update the view data to mark the pending value as invalid. ValidationData viewData = addressCell.getViewData(ContactInfo.KEY_PROVIDER.getKey(object)); viewData.setInvalid(true); // We only modified the cell, so do a local redraw. table.redraw(); } } }.schedule(1000); } }); // Add the table to the database. ContactDatabase.get().addDataDisplay(table); return table; } @Override protected void asyncOnInitialize(final AsyncCallback<Widget> callback) { GWT.runAsync(CwCellValidation.class, new RunAsyncCallback() { public void onFailure(Throwable caught) { callback.onFailure(caught); } public void onSuccess() { callback.onSuccess(onInitialize()); } }); } }