GWT Header CheckBox requires two clicks to fire setValue, after changing its value programatically - gwt

I have a GWT DataGrid, and a CheckBox in the Header to select/deselect all rows in the grid.
The code for the CheckBox Header is as follows:
private class CheckboxHeader extends Header<Boolean> implements HasValue<Boolean> {
private boolean checked;
private HandlerManager handlerManager;
/**
* An html string representation of a checked input box.
*/
private final SafeHtml INPUT_CHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\" checked/>");
/**
* An html string representation of an unchecked input box.
*/
private final SafeHtml INPUT_UNCHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\"/>");
#Override
public void render(Context context, SafeHtmlBuilder sb) {
if (Boolean.TRUE.equals(this.getValue())) {
sb.append(INPUT_CHECKED);
} else {
sb.append(INPUT_UNCHECKED);
}
};
public CheckboxHeader() {
super(new CheckboxCell(true, false));
checked = true;
}
// 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) {
setValue(value, true);
}
#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;
}
}
So, I add the Header to the grid, and I add a ValueChangeHandler to it to do the actual selecting/deselecting of individual CheckBox cells in every row of the grid. This all works.
Every CheckBoxCell has a Field Updater, and on every update it loops through every item in the grid to see if they are all checked, and update the header check box. If at least one is unchecked, the header checkbox will be unchecked. I call setValue() on the header check box, and after that I call redrawHeaders() on the entire grid. This also works.
What doesn't work is - after changing the "state" of the header check box programatically, it takes two clicks for it to fire it's internal setValue again, and therefore trigger my handler. And what's even funnier - the first click does change the state of the check box, but it just doesn't fire the event.
Any help would be appreciated.

How are you constructing the CheckboxCells themselves? I ran into a similar issue with a column of checkboxes "eating" clicks, and the solution was to call CheckboxCell cell = new CheckboxCell(true,true) and then pass that cell into the constructor of the column.

Related

In GWT CellTable, how can I make a cell consume a click event so it wont reach the row?

I have a CellTable with a few rows for which I use MultiSelectionModel. The first column is a column of checkbox cells.
When the user checks one of the checkboxes the row becomes selected (as per the example in the site) and when the user clicks a row a function "doActionOnRow()" is called.
My problem is that when the user checks a checkbox the "doActionOnRow()" is also called. How can I make the CheckboxCell consume the click event so it wont be triggered on the celltable as well ?
EDIT:
This is my code:
table.addCellPreviewHandler(new Handler<Patient>() {
#Override
public void onCellPreview(CellPreviewEvent<Patient> event) {
boolean isClick = "click".equals(event.getNativeEvent().getType());
if (isClick) {
doSomething();
}
}});
Column<Patient, Boolean> checkColumn = new Column<Patient, Boolean>(new CheckboxCell(true, false)) {
#Override
public Boolean getValue(Patient object) {
// Get the value from the selection model.
return sm.isSelected(object);
}
#Override
public void onBrowserEvent(Context context, Element elem, Patient patient, NativeEvent event) {
if ("click".equals(event.getType()))
event.stopPropagation();
super.onBrowserEvent(context, elem, patient, event);
}
};
table.addColumn(checkColumn, SafeHtmlUtils.fromSafeConstant("<br/>"));
Even when I add event.stopPropagation(); doSomething(); is called when I click the checkbox.
Any idea why ?
You need to check where the event happens first (on the cell or on the row: I forgot that point). then you just call event.stopPropagation()
You can also decide in your column whether you want to do something with the click this way (snippet from my project)
productColumn = new Column<ProductProxy, ProductProxy>(productCoreCell) {
#Override
public ProductProxy getValue(ProductProxy productProxy) {
return productProxy;
}
#Override
public void onBrowserEvent(Context context, Element elem, ProductProxy object, NativeEvent event) {
getProductTableManager().rowSelected(object);
}
};

GWT CellTable Cells readOnly/disabled/non-editable

I want to make that some cells of the rows can be non-editable.
by now my solution is when i create the columns, if one is readOnly, y make a TextCell, if not, i go with the default Cell wich can be EditTextCell,DatePickerCell,etc.
The problem with this is that i can't make some rows readOnly and others not. Or they are ALL the fields readOnly or they are not.
How can i do to make this for example
TABLE:
Data1 | Data2 | Data3
--------------------------------------
readOnly | non-readOnly | readOnly
readOnly | readOnly | non-readOnly
when i mean "readOnly" it can be "enabled" or make it a "TextCell"
celda = new TextInputCell();
Column<ObjetoDato, String> columna = new Column<ObjetoDato, String>(celda) {
#Override
public String getValue(ObjetoDato object) {
if(actual.getValorDefault()!=null && object.getValor(actual.getNombreCampo()).isEmpty()){
object.setValor(actual.getNombreCampo(), actual.getValorDefault());
return actual.getValorDefault();
}
return object.getValor(actual.getNombreCampo());
}
};
tabla.agregarColumna(columna, actual.getCaption());
columna.setFieldUpdater(new FieldUpdater<ObjetoDato, String>() {
#Override
public void update(int index, ObjetoDato object, String value) {
object.setValor(actual.getNombreCampo(), value);
new Scripter(object,actual.getComportamiento(),true);
tabla.actualizar();
Sistema.get().getIG().actualizarTotales();
}
});
I tried creating my cutom cell already and replacing the TextImputCell, but the methods never trigger
celda = new FabriCel();
and
public class FabriCel extends TextInputCell {
private String campo;
public FabriCel(String campo){
this.campo=campo;
}
#Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater){
Boolean editable = false;///get it from your model
if(editable != null && !editable){
event.preventDefault();
}else{
super.onBrowserEvent(context, parent, value, event, valueUpdater);
}
}
Also this
#Override
public void render(com.google.gwt.cell.client.Cell.Context context, String value, SafeHtmlBuilder sb) {
Boolean editable = false;///get it from your model
if(editable){
Log.log();
sb.appendHtmlConstant("<div contentEditable='false'>" +value+"</div>");
}else{
Log.log("No entra");
super.render(context, value, sb);
}
}
Thanks!
You have to create one custom cell. In that, you have tell runtime like it should be readonly or no-readonly. just example.
private class CustomCell extends EditTextCell {
public void render(com.google.gwt.cell.client.Cell.Context context,
String value, SafeHtmlBuilder sb) {
Data data=context.getKey();
if(data.isReadOnly()){
sb.appendHtmlConstant("<div contentEditable='false'
unselectable='false' >" +value+"</div>");
}else{
super.render(context, value, sb);
}
}
}
In given bean, there is some condition which says readonly or no-readonly.
And create column like
Column<Data, String> nameColumn = new Column<Data, String>(new CustomCell()) {
#Override
public String getValue(Data object) {
return object.getName();
}
};
A way to do this is to override the onBrowserEvent event of your Editable Cells and consume the event if the cell is not editable.
final EditTextCell cell = new EditTextCell(renderer)
{
#Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater)
{
Boolean editable = false;///get it from your model
if(editable != null && !editable)
{
event.preventDefault();
}
else
{
super.onBrowserEvent(context, parent, value, event, valueUpdater);
}
}
}
I had the same need; and tested out various combinations of overriding render, isEditing, resetFocus, and edit on EditTextCell (I didn't try the onBrowserEvent solution).
When I only overrode render (to show an HTML value if non-editable); I got errors resetting focus (as discussed here). This continued even if I overrode resetFocus. When I only override isEditing, the cell would flash to editing when clicked, and then flash back. What worked perfectly was overriding edit. I triggered based on adding a tag to the value passed in by Column.getValue, you can trigger however you like, but it turned out to be as simple as:
private static class LockableEditTextCell extends EditTextCell {
#Override
protected void edit(Context context, Element parent, java.lang.String value) {
if (!value.startsWith(LOCKED_CELL_VALUE)) {
super.edit(context, parent, value);
}
}
}

Getting header column names in Cell table on click in GWT

I am using Cell Table of GWT 2.2 version. I want to get the name of the header column on which I have clicked. I didn't get any click event on the same.
Is there any work around by which I can accomplish my task.
Something like this? ;)
public class CellTableExample implements EntryPoint, ClickHandler {
private static class SomeEntity {
/* ... */
}
private static class ClickableTextHeader extends TextHeader {
private ClickHandler handler;
public ClickableTextHeader(String text, ClickHandler handler) {
super(text);
this.handler = handler;
}
#Override
public void onBrowserEvent(Context context, final Element elem,
final NativeEvent event) {
//maybe hijack click event
if(handler != null) {
if(Event.ONCLICK == Event.getTypeInt(event.getType())) {
handler.onClick(new ClickEvent() {
{
setNativeEvent(event);
setRelativeElement(elem);
setSource(ClickableTextHeader.this);
}
});
}
}
//default dom event handler
super.onBrowserEvent(context, elem, event);
}
}
CellTable<SomeEntity> cellTable;
TextColumn<SomeEntity> firstColumn;
TextColumn<SomeEntity> secondColumn;
TextColumn<SomeEntity> thirdColumn;
#Override
public void onModuleLoad() {
/* somehow init columns - it's not the point for this example */
cellTable.addColumn(firstColumn, new ClickableTextHeader("First column header", this));
cellTable.addColumn(secondColumn, new ClickableTextHeader("Second column header", this));
cellTable.addColumn(thirdColumn, new ClickableTextHeader("Third column header", this));
}
#Override
public void onClick(ClickEvent event) {
ClickableTextHeader source = (ClickableTextHeader) event.getSource();
Window.alert(source.getValue());
}
}
Hijacking event could look simpler if we used "simple listener interface" - i just wanted to be "semanticaly compliant with out-of-the-box Handlers" :)

GWT: how can i add/remove a button in a celltable on the go

I have this button cell in my CellTable
ButtonCell reListCell = new ButtonCell();
reListColumn = new Column<EmployerJobs, String>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return "ReList";
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
but i only want this cell to be appear if the below condition pass
public void getDateDiff(final EmployerJobs object) {
rpcService.getDateDiff(object.getJobValidDate(), new AsyncCallback<Boolean>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(Boolean jobExpired) {
if(jobExpired) {
// HERE I WANT TO SHOW MY RELISTCELL, means if the job is expired only then
// there will be a button showing relist would be appear in that row ,for
// the jobs which are not expired NO button should appear..
}
}
});
}
how can i achieve this?
thanks
I agree with DTing.
Quering the backend for each cell/row is not really efficient.
I would rather put the info (jobExpired) into your EmployerJobs class and transfer the info when you request the list of your EmployerJobs to be displayed in your CellTable.
You can update the list periodically to account for changes (see the expenses sample on how to do that).
But to your initial question (hiding the cell). There are two solutions:
Use an ActionCell and override the render method.
ActionCell:
ActionCell<EmployerJobs> reListCell = new ActionCell<EmployerJobs>("ReList",
new ActionCell.Delegate<EmployerJobs>() {
#Override
public void execute(EmployerJobs object) {
// code to be executed
}
})
{
#Override
public void render(Cell.Context context,EmployerJobs value,SafeHtmlBuilder sb) {
if (value.isJobExpired()) // isJobExpired returns the field jobExpired.
super.render(context,value,sb);
}
};
reListColumn = new Column<EmployerJobs, EmployerJobs>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return object;
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
Use a ButtonCell and override the render method of your Column.
ButtonCell:
ButtonCell reListCell = new ButtonCell();
reListColumn = new Column<EmployerJobs, String>(reListCell) {
#Override
public String getValue(EmployerJobs object) {
return "ReList";
}
#Override
public void render(Cell.Context context,EmployerJobs object,SafeHtmlBuilder sb) {
if (value.isJobExpired()) // isJobExpired returns the field jobExpired.
super.render(context,value,sb);
}
};
ctJobs.addColumn(reListColumn,
EmployerDashBoardConstants.EMPTYHEADERCOLUMN);
Just tried Umit solution #2 ButtonCell. It works!
To link an specific action to the button, reListColumn.setFieldUpdater(new FieldUpdater....
would be needed
I tried ButtonCell solution too. But if you click in a cell who as no button then an error on client side occur:
com.google.gwt.core.client.JavaScriptException: (TypeError) #com.google.gwt.core.client.impl.Impl::apply(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)([JavaScript object(445), JavaScript object(240), JavaScript object(637)]): parent is null
So I added this to override the Event and avoid Event if I want:
#Override
public void onBrowserEvent(com.google.gwt.cell.client.Cell.Context context,
Element parent, YourObject object, NativeEvent event) {
if (object.isCompleted())
super.onBrowserEvent( context, parent, object, event);
}
I don't know if it's the better way to do it but it works.

Handling onClick for a checkbox in a CellTable Header

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;
}
}
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;
}
}