Handling onClick for a checkbox in a CellTable Header

anotherdjohnson picture anotherdjohnson · Jan 12, 2012 · Viewed 8.5k times · Source

I am trying to create a CellTable that has a column with some text and a checkbox, which will be used as a select all checkbox (see the drawing below, "cb" is checkbox). Currently I am using an class derived from Header and overriding it's render method to output the text and a checkbox. I am overriding onBrowserEvent() however it is only giving me onChange events, which would work fine except that the checkbox doesn't function correctly. Does anyone have any ideas on this?

+-------+------------+
| col 1 | Select All |
|       |     cb     | 
+-------+------------+
| row 1 |     cb     |
+-------+------------+

The issues I'm having with the checkbox is that when it's not checked, you have to click it twice for the checkmark to appear (at least on Chrome), even though it's "checked" property is true the first time. One click unchecks it correctly.

Here is some code:

Setup the CellTable columns:

/** Setup the table's columns. */
private void setupTableColumns() {
    // Add the first column:
    TextColumn<MyObject> column1 = new TextColumn<MyObject>() {
        @Override
        public String getValue(final MyObject object) {
            return object.getColumn1Text();
        }
    };
    table.addColumn(macColumn, SafeHtmlUtils.fromSafeConstant("Column1"));

    // the checkbox column for selecting the lease
    Column<MyObject, Boolean> checkColumn = new Column<MyObject, Boolean>(
            new CheckboxCell(true, false)) {
        @Override
        public Boolean getValue(final MyObject object) {
            return selectionModel.isSelected(object);
        }
    };

    SelectAllHeader selectAll = new SelectAllHeader();
    selectAll.setSelectAllHandler(new SelectHandler());
    table.addColumn(checkColumn, selectAll);
}

My Select All Header:

public static class SelectAllHeader extends Header<Boolean> {
    private final String checkboxID = "selectAllCheckbox";
    private ISelectAllHandler handler = null;

    @Override
    public void render(final Context context, final SafeHtmlBuilder sb) {
        String html = "<div>Select All<div><input type=\"checkbox\" id=\"" + checkboxID + "\"/>";

        sb.appendHtmlConstant(html);
    }

    private final Boolean allSelected;

    public SelectAllHeader() {
        super(new CheckboxCell());

        allSelected = false;
    }

    @Override
    public Boolean getValue() {
        Element checkboxElem = DOM.getElementById(checkboxID);

        return checkboxElem.getPropertyBoolean("checked");

    }

    @Override
    public void onBrowserEvent(final Context context, final Element element, final NativeEvent event) {
        Event evt = Event.as(event);
        int eventType = evt.getTypeInt();

        super.onBrowserEvent(context, element, event);

        switch (eventType) {
            case Event.ONCHANGE:
                handler.onSelectAllClicked(getValue());
                event.preventDefault();
                break;

            default:
                break;
        }

    }


    public void setSelectAllHandler(final ISelectAllHandler handler) {
        this.handler = handler;
    }

}

Answer

David Mason picture David Mason · Apr 18, 2012

It looks like you're rendering a non-checked checkbox whenever you render the header, which could be wiping out the selection state whenever the celltable re-renders.

Try storing the checked state and rendering the checkbox with the state. It looks like you're half way there with allSelected, you're just not using it.

EDIT Here is a working implementation I've just written for Zanata (see SearchResultsView.java). The HasValue interface is implemented so that value change events can be handled in a standard way. I have not overridden the render method, if you want to do so make sure you use getValue() to determine whether you render a checked or an unchecked checkbox. The selection/de-selection logic is handled in the associated presenter class (see SearchResultsPresenter.java).

private class CheckboxHeader extends Header<Boolean> implements HasValue<Boolean> {

   private boolean checked;
   private HandlerManager handlerManager;

   public CheckboxHeader()
   {
      //TODO consider custom cell with text
      super(new CheckboxCell());
      checked = false;
   }

   // This method is invoked to pass the value to the CheckboxCell's render method
   @Override
   public Boolean getValue()
   {
      return checked;
   }

   @Override
   public void onBrowserEvent(Context context, Element elem, NativeEvent nativeEvent)
   {
      int eventType = Event.as(nativeEvent).getTypeInt();
      if (eventType == Event.ONCHANGE)
      {
         nativeEvent.preventDefault();
         //use value setter to easily fire change event to handlers
         setValue(!checked, true);
      }
   }

   @Override
   public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler)
   {
      return ensureHandlerManager().addHandler(ValueChangeEvent.getType(), handler);
   }

   @Override
   public void fireEvent(GwtEvent<?> event)
   {
      ensureHandlerManager().fireEvent(event);
   }

   @Override
   public void setValue(Boolean value)
   {
      checked = value;
   }

   @Override
   public void setValue(Boolean value, boolean fireEvents)
   {
      checked = value;
      if (fireEvents)
      {
         ValueChangeEvent.fire(this, value);
      }
   }

   private HandlerManager ensureHandlerManager()
   {
      if (handlerManager == null)
      {
         handlerManager = new HandlerManager(this);
      }
      return handlerManager;
   }
}