title: GWT BrowserEvent, Internet Explorer, and dynamically applied inline style on Input or Select
body: <p>I'm using GWT 2.5.0.</p>
  
  <p>I've impl'd a custom abstract base widget for client-side validation feedback.  My question is related to dynamic inline styling of <code>&lt;select/&gt;</code> and <code>&lt;input type="text"/&gt;</code> widgets, particularly in Internet Explorer.</p>
  
  <p>I cannot get the render to do its job properly and consistently on Internet Explorer 8/9.  For other browsers, like Safari, Chrome, Opera, Firefox, my impl functions just the way I want it to.</p>
  
  <p>Basically, for pending, valid input, I want the background of the aforementioned widgets to be yellow, and the foreground color (text) to be blue.</p>
  
  <p>I am using Tips and Tricks mentioned here: <a href="https://developers.google.com/web-toolkit/doc/latest/DevGuideIE9" rel="nofollow noreferrer">https://developers.google.com/web-toolkit/doc/latest/DevGuideIE9</a>.</p>
  
  <p>I think the basic problem is that I'm running afoul of IE not supporting "event bubbling".
  See <a href="https://stackoverflow.com/questions/13452172/change-browser-event-in-gwt">&quot;Change&quot; browser event in GWT</a>.  But I do not know how to address it.</p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>It appears my implementation may be held hostage until </p>
  
  <pre><code>http://code.google.com/p/google-web-toolkit/issues/detail?id=7139 
  </code></pre>
  
  <p>is fixed. </p>
  
  <p>Here's my base abstract class. Note the user agent "hack" for Internet Explorer in the <code>onBrowserEvent</code> and <code>render</code> methods. (If there's some other way, please let me know).</p>
  
  <pre><code>public abstract class AbstractValidatableInputCell&lt;E extends Element&gt; extends AbstractInputCell&lt;String, ValidationData&gt; {
  
  /**
   * The error message text, pop-up, callback, and position offsets -- error message will be displayed near a field that violated constraint.
   */
  private String errorMessage;
  
  private DecoratedPopupPanel errorMessagePopup;
  private PopupPanel.PositionCallback popupPositionCallback;
  private int popupPositionLeft;
  private int popupPositionTop;
  
  
  /**
   * Constructs a ValidatableInputCell that renders its text without HTML
   * markup.
   */
  public AbstractValidatableInputCell(String... consumedEvents) {
      super(consumedEvents);
  }
  
  
  public void setErrorMessage(final String errorMessage) {
      this.errorMessage = SafeHtmlUtils.fromSafeConstant(errorMessage).asString();
  }
  
  @Override
  public void onBrowserEvent(final Context context, final Element parent, final String value,
          final NativeEvent event, final ValueUpdater&lt;String&gt; valueUpdater) {
      super.onBrowserEvent(context, parent, value, event, valueUpdater);
  
      // Ignore events that don't target the input.
      final E input = getInputElement(parent);
      final Element target = event.getEventTarget().cast();
      if (!input.isOrHasChild(target)) {
          return;
      }
  
      final String eventType = event.getType();
      final Object key = context.getKey();
      ValidationData vd = getViewData(key);
      final boolean invalid = vd == null ? false : vd.isInvalid();
  
  
      if (BrowserEvents.CHANGE.equals(eventType)) {
          finishEditing(parent, value, key, valueUpdater);
  
      } else if (Window.Navigator.getUserAgent().contains("MSIE")
              &amp;&amp; (event.getKeyCode() == KeyCodes.KEY_TAB)) { 
          finishEditing(parent, value, key, valueUpdater);
          getInputElement(parent).focus();
  
      } else if (BrowserEvents.KEYUP.equals(eventType)) {
          // Record keys as they are typed.
          if (vd == null) {
              vd = new ValidationData(value);
              setViewData(key, vd);
          }
          vd.setCurrentValue(getValue(input));
  
      } else if (BrowserEvents.MOUSEOVER.equals(eventType)) {
          if (invalid) {
              showErrorMessagePopup(parent);
          }
  
      } else if (BrowserEvents.MOUSEOUT.equals(eventType)) {
          if (invalid) {
              hideErrorMessagePopup();
          }
      }
  }
  
  protected void onFinishEditing(final String value, final E input, final Object key) {
      /*
       * 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.
       */
      final ValidationData viewData = getViewData(key);
      final String pendingValue = viewData == null ? null : viewData.getCurrentValue();
      final boolean invalid = viewData == null ? false : viewData.isInvalid();
  
      String color;
      String backgroundColor;
  
      if (invalid) {
          color = App.INSTANCE.invalidCellInputTextColor();
          backgroundColor = App.INSTANCE.invalidCellInputTextBackgroundColor();
      } else if (pendingValue != null &amp;&amp; !pendingValue.equals(value)) {
          color = App.INSTANCE.pendingCellInputTextColor();
          backgroundColor = App.INSTANCE.pendingCellInputTextBackgroundColor();
      } else {
          color = App.INSTANCE.defaultCellInputTextColor();
          backgroundColor = App.INSTANCE.defaultCellInputTextBackgroundColor();
      }
  
      // Mark cell as containing a pending change
      input.getStyle().setColor(color);
      input.getStyle().setBackgroundColor(backgroundColor);
  }
  
  private void showErrorMessagePopup(final Element parent) {
      errorMessagePopup = new DecoratedPopupPanel(true);
      final FlowPanel messageContainer = new FlowPanel();
      messageContainer.setWidth(App.INSTANCE.errorMessagePopupWidth());
      final Label messageTxt = new Label(errorMessage, true);
      messageTxt.setStyleName(UiResources.INSTANCE.style().error());
      messageContainer.add(messageTxt);
      errorMessagePopup.setWidget(messageContainer);
  
      final Node child = parent.getChild(0);
      final Element childElement = (Element) child;
  
      // Reposition the popup relative to input field
      popupPositionLeft = childElement.getAbsoluteLeft() - 15;
      popupPositionTop = childElement.getAbsoluteBottom() + 5;
  
      popupPositionCallback = new PopupPanel.PositionCallback() {
  
          @Override
          public void setPosition(final int offsetWidth, final int offsetHeight) {
              errorMessagePopup.setPopupPosition(popupPositionLeft, popupPositionTop);
          }
      };
      errorMessagePopup.setPopupPositionAndShow(popupPositionCallback);
  }
  
  private void hideErrorMessagePopup() {
      errorMessagePopup.hide();
  }
  
  @Override
  public void render(final Context context, final String value, final SafeHtmlBuilder sb) {
      // Get the view data.
      final Object key = context.getKey();
      final ValidationData viewData = getViewData(key);
  
      final String pendingValue = viewData == null ? null : viewData.getCurrentValue();
      final boolean invalid = viewData == null ? false : viewData.isInvalid();
  
      String inputValue = value;
      if (pendingValue != null) {
          inputValue = pendingValue;
      }
  
      String color;
      String backgroundColor;
  
      if (invalid) {
          color = App.INSTANCE.invalidCellInputTextColor();
          backgroundColor = App.INSTANCE.invalidCellInputTextBackgroundColor();
      } else if (pendingValue != null) {
          color = App.INSTANCE.pendingCellInputTextColor();
          backgroundColor = App.INSTANCE.pendingCellInputTextBackgroundColor();
      } else {
          color = App.INSTANCE.defaultCellInputTextColor();
          backgroundColor = App.INSTANCE.defaultCellInputTextBackgroundColor();
      }
  
      // shame on you IE!
      if (Window.Navigator.getUserAgent().contains("MSIE") &amp;&amp; viewData != null &amp;&amp; !viewData.isPended() &amp;&amp; !invalid) {
          color = App.INSTANCE.defaultCellInputTextColor();
          backgroundColor = App.INSTANCE.defaultCellInputTextBackgroundColor();
      }
  
      finishRender(inputValue, color, backgroundColor, sb);
  }
  
  protected abstract void finishRender(final String inputValue, final String color, final String backgroundColor, final SafeHtmlBuilder sb);
  
  @Override
  protected void finishEditing(final Element parent, final String value, final Object key,
          final ValueUpdater&lt;String&gt; valueUpdater) {
      final String newValue = getNewValue(parent);
  
      // Get the view data.
      ValidationData vd = getViewData(key);
      if (vd == null) {
          vd = new ValidationData(value);
          setViewData(key, vd);
      }
      vd.setCurrentValue(newValue);
  
      // Fire the value updater if the value has changed.
      if (valueUpdater != null &amp;&amp; !vd.getCurrentValue().equals(vd.getLastValue())) {
          vd.setLastValue(newValue);
          valueUpdater.update(newValue);
      }
  
      onFinishEditing(value, getInputElement(parent), key);
  
      // Blur the element.
      super.finishEditing(parent, newValue, key, valueUpdater);
  }
  
  @Override
  protected E getInputElement(final Element parent) {
      return super.getInputElement(parent).&lt;E&gt; cast();
  }
  
  protected abstract String getValue(E element);
  
  protected abstract String getNewValue(Element parent);
  }
  </code></pre>
  
  <p>Here's a sample sub-class (for <code>input</code>):</p>
  
  <pre><code>public class ValidatableInputCell extends AbstractValidatableInputCell&lt;InputElement&gt; {
  
  interface Template extends SafeHtmlTemplates {
  
      @Template("&lt;input type=\"text\" value=\"{0}\" size=\"{1}\" style=\"color: {2}; background-color: {3}; display: block; text-align: right\"&gt;&lt;/input&gt;")
      SafeHtml input(String value, String width, String color, String backgroundColor);
  }
  
  private static Template template;
  
  private static final int DEFAULT_INPUT_SIZE = App.INSTANCE.defaultValidatableInputCellSize();
  
  /**
   * Specifies the width, in characters, of the &amp;lt;input&amp;gt; element contained within this cell
   */
  private int inputSize = DEFAULT_INPUT_SIZE;
  
  /**
   * Constructs a ValidatableInputCell that renders its text without HTML
   * markup.
   */
  public ValidatableInputCell() {
      // Since onBrowserEvent method is overridden, we must register all
      // events that handled in overridden method impl
      // Events below are added in
      // AbstractInputCell#getConsumedEventsImpl(Set&lt;String&gt; userEvents)
      super(BrowserEvents.CHANGE, BrowserEvents.KEYUP, BrowserEvents.MOUSEOVER, BrowserEvents.MOUSEOUT);
      if (template == null) {
          template = GWT.create(Template.class);
      }
  }
  
  public void setInputSize(final int inputSize) {
      this.inputSize = inputSize;
  }
  
  
  @Override
  public void finishRender(final String inputValue, final String color, final String backgroundColor, final SafeHtmlBuilder sb) {
      sb.append(template.input(inputValue, String.valueOf(inputSize), color, backgroundColor));
  }
  
  @Override
  protected String getValue(InputElement element) {
      return element.getValue();
  }
  
  @Override
  protected String getNewValue(Element parent) {
      return getValue(getInputElement(parent));
  }
  
  
  }
  </code></pre>
  
  <p>Here's another sample sub-class (for <code>select</code>):</p>
  
  <pre><code>public class ReferenceDataBackedSelectionCell extends AbstractValidatableInputCell&lt;SelectElement&gt; {
  
  interface SelectTemplate extends SafeHtmlTemplates {
      @Template("&lt;select style=\"color: {0}; background-color: {1};\"&gt;")
      SafeHtml select(String color, String backgroundColor);
  }
  
  interface OptionTemplate extends SafeHtmlTemplates {
  
      @Template("&lt;option style=\"color: {2}; background-color: {3}; text-align: right\" value=\"{0}\"&gt;{1}&lt;/option&gt;")
      SafeHtml deselected(String optionSubmitValue, String optionDisplayValue, String color, String backgroundColor);
  
      @Template("&lt;option style=\"color: {2}; background-color: {3}; text-align: right\" value=\"{0}\" selected=\"selected\"&gt;{1}&lt;/option&gt;")
      SafeHtml selected(String optionSubmitValue, String optionDisplayValue, String color, String backgroundColor);
  }
  
  private static SelectTemplate selectTemplate; 
  private static OptionTemplate optionTemplate;
  
  private final Map&lt;String, String&gt; options;
  
  /**
   * Construct a new {@link ReferenceDataBackedSelectionCell} from
   * {@link ReferenceData}.
   * 
   * @param refData data used to create the options in the cell
   */
  public ReferenceDataBackedSelectionCell(final ReferenceData refData) {
      super(BrowserEvents.CHANGE);
      if (optionTemplate == null) {
          optionTemplate = GWT.create(OptionTemplate.class);
      }
      if (selectTemplate == null) {
          selectTemplate = GWT.create(SelectTemplate.class);
      }
      if (refData == null) {
          throw new IllegalArgumentException("Reference data for selection cell may not be null");
      }
      options = new LinkedHashMap&lt;String, String&gt;(refData.allData());
  }
  
  @Override
  public void finishRender(final String inputValue, final String color, final String backgroundColor, final SafeHtmlBuilder sb) {
      final String selectedIndex = inputValue == null ? "" : inputValue;
      sb.append(selectTemplate.select(color, backgroundColor));
      String optionSubmitValue, optionDisplayValue = null;
      for (final Map.Entry&lt;String, String&gt; option : options.entrySet()) {
          optionSubmitValue = option.getValue();
          optionDisplayValue = option.getKey();
          if (optionSubmitValue.equals(selectedIndex)) {
              sb.append(optionTemplate.selected(optionSubmitValue, optionDisplayValue, color, backgroundColor));
          } else {
              sb.append(optionTemplate.deselected(optionSubmitValue, optionDisplayValue, color, backgroundColor));
          }
      }
      sb.appendHtmlConstant("&lt;/select&gt;");
  }
  
  @Override
  protected String getValue(SelectElement element) {
      return element.getValue();
  }
  
  @Override
  protected String getNewValue(Element parent) {
      final SelectElement select = parent.getFirstChild().cast();
      final Collection&lt;String&gt; optionCollection = options.values();
      final String[] array = optionCollection.toArray(new String[optionCollection.size()]);
      final String newValue = array[select.getSelectedIndex()];
      return newValue;
  }
  } 
  </code></pre>