      /**
       * The constants used in this Content Widget.
       */
      public static interface CwConstants extends Constants {
        String cwCellValidationColumnAddress();

        String cwCellValidationColumnName();

        String cwCellValidationDescription();

        String cwCellValidationError();

        String cwCellValidationName();
      }

      /**
       * An input cell that changes color based on the validation status.
       */
      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}.
       */
      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;
        }
      }

      /**
       * 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
       */
      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.
       */
      private final CwConstants constants;

      /**
       * Initialize this example.
       */
      @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;
      }